我結束了黑客行爲ActiveRecord::Relation
的method_missing
畢竟它沒有變得太難看。這是從頭到尾的完整過程。
我的寶石定義了一個Interceptor
類,打算成爲開發人員可以繼承的DSL。該對象需要從Model
或Relation
中獲取一些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設立您在模型上建立的任何查詢的頂部。
看起來有'Interceptor'子類'關係'可能是要走的路,但我不想承諾,直到我找出更多的'關係'。 –
我沒有答案,但如果你讓我連鎖:攔截器我會假設我可以把它放在鏈中的任何地方,它仍然會工作。真的嗎?否則,讓我將它用作範圍鏈的一部分可能有點誤導,但結果本身並不是一個範圍... –
它的作用類似於'.all'或'.first',因爲它是因爲它評估查詢並返回一個非'關係'。但它的作用與'.where'相似,因爲它在評估之前修改了查詢,因此必須在某個時刻訪問該鏈。 –