Как вы определяете границы ассоциаций ActiveRecord в Rails 3?
-
18-09-2019 - |
Вопрос
У меня есть проект Rails 3.С Rails 3 появился Arel и возможность повторно использовать одну область видимости для создания другой.Мне интересно, есть ли способ использовать области при определении отношений (например,«has_many»).
У меня есть записи со столбцами разрешений.Я хотел бы создать default_scope, который учитывает мои столбцы разрешений, чтобы фильтровать записи (даже те, к которым доступ осуществляется через отношения).
В настоящее время в Rails 3 default_scope (включая найденные мной патчи) не предоставляет работоспособных средств передачи процедуры (которая мне нужна для позднего связывания переменных).Можно ли определить 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, март 2014 г.
Как утверждают некоторые комментарии, разница теперь является вопросом личного вкуса.
Тем не менее, я по-прежнему лично рекомендую избегать раскрытия области действия Arel верхнему уровню (будучи контроллером или чем-то еще, имеющим прямой доступ к моделям), и для этого потребуется:
- Создайте область действия и откройте ее с помощью метода в вашей модели.Этот метод будет тем, который вы предоставляете контроллеру;
- Если вы никогда не предоставляете свои модели контроллерам (поэтому у вас есть какой-то сервисный уровень поверх них), тогда все в порядке.Антикоррупционный слой является ваш сервис, и он сможет получить доступ к области вашей модели, не слишком беспокоясь о том, как реализованы области.
Другие советы
Как насчет расширений ассоциации?
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 возвращает связанный объект для own_to или has_one или коллекцию связанных объектов для has_many или has_and_belongs_to_many.
Более подробная информация здесь: http://guides.rubyonrails.org/association_basics.html#association-extensions
Вместо областей видимости я просто определял методы класса, и это отлично работало.
def self.age0 do
where("blah")
end
Я использую что-то вроде:
class Invoice < ActiveRecord::Base
scope :aged_0, lambda{ where("created_at IS NULL OR created_at < ?", Date.today + 30.days).joins(:owner) }
end
Вы можете использовать слить метод для объединения областей действия из разных моделей.Для получения более подробной информации найдите слияние в этом рельскаст
Если вы просто пытаетесь получить заказы пользователя, почему бы вам просто не использовать эту связь?
Предполагая, что текущий пользователь доступен из метода current_user в вашем контроллере:
@my_orders = current_user.orders
Это гарантирует, что будут показаны только конкретные заказы пользователя.Вы также можете выполнять произвольные вложенные соединения, чтобы получить более глубокие ресурсы, используя joins
current_user.orders.joins(:level1 => { :level2 => :level3 }).where('level3s.id' => X)