世界上最伟大的投资就是投资自己的教育
scope 原理浅析
最近有朋友有问我ActiveRecord
中 scope
和 validate
方法的实现机制,之前一直在使用它但是我还真的没有细细的了解过这个方法,于是决定深入探究一下。
scope(name, scope_options = {}) public
添加一个用于检索和查询对象的类方法。
可以这样使用:
class Shirt < ActiveRecord::Base
scope :red, where(:color => 'red')
scope :end_date, ->(date) { where(:end_date => date) }
scope :dry_clean_only, joins(:washing_instructions).where('washing_instructions.dry_clean_only = ?', true)
end
上面的调用
scope
定义了类方法Shirt.red
和Shirt.dry_clean_only
。Shirt.red
实际上代表查询Shirt.where(:color =>'red')
。
请注意,这只是用于定义实际类方法的语法糖
实现的作用和类方法一样,但是类方法是在载入类的时候就会一起加载,而scope
定义的方法是在方法调用时才会加载
class Shirt < ActiveRecord::Base
def self.red
where(color: 'red')
end
end
不过需要注意的是,scope
方法即使在什么也没查到的情况下依然会返回Relation
对象,也就是说 scope
方法可以进行链式调用而不担心会抛出nil:NilClass
异常。
class Article < ActiveRecord::Base
scope :published, -> { where(published: true) }
scope :featured, -> { where(featured: true) }
def self.latest_article
order('published_at desc').first
end
def self.titles
pluck(:title)
end
end
我们可以这样调用方法:
Article.published.featured.latest_article
Article.featured.titles
下面看一下具体的实现源码:
# File activerecord/lib/active_record/scoping/named.rb, line 141
def scope(name, body, &block)
unless body.respond_to?(:call)
raise ArgumentError, 'The scope body needs to be callable.'
end
if dangerous_class_method?(name)
raise ArgumentError, "You tried to define a scope named \"#{name}\" " "on the model \"#{self.name}\", but Active Record already defined " "a class method with the same name."
end
extension = Module.new(&block) if block
singleton_class.send(:define_method, name) do |*args|
scope = all.scoping { body.call(*args) }
scope = scope.extending(extension) if extension
scope || all
end
end
除去数据验证以外,值得关注的方法就是
singleton_class.send(:define_method, name)
这一句实现了将传入的方法名定义成一个类方法的过程,我们试着简易的实现以下scope
方法的原理
def self.scope(name, body)
singleton_class.send(:define_method, name, &body)
end
参数接受名字和代码块,然后将名字定义为方法名,代码块作为方法内部可执行代码。这样我们就简易的实现了scope
这个方法。
这时候 scope 的内部实现机制就算比较了解了,但是我对这个singleton_class
非常好奇,它是如何实现的呢?
singleton_class → class
返回 obj 的单例类。 如果 obj 没有,则此方法创建一个新的单例类。
如果 obj 为 nil,true 或 false,则分别返回 NilClass,TrueClass 或 FalseClass。 如果 obj 是一个 Fixnum 或一个符号,它会引发一个 TypeError。
我们可以这么用它:
Object.new.singleton_class #=> #<Class:#<Object:0xb7ce1e24>>
String.singleton_class #=> #<Class:String>
nil.singleton_class #=> NilClass
那么scope
就算是告一段落,算是浅尝辄止,下次我们要仔细的研究一下singleton_class
,下一步看看validate
的实现机制。
validate(*methods, &block) public
向类中添加验证方法或块。 当重写 #validate 实例方法变得过于强硬时,这是很有用的,而且您正在寻找更多关于验证的描述性声明。
这可以通过一个指向方法的符号来完成:
这个非常常用的校验方法,我们经常这样用:
class Comment < ActiveRecord::Base
validate :must_be_friends
def must_be_friends
errors.add_to_base("Must be friends to leave a comment") unless commenter.friend_of?(commentee)
end
end
或者用一个传递当前记录的块进行验证:
class Comment < ActiveRecord::Base
validate do |comment|
comment.must_be_friends
end
def must_be_friends
errors.add_to_base("Must be friends to leave a comment") unless commenter.friend_of?(commentee)
end
end
看得出这里 参数接受方法或者代码块,然后定义了一个实例方法,当实例不满足校验条件时,将会抛出异常。
本站文章均为原创内容,如需转载请注明出处,谢谢。
© 汕尾市求知科技有限公司 | Rails365 Gitlab | 知乎 | b 站 | csdn
粤公网安备 44152102000088号 | 粤ICP备19038915号
Top
不错