0

I am trying to make an app with Rails 4. I use, Devise, Rolify and Simple Form.

My current problem is in trying to assign rolify roles to users.

I have the following code:

User.rb

def self.find_for_oauth(auth, signed_in_resource = nil)
    # Get the identity and user if they exist
    identity = Identity.find_for_oauth(auth)

    # If a signed_in_resource is provided it always overrides the existing user
    # to prevent the identity being locked with accidentally created accounts.
    # Note that this may leave zombie accounts (with no associated identity) which
    # can be cleaned up at a later date.
    user = signed_in_resource ? signed_in_resource : identity.user

    # p '11111'

    # Create the user if needed
    if user.nil?
      # p 22222
      # Get the existing user by email if the provider gives us a verified email.
      # If no verified email was provided we assign a temporary email and ask the
      # user to verify it on the next step via UsersController.finish_signup
      email_is_verified = auth.info.email && (auth.info.verified || auth.info.verified_email)
      email = auth.info.email
      user = User.where(:email => email).first if email

      # Create the user if it's a new registration
      if user.nil?
        # p 33333
        user = User.new(
          # at least one problem with this is that each provider uses different terms to desribe first name/last name/email. See notes on linkedin above
          first_name: auth.info.first_name,
          last_name: auth.info.last_name,
          email: email,
          #username: auth.info.nickname || auth.uid,
          password: Devise.friendly_token[0,20])
# 
        # debugger

        # if email_is_verified
        #   user.skip_confirmation!
        # end
        user.skip_confirmation! 

        user.save!
      end
    end

    # Associate the identity with the user if needed
    if identity.user != user
      identity.user = user
      identity.save!
    end
    user
  end

  def email_verified?
    self.email && TEMP_EMAIL_REGEX =~ self.email
  end



    def full_name
        [*first_name.capitalize, last_name.capitalize].join(" ")
    end

after_create :add_default_role


  def add_default_role
    add_role(:pending) if self.roles.blank?
  end

Role.rb

class Role < ActiveRecord::Base
  has_and_belongs_to_many :users, :join_table => :users_roles
  belongs_to :resource, :polymorphic => true

  validates :resource_type,
            :inclusion => { :in => Rolify.resource_types },
            :allow_nil => true

  scopify
end

Users/omniauth_callbacks_controller

def self.provides_callback_for(provider)
    class_eval %Q{
      def #{provider}
        @user = User.find_for_oauth(env["omniauth.auth"])

        if @user.persisted?
           sign_in_and_redirect @user,  event: :authentication

          set_flash_message(:notice, :success, kind: "#{provider}".capitalize) if is_navigational_format?
        else
          session["devise.#{provider}_data"] = env["omniauth.auth"]
          redirect_to new_user_registration_url
        end
      end
    }
  end

          # sign_in_and_redirect_user(:user, event: :authentication)


  [:twitter, :facebook, :linkedin, :google_oauth2].each do |provider|
    provides_callback_for provider
  end

  def after_sign_in_path_for(resource)
    if resource.email_verified?
      super resource
    else
      finish_signup_path(resource)
    end
  end 

Users controller

private
    def set_user
      @user = User.find(params[:id])
    end

    def user_params
      accessible = [ :first_name, :last_name, :email, {role_ids: []}] # extend with your own params
      accessible << [ :password, :password_confirmation ] unless params[:user][:password].blank?
      accessible << [:approved] if user.admin
      params.require(:user).permit(accessible)
    end

Users#index

<% Users.each do |user| %>
      <div class="row">
            <div class="col-xs-5">
                <%= link_to "#{user.full_name}", user %>
            </div>
            <div class="col-xs-5">
                <%= link_to "#{user.email}", user %>
            </div>
            <div class="col-xs-2">
                 <%= link_to "edit", edit_user_path(user) %>
            </div>
    </div> 
    <% end %> 

Users#form

<%= simple_form_for(@user) do |f| %>
            <%= f.error_notification %>
              <% Role.all.each do |role| %>
              <div class="form-inputs">

              <%= f.input "user[role_ids][]", role.id, collection: @user.role_ids.include?(role.id) %>
              <%= role.name %>

              </div>

              <div class="form-actions">
                <%= f.button :submit, "Add role", :class => 'formsubmit' %>
              </div>

I have also tried:

<%= f.association :roles %>
<%= role.name %>

in the user#form Migration to add roles to role table:

