Creating Extensions

This guide covers the technical details of extensions and is oriented towards developers. See the customization guide for a more basic treatment on how to customize Spree.

After this guide you should understand:

  • The important role extensions play in Spree
  • How to install a third party extension
  • How to write your own custom extensions
  • How to share your extensions with others

1 Extension Basics

There are a few basic steps to know when it comes to creating your own extension. We’ll walk you through how to create, maintain and test your extensions in the next few sections.

1.1 Integrated vs. Standalone

Extensions can be created in their own standalone directory (like the ones shared on Github) or they can be created within your Rails application. We refer to the extensions that sit in an existing Rails application as “integrated” extensions.

Integrated extensions are usesful during the active development phase of your store. Instead of switching projects all of the time you can just make little tweaks to your extension inside of your store repo. Generally you would only do this if you intended to one day share the extension with someone else (or reuse it elsewhere in a private project.)

The distinction between standlone and integrated extensions is not very important. The instructions for this guide can be assumed to apply to both cases (unless stated otherwise.)

You may want to consider the integrated approach if the extension is potentially useful on another project but you don’t want to make the extension publicly available for others to use.

1.2 Creating

You can create a new extension using the spree command. This requires that you have a gem version of Spree installed that is version 0.70.0 or greater. Once you’ve navigated the directory in which you wish to create the extension, you can create a new extension by typing the following in your console:

$ spree extension foofah

If you’re inside of a Rails application that uses Spree, you may have to prefix this command with bundle exec so that the correct version of Spree is used:

$ bundle exec spree extension foofah

This approach relies on an executable script inside the installed gem. You can always build and install the edge gem using the source if you want to work with the latest and greatest version of the extension generator.

See the Source Code guide which explains how to build the gem from the source.

You probably also need to create a test application (inside the extension) which is necessary for using the Rails generators or running the tests.

$ cd foofah
$ bundle exec rake test_app

The test app is not necessary when you have an integrated extension. The Rails app that you are integrated with will suffice.

1.3 Resources

Once you’ve created your extension you can now create resources in the typical Rails way. For example, to create a new resource SomeBar you would use the standard Rails generator.

$ rails g resource some_bar

This will create a typical Rails resource with output similar (not identical) to the following:

invoke  active_record
create    db/migrate/20110907191441_create_some_bars.rb
create    app/models/some_bar.rb
invoke    rspec
create      spec/models/some_bar_spec.rb
invoke  controller
create    app/controllers/some_bars_controller.rb
invoke    erb
create      app/views/some_bars
invoke    rspec
create      spec/controllers/some_bars_controller_spec.rb
invoke    helper
create      app/helpers/some_bars_helper.rb
invoke      rspec
create        spec/helpers/some_bars_helper_spec.rb
invoke    assets
invoke      js
create        app/assets/javascripts/some_bars.js
invoke      css
create        app/assets/stylesheets/some_bars.css
 route  resources :some_bars

If you’re working on an integrated extension then you should make sure your Gemfile includes the following before attempting to generate a new resource.

gem 'rspec-rails'

1.4 Testing

You can test your extensions in the manner you’re accustomed to with a standard rails application

$ rake test_app
$ bundle exec rspec spec

The rake test_app step is not necessary for integrated extensions.

2 Sharing Your Extension

2.1 Publishing Your Source

The first order of business is to get your extension on Github where everybody can see it. Most importantly, you will want to allow others to have access to the source code. Github provides a convenient (and free) place to store your source code along with the ability to track issues and accept code patches.

It is convention to use the spree- naming convention for your Github repository and spree_ for your gem name. So for example, if you are creating a “foofah” extension the Github project would be named spree-foofah and the gem would be spree_foofah.

2.2 Publishing Your Gem

If your extension is ready to be released into the wild you can publish it as a gem on RubyGems.org. Assuming you used the extension generator to build your extension, its already a gem and ready to be published. You’ll just want to edit a few details before you proceed.

s.name = ‘foofah’ s.version = ‘1.0.0’ s.summary = ‘Add gem summary here’ #s.description = ‘Add (optional) gem description here’ s.required_ruby_version = ‘>= 1.8.7’

  1. s.author = ‘David Heinemeier Hansson’
  2. s.email = ‘david@loudthinking.com’
  3. s.homepage = ‘http://www.rubyonrails.org’
  4. s.rubyforge_project = ‘actionmailer’

