Query-Chain Object Pattern в Ruby
ноября 25, 2011 | Published in Ruby | 3 Comments
Предупреждение: На самом деле я не знаю, как в действительности называюется этот паттерн, но название Query-Chain Object Pattern мне кажется очень подходящим.
Я был очень рад увидеть в ActiveRecord 3.0 такое значительное изменение, как добавление класса ActiveRecord::Relation. Этот класс, точнее его объекты, занимаются тем, что «накапливают» запросы в виде вызова методов и на их основе генерируют SQL-запрос к БД.
Post.where(:id => 3).class #=> ActiveRecord::Relation Post.where(:id => 3).order('id desc').class #=> ActiveRecord::Relation
Объект типа Relation позволяет вместо использования одного метода с кучей атрибутов использовать цепочку вызова методом, что выглядит более кратко и красиво, кроме того, мы можем, при необходимости, переписать или дополнить запрос в любом месте нашего приложения.
Эта статья не о работе с ActiveRecord, но о паттерне Query Chain.
Описание Query Chain:
При определенных запросах к некоторому объекту A объект A возвращает не запрашиваемые результаты, а query-chain объект B в котором записываются запросы, которые позже выполняются вместе и изменяют состояние объекта A, или который изменяет состояние объекта A сразу при каждом запросе (здесь запрос — просто вызов метода).
Самый простой пример реализации Query Chain паттерна:
class ConnectConfig < Hash def initialize @@query_chain = QueryChain.new(self) end def get_info puts self.inspect end def method_missing(name, *value, &block) [:user, :password,:host].include?(name) ? @@query_chain.send(name,value) : super(name, value, block) end end class QueryChain def initialize(connect_config) @config = connect_config end def method_missing(name, *values, &block) if [:user, :password, :host].include?(name) @config[name] = values return self else @config.send(name, *values, block) end end end @connect_config = ConnectConfig.new @connect_config.get_info #=> {} @connect_config.user("Vladimir").password("root").host("localhost") @connect_config.get_info #=> {:user=>"Vladimir", :password=>"root", :host=>"localhost"} #Эсли возникла проблема с соединением, то мы можем легко изменить его параметры и попробовать заново: @connect_config.host("http://rubydev.ru").class #=> QueryChain @connect_config.get_info #=> {:user=>"Vladimir", :password=>"root", :host=>"http://rubydev.ru"}
Реализаций этого паттерна может быть очень много. Наример, если вы решите написать свою ORM типа ActiveRecord, то вы можете в Query-Chain записывать лог вызова методов для выборки, а затем на основе этого лога генерировать окончательный вариант SQL запроса и выполнять его при попытке обратиться к вибираемым из БД данным.
P.S. Если кто-то знает, как этот паттерн в действительности называется — отпишитесь об этом в комментариях.
ноября 26, 2011 at 10:31 (#)
Называется по-моему fluent interface , а сама техника работы с методами method chaining
ноября 26, 2011 at 11:26 (#)
Есть удобный метод tap для реализации таких вещей.
ноября 26, 2011 at 17:08 (#)
Ильяс, спасибо за инфу, а то я никак не мог найти как это в оригинале называется.
Федор, tap он вроде только в 1.9 появился, или его уже в 1.8.x портировали? И все же .tap не всегда годится. .tap организовывает цепочку вызова к объекту, а fluent interface позволяет еще что-то там делать.