Installing Devise Invitable on Spree

Alessio Rocco

8 Sept 2014 Development, Ruby On Rails

Alessio Rocco

3 mins
Devise gem logo banner

Usually it's easy to add a gem (not an extension) to Spree but sometimes things can get a little rough, devise_invitable is just like that, at first it might seem easy to install, but that's not true, follow us to see how we have installed this good gem on Spree commerce.

We recently added an "invite a friend" feature to a Spree ecommerce. Looking for a solution we didn't find any valid extension but we knew there was the devise_invitable gem. At first it seemed an easy task but devise_invitable didn't just work out of the box with Spree so we needed to get our hands dirty.

Let's start by adding the devise_invitable gem to our Gemfile:

gem 'devise_invitable', '~> 1.3'

then as usual run bundle install and generate everything that's needed by devise_invitable:

$ bundle install
$ rails generate devise_invitable:install
$ rails generate devise_invitable:views spree
$ rails generate devise_invitable Spree::User
$ rake db:migrate

Now it's time to make some changes to our user model so we need to decorate the Spree::User class and add the invitable module, the other modules come from spree_auth_devise and as usual we can change those if we need some custom behavior.

# app/models/spree/user_decorator.rb

Spree::User.class_eval do
  devise :database_authenticatable, :registerable, :recoverable, :rememberable,
         :trackable, :validatable, :encryptable, :invitable,
         encryptor: 'authlogic_sha512'
end

Then we run the application, go to /signup and add a user we can use to invite our friends. If we go to /user/spree_user/invitation/new we see a form for inviting new users. Here is where the trouble starts, an uninitialized constant Spree::InvitationsController exception is raised because devise_invitable is not under the Spree namespace.

When working with Spree, namespace troubles are to be expected, since Spree uses a namespace for everything if you want to integrate something that is not an extension you'll probably end up in this situation.

In this case we need to create a Spree::InvitationsController class which inherits from Devise::InvitationsController.

# app/controllers/spree/invitations_controller.rb

class Spree::InvitationsController < Devise::InvitationsController
end

then we try to reload the previus path another exception is raised:

undefined method 'spree_user_invitation_path'

this time the exception is raised because the path helper used by the devise_invitable views is wrong. To fix it we have two options, we can either change the devise router_name or change the path to the devise_invitable views.

The first option is faster to apply but it makes devise only work in the Spree namespace and in some cases that is not what we want. If in an application is using the Spree engine alone this solution can safely be used, we can add this to the devise initializer:

# config/initializers/devise.rb

Devise.router_name = :spree
Devise.scoped_views = true

and we're done with this option.

The other option needs a little more work but it's the one we prefer since it's more flexible and more versatile. We have to completely change the path helpers in the views to use the Spree namespace:

# app/views/spree/invitations/new.html.erb
...
<%= form_for resource, :as => resource_name, :url => spree.spree_user_invitation_path, :html => {:method => :post} do |f| %>
...

# app/views/spree/invitations/edit.html.erb
...
<%= form_for resource, :as => resource_name, :url => spree.spree_user_invitation_path, :html => { :method => :put } do |f| %>
...

# app/views/spree/mailer/invitation_instructions.html.erb
...
<p><%= link_to I18n.t("devise.mailer.invitation_instructions.accept"), accept_spree_user_invitation_url(:invitation_token => @token) %></p>
...

The exception should be gone, we should be able to go to /user/spree_user/invitation/new see the form and send an invite. While sending the email another exception is raised, devise_invitable is looking for the email views in the wrong path because Devise.mailer is set to Spree::UserMailer. This should be a simple fix:

$ mv app/views/spree/mailer app/views/spree/user_mailer

At last we have to setup the default_url_options for the mailer, like we would normally do with devise:

# config/environments/development.rb

config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

Now we should be able to go to /user/spree_user/invitation/new to invite a user, all should be good, the email is sent and the new user can click on the link to setup a password, but... there is no layout for these views, lets fix this.

Normally we would make InvitationsController inherit from Spree::StoreController but because our controller already inherits from Devise::InvitationsController we need to add some modules and helpers to make sure it works as expected:

module Spree
  class InvitationsController < Devise::InvitationsController
    include Spree::Core::ControllerHelpers::Common
    include Spree::Core::ControllerHelpers::Store
    helper 'spree/base', 'spree/store'
  end
end

now we can just add a link to the invitation page using deface with the route new_spree_user_invitation to let your users invite their friends.

If you want to take a look at the final result you can go here to download the demo application. In this application you can find two branches, the solution_with_router_name and the master branch that use the last option in this article.

You may also like

Let’s redefine
eCommerce together.