Уровень
Давайте поговорим о наиболее распространенных типах ассоциаций, с которыми мы сталкиваемся.
У меня есть пользователь, который :has_many
опубликовал(а)
class User < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :user
end
Постановка задачи
Я хочу сделать некоторую (очень легкую и быструю) обработку всех сообщений пользователя. Я ищу лучший способ структурировать свой код для достижения этой цели. Ниже приведены несколько способов и почему они работают или не работают.
Способ 1
Сделайте это в самом классе User
.
class User < ActiveRecord::Base
has_many :posts
def process_posts
posts.each do |post|
# code of whatever 'process' does to posts of this user
end
end
end
Почтовый класс остается прежним:
class Post < ActiveRecord::Base
belongs_to :user
end
Метод называется так:
User.find(1).process_posts
Почему это не лучший способ сделать это
Логика действий с сообщениями пользователя действительно должна принадлежать классу Post. В реальном мире пользователь также может иметь отношения :has_many
со многими другими классами, например. orders
, comments
, children
и т. д.
Если мы начнем добавлять похожие методы process_orders
, process_comments
, process_children
(угу) в класс User, это приведет к одному гигантскому файлу с большим количеством кода, большая часть которого может (и должна) быть распределена туда, где она принадлежит, то есть в целевые ассоциации.
Способ 2
Прокси-ассоциации и области действия
Обе эти конструкции требуют добавления методов/кода в класс User, что снова делает его раздутым. Я бы предпочел, чтобы вся реализация была перенесена на целевые классы.
Способ 3
Метод класса для целевого класса
Создайте методы класса в целевом классе и вызовите эти методы для объекта User.
class User < ActiveRecord::Base
has_many :comments
# all target specific code in target classes
end
class Post < ActiveRecord::Base
belongs_to :user
# Class method
def self.process
Post.all.each do |post| # see Note 2 below
# code of whatever 'process' does to posts of this user
end
end
end
Метод называется так:
User.find(1).posts.process # See Note 1 below
Теперь это выглядит лучше, чем методы 1 и 2, потому что:
- Пользовательская модель остается свободной от беспорядка.
- Функция процесса называется
process
вместоprocess_posts
. Теперь мы можем иметьprocess
и для других классов и вызывать их как:User.find(1).orders.process
и т. д. вместоUser.find(1).process_orders
(Метод 1).
Примечание 1:
Да, вы можете вызвать такой метод класса в ассоциации. Почему читайте здесь. TL;DR заключается в том, что User.find(1).posts
возвращает объект CollectionProxy, который имеет доступ к методам класса целевого (Post) класса. Он также удобно передает scope_attributes
, в котором хранится user_id пользователя, вызвавшего posts.process
. Это удобно. См. примечание 2 ниже.
Примечание 2:
Для тех, кто не знает, что происходит, когда мы выполняем Post.all.each
в методе класса, он возвращает все сообщения пользователя, для которого был вызван этот метод, а не все сообщения в базе данных.
Поэтому при вызове как User.find(99).posts.process
Post.all
выполняет:
SELECT "notes".* FROM "posts" WHERE "posts"."user_id" = $1 [["user_id", 99]]
которые являются всеми сообщениями для идентификатора пользователя: 99.
Согласно комментарию @Jesuspc ниже, Post.all.each
можно кратко записать как all.each
. Это более идиоматично и не выглядит так, как будто мы запрашиваем все сообщения в базе данных.
Ответ, который я ищу
- Объясняет, как лучше всего обращаться с такими ассоциациями. Как люди обычно это делают? и есть ли какие-либо очевидные конструктивные недостатки в методе 3.
(very light and quick) processing
в вопросе, чтобы указать, что, возможно, несколько строк кода не могут полностью гарантировать новый класс. Тем не менее, хорошо, что вы указали на Четвертый способ. 06.01.2016