16

I have setup a role based access controll system with the following models:

  • Role (as STI),
    • UserRole (global roles)
    • ProjectRole (project specific roles)
  • Assignment (Polymorphic with different resources)
  • User
  • Project (as one resource type for assignments)

Users are only allowed to be responsible for a project if they have a specific UserRole. This Userrole is name "responsible for projects" and has ID 2.

In User model there are two has_many associations :responsible_assignments and responsible_projects. This associations are only valid if the user has the UserRole "responsible for projects" with ID 2.

Is it possible to create a conditional association in user model for responsible_* association and is this a common way to setup this kind of relations?

What is the best practise to solve this kind of problems?

class Role < ActiveRecord::Base
  has_many :assignments
  has_many :users, :through => :assignments

class UserRole < Role

class ProjectRole < Role

class Assignment < ActiveRecord::Base
  belongs_to :user
  belongs_to :role
  belongs_to :resource, :polymorphic => true

class User < ActiveRecord::Base
  has_many :assignments
  has_many :roles, :through => :assignments, 
                   :class_name => "UserRole"
  has_many :responsible_assignments, :class_name => "Assignment",
                                     :conditions => { :role_id => 4 }     // specific project role
  has_many :responsible_projects, :through => :responsible_assignments, 
                                 :source => :resource, 
                                 :source_type => 'Project',
                                 :conditions => { :status => 1 }          // project is active
  ...

class Project < ActiveRecord
  ...
tonymarschall
  • 3,646
  • 3
  • 27
  • 51

3 Answers3

35

In case anyone finds this later - this feature is now actually available in rails 4:

http://guides.rubyonrails.org/association_basics.html

Syntax is:

has_many :orders, -> { where processed: true }
Mikey Hogarth
  • 4,282
  • 6
  • 25
  • 43
  • Thanks a lot! I also needed a custom name to the association, so I was able to do it with class_name: `has_many :processed_orders, -> { where processed: true }, class_name: "Order"` – Ben Jul 27 '20 at 04:43
  • 2
    I do not think this solves the problem of original question, because it allows you to filter by fields of an *associated* objects. But the question's author wanted to make conditional to work only if user's role_id has specific value – Kirill Sep 10 '20 at 18:53
  • this is not the correct answer. OP is looking for something which will cause the association to exist based on a condition. If that condition is fulfilled, only in that case the association should exist otherwise not. – Prime Apr 13 '21 at 13:25
10

You cannot put such conditions in associations. Such things are handled in scopes.

Read http://guides.rubyonrails.org/active_record_querying.html#scopes for more information.

Example for your situation,

You want all assignments (ids) under a user with a specific project role

scope :responsible_users, where('users.role_id = 4')
scope :select_assignment_ids, select('assignments.id')
scope :responsible_assignments, joins(:assignments).responsible_users.select_assignment_ids

You want all projects (ids), under a user with a specific project role, which are active.

scope :active_projects, where('projects.status = 1')
scope :select_project_ids, select('projects.id')
scope :responsible_projects, joins(:assignments => :projects).responsible_users.active_projects.select_project_ids
Rahul
  • 1,827
  • 16
  • 32
4

Those associations are created on loading the model. Your condition is unknown at that time. You can only include the conditions in the associations to filter out unwanted records.

Yanhao
  • 5,134
  • 1
  • 19
  • 14
  • this is the correct answer. Associations at the time of creation do not know the business logic therefore, they cannot be created or prevented if created at that time. They can only be utilised and then filtered based on a condition. – Prime Apr 13 '21 at 13:27