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. Если кто-то знает, как этот паттерн в действительности называется — отпишитесь об этом в комментариях.

Tags: ,

Responses

  1. says:

    ноября 26, 2011 at 10:31 (#)

    Называется по-моему fluent interface , а сама техника работы с методами method chaining

  2. Федор says:

    ноября 26, 2011 at 11:26 (#)

    Есть удобный метод tap для реализации таких вещей.

  3. admin says:

    ноября 26, 2011 at 17:08 (#)

    Ильяс, спасибо за инфу, а то я никак не мог найти как это в оригинале называется.

    Федор, tap он вроде только в 1.9 появился, или его уже в 1.8.x портировали? И все же .tap не всегда годится. .tap организовывает цепочку вызова к объекту, а fluent interface позволяет еще что-то там делать.

Leave a Response

Для подсветки кода используйте BB - коды: [language]...[/language], где language может быть: ruby, javascript, css, html.