Не прошло и полугода с моей последней более-менее пристойной заметки :) Надо сказать, последние несколько месяцев выдались настолько насыщенные, что даже не было желания что-либо такое интересное написать – все мозги уходили на разработку действительно классных вещей. Однако сегодня, наконец, пришло понимание, что дальше так продолжаться не может – пора делиться мыслями, бешено роящимися в голове.
Давайте немного поговорим об ассоциациях. Если вы программируете на Rails дольше 15 минут, то уже наверняка знаете, что рельсы поддерживают несколько типов ассоциаций – belongs_to, has_one, has_many и has_and_belongs_to_many. Каждый тип ассоциаций решает определенную задачу, у каждого свой API. Но есть у них одна общая замечательная особенность – ассоциации можно расширять.
Допустим, у нас есть проект “Доска объявлений”, где пользователь может опубликовать до 5 объявлений каждое стоимостью 10 долларов. Давайте взглянем на модель пользователя, содержащую несколько методов для проверки возможности публикации объявлений:
class User < ActiveRecord::Base
has_many :classifieds
def can_publish_classified?
has_free_classified_slots? and enough_money_to_publish_classified?
end
def has_free_classified_slots?
self.classifieds.size <= 5
end
def enough_money_to_publish_classified?
self.balance >= 10
end
end
Вот так выглядят вызовы методов проверок:
@user.can_publish_classified?
@user.has_free_classified_slots?
@user.enough_money_to_publish_classified?
Обратите внимание на постоянно повторяющееся слово classified в названиях методов. Было бы гораздо удобнее, если бы методы вызывались в контексте того, к чему они непосредственно относятся, а именно – в контексте ассоциации classifieds. Именно для таких случаев в Rails реализована возможность расширения ассоциаций, а точнее – прокси-классов, которые эти ассоциации обслуживают. Делается это следующим образом:
class User < ActiveRecord::Base
has_many :classifieds do
def can_publish?
free_slots? and enough_money_to_publish?
end
def free_slots?
self.size <= 5
end
def enough_money_to_publish?
proxy_owner.balance >= 10
end
end
end
Теперь проверки возможности публикации объявлений выглядят у нас так:
@user.classifieds.can_publish?
@user.classifieds.free_slots?
@user.classifieds.enough_money_to_publish?
Существенно лучше, не так ли? Обратите внимание на метод proxy_owner, который вызывается в методе enough_money_to_publish?. Его описание, а так же описание других полезных методов прокси-класса ассоциации можно найти в документации (см. Association extensions):
proxy_owner – объект, частью которого является данная ассоциация (в нашем случае экземпляр класса User)
proxy_reflection – объект класса ActiveRecord::Reflection::AssociationReflection, хранящий в себе сведения об ассоциации
proxy_target – объект или коллекция объектов, которые загружаются ассоциацией (в нашем случае это коллекция объявлений, привязанных к пользователю); загрузка этих данных выполняется вызовом метода load_target
Однако давайте допустим, что авторами объявлений у нас могут стать не только пользователи, но еще и компании (модель Company). Есть два пути добавления методов в ассоциацию второй модели – copy-paste и вынос методов в отдельный модуль. Я думаю, нет сомнений, что второй путь более правильный.
Выносим код расширения в отдельный модуль:
module ClassifiedExtension
def can_publish?
free_slots? and enough_money_to_publish?
end
def free_slots?
self.size <= 5
end
def enough_money_to_publish?
proxy_owner.balance >= 10
end
end
А затем расширяем им ассоциации:
class User < ActiveRecord::Base
has_many :classifieds, :extend => ClassifiedExtension
end
class Company < ActiveRecord::Base
has_many :classifieds, :extend => ClassifiedExtension
end
Как видите, расширять ассоциации совсем не сложно. Однако, как ни странно, далеко не все разработчики, даже опытные, используют эту возможность. А зря :)