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’
- s.author = ‘David Heinemeier Hansson’
- s.email = ‘david@loudthinking.com’
- s.homepage = ‘http://www.rubyonrails.org’
- 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.
