【问题标题】:In Rails how do I build a has_many association that has a scope在 Rails 中,我如何构建具有范围的 has_many 关联
【发布时间】:2011-10-05 10:02:34
【问题描述】:

我有类似以下内容:

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

将“lead” 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 月)集中在我的具体示例而不是实际问题上。我的问题不是专门针对“lead”和“member”作用域(有时作用域比这复杂得多),而是如果可以使用作用域,然后在 ActiveRecord 模型上使用 build 方法。我希望我错了,但目前似乎没有对此的支持。

【问题讨论】:

  • 刚刚也得到了这个——如果能在示波器上工作会非常好!
  • 只是一个观察结果,但是由于您在布尔值上进行操作,因此如果您将 default: false 添加到数据库架构中,则可以避免使用第二个范围is_lead 列。从本质上讲,您只是选择该人退出默认案例,这将更容易维护,并且您不必检查空白或零案例。

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


【解决方案1】:

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

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

在我看来,我认为 Member 会比 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">]

【讨论】:

  • 字母约定仅适用于 habtm 关系,因为问题显然是使用不是这种情况的连接模型。
  • 是的,你对 habtm 的看法绝对正确。它看起来很像我写它时的名字。决定修改为更好的建议。
  • 感谢您的回答,即使答案是“不可能” :-)
  • 这是可能的。您只需保存它即可从 Project 模型中访问它。如果你做了lead = Project.project_person.lead.build,那么你可以在构建的lead 上工作,但你必须保存lead 才能显示project.project_person.lead
【解决方案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 &lt;&lt; some_person 之类的操作并正确构建连接记录。

【讨论】:

  • 使用 rails 4 版本;这对我不起作用:使用@project.people_projects_as_lead.new,对于未保存的新项目,所有内容 (leads, members, peope) 始终为空。对于@project.leads &lt;&lt; Person.new,只有@project.leads 有一个条目,但@project.people 是空的。请记住:仅谈论未保存的记录。
  • @markus - 手动添加连接记录(到 people_projects 关系)不会填写 people 中的相应值。在第二种情况下,ActiveRecord (通常)不可能确定添加到一个关联(潜在客户)的记录实际上会出现在另一个(人)中,而无需访问数据库。
【解决方案3】:

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

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

【讨论】:

  • 感谢您的回复。本节的最后一个示例 (api.rubyonrails.org/classes/ActiveRecord/NamedScope/…) 指出“范围也可以在创建/构建记录时使用。”但是代码示例不包含任何关联。嗯,我仍然想知道这是否可能......我上面的示例很简单,因为作用域只修改一个属性,但这对于更复杂的作用域/链接很有用。有什么想法吗?
  • 您对有用性的看法绝对正确!您拥有的代码将是一个简单的用例,并且由于它不起作用,我认为它不适用于关联。抱歉,我没办法了!
猜你喜欢
  • 2023-04-10
  • 2011-05-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-05
相关资源
最近更新 更多