在Rails中如何构建具有范围的has_many关联

时间:2011-07-14 14:38:46

标签: ruby-on-rails build scope has-many

我有以下内容:

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

将“引导”ProjectPerson添加到新项目时,它似乎构建正确,但在调用“@ project.project_people”时,数组为空:

@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
 => []

当我在没有范围的情况下尝试此操作时,ProjectPerson将显示在数组中:

@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>]

如何获取它以便包含构建的范围关联记录?

更新:这是一个老问题,最近引起了一些关注。最初我包含了一个使用布尔值的两个范围的简单示例。最近的几个答案(2014年2月)专注于我的具体例子,而不是实际的问题。我的问题不是专门用于“引导”和“成员”范围(有时范围比这要复杂得多),而是,如果可以在ActiveRecord模型上使用范围然后使用build方法。我希望我错了,但目前似乎没有对此表示支持。

3 个答案:

答案 0 :(得分:5)

TLDR:在您实际保存构建的潜在客户之前,Build不会将建立的潜在客户添加到关联。

我已经制作了一个简单的rails应用程序,您可以查看是否对使用rails 4.0感到好奇。 https://github.com/TalkativeTree/challenges-learning/tree/master/scope_has_many

在我看来,我认为会员将是ProjectPerson更好的名字。这样你就可以Project.first.membersProject.first.members.lead。如果您想要一个项目的非主要成员,您可以Project.first.members.where(is_lead: false)

型号:

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

以及如何创建潜在客户。

> 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不会更新关联。

> 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">]

此外,如果您的数据未显示p,则尝试重新加载模型将反映对数据库的更改。

> 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">]

答案 1 :(得分:2)

您可以这样做,但它往往会在您的模型上产生许多关联。

如果您正在使用Rails 3(请参阅Rails 4版本更远):

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

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

class Project < ActiveRecord::Base
  has_many :people_projects_as_lead, conditions: { is_lead: true }, class_name: 'PeopleProject'
  has_many :people_projects_as_member, conditions: { is_lead: false }, class_name: 'PeopleProject'

  has_many :leads, through: :people_projects_as_lead, source: :person
  has_many :members, through: :people_projects_as_member, source: :person

  has_many :people_projects
  has_many :people, through: :people_projects
end

class Person < ActiveRecord::Base
  has_many :people_projects_as_lead, conditions: { is_lead: true }, class_name: 'PeopleProject'
  has_many :people_projects_as_member, conditions: { is_lead: false }, class_name: 'PeopleProject'

  has_many :lead_projects, through: :people_projects_as_lead, source: :project
  has_many :member_projects, through: :people_projects_as_member, source: :project

  has_many :people_projects
  has_many :projects, through: :people_projects
end

通过此设置,执行@project.people_projects_as_lead.build将执行您期望的操作。附加关联名称是否增加清晰度或删除它几乎取决于您的问题域。

上面conditions与范围之间的重复并不是那么好。 Rails 4可以避免重复的条件:

class Project < ActiveRecord::Base
  has_many :people_projects_as_lead, -> { lead }, class_name: 'PeopleProject'
  has_many :people_projects_as_member, -> { member }, class_name: 'PeopleProject'

  has_many :leads, through: :people_projects_as_lead, source: :person
  has_many :members, through: :people_projects_as_member, source: :person

  has_many :people_projects
  has_many :people, through: :people_projects
end

class Person < ActiveRecord::Base
  has_many :people_projects_as_lead, -> { lead }, class_name: 'PeopleProject'
  has_many :people_projects_as_member, -> { member }, class_name 'PeopleProject'

  has_many :lead_projects, through: :people_projects_as_lead, source: :project
  has_many :member_projects, through: :people_projects_as_member, source: :project

  has_many :people_projects
  has_many :projects, through: :people_projects
end

注意:您可能需要其他inverse_of个选项,以确保所有内容都能正确保存,尤其是Project / PersonPeopleProject之间的关系。如果使用此代码正确设置了所有内容,您将能够执行@project.leads << some_person之类的操作并正确构建连接记录。

答案 2 :(得分:1)

我不认为范围和构建是为了协同工作。范围用于搜索和构建是用于构建/创建新的关联记录。

# this should do the trick
@project.project_people.build(:is_lead=>true)