People are often asking us about how to install Spree on the popular Heroku service. For those of you who don't know what Heroku is, its a popular cloud-based hosting choice for Rails applications. The reason why people have a lot of questions about Heroku specifically is due to the unique design of the Heroku platform which presents a few constraints not found in a typical Rails hosting environment.

Assumptions

This is not a how-to guide for Rails applications in general. So lets start off with a few assumptions about your system. You can easily Google for some additional information if you do not have the following installed on your system:

  • Ruby
  • ImageMagick
  • Git
  • Rubygems
  • bundler gem
  • heroku gem

ImageMagick is not strictly necessary - it is only needed if you plan to upload new pictures for your products. It is also installed by default on Heroku so it will only be a limitation for you in terms of testing the product image upload locally.

Amazon S3 Account

The primary limitation of Heroku is that it contains a read-only filesystem. This means that you cannot simply upload images to the server for your products. The images need to be stored on a third-party service such as Amazon's Simplified Storage System (S3). We will save the details for configuring Spree to work with S3 for a separate blog post. For now we'll just be satisfied to deploy a basic Spree store.

Creating a Basic Spree Application

We'll start by creating a basic Rails application.

$ rails new spree_demo
$ cd spree_demo
$ bundle install
$ git init
$ git add .
$ git commit -m "First commit"

Now we'll add the Spree gem by editing the Gemfile in the spree_demo application as follows:


gem 'spree'

Once this is done we can install the necessary migrations, etc.

$ rails g spree:site
$ rake spree:install
$ rake spree_sample:install
$ rake db:bootstrap

Now we'll do another git commit to save our progress.

$ git add .
$ git commit -m "Added Spree"

Heroku Code Tweaks

Now comes the stuff that is specfic to Heroku. We'll start by editing edit config/environments/production.rb as follows:


config.action_controller.perform_caching = false

This tells Rails that the stylesheets and javascript stuff should not be cached in the read-only filesystem (which is not possible on Heroku.) Once again we perform a git commit to save our progress.

$ git add .
$ git commit -m "Heroku tweaks"

Creating the Heroku App

Its pretty simple to create a new Heroku application from an existing Rails application already under source control with Git.

NOTE: The following example uses an application name of heroku-spree. Each Heroku application needs a unique name so you will have to choose your a name that is not already in use.

$ heroku create heroku-spree
Creating heroku-spree....... done
http://heroku-spree.heroku.com/ | git@heroku.com:heroku-spree.git
Git remote heroku added

Now we'll deploy the Heroku application:

$ git push heroku master
heroku db:push
heroku restart

Heroku Configuration

Spree requires SSL for checkout in production mode (and this is the default mode for Heroku applications.) So we'll use the Heroku gem to configure a simple wildcard SSL certificate. We'll also enable the Sendgrid add-on to send emails.

$ heroku addons:add piggyback_ssl
$ heroku addons:add sendgrid:free

These are free add on services for testing purposes. You will need your own SSL certificate eventually when you deploy to production.

Spree Configuration

You should now be up and running on Heroku! The final step is to configure Spree itself so that you can do a complete checkout. To do this, just log into the admin interface and create a production payment method that uses the Gateway::Bogus gateway. For more information on configuring a payment method see the official Spree documentation.

Rails applications by default will log every parameter that is passed to a given controller. Normally this is desirable behavior but in the case of sensitive information (ex. passwords and credit card numbers) you should never log these values.

The recommended approach for this is to add a filter_parameters directive in your application configuration as shown below.


module SampleApp
  class Application < Rails::Application
    # Filter sensitive parameters from the log file.
    config.filter_parameters += [:password]
  end
end

But what if you are working within the context of a Rails Engine? For instance, in the Spree application there is an engine that has a controller responsible for posting credit card information (over SSL of course.) After a little bit of digging I came up with the following solution:


module SpreeCore
  class Engine < Rails::Engine
    # filter sensitive information during logging
    initializer "spree.params.filter" do |app|
      app.config.filter_parameters += [:number]
    end
  end
end

It turns out you can dynamically declare an initializer in your Railtie and then just add the filter there.

Deleting a record using ajax

The rails scaffold produces following code for deleting a record.

  
<%= link_to 'Destroy', user,  :confirm => 'Are you sure?',
                              :method => :delete %>
  

I want to delete the record using Ajax so that the whole page is not reloaded after every single delete. Since I use jQuery, I would add following line to my Gemfile.

  
gem 'jquery-rails', '>= 0.2.6'
  

And then I would run

  
rails generate jquery:install
  

Now I am all set. Howeer if I click "Destroy" then record is getting destroyed but the browser version is not being removed. Let's fix that.

First thing I need to do is to respond_to "ajax" requets in destroy action. After changing the controller the method looks like this.

  
def destroy
  @user = User.find(params[:id])
  @user.destroy
  respond_to do |format|
    format.html { redirect_to(users_url) }
    format.js { render :text => "alert('user has been deleted')" }
  end
end
  

And in application.js I would add just one line.

  
$('a[data-method="delete"]').live('ajax:success', function(){});
  

Then index.html.erb will look like this.

  
<%= link_to 'Destroy', user,  :confirm => 'Are you sure?',
                              :method => :delete,
                              :remote => true
%>
  

Now when you delete a user, the user record will be deleted using ajax and an alert will come up with the message user has been deleted.

Deleting record using JSON data-type

JSON is the currency of communication in JavaScript world. So let's say that I want ajax request to respond with the name of the user who is being deleted and then I would construct the message on the client. Changed code would look like this.

  
    def destroy
      @user = User.find(params[:id])
      @user.destroy
      respond_to do |format|
        format.html { redirect_to(users_url) }
        format.js { render :json => {:name => 'John'} }
      end
    end
  

And in application.js I would have following lines.

  
$('a[data-method="delete"]').live('ajax:success',
  function(data, textStatus, jqXHR){
    alert(data.name + ' has been deleted');
  });
  

Then index.html.erb will look like this.

  
<%= link_to 'Destroy', user,  :confirm => 'Are you sure?',
                              :method => :delete,
                              :remote => true,
                              'data-type' => 'json' %>
  

Now when you delete a user, you will get the message undefined has been deleted. What's going wrong.

Notice that the function that is invoked when ajax:success callbacks happen has three parameters. The order of the parameters matches with the order of the paramers in jQuery ajax call for success.

In order to solve the problem you need to know how rails.js invokes the callbacks. The relevant code in rails.js is

  
element.trigger('ajax:success', [data, status, xhr]);
  

When trigger is used then the very first parameter passed is the event object. The function that is invoked will get the event object as the first parameter and then rest of the parameters are passed. This is just the way trigger works in jQuery.

Based on that information the JavaScript code should be changed to following in application.js .

  
$('a[data-method="delete"]').live('ajax:success',
  function(e, data, textStatus, jqXHR){
    alert(data.name + ' has been deleted');
  }
);
  

You can follow this issue to see a real world example of this topic.