class AddRolesToRolifyTable < ActiveRecord::Migration
  def change

    ['admin', # internal  admin
     'manager', # internal  manager
     'editor', # internal  web content editor
     'support', # internal  user support
     'educator', # lecturers
     'faculty_manager', #manage the business side 
     'project_manager', 
     'pending', # new people that are not yet confirmed in a role - default role assignment

     ].each do |role_name|
      Role.create! name: role_name
  end

  end
end

When I save this and try to run the local host and go to users#index, I get an error that says:

 Couldn't find User with 'id'=

This method is highlighted:

 private
    def set_user
      @user = User.find(params[:id])
    end

I can't say I've properly understood how rolify works with devise. My console shows that I have two test users in the db, each of which has an id (so im not sure how to explore this error further). Does anyone see where I've gone wrong?

I have adapted this setup using advice in this post:

Defining Roles with Rolify

Community
  • 1
  • 1
Mel
  • 2,481
  • 26
  • 113
  • 273
  • By the way, is it neccesary to do all this stuff when you can define a variable in devise like "t.string permission_level" and add the role you want? – karlihnos May 09 '16 at 13:56

1 Answers1

1

Rolify and Devise

I can't say I've properly understood how rolify works with devise.

Rolify and Devise do very different jobs. There is no actual integration between the two.

Devise is an authentication solution, authentication is only about determining if there is a signed in user and if that user is who he/she claims to be.

Authorization on the other hand is about who is allowed to do what in a system. Rolify is a generic roles library which is meant to be used as a smaller part of an authentication solution. Just defining your authorization with .has_role? is going to get very messy quick.

It is most often combined with something like CanCanCan or Pundit which provide facilities to define authentication rules.

Collection helpers

Rails has built in helpers for creating selects and checkboxes for associations and simple_form takes it a bit further.

So lets say you have a users/:user_id/edit route where admins edit a users profile and add roles:

<%= simple_form_for(@user) do |f| %>
  <fieldset>
    <legend>Roles</legend>
    <%= f.association :roles, as: :check_boxes, collection: Role.all %>
  </fieldset>
  <% f.submit %>
<% end %>

Note that you don't need a special form for editing the roles in this case - this will work both when creating and editing users.

Your params sanitation is bit off:

def user_params
  accessible = [ :first_name, :last_name, :email, role_ids: []]
  accessible << [ :password, :password_confirmation ] unless params[:user][:password].blank?
  accessible << [:approved] if user.admin? # don't do this. There are better ways.
  params.require(:user).permit(accessible)
end

Also:

Migrations are for altering the database schema, not for seeding the system with default info. Use rake db:seeds or create your own rake task instead.

I would also question if you really need a pending role and the approved param. I would simply check if the user has no roles - which defacto means that he is pending. When a admin or peer verifies a user you would add the approved role.

max
  • 96,212
  • 14
  • 104
  • 165
  • Hi Max, approved is not a role. It's an attribute on the user model which confirms the user is approved in the system, They next get a role. I don't understand a few things you've suggested. First, why have you made a comment about seeds? I don't understand the purpose of doing this. Second, you say that approved is not a useful param - why not? BTW- I use pundit for authorisations. – Mel Dec 13 '15 at 05:25
  • "First, why have you made a comment about seeds?" - because using migrations to seed the system can lead to all kinds of headaches and data loss. – max Dec 13 '15 at 05:28
  • "Hi Max, approved is not a role. It's an attribute on the user model which confirms the user is approved in the system, They next get a role.". If you have a role based authentication system why not use it instead? Adding one more heuristic on the side does not really make that much sense. Your just making things more complicated in the long run. – max Dec 13 '15 at 05:29
  • What sort of problems are caused by using migrations to define roles? Also, why isn't 'approved' a useful param. It's useful in my app because roles will change from time to time but users are constant. I have a use case for keeping them separate – Mel Dec 13 '15 at 05:32
  • Basically using migrations to insert data is frowned upon because its not really reversible and its not what migrations are for anyways. There is a separate mechanism for that. – max Dec 13 '15 at 05:36
  • "It's useful in my app because roles will change from time to time but users are constant. I have a use case for keeping them separate". If you insist... But adding boolean flags usually is an anti-pattern. – max Dec 13 '15 at 05:37
  • And I think you are missing the point of having a cascading roles system. – max Dec 13 '15 at 05:39
  • accessible << [:approved] if user.admin? # don't do this. There are better ways. It would be better if you propose a solution and gives any argumentation about this since in cancancan they prmote this practice. – karlihnos May 09 '16 at 13:54