世界上最伟大的投资就是投资自己的教育

全场限时 5 折

首页Ruby
Mikasa·Ackerman · 真仙

scope 原理浅析

Mikasa·Ackerman发布于4250 次阅读

最近有朋友有问我ActiveRecordscopevalidate 方法的实现机制,之前一直在使用它但是我还真的没有细细的了解过这个方法,于是决定深入探究一下。

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.redShirt.dry_clean_onlyShirt.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

看得出这里 参数接受方法或者代码块,然后定义了一个实例方法,当实例不满足校验条件时,将会抛出异常。

本站文章均为原创内容,如需转载请注明出处,谢谢。

1 条回复
喜欢
统计信息
    学员: 29204
    视频数量: 1985
    文章数量: 489

© 汕尾市求知科技有限公司 | Rails365 Gitlab | Qiuzhi99 Gitlab | 知乎 | b 站 | 搜索

粤公网安备 44152102000088号粤公网安备 44152102000088号 | 粤ICP备19038915号

Top