3 Versionfile

Spree is moving at a rapid pace with the code constantly evolving as new releases are being pushed out. These changes sometimes make it difficult to keep track of which version of Spree is compatible with which version of an extension. To solve this problem extension authors are encouraged to add a Versionfile to their extension.

3.1 Versionfile Basics

Versionfile is a plain text file which keeps information about which version of Spree an extension is compatible with. It was inspired by the Gemfile used by bundler. Lets take a look at a sample Versionfile.

"0.50.x" => { :branch => "master" }
"0.40.x" => { :tag => "v1.0.0", :version => "1.0.0" }

The above Versionfile is saying that if you are using Spree version “0.50.x” then use “master” branch. If you are using Spree version “0.40.x” then you should use tag “v1.0.0”.

You should note that we are dealing with two different concepts of “version” here. One is the version of Spree supported and the other is the version of the extension.

All Spree extensions should have a version. This version number has nothing to do with what version of Spree is supported. It is listed purely as a convenient way to refer to an extension. For example “I’m having a problem with spree_active_shipping 1.0.1”

3.2 Syntax

A Versionfile can have any number of lines. It is recommended that you put the latest Spree releases at the top. The very first thing a line should is the Spree version it is supporting. Next within curly braces how to get extension supporting the specified Spree version is mentioned.

There are four different ways of specifying an extension’s compatibilty information.

All four variations for identifying extension compatibility can be mapped to more than one Spree version. In other words, its entirely possible that the same version of an extension can be compatible with multiple versions of Spree.

Gem Version

"0.30.x" => { :version => "1.2" }

Above case is saying that if you are using Spree version “0.30.x” then use extension as a gem and the version of gem you should be using is “1.2”.

Gem versions are considered “stable.” They will show up in the extension registry as such.

Git Branch

"0.30.x" => { :branch => "0-30-stable" }

Above case is telling that if you are using Spree version “0.30.x” then use branch named “0-30-stable”. Notice that when you suggesting to use “branch” then “version” should not be specified.

Versions identified by Git branch are considered “unstable” or “edge.” They will be identified as such in the extension registry.

Git Tag

"0.30.x" => { :tag => "v0.30", :version => '1.1' }

Above case is suggesting that if you are using Spree version “0.30.x” then use tag named “v0.30”. The version information indicates that the author of the extension is calling that release as version “1.1”.

Versions identified by tags are considered “stable.” They will show up in the extension registry as such. This is also a nice alternative to having to release an extension as an actual gem.

Git SHA

"0.30.x" => { :ref => "4aedfg", :version => '1.2' }

Above case is suggesting that if you are using Spree version “0.30.x” then use ref named “4aedfg” . The version information indicates that the author of the extension is calling that release as version “1.2”

Versions identified by a Git SHA are considered “stable.” They will show up in the extension registry as such. This is also a nice alternative to having to release an extension as an actual gem.

#"0.30.x" => { :ref => "4aedfg", :version => '1.2' }

Above case is indicating how you can comment out a line. A line beginning with hash “#” is treated as comment.

3.3 Using the Versionfile

Versionfile has no impact on the actual ability of an extension to work with Spree. It is simply a declaration by the extension author that a certain version of the extension is known to work with a particular version(s) of Spree. Its also possible that an extension version might be compatible with versions of Spree not listed. If you discover this to be the case please send a pull request to the extension author so they can update their Versionfile.

There is a crawler which crawls the Versionfile of all the registered extensions once every day. So any changes you make to your extension’s Versionfile should be captured within 24 hours.

You must actually register your extension in the Extension Registry before it can be discovered by the crawler.

After the Versionfile info has been processed you will see it listed in the Extension Registry. Depending on the syntax you used to specify the version you will see it listed under “Stable Release” or “Dev Release”. In both cases there will also be a link labeled “Show”. Clicking that link will show you the exact information you need to paste into your Gemfile in order to use the extension.

3.4 Troubleshooting

While parsing the lines gathered from Versionfile if a line is invalid then that line is skipped. So make sure that entries in Versionfile are valid. Soon we would be releasing a tool that can verify the Versionfile and provide instant feedback if something is not right.

4 Testing Against a Release Candidate

