Installing Devise Invitable on Spree

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.