-1

I have implemented Devise and Cancan using documentation for a simple Todo Rails App. I was able to hide the content Edit feature for user without admin role.

However, I cannot verify that user with admin roll can access the Edit feature because I cannot add/edit user roles.

How do you allow users to choose role via New and Edit Registration (Devise) views???

Here is the assignment I am trying to complete:

Add Multiple Roles to Your User to Create an ‘Admin’

Goal: To set up multiple roles for each User in order to create an admin.

Steps: a. Add a an attribute of name(string) to your ‘user’ model. b. Create a new model called ‘role’ and create an additional model called ‘user_role.’ The ‘user_role’ model will be the join table. Use a many to many association so that a user has many user_roles, and a role has many user_roles. The user_role model should belong to both user and role.
c. Add a an attribute of admin(string) to your ‘role’ model. d. Create a method to check if a user is an admin in your ‘user’ model. You can use a method called ‘def admin?’

Here is my code:

app/models/user.rb

  class User < ActiveRecord::Base
    # Include default devise modules. Others available are:
    # :confirmable, :lockable, :timeoutable and :omniauthable
    devise :database_authenticatable, :registerable,
           :recoverable, :rememberable, :trackable, :validatable

    has_many :todos
    has_many :roles, :through => :user_roles

    before_create :setup_default_role_for_new_users

    ROLES = %w[admin user]

    def role_symbols
      [role.to_sym]
    end

    def roles=(roles)
      self.roles_mask = (roles & ROLES).map { |r| 2**ROLES.index(r) }.inject(0, :+)
    end

    def roles
      ROLES.reject do |r|
        ((roles_mask.to_i || 0) & 2**ROLES.index(r)).zero?
      end
    end

    def role?(role) 
      roles.include?(role.to_s)
    end

    def setup_default_role_for_new_users
      if self.role.blank?
        self.role = "user"
      end
    end
  end

app/models/todo.rb

  class Todo < ActiveRecord::Base
    belongs_to :user
  end

app/db/migrate/20140827183230_add_role_to_users.rb

class AddRoleToUsers < ActiveRecord::Migration
  def change
    add_column :users, :role, :string
  end

  def self.up
    add_column :users, :role, :string
  end

  def self.down
    remove_column :users, :role
  end
end

views/devise/registrations/new.html.erb

<h2>Sign up</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= devise_error_messages! %>

  <div><%= f.label :email %><br />
  <%= f.email_field :email, autofocus: true %></div>

  <div><%= f.label :password %> <% if @validatable %><i>(<%= @minimum_password_length %> characters minimum)</i><% end %><br />
    <%= f.password_field :password, autocomplete: "off" %></div>

  <div><%= f.label :password_confirmation %><br />
    <%= f.password_field :password_confirmation, autocomplete: "off" %></div>

    <% for role in Role.all %>
      <div>
        <%= check_box_tag "user[role_ids][]", role.id, @user.roles.include?(role) %>
        <%=h role.name %>
      </div>
    <% end %>
    <%= hidden_field_tag "user[role_ids][]", "" %>

  <div><%= f.submit "Sign up" %></div>
<% end %>

<%= render "devise/shared/links" %>

views/devise/registrations/new.html.erb

<h2>Edit <%= resource_name.to_s.humanize %></h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
  <%= devise_error_messages! %>

  <div><%= f.label :email %><br />
  <%= f.email_field :email, autofocus: true %></div>

  <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
    <div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div>
  <% end %>

  <div><%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br />
    <%= f.password_field :password, autocomplete: "off" %></div>

  <div><%= f.label :password_confirmation %><br />
    <%= f.password_field :password_confirmation, autocomplete: "off" %></div>

  <div><%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
    <%= f.password_field :current_password, autocomplete: "off" %></div>

    <% for role in Role.all %>
      <div>
        <%= check_box_tag "user[role_ids][]", role.id, @user.roles.include?(role) %>
        <%=h role.name %>
      </div>
    <% end %>
    <%= hidden_field_tag "user[role_ids][]", "" %>

  <div><%= f.submit "Update" %></div>
<% end %>

<h3>Cancel my account</h3>

<p>Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %></p>

<%= link_to "Back", :back %>

Please let me know if more code samples are required to assist in answering this question.

  • Your question appears to be highly localized – OneChillDude Aug 28 '14 at 18:45
  • Sorry about that as this is the first time posting a question... Any constructive suggestions on how to improve my questions? – ThinkADRIAN Aug 29 '14 at 05:05
  • Yeah, we all have specific problems we need help with, and that's OK. What I like to do is try and isolate the problem to something that covers a wider scope. This helps people understand your problem better, and it may help people with the same problem. Other than that the question looks good – OneChillDude Aug 29 '14 at 15:27
  • Thanks Brian! Will take the suggestions into account on the next question. – ThinkADRIAN Aug 30 '14 at 16:14

2 Answers2

1

user can't choose role for yourself, you could create it from console and then login with this user as admin and change roles for other users

Igor Guzak
  • 2,155
  • 1
  • 13
  • 20
  • I would like to be able to change the roles from the app. I know it doesn't make sense for user to be able to upgrade themselves to admin. However, that is part of the assignment... – ThinkADRIAN Aug 27 '14 at 22:23
  • you could create part for changing `role` and make it available just for privileged user, or give access to this part for all users and lose sense – Igor Guzak Aug 28 '14 at 11:19
0

I was able to implement the Roles Based Authorization once I used the corresponding code for the form rather than for Separate Role Model.

Change to the form:

+  <% for role in User::ROLES %>
+    <%= check_box_tag "user[roles][#{role}]", role, @user.roles.include?(role), {:name => "user[roles][]"}%>
+    <%= label_tag "user_roles_#{role}", role.humanize %><br />
+  <% end %>
+  <%= hidden_field_tag "user[roles][]", "" %>

Once the form showed correctly, it would not save changes to the backend. I then had to update the parameter sanitizer in the application controller:

+    devise_parameter_sanitizer.for(:sign_up) {|u| u.permit(:email, :password, :password_confirmation, :current_password, roles: [])}
+    devise_parameter_sanitizer.for(:account_update) {|u| u.permit(:email, :password, :password_confirmation, :current_password, roles: [])}

I guess the key is to take a break and not mix the documentation implementations.

You can see the finished product at: todo.startco.de