public
Description: Spree is a complete open source e-commerce solution for Ruby on Rails.
Home | Edit | New

Extension Tutorial

Don’t let the length of this very detailed tutorial discourage you. The later part of the tutorial is dedicated to showing the specific code for our example extension. It goes way beyond the knowledge necessary to create a simple extension.

Generating an Extension

We’re assuming you have already created a new Spree application and run the migrations. If you haven’t done so, create a simple application now.

spree tutorial
cd tutorial
rake db:bootstrap

Let’s start by building a simple extension. Let’s pretend we want the ability to mark certain products as part of a promotion. We’d like to add an admin interface for marking certain items as being part of the promotion. We’d also like to highlight these products in our store view. This is a great example of how an extension can be used to build on the solid Spree foundation. We’ll be adding our own custom models, views and controllers through the new extension.

Spree comes with generators for creating your new extensions. To create a new extension use

$ script/generate extension <extension_name>

So in the case of our Promotion Manager extension we type

$ script/generate extension PromotionManager

This command generates the following output


create vendor/extensions/promotion_manager/app/controllers
create vendor/extensions/promotion_manager/app/helpers
create vendor/extensions/promotion_manager/app/models
create vendor/extensions/promotion_manager/app/views
create vendor/extensions/promotion_manager/db/migrate
create vendor/extensions/promotion_manager/lib/tasks
create vendor/extensions/promotion_manager/README
create vendor/extensions/promotion_manager/promotion_manager_extension.rb
create vendor/extensions/promotion_manager/lib/tasks/promotion_manager_extension_tasks.rake
create vendor/extensions/promotion_manager/spec/controllers
create vendor/extensions/promotion_manager/spec/models
create vendor/extensions/promotion_manager/spec/views
create vendor/extensions/promotion_manager/spec/helpers
create vendor/extensions/promotion_manager/Rakefile
create vendor/extensions/promotion_manager/spec/spec_helper.rb
create vendor/extensions/promotion_manager/spec/spec.opts

This creates the promotion manager extension in vendor/extensions. The extension is entirely self contained in that directory. Once we’re done, we can reuse this extension in other Spree applications by simply copying the promotion_manager folder and dropping it into another application’s vendor/extensions directory.

TODO: Add some information about the promotion_extension.rb file and activation once activation feature is in place

Generating a Model

Extensions allow you to add your own models to the application. You can also use extensions to mixin additional functionality into the existing models. For our promotion extension let’s create a brand new promotion model. Note that the field names are optional but if we provide them they will be automatically added to the migration.

$ script/generate extension_model PromotionManager promotion name:string start:date stop:date

This command generates the following output

exists  app/models/
exists  spec/models/
create  app/models/promotion.rb
create  spec/models/promotion_spec.rb
create  vendor/extensions/promotion_manager/db/migrate
create  db/migrate/001_create_promotions.rb

Reviewing the output of this command you can see that the generator produced the following:

  • Model file
  • RSpec test
  • Database migration

Let’s examine the contents of the migration in our favorite text editor.

class CreatePromotions < ActiveRecord::Migration
  def self.up
    create_table :promotions do |t|
      t.string :name
      t.date :start
      t.date :stop

      t.timestamps
    end
  end

  def self.down
    drop_table :promotions
  end
end

So we now can see that Spree extensions support their own migrations and the extension_model generator will even help us to create them. Let’s run the migrations. Just run the regular migration task to migrate your project and all of your extensions. This is made possible by the UTC based migration system introduced by Rails 2.1

rake db:migrate

This should output

== 1 CreatePromotions: migrating ==============================================
-- create_table(:promotions)
   -> 0.0025s
== 1 CreatePromotions: migrated (0.0030s) =====================================

Of course you can have more then one model in your extension. If we had additional models their migrations would have been run at this point as well. If you decide to make your extension available to the public (via Github or some other mechanism) you can write additional migrations to support new features for your extension in subsequent versions using the command:

script/generate extension_migration <extension_name> <migration_name>

As we mentioned, the previous command migrates all of your extensions. If you want to migrate your extension to a specific version use something like this:

rake spree:extensions:promotion_manager:migrate VERSION=0

Note the use of the familiar VERSION environment variable to specify the version. The version number corresponds to the number of your extension’s migration (file) and not the number in the schema info table. You can also use RAILS_ENV to specify an environment other then the default (which is development.)

Generating a Controller

Now that we have the model setup for our extension we can move onto the controller. To generate a controller use the following command:

script/generate extension_controller PromotionManager admin/promotions

This should generate the following output:

create  app/controllers/admin
create  app/helpers/admin
create  app/views/admin/promotions
create  spec/controllers/admin
create  spec/helpers/admin
create  spec/views/admin/promotions
create  spec/controllers/admin/promotions_controller_spec.rb
create  spec/helpers/admin/promotions_helper_spec.rb
create  app/controllers/admin/promotions_controller.rb
create  app/helpers/admin/promotions_helper.rb

