扩展 ActiveRecord::Base
Extending ActiveRecord::Base
我正在尝试向 ActiveRecord 添加一些自定义方法。我想为模型的每个 date
字段添加一个 *_after
和 *_before
范围,这样我就可以做这样的事情:
User.created_at_after(DateTime.now - 3.days).created_at_before(DateTime.now)
我遵循了此处解释的解决方案 Rails extending ActiveRecord::Base 但是当我执行 rails 控制台并尝试调用这些方法时,我收到 undefined method
错误。
这是我的代码:
# config/initializers/active_record_date_extension.rb
require "active_record_date_extension"
# lib/active_record_date_extension.rb
module ActiveRecordDateExtension
extend ActiveSupport::Concern
included do |base|
base.columns_hash.each do |column_name,column|
if ["datetime","date"].include? column.type
base.define_method("#{column_name}_after") do |date|
where("#{column_name} > ?", date)
end
base.define_method("#{column_name}_before") do |date|
where("#{column_name} < ?", date)
end
end
end
end
end
Rails.application.eager_load!
ActiveRecord::Base.descendants.each do |model|
model.send(:include, ActiveRecordDateExtension)
end
我做错了什么?
使用 Rails 4.1.9 和 Ruby 2.2.1,我注意到上面的代码存在一些问题。
- 您正在将
column.type
与字符串进行比较,并且该属性的 Rails returns 符号。
base.define_method
正在尝试调用私有方法,您可以使用 send
来解决这个问题
这是调整后的代码
module ActiveRecordDateExtension
extend ActiveSupport::Concern
included do |base|
base.columns_hash.each do |column_name,column|
if [:datetime, :date].include? column.type
base.class.send(:define_method, "#{column_name}_after") do |date|
where("#{column_name} > ?", date)
end
base.class.send(:define_method, "#{column_name}_before") do |date|
where("#{column_name} < ?", date)
end
end
end
end
end
感谢前面的回答,我意识到了部分问题。以下是我经过一番研究后得出的所有问题和解决方案:
column.type
是一个符号,我将它与字符串进行比较。
base.define_method
是私有方法
- 我必须在
singleton_class
中定义方法,而不是在 base
class 或 class
. 中
Rails.application.eager_load!
即使在不需要时也会导致急切加载。这不会影响功能,但首先,急切加载不应该是这个 "extension" 的责任,其次,它取决于 Rails,使 "extension" 仅 Rails 兼容。
考虑到这些问题,我决定使用 ruby 的 method_missing
功能来实现它,我写了这个 gem (https://github.com/simon0191/date_supercharger)。这是这个问题的相关部分:
module DateSupercharger
extend ActiveSupport::Concern
included do
def self.method_missing(method_sym, *arguments, &block)
return super unless descends_from_active_record?
matcher = Matcher.new(self,method_sym)
# Inside matcher
# method_sym.to_s =~ /^(.+)_(before|after)$/
if matcher.match?
method_definer = MethodDefiner.new(self) # self will be klass inside Matcher
method_definer.define(attribute: matcher.attribute, suffix: matcher.suffix)
# Inside MethodDefiner
# new_method = "#{attribute}_#{suffix}"
# operators = { after: ">", before: "<" }
# klass.singleton_class.class_eval do
# define_method(new_method) do |date|
# where("#{attribute} #{operators[suffix]} ?", date)
# end
# end
send(method_sym, *arguments)
else
super
end
end
def self.respond_to?(method_sym, include_private = false)
return super unless descends_from_active_record?
if Matcher.new(self,method_sym).match?
true
else
super
end
end
end
end
ActiveRecord::Base.send :include, DateSupercharger
我正在尝试向 ActiveRecord 添加一些自定义方法。我想为模型的每个 date
字段添加一个 *_after
和 *_before
范围,这样我就可以做这样的事情:
User.created_at_after(DateTime.now - 3.days).created_at_before(DateTime.now)
我遵循了此处解释的解决方案 Rails extending ActiveRecord::Base 但是当我执行 rails 控制台并尝试调用这些方法时,我收到 undefined method
错误。
这是我的代码:
# config/initializers/active_record_date_extension.rb
require "active_record_date_extension"
# lib/active_record_date_extension.rb
module ActiveRecordDateExtension
extend ActiveSupport::Concern
included do |base|
base.columns_hash.each do |column_name,column|
if ["datetime","date"].include? column.type
base.define_method("#{column_name}_after") do |date|
where("#{column_name} > ?", date)
end
base.define_method("#{column_name}_before") do |date|
where("#{column_name} < ?", date)
end
end
end
end
end
Rails.application.eager_load!
ActiveRecord::Base.descendants.each do |model|
model.send(:include, ActiveRecordDateExtension)
end
我做错了什么?
使用 Rails 4.1.9 和 Ruby 2.2.1,我注意到上面的代码存在一些问题。
- 您正在将
column.type
与字符串进行比较,并且该属性的 Rails returns 符号。 base.define_method
正在尝试调用私有方法,您可以使用send
来解决这个问题
这是调整后的代码
module ActiveRecordDateExtension
extend ActiveSupport::Concern
included do |base|
base.columns_hash.each do |column_name,column|
if [:datetime, :date].include? column.type
base.class.send(:define_method, "#{column_name}_after") do |date|
where("#{column_name} > ?", date)
end
base.class.send(:define_method, "#{column_name}_before") do |date|
where("#{column_name} < ?", date)
end
end
end
end
end
感谢前面的回答,我意识到了部分问题。以下是我经过一番研究后得出的所有问题和解决方案:
column.type
是一个符号,我将它与字符串进行比较。base.define_method
是私有方法- 我必须在
singleton_class
中定义方法,而不是在base
class 或class
. 中
Rails.application.eager_load!
即使在不需要时也会导致急切加载。这不会影响功能,但首先,急切加载不应该是这个 "extension" 的责任,其次,它取决于 Rails,使 "extension" 仅 Rails 兼容。
考虑到这些问题,我决定使用 ruby 的 method_missing
功能来实现它,我写了这个 gem (https://github.com/simon0191/date_supercharger)。这是这个问题的相关部分:
module DateSupercharger
extend ActiveSupport::Concern
included do
def self.method_missing(method_sym, *arguments, &block)
return super unless descends_from_active_record?
matcher = Matcher.new(self,method_sym)
# Inside matcher
# method_sym.to_s =~ /^(.+)_(before|after)$/
if matcher.match?
method_definer = MethodDefiner.new(self) # self will be klass inside Matcher
method_definer.define(attribute: matcher.attribute, suffix: matcher.suffix)
# Inside MethodDefiner
# new_method = "#{attribute}_#{suffix}"
# operators = { after: ">", before: "<" }
# klass.singleton_class.class_eval do
# define_method(new_method) do |date|
# where("#{attribute} #{operators[suffix]} ?", date)
# end
# end
send(method_sym, *arguments)
else
super
end
end
def self.respond_to?(method_sym, include_private = false)
return super unless descends_from_active_record?
if Matcher.new(self,method_sym).match?
true
else
super
end
end
end
end
ActiveRecord::Base.send :include, DateSupercharger