Как объединить ассоциации ActiveRecord в Rails 3?

У меня есть проект Rails 3. С Rails 3 появился Arel и возможность повторного использования одной области для создания другой. Мне интересно, есть ли способ использования областей при определении отношений (например, "has_many" ).

У меня есть записи, у которых есть столбцы разрешений. Я хотел бы создать default_scope, который учитывает мои столбцы разрешений, чтобы отфильтровать записи (даже те, которые доступны через отношение).

В настоящее время в Rails 3 default_scope (включая найденные исправления) не предоставляет работоспособного способа передачи proc (который мне нужен для поздней привязки переменных). Можно ли определить has_many, в который может быть передан именованный объект?

Идея повторного использования именованного поля будет выглядеть так:

Orders.scope :my_orders, lambda{where(:user_id => User.current_user.id)}
has_many :orders, :scope => Orders.my_orders

Или неявное кодирование, что именованная область в отношении будет выглядеть так:

has_many :orders, :scope => lambda{where(:user_id => User.current_user.id)}

Я просто пытаюсь применить default_scope с поздним связыванием. Я бы предпочел использовать подход Arel (если он есть), но будет использовать любой работоспособный вариант.

Поскольку я имею в виду текущего пользователя, я не могу полагаться на условия, которые не оцениваются в последний момент, например:

has_many :orders, :conditions => ["user_id = ?", User.current_user.id]

Ответ 1

Я предлагаю вам взглянуть на "Именованные области мертвы"

Автор объясняет, насколько мощным является Arel:)

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

РЕДАКТИРОВАТЬ № 1 Март 2014

Как говорится в некоторых комментариях, разница теперь зависит от личного вкуса.

Тем не менее, я по-прежнему лично рекомендую избегать раскрытия области Arel на верхнем уровне (будучи контроллером или чем-либо еще, напрямую обращающимся к моделям), и для этого потребуется:

  • Создайте область действия и выложите ее через метод в вашей модели. Этот метод будет тем, который вы выставляете контроллеру;
  • Если вы никогда не выставляете свои модели вашим контроллерам (у вас есть какой-то сервисный уровень поверх них), тогда вы в порядке. Уровень защиты от коррупции - это ваш сервис, и он может получить доступ к области вашей модели, не беспокоясь о том, как реализуются области применения.

Ответ 2

Как насчет расширений?

class Item < ActiveRecord::Base
  has_many :orders do
    def for_user(user_id)
      where(user_id: user_id)
    end
  end
end

Item.first.orders.for_user(current_user)

ОБНОВЛЕНИЕ: я хотел бы указать на преимущество расширений ассоциаций, в отличие от методов класса или областей, что у вас есть доступ к внутренним элементам прокси-сервера ассоциации:

proxy_association.owner возвращает объект, частью которого является ассоциация. proxy_association.reflection возвращает объект отражения, который описывает ассоциацию. proxy_association.target возвращает связанный объект для belongs_to или has_one или коллекцию связанных объектов для has_many или has_and_belongs_to_many.

Подробнее здесь: http://guides.rubyonrails.org/association_basics.html#association-extensions

Ответ 3

Вместо областей, которые я только что определял класс-методы, которые отлично работают

def self.age0 do
  where("blah")
end

Ответ 4

Я использую что-то вроде:

class Invoice < ActiveRecord::Base
  scope :aged_0,  lambda{ where("created_at IS NULL OR created_at < ?", Date.today + 30.days).joins(:owner) }
end 

Ответ 5

Вы можете использовать метод merge, чтобы объединить области из разных моделей. Для более подробной информации найдите слияние в railscast

Ответ 6

Если вы просто пытаетесь получить пользовательские заказы, почему бы вам просто не использовать отношения?

Предполагая, что текущий пользователь доступен из метода current_user в вашем контроллере:

@my_orders = current_user.orders

Это гарантирует, что будут показаны только определенные пользователем заказы. Вы также можете делать произвольно вложенные соединения для получения более глубоких ресурсов с помощью joins

current_user.orders.joins(:level1 => { :level2 => :level3 }).where('level3s.id' => X)