7

I'm building a Rails 3 app, where I need to give users specific roles based on the path they take to sign up on the site. I am using Devise and Cancan.

So for instance, the path

new-fundraiser (or /users/new/fundraiser)

Needs to set user.fundraiser = true on user creation, and

new-charity-user (or /users/new/charity)

Needs to set user.charity_owner = true on user creation.

What is the easiest / best-practice way to accomplish this using Devise and Cancan?

Jai Chauhan
  • 4,035
  • 3
  • 36
  • 62
Houen
  • 1,039
  • 1
  • 16
  • 35
  • do you consider alternative with two models? Fundraise and CharityOwner (and two routes, each for model), not the DRYest solution. – Mikhail Nikalyukin Jun 05 '11 at 12:45
  • 1
    why not use single sign up, with either a select box to select the "role" they want for sign-up. maybe even leave the role outside the signup process, and let the user to set the role as soon as he first logs in. signup processes should be super simple – Andrei S Jun 05 '11 at 13:32
  • Unfortunately, thats not an option, as we have two different kinds of users that should be treated completely different, and the checkbox solution isnt polished enough – Houen Jun 05 '11 at 15:44

3 Answers3

9

The original question suggests that @Houen had already customized Devise somewhat. Here's a slightly tweaked answer that works with an out-of-the-box Devise set-up and many-to-many user-roles CanCan set-up.

First, in your routes, add:

devise_scope :user do
    match "/users/sign_up/:initial_role" => 'devise/registrations#new', :as => 'new_user_with_role'
end

You can then refer to this route like:

<%= link_to 'here', new_user_with_role_path(:initial_role => 'human') %>

Then, in your User model add

attr_accessible :initial_role

def initial_role=(role_name)
    @initial_role = role_name
    role = Role.find_by_name(role_name)
    self.roles << role
end

def initial_role
    @initial_role
end

And finally in views/devise/registrations/new.html.erb add

<%= f.hidden_field :initial_role, :value => params[:initial_role] %>
Jai Chauhan
  • 4,035
  • 3
  • 36
  • 62
cailinanne
  • 8,332
  • 5
  • 41
  • 49
  • Hi Cailinanne - I really like our answer! Very clean - how would you tweak this best to work with the roles added on the Devise side as user.admin?, user.editor?, like in the Option 2 on the Devise wike here: https://github.com/plataformatec/devise/wiki/How-To:-Add-an-Admin-Role – Houen Jun 14 '11 at 13:46
  • 1
    @Houen I would recommend using Rolify to do that – AZ. Oct 31 '13 at 04:47
  • @AZ.Completely agree. This old question has new and improved tools at the ready. To anybody coming here now, AZ is talking about https://github.com/EppO/rolify – Houen Nov 01 '13 at 11:00
  • I really liked this answer. So for anyone coming here after Rails 4.x, you should care about permitting the additional parameter in the sign_up form. Please refer to this answer for more details [Unpermitted Parameters adding new fields to Devise in rails 4.0](http://stackoverflow.com/a/19036427/119958) – Donato Azevedo Mar 05 '16 at 13:02
5

I would set up a route like:

match "/users/new/:role", :to => 'users#new'

In your user model I'd add an accessor:

class User < ActiveRecord::Base
  def role=(role_name)
    @role = role_name
    case role_name
    when 'charity'
      self.charity_owner = true
    when 'fundraiser'
      self.fundraiser = true
    else
      # raise some exception
    end
  end

  def role
    @role
  end
end

Then in your users#new.html.erb form add a hidden field:

<%= form.hidden_field :role, :value => params[:role] %>

Then your won't need to change your controller code at all.

Andrew Nesbitt
  • 5,976
  • 1
  • 32
  • 36
1

Using out-of-the-box Devise and CanCan, I set up my user "roles" according to Rails Casts #189 "Embedded Association." However, I was unable to get @cailinanne's answer to work with my set up, as a different Roles approach was assumed. If anyone has a similar problem, here is a much less elegant/robust approach that borrows from @cailinanne's answer. It has the one advantage of not needing to modify the User model.

Use the "devise_scope" route and "link_to" code that @cailinanne provided. Skip the changes to the User model. Finally use the following code in views/devise/registrations/new.html.erb:

  <% if params[:initial_role] == "admin" %>
    <%= hidden_field_tag "user[roles][]", :admin %>
  <% elsif params[:initial_role] == "moderator" %>
    <%= hidden_field_tag "user[roles][]", :moderator %>
  <% elsif params[:initial_role] == "author" %>
    <%= hidden_field_tag "user[roles][]", :author %>
  <% end %>

This code replaces the check_box_tag code provided in the Rails Cast code.

This definitely needs more work. I'll move the if-elsif-elsif logic into controller at some point, and reference it against User.ROLES so that it doesn't break if I add/remove a role. But, this will get help you get the initial role assignment working if you took the Rails Casts "roles" approach.

Don Leatham
  • 2,694
  • 4
  • 29
  • 41