matiss matiss - 1 month ago 14
Ruby Question

Rails 5: check if role ID belongs to user belonging to current_user (through association)

In my controllers/common/roles_controller.rb I would like to check if particular role (ID) belongs to current_user company users and if not, redirect to errors_path:

def correct_role
role_user = Role.where(:id => params[:id]).select('user_id').first
company_user = current_user.companies.includes(:users)
redirect_to(errors_path) unless company_user.include? role_user.id
end


Definitions:


  • role_user
    - finds user ID for particular role ID ("user_id" is column of roles table)

  • company_user
    - finds all user ID who belong to companies, which belong to current_user



models/role.rb

belongs_to :user, optional: true, inverse_of: :roles
accepts_nested_attributes_for :user

enum general: { seller: 1, buyer: 2, seller_buyer: 3}, _suffix: true
enum dashboard: { denied: 0, viewer: 1, editer: 2, creater: 3, deleter: 4}, _suffix: true


models/user.rb

#User has roles
has_many :roles
accepts_nested_attributes_for :roles, reject_if: proc { |attributes| attributes[:name].blank? }

# User has many companies
has_many :accounts, dependent: :destroy
has_many :companies, through: :accounts


models/account.rb

class Account < ApplicationRecord
belongs_to :company
belongs_to :user
accepts_nested_attributes_for :company, :user
end


models/company.rb

has_many :accounts, dependent: :destroy
has_many :users, through: :accounts


At the moment with
role_user
and
company_user
I can find both ID, however I cannot do the checking part. How do I do that correctly, please? Thank you for any help!

Update

@sajan code give this in console when I open /common/roles/1/edit (current_user ID=1 and should be allowed to edit):

Parameters: {"id"=>"1"}
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
(0.6ms) SELECT COUNT(*) FROM "companies" INNER JOIN "accounts" ON "companies"."id" = "accounts"."company_id" WHERE "accounts"."user_id" = ? [["user_id", 1]]
(0.3ms) SELECT "roles".id FROM "roles" WHERE "roles"."user_id" = ? [["user_id", 1]]


@Abhishek Kumar code in console gives:

Parameters: {"id"=>"1"}
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
Company Load (0.5ms) SELECT "companies".* FROM "companies" INNER JOIN "accounts" ON "companies"."id" = "accounts"."company_id" WHERE "accounts"."user_id" = ? [["user_id", 1]]
(0.4ms) SELECT "users".id FROM "users" INNER JOIN "accounts" ON "users"."id" = "accounts"."user_id" WHERE "accounts"."company_id" = ? [["company_id", 13]]
Role Load (0.2ms) SELECT "roles"."user_id" FROM "roles" WHERE "roles"."id" = ? ORDER BY "roles"."id" ASC LIMIT ? [["id", 1], ["LIMIT", 1]]


Update v2

So I'm trying to use this code:

def correct_role
company_user_ids = current_user.companies.map(&:user_ids)
role_user = Role.where(:id => params[:id]).select('user_id').first
unless role_user.user_id.in?(company_user_ids)
redirect_to(errors_path)
end
end


however it redirects to errors_path in any case, this is what I have in console:

Parameters: {"id"=>"1"}
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
Company Load (0.5ms) SELECT "companies".* FROM "companies" INNER JOIN "accounts" ON "companies"."id" = "accounts"."company_id" WHERE "accounts"."user_id" = ? [["user_id", 1]]
(0.4ms) SELECT "users".id FROM "users" INNER JOIN "accounts" ON "users"."id" = "accounts"."user_id" WHERE "accounts"."company_id" = ? [["company_id", 13]]
Role Load (0.3ms) SELECT "roles"."user_id" FROM "roles" WHERE "roles"."id" = ? ORDER BY "roles"."id" ASC LIMIT ? [["id", 1], ["LIMIT", 1]]

Answer

It seems that a better logic would be to define on the model for the current_user an association through companies and users to roles.

Then you can check:

def correct_role
  redirect_to(errors_path) unless current_user.company_user_roles.where(id: params[:id]).exists?
end

It would be a single SQL statement that would return zero or one rows, and execute very quickly with the appropriate indexes in place.

Edit:

To company.rb, add:

has_many :user_roles, through: :users, source: :roles

To user.rb (assuming that current_user is an instance of this model) add:

has_many :company_user_roles, through: :companies, source: :user_roles
Comments