Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Rails how do I build a has_many association that has a scope

I have something like the following:

class Project < ActiveRecord::Base
  has_many :project_people
  has_many :people, :through => :project_people
end

class Person < ActiveRecord::Base
  has_many :project_people
  has_many :projects, :through => :project_people
end

class ProjectPerson < ActiveRecord::Base
  belongs_to :project
  belongs_to :person
  scope :lead, where(:is_lead => true)
  scope :member, where(:is_lead => false)
end

When adding a "lead" ProjectPerson to a new Project, it appears to build correctly, but when calling "@project.project_people" the array is empty:

@project = Project.new
 => #<Project id: nil, name: nil>
@project.project_people.lead.build
 => #<ProjectPerson id: nil, project_id: nil, person_id: nil, is_lead: true>
@project.project_people
 => []

When I try this without the scope, the ProjectPerson shows up in the array:

@project.project_people.build
 => #<ProjectPerson id: nil, project_id: nil, person_id: nil, is_lead: false>
@project.project_people
 => [#<ProjectPerson id: nil, project_id: nil, person_id: nil, is_lead: false>]

How can I get it so that built scoped association records are also included?

UPDATE: This is an old question that's recently gained some attention. Originally I included a simple example of two scopes that use a boolean. A couple of the recent answers (Feb 2014) have focused on my specific examples instead of the actual question. My question was not for the "lead" and "member" scopes specifically (sometimes scopes are a lot more complex than this), but rather, if it's possible to use a scope and then the build method on an ActiveRecord model. I'm hoping I'm wrong, but there currently doesn't seem to be support for this.

like image 868
robertwbradford Avatar asked Jul 14 '11 14:07

robertwbradford


1 Answers

TLDR: Build won't add the built lead to the association until you actually save the built lead.

I've made a simple rails app with the associations for you to check out if you're curious using rails 4.0. https://github.com/TalkativeTree/challenges-learning/tree/master/scope_has_many

In my opinion, I think Member would be a better name that ProjectPerson. That way you could just do Project.first.members and Project.first.members.lead. If you want a project's non-lead members, you could do Project.first.members.where(is_lead: false)

Models:

class Member < ActiveRecord::Base
  belongs_to :project
  belongs_to :person

  scope :lead,   -> { where(is_lead: true) }
end

class Project < ActiveRecord::Base
  has_many :members
  has_many :people, through: :members
end

class Person < ActiveRecord::Base
  has_many :members
  has_many :projects, through: :members
end

and how to create the lead.

> p = Project.new
=> #<Project id: nil, created_at: nil, updated_at: nil>
> p.save
=> true
> p.members
=> []
> p.members.lead.create
=> #<Member id: 1, is_lead: true, person_id: nil, project_id: 1, created_at: "2014-02-16 06:18:59", updated_at: "2014-02-16 06:18:59">
> p
=> #<Project id: 1, created_at: "2014-02-16 06:18:51", updated_at: "2014-02-16 06:18:51">
> p.members.create
=> #<Member id: 2, is_lead: false, person_id: nil, project_id: 1, created_at: "2014-02-16 06:19:07", updated_at: "2014-02-16 06:19:07">
> p.members
=> [#<Member id: 2, is_lead: false, person_id: nil, project_id: 1, created_at: "2014-02-16 06:19:07", updated_at: "2014-02-16 06:19:07">]

Build won't update the association until you actually save the association.

> l = p.members.lead.build
=> #<Member id: nil, is_lead: true, person_id: nil, project_id: 1, created_at: nil, updated_at: nil>
> l
=> #<Member id: nil, is_lead: true, person_id: nil, project_id: 1, created_at: nil, updated_at: nil>
> l.save
=> true
> p.members.lead
=> [#<Member id: 1, is_lead: true, person_id: nil, project_id: 1, created_at: "2014-02-16 06:18:59", updated_at: "2014-02-16 06:18:59">,
 #<Member id: 3, is_lead: true, person_id: nil, project_id: 1, created_at: "2014-02-16 06:23:04", updated_at: "2014-02-16 06:23:04">]
> l2 = p.members.lead.build
=> #<Member id: nil, is_lead: true, person_id: nil, project_id: 1, created_at: nil, updated_at: nil>
> p.members.lead
=> [#<Member id: 1, is_lead: true, person_id: nil, project_id: 1, created_at: "2014-02-16 06:18:59", updated_at: "2014-02-16 06:18:59">,
 #<Member id: 3, is_lead: true, person_id: nil, project_id: 1, created_at: "2014-02-16 06:23:04", updated_at: "2014-02-16 06:23:04">]
> l2.save
=> true
> p.members.lead
=> [#<Member id: 1, is_lead: true, person_id: nil, project_id: 1, created_at: "2014-02-16 06:18:59", updated_at: "2014-02-16 06:18:59">,
 #<Member id: 3, is_lead: true, person_id: nil, project_id: 1, created_at: "2014-02-16 06:23:04", updated_at: "2014-02-16 06:23:04">,
 #<Member id: 4, is_lead: true, person_id: nil, project_id: 1, created_at: "2014-02-16 06:23:34", updated_at: "2014-02-16 06:23:34">]

Also, if your data isn't showing for p, trying reloading the model will reflect the changes to the database.

> p
=> #<Project id: 2, created_at: "2014-02-14 03:21:55", updated_at: "2014-02-14 03:21:55">
> p.members
=> []
> p.reload
=> #<Project id: 2, created_at: "2014-02-14 03:21:55", updated_at: "2014-02-14 03:21:55">
> p.members
=> [#<Member id: 6, is_lead: true, person_id: nil, project_id: 2, created_at: "2014-02-14 03:22:24", updated_at: "2014-02-14 03:22:24">]
like image 116
TalkativeTree Avatar answered Sep 27 '22 21:09

TalkativeTree