В Rails, как мне построить ассоциацию has_many, которая имеет область видимости - PullRequest
9 голосов
/ 14 июля 2011

У меня есть что-то вроде следующего:

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) были сосредоточены на моих конкретных примерах, а не на самом вопросе. Мой вопрос касался не областей «ведущий» и «член» (иногда области гораздо более сложные, чем эта), а скорее, если возможно использовать область, а затем метод build в модели ActiveRecord. Я надеюсь, что ошибаюсь, но в настоящее время, похоже, нет поддержки для этого.

Ответы [ 3 ]

5 голосов
/ 14 февраля 2014

TLDR: Сборка не добавит встроенное преимущество в ассоциацию, пока вы не сохраните встроенное преимущество.

Я сделал простое приложение rails с ассоциациями, чтобы вы могли проверить, если вылюбопытно с помощью рельсов 4.0.https://github.com/TalkativeTree/challenges-learning/tree/master/scope_has_many

На мой взгляд, я думаю, что Member будет лучшим именем, чем ProjectPerson.Таким образом, вы можете просто сделать Project.first.members и Project.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">]

Сборкане будет обновлять связь до тех пор, пока вы не сохраните ее.

> 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">]
2 голосов
/ 16 февраля 2014

Вы можете сделать это, но это приводит к МНОЖЕСТВУ ассоциаций на ваших моделях.

Если вы используете 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 / Personи PeopleProject.Если с этим кодом все настроено правильно, вы сможете делать что-то вроде @project.leads << some_person и правильно создавать запись соединения.

1 голос
/ 14 июля 2011

Я не думаю, что области видимости и сборка предназначены для совместной работы.Области предназначены для поиска, а сборка - для создания / создания новых связанных записей.

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