Как объединить ассоциации 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)