Is it possible to create a conditional association in model?
C

4

18

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
  ...
Consequently answered 4/3, 2012 at 15:31 Comment(2)
What do you mean by a conditional association? What're the conditions?Thumbscrew
The condition is: if a user does not have a role with id 2, responsible_* associations are not valid / should not be set.Consequently
D
11

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
Deoxygenate answered 8/3, 2012 at 5:40 Comment(0)
S
36

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 }
Spitfire answered 21/5, 2015 at 10:37 Comment(3)
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"Sensate
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 valueHarbinger
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.Archiphoneme
D
11

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
Deoxygenate answered 8/3, 2012 at 5:40 Comment(0)
T
5

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.

Thumbscrew answered 8/3, 2012 at 4:5 Comment(1)
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.Archiphoneme
H
1

I think even from Rails 4.0 onwards, but definitely after 5, you could use the lambda like this:

has_many :orders, ->(self_obj) { where('2 = ?', self_obj.role_id }

and more generally:

has_many :children, ->(parent) { where('true = ?', parent.should_have_children? }

Not sure of the implications of such solution (the parent will conditionally not see the children, so destroy callbacks won't be called if needed, etc)

Haggle answered 1/7, 2021 at 19:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.