The Spree core team will often announce a so-called release candidate shortly before an official release. This is to allow Spree developers to test their sites and extensions against the upcoming release code.

In order to test your extension against the release candidate you will need to first update your gemspec file. Lets look at how we would update the spree_social gem to use the new 0.60.0.rc1 version of Spree. We’ll need to modify spree_social.gemspec as follows:

Gem::Specification.new do |s|
  ...
  s.add_dependency('spree_core', '>= 0.60.0.rc1')
  s.add_dependency('spree_auth', '>= 0.60.0.rc1')
  ...
end

You’ll also want to update your Gemfile that belongs to the Spree application using the extension.

  gem 'spree', '0.60.0.rc1'

Next, you’ll need to update the dependencies locally using bundler and commit the resulting Gemfile.lock to your source code respository.

$ bundle update spree_social

Now its time to fire up Spree and make sure everything is working properly. Assuming everything looks good you’ll also want to update the Versionfile associated with your extension so that people can use the new and improved version.

In our example we’re adding support for a new 0.60.0RC1 release candidate which is equivalent to 0.60.x in the extension directory.

"0.60.x" => { :tag => "v1.2", :version => "1.2" }
"0.50.x" => { :tag => "v1.0.2", :version => "1.0.2" }

If we’re not 100% sure the extension will support the release candidate (or if its a work in progress) we could reference the “edge” branch (ie. master) in the Versionfile instead.

"0.60.x" => { :branch => "master" }
"0.50.x" => { :tag => "v1.0.2", :version => "1.0.2" }

Using a branch in the Versionfile designates the gem as a “development release” which is possibly unstable and subject to change. Once things are stable you can point ot a specific Git SHA or tag so that people can rely on the extension not changing in a production release.

5 Extension Tutorial

This tutorial assumes a basic familiarity with Spree extensions. For more detailed information on how extensions, please see the previous sections. The tutorial will, however, walk you through a complete example touching on all of the major aspects of an extension so if you like to learn through “step by step” instructions you may want to start here.

5.1 Getting Started

Let’s start by building a simple extension. Suppose 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, controllers, routes and locales via the new extension.

We’re going to assume you already have a functioning Spree application. If you have not yet achieved this you should read the Getting Started Guide first.

So let’s start by generating the new extension

  $ spree extension FlagPromotions

This creates a spree_flag_promotions directory with several additional files and directories as the following generator output shows:

  create  spree_flag_promotions
  create  spree_flag_promotions/app
  create  spree_flag_promotions/app/assets/javascripts/admin/spree_flag_promotions.js
  create  spree_flag_promotions/app/assets/javascripts/store/spree_flag_promotions.js
  create  spree_flag_promotions/app/assets/stylesheets/admin/spree_flag_promotions.css
  create  spree_flag_promotions/app/assets/stylesheets/store/spree_flag_promotions.css
  create  spree_flag_promotions/app/controllers
  create  spree_flag_promotions/app/helpers
  create  spree_flag_promotions/app/models
  create  spree_flag_promotions/app/views
  create  spree_flag_promotions/config
  create  spree_flag_promotions/db
  create  spree_flag_promotions/lib
  create  spree_flag_promotions/lib/spree_flag_promotions.rb
  create  spree_flag_promotions/lib/spree_flag_promotions/engine.rb
  create  spree_flag_promotions/lib/spree_flag_promotions/hooks.rb
  create  spree_flag_promotions/script
  create  spree_flag_promotions/script/rails
  create  spree_flag_promotions/spec
  create  spree_flag_promotions/LICENSE
  create  spree_flag_promotions/Rakefile
  create  spree_flag_promotions/README
  create  spree_flag_promotions/.gitignore
  create  spree_flag_promotions/spree_flag_promotions.gemspec
  create  spree_flag_promotions/Versionfile
  create  spree_flag_promotions/config/routes.rb
  create  spree_flag_promotions/Gemfile
  create  spree_flag_promotions/spec/spec_helper.rb
  create  spree_flag_promotions/.rspec

5.2 Creating a Resource

Lets create a new PromotedItem resource for our extension. Since Spree let us take advantage of normal Rails generators we can do this pretty easily.

First we’ll make sure we have a test app created.

$ cd spree_flag_promotions
$ bundle exec rake test_app

The test app is needed as a context for the Rails generators and your extension tests to run in. It is not needed if you are creating a so-called integrated extension.

Now we’re ready to create the resource

$ rails g resource promoted_item

This should generate output similar to the following:

  invoke  active_record
  create    db/migrate/20110907202658_create_promoted_items.rb
  create    app/models/promoted_item.rb
  invoke    rspec
  create      spec/models/promoted_item_spec.rb
  invoke  controller
  create    app/controllers/promoted_items_controller.rb
  invoke    erb
  create      app/views/promoted_items
  invoke    rspec
  create      spec/controllers/promoted_items_controller_spec.rb
  invoke    helper
  create      app/helpers/promoted_items_helper.rb
  invoke      rspec
  create        spec/helpers/promoted_items_helper_spec.rb
  invoke    assets
  invoke      js
  create        app/assets/javascripts/promoted_items.js
  invoke      css
  create        app/assets/stylesheets/promoted_items.css
   route  resources :promoted_items

5.3 Verifying the Results

The PromotedItem model will represent products that are “flagged” for promotion on the front page along with a schedule of when the promotion begins and ends.

# app/models/promoted_item.rb
class PromotedItem < ActiveRecord::Base
end

And of course there’s also the corresponding migration.

# db/migrate/20110907202658_create_promoted_items.rb
class CreatePromotedItems < ActiveRecord::Migration
  def change
    create_table :promoted_items do |t|
      t.timestamps
    end
  end
end

The exact timestamp used in the filename will differ on your system depending on when you generate it.

There’s also a new controller file so that we can manage these promoted items.

#app/controllers/promoted_items_controller.rb
class PromotedItemsController < ApplicationController
end

You may want to add this controller to the Admin namespace as well as extend Admin::ResourceController. This step is omitted in order to keep the tutorial as simple as possible.

Finally, the routes have also been configured for us

#config/routes.rb
Rails.application.routes.draw do
  resources :promoted_items
  # Add your extension routes here
end

5.4 Customizing an Existing Spree View

Please see the View Customization pages for details.

5.5 Internationalization (I18n)

Spree extensions can also provide their own locales/translations. If you’re adding additional view text and you wish to support multiple locales, or if you are interested in sharing your extension with others, its a good idea to enable your extension with the i18n features of Spree.

In order to properly display the “Promoted Items” tab we’ll need to provide an English localization in the config/locales/en.yml file.

  ---
  en:
    promoted_items: Promoted Items

5.6 Overriding an Existing Spree Class

Please refer to the Logic Customization page.

5.7 Defining Extension Preferences

Let’s now define a preference for our extension.

  #lib/spree/flag_promotion_configuration.rb
  class Spree::FlagPromotionConfiguration < Spree::Preferences::Configuration
    preference :show_flags, :boolean
  end

The above example defines a single boolean preference for our extension, :show_flags.

In order to configure these preferences within the extension, we must first instantiate the configuration object in our engine file.

  #lib/spree_flag_promotions/engine.rb
  module Spree::ActiveShipping; end
  ....
  module SpreeFlagPromotions
    class Engine < Rails::Engine
      initializer "spree.flag_promotions.preferences", :after => "spree.environment" do |app|
        Spree::FlagPromotions::Config = Spree::FlagPromotionConfiguration.new
      end
      ....
    end
  end

Now this preference can be accessed using Spree::FlagPromotions::Config

  Spree::FlagPromotions::Config[:show_flags] = false
  Spree::FlagPromotions::Config[:show_flags] #=> false

Please refer to the Preferences guide for more information.

5.8 Adding the Extension to Your Application

Lets now add the extension to our application. We’ll create a simple Spree application for this purpose just to illustrate how its done.

We’ll start by creating a new Rails app

$ rails new my-store

Then edit the Gemfile and add dependencies for Spree and the spree_flag_promotions extension

#Gemfile
gem 'spree'
gem 'spree_flag_promotions', :path => '../spree_flag_promotions'

This is a relative path to the ‘spree_flag_promotions’ directory. You could also refer to a git location or any other convention that is acceptable to bundler. See the bundler site for more details on bundler.

$ bundle install

Now that the extension is setup its time to install (and then run) the migrations.

$ rake railties:install:migrations
$ rake db:migrate

That’s it! Your store should be ready to go with your new custom extension.

This project is maintained by a core team of developers and is freely available for commercial use under the terms of the New BSD License.