簡體   English   中英

在不殺死小貓的情況下“攔截” ActiveRecord :: Relation的ARel

[英]'Intercept' ActiveRecord::Relation's ARel without killing a kitten

在我制作的gem中,我希望允許開發人員以經典的Devise語法將我編寫的類方法(稱為interceptor )添加到模型中:

class User < ActiveRecord::Base
  has_interceptor
end

這使您可以調用User.interceptor ,該方法返回一個Interceptor對象,該對象通過Squeel gem查詢數據庫時會做一些神奇的事情。 都好。

但是,我想找到一種優雅的方法,允許開發人員首先確定攔截器執行的查詢范圍。 這可以通過允許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比正常情況更糟。

我最終還是對ActiveRecord::Relationmethod_missing進行了黑客入侵,結果並沒有太丑陋。 這是從頭到尾的完整過程。

我的gem定義了一個Interceptor類,旨在作為開發人員可以繼承的DSL。 該對象從ModelRelation獲取一些root ARel,並在呈現之前進一步處理查詢。

# 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

現在, User.where('created_at > (?)', Time.current - 2.weeks).custom_interceptor將在Interceptor DSL中設置的所有范圍設定應用於您在模型上建立的任何查詢之上。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM