Rails 3 has_many: через условия объединения таблиц / область действия

#ruby-on-rails-3 #activerecord #arel

#ruby-on-rails-3 #activerecord #arel

Вопрос:

Я работаю над приложением, которое имеет модели User и Project , и User может быть назначено нескольким Project s через ProjectUser роль (например, разработчик, дизайнер).

 Project
  has_many :project_users
  has_many :users, :through => :project_users

User
  has_many :project_users
  has_many :projects, :through => :project_users

ProjectUser (user_id, project_id, role)
  belongs_to :user
  belongs_to :project
  

Я могу вызвать @project.users и @user.projects , но поскольку существуют разные роли, я хотел бы быть немного более конкретным в отношениях. В идеале я хочу иметь возможность выполнять следующее:

 @project.developers
  # returns @project.users, but only where ProjectUser.role = 'Developer'

@project.designers << @user
  # creates a ProjectUser for @project, @user with role 'Designer'

@user.development_projects
  # returns projects where @user is assigned as a 'Developer'

@user.design_projects << @project
  # creates a ProjectUser for @project, @user with role 'Designer'
  

В настоящее время у меня есть следующий код:

 has_many :developers, :through => :project_users, :source => :user,
                      :class_name => "User",
                      :conditions => ['project_users.role = ?','Developer']
  

Но это действительно выполняет выборку только в одну сторону и не дает мне ничего другого — я не могу построить или назначить или что-то еще.

Я пытаюсь использовать более сложную логику, которая, как мне кажется, может сработать, но был бы признателен за некоторые указания:

 has_many :developer_assignments, :source => :project_user,
                                 :conditions => { :role => 'Developer' }
has_many :developers, :through => :developer_assignments # class_name?
  

Есть предложения? Спасибо!

Ответ №1:

has_many принимает блок, который может определять / переопределять методы для ассоциации. Это позволит вам создать пользовательский метод для << . Я создал для вас небольшой пример, вы могли бы создать сборку аналогичным образом.

 # Project.rb
has_many :developers, :through => :project_users, :source => :user,
         :conditions => "project_users.role = 'developer'" do
         def <<(developer)
           proxy_owner.project_users.create(:role => 'developer', :user => developer)
         end
       end
  

Теперь вы можете добавить нового разработчика в свой проект с помощью: @project.developers << @user по запросу. @project.developers предоставляет вам всех разработчиков.

Если у вас много ролей, может быть полезно создавать эти операторы has_many динамически.

 # Project.rb
ROLES = ['developer','contractor']

ROLES.each do |role|         
  self.class_eval <<-eos
    has_many :#{role.downcase}s, :through => :project_users, :source => :user,
           :conditions => "project_users.role = '#{role}'" do
             def <<(user)
               proxy_owner.project_users.create(:role => '#{role}', :user => user)
             end
           end
  eos
end
  

Оглядываясь назад на все вышесказанное, это не похоже на способ выполнения rails. Это должно позволить заставить команды сборки и создания работать без переопределения всего.

Надеюсь, это поможет!

Комментарии:

1. Спасибо за ваш ответ. Это решает мою проблему, но делает это не так, как я надеялся, — используя области видимости в ProjectUsers модели. Объявление :conditions => "project_users.role = '#{role}'" кажется не очень рельсовым, так как мне хотелось бы вызвать что-то вроде :conditions => { :scope => :developer } . Я все еще уверен, что это как-то возможно. В любом случае, я присуждаю вам награду за ваши усилия, хотя этот ответ не будет отмечен как правильный. Спасибо за ваш вклад!

2. Спасибо. Я действительно ожидал, что has_many :designers, :through => :project_users, :source => :user, :conditions => {:project_users => {:role => :designer}} будет работать автоматически, но, по-видимому, хэши вложенных условий не ограничены с помощью build и create. Это было лучшее, что я мог придумать.

3. что proxy_owner в приведенном выше? является ли эта ссылка другой стороной ассоциации, аналогичной proxy_association той, которую вы получаете с :extend ?

4. proxy_owner относится к экземпляру (в данном случае) Project объекта.

Ответ №2:

Похоже, то, что вы ищете, представляет собой комбинацию наследования одной таблицы RoR и именованных областей.

Взгляните на следующую статью для получения хорошего примера о полиморфных ассоциациях. Это должно помочь вам в достижении следующего:

 @project.developers
  # returns @project.users, but only where ProjectUser.role = 'Developer'

@project.designers << @user
  # creates a ProjectUser for @project, @user with role 'Designer'
  

Области видимости дадут вам простой способ реализации, @user.development_projects но для получения << оператора может потребоваться больше хитрости.

Ответ №3:

Вы уже пробовали использовать области? Это не позволяет вам делать <<. Но это упрощает выполнение запросов.

Попробуйте:

 Project
  scope :developers, lambda {
    includes(:project_users).where("project_users.role = ?", "developer")
  }
  

Вы сможете заставить всех разработчиков использовать: @project.developers