Reviewing this output we see that it generated

  • Admin::PromotionsController
  • Helper for the controller
  • RSpec test for the controller
  • Folder for the promotion controller views

Routing

Spree extensions have the ability to configure their own routes. Let’s open vendor/extensions/promotion_manager/config/routes.rb and take a closer look.

# Put your extension routes here.

# map.namespace :admin do |admin|
#   admin.resources :whatever
# end 

Notice how the generator created a placeholder for you to add your routes. Go ahead an uncomment that block of code and change :whatever to :promotions.

  map.namespace :admin do |admin|
    admin.resources :promotions
  end  

Activation

TODO: Activation Tabs (once they are supported!)

At this point, you have enough information to get up and running with your own extensions. We haven’t done anything useful with the extension yet but you get the idea of how to create one and the ways in which you can provide your own functionality in a familiar way. The rest of this tutorial will provide some more specifics on how we can make the extension do something useful.

Adding Views into Views

Adding Fields to the User Form

So, as you may have already noticed, you can easily add/overwrite functions in existing models and controllers using an extension. See here for another example of this. I was writing a plugin so you could have your users subscribe to your mailing lists, and I realized, I need a way to add fields to the user form. For example, a check box field that said “Yes, notify me when you release new products”. So, I needed a way to add these fields, but I didn’t want to have to overwrite the whole users form view (which you can do simply by simply overriding it, but I didn’t want to).

Instead, I contributed to spree, and now you can do something like this:

    UsersController.class_eval do
      before_filter :add_mailing_list_fields
      def add_mailing_list_fields
        @extension_partials << 'mailing_lists'
      end
    end

The mailing list partial which is in extension/app/views/users/_mailing_lists.html.erb contains the extra fields I want rendered on the user form.

Adding Admin Configuration Links

If you add a new admin controller, you can link to it from the configuration page by doing this:

    Admin::ConfigurationsController.class_eval do
      before_filter :add_mailing_list_links, :only => :index
 
      def add_mailing_list_links
        @extension_links << {:link => admin_mailing_lists_path, :link_text => t('Mailing Lists'), :description => "Add Mailing Lists your users can opt-in to."}
      end
 
    end

Add REST Actions to the Controller

Let’s add some REST actions to the controller. If you are not already aware of the excellent resource_controller plugin, then you should take a look at it when you get a chance. It will save you a massive amount of tedious typing. For now though, we’ll create our REST actions the old fashioned way.

Go ahead and add the REST methods to the controller.

class Admin::PromotionsController < Admin::BaseController

  # GET /promotions
  # GET /promotions.xml
  def index
    @promotions = Promotion.find(:all)

    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :xml => @promotions }
    end
  end

  # GET /promotions/1
  # GET /promotions/1.xml
  def show
    @promotion = Promotion.find(params[:id])

    respond_to do |format|
      format.html # show.html.erb
      format.xml  { render :xml => @promotion }
    end
  end

  # GET /promotions/new
  # GET /promotions/new.xml
  def new
    @promotion = Promotion.new

    respond_to do |format|
      format.html # new.html.erb
      format.xml  { render :xml => @promotion }
    end
  end

  # GET /promotions/1/edit
  def edit
    @promotion = Promotion.find(params[:id])
  end

  # POST /promotions
  # POST /promotions.xml
  def create
    @promotion = Promotion.new(params[:promotion])

    respond_to do |format|
      if @promotion.save
        flash[:notice] = 'Promotion was successfully created.'
        format.html { redirect_to collection_url }
        format.xml  { render :xml => @promotion, :status => :created, :location => @promotion }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @promotion.errors, :status => :unprocessable_entity }
      end
    end
  end

  # PUT /promotions/1
  # PUT /promotions/1.xml
  def update
    @promotion = Promotion.find(params[:id])

    respond_to do |format|
      if @promotion.update_attributes(params[:promotion])
        flash[:notice] = 'Promotion was successfully updated.'
        format.html { redirect_to collection_url }
        format.xml  { head :ok }
      else
        format.html { render :action => "edit" }
        format.xml  { render :xml => @promotion.errors, :status => :unprocessable_entity }
      end
    end
  end

  # DELETE /promotions/1
  # DELETE /promotions/1.xml
  def destroy
    @promotion = Promotion.find(params[:id])
    @promotion.destroy

    respond_to do |format|
      format.html { redirect_to(promotions_url) }
      format.xml  { head :ok }
    end
  end
 
end

Add access to Spree Base helpers

To gain access to standard spree helper methods in your new controller, add helper 'spree/base' to your controller.

Extension and Repository Naming Conventions

You create a new extension this way:

script/generate extension YourOwnExtension

This is supposed to be stored in a repository called spree-your-own-extension.

And install this way:

script/extension install git://github.com/developer/spree-your-own-extension.git

Using this convention the extension will be placed inside vendor/extensions/your_own_extension, the spree- part will be erased and traces will be substituted by underlines.

[TODO: Finish up tutorial]

Last edited by jaymendoza, Thu Nov 05 23:55:01 -0800 2009
Home | Edit | New
Versions: