2

私が作っている宝石では、開発者が私が書いたクラスメソッドをinterceptorモデルに追加できるようにしたいと思っています。

class User < ActiveRecord::Base
  has_interceptor
end

これにより、 gemを介してデータベースにクエリを実行して魔法のようなことをUser.interceptor行うオブジェクトを返すを呼び出すことができます。すべて良い。InterceptorSqueel

ただし、開発者がインターセプターが実行するクエリのスコープを設定できるようにする適切な方法を最初に見つけたいと思います。これは、それからand チェーンを取り込めるようinterceptorにすることで実現できます。それ以外の場合は、モデルにフォールバックします。この実装は次のように機能します。ActiveRecord::RelationSqueel

# Builds on blank ARel from User:
User.interceptor.perform_magic
#=> "SELECT `users`.* FROM `users` WHERE interceptor magic"

# Build on scoped ARel from Relation:
User.interceptor( User.where('name LIKE (?)', 'chris') ).perform_magic
#=> "SELECT `users`.* FROM `users`  WHERE `users`.`name` LIKE 'chris' AND  interceptor magic"

これは効果的ですが、醜いです。私が本当に欲しいのは次のようなものです:

# Build on scoped ARel:
User.where('name LIKE (?)', 'chris').interceptor.perform_magic
#=> "SELECT `users`.* FROM `users`  WHERE `users`.`name` LIKE 'chris' AND  interceptor magic"

基本的に、私はActiveRecord::Relationチェーンを「タップ」して ARel を盗み、それをInterceptorオブジェクトに渡して、評価する前に変更したいと考えています。しかし、これを行うために考えられるすべての方法は、非常に恐ろしいコードを使用しているため、実装した場合、神が子猫を殺すことを知っています. 私は私の手にその血を必要としません。子猫を助けて?

問題:

私の複雑さに加えて、

class User < ActiveRecord::Base
  has_interceptor :other_interceptor_name
end

を呼び出すUser.other_interceptor_nameことができ、モデルは複数のインターセプターを持つことができます。それはうまく機能しますが、method_missing通常よりもさらに悪いアイデアを使用します。

4

1 に答える 1

1

結局、ハッキングしてしまいましたActiveRecord::Relationmethod_missing、それほど醜いものではありませんでした。これが最初から最後までの完全なプロセスです。

私のgemは、Interceptor開発者がサブクラス化できるDSLを目的としたクラスを定義しています。このオブジェクトは、またはからいくつかのARelrootを取り込み、レンダリングする前にクエリをさらに操作します。ModelRelation

# gem/app/interceptors/interceptor.rb
class Interceptor
  attr_accessor :name, :root, :model
  def initialize(name, root)
    self.name = name
    self.root = root
    self.model = root.respond_to?(:klass) ? root.klass : root
  end
  def render
    self.root.apply_dsl_methods.all.to_json
  end
  ...DSL methods...
end

実装:

# sample/app/interceptors/user_interceptor.rb
class UserInterceptor < Interceptor
  ...DSL...
end

次に、モデルにhas_interceptor新しいインターセプターを定義し、interceptorsマッピングを構築するメソッドを提供します。

# gem/lib/interceptors/model_additions.rb
module Interceptor::ModelAdditions

  def has_interceptor(name=:interceptor, klass=Interceptor)
    cattr_accessor :interceptors unless self.respond_to? :interceptors
    self.interceptors ||= {}
    if self.has_interceptor? name
      raise Interceptor::NameError,
        "#{self.name} already has a interceptor with the name '#{name}'. "\
        "Please supply a parameter to has_interceptor other than:"\
        "#{self.interceptors.join(', ')}"
    else
      self.interceptors[name] = klass
      cattr_accessor name
      # Creates new Interceptor that builds off the Model
      self.send("#{name}=", klass.new(name, self))
    end
  end

  def has_interceptor?(name=:interceptor)
    self.respond_to? :interceptors and self.interceptors.keys.include? name.to_s
  end

end

ActiveRecord::Base.extend Interceptor::ModelAdditions

実装:

# sample/app/models/user.rb
class User < ActiveRecord::Base
  # User.interceptor, uses default Interceptor Class
  has_interceptor
  # User.custom_interceptor, uses custom CustomInterceptor Class
  has_interceptor :custom_interceptor, CustomInterceptor

  # User.interceptors #show interceptor mappings
  #=> {
  #     interceptor: #<Class:Interceptor>,
  #     custom_interceptor: #<Class:CustomInterceptor>
  #   }
  # User.custom_interceptor #gets an instance
  #=> #<CustomInterceptor:0x005h3h234h33>
end

それだけで、User.interceptorを呼び出してInterceptor、すべてのインターセプタークエリ操作のルートとしてクリーンなクエリを使用してを構築できます。ただし、もう少し努力ActiveRecord::Relationすれば、スコープのチェーンのエンドポイントとしてインターセプターメソッドを呼び出すことができるように拡張できます。

# gem/lib/interceptor/relation_additions.rb
module Interceptor::RelationAdditions

  delegate :has_interceptor?, to: :klass

  def respond_to?(method, include_private = false)
    self.has_interceptor? method
  end

protected

  def method_missing(method, *args, &block)
    if self.has_interceptor? method
      # Creates new Interceptor that builds off of a Relation
      self.klass.interceptors[method.to_s].new(method.to_s, self)
    else
      super
    end
  end

end

ActiveRecord::Relation.send :include, Interceptor::RelationAdditions

次に、モデルで作成したクエリに加えて、DSLでUser.where('created_at > (?)', Time.current - 2.weeks).custom_interceptor設定されたすべてのスコープを適用します。Interceptor

于 2012-12-03T20:24:25.637 に答える