Rails 修改 Ruby 核心类和模块的方法

heroyct · 2018年10月08日 · 最后由 heroyct 回复于 2018年10月31日 · 3915 次阅读
本帖已被管理员设置为精华贴

ruby 的类和模块是开放的,这是 ruby 的重要特征之一

ruby 核心类的扩展是非常容易的
总结了一下修改 ruby 核心类的一些常用方法以及可能面临的风险

1. 追加一个新功能

最常见的方法就是添加一个新的不存在的方法

好处是不会和其他函数冲突,坏处是你无法判断是否会和 ruby 的下一本版本的同名函数冲突

比较经典的有 active support 对 ruby 核心类的扩展

比如这个函数返回单词的复数形式

 # https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/string/inflections.rb#L33
class String
 def pluralize(count = nil, locale = :en)
    locale = count if count.is_a?(Symbol)
    if count == 1
      dup
    else
      ActiveSupport::Inflector.pluralize(self, locale)
    end
  end
end

2. 保持已有函数的接口,只是添加一些新的行为

比如添加日志,调试信息等等

下面演示了在 ruby 的方法中添加一个输出

class String
  alias _ruby_chars chars

  def chars
    puts 'char method done'
    _ruby_chars
  end
end
'test'.chars
# 输出
=> char method done

感觉这种相对安全,但是实际中用的不太多

3. 扩展已有的函数

比如 ActiveSupport 对 Time 的 to_s 方法的扩展
通过传递不同的参数来进行扩展

# 一部分源代码
# https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/time/conversions.rb#L6
DATE_FORMATS = {
  db: "%Y-%m-%d %H:%M:%S",
  number: "%Y%m%d%H%M%S",
}

def to_formatted_s(format = :default)
  if formatter = DATE_FORMATS[format]
    formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter)
  else
    to_default_s
  end
end
alias_method :to_default_s, :to_s
alias_method :to_s, :to_formatted_s

# 得到扩展后的结果
Time.now.to_s(:db)
=> "2018-09-29 12:47:10"

## 得到ruby默认的结果
Time.now.to_s
=> "2018-09-29 12:47:36 +0000"

这种扩展也是有风险的,你无法知道 ruby 的下个版本是否也有相同的扩展导致其中一个被覆盖

4. 扩展独立的对象

这可以说是比较安全的扩展的方法,影响的范围也要小很多

比如要添加一个移除空格的方法到 string 对象,注意不是扩展到 String 类
可以通过 extend 方法来实现

module StringExtension
  def remove_space
    gsub(' ', '').gsub(' ', '')
  end
end

str = 'hello world'
str.extend(StringExtension)
str.remove_space
=> "helloworld"

有时候我们需要覆盖 Gem 已经存在的方法,也可以使用这个方法来不破坏 Gem 并且实现我们的目的

如果你使用 devise 的话,在重设密码之后会默认发送邮件
可以对 user 对象覆盖它默认的方法,进行单独处理

def user.send_reset_password_instructions_notification(token)
  # do something
end

5. refinement

扩展核心类最大的风险在于影响范围非常大
如果我们可以减小范围那么风险就小了很多
ruby2.1 开始提供了 refinement 来解决这个问题

module StringExtension
  refine String do
    def remove_space
      gsub(' ', '').gsub(' ', '')
    end
  end
end

# 直接使用的话会出现一个error
'hello world'.remove_space
=> NoMethodError: undefined method `remove_space' for "hello world":String

# 使用using在指定的类里面使用
class Sample
  using StringExtension
  'hello world'.remove_space
end
=> "helloworld"

总结

总结了修改 rubu core 的一些方法和其中的风险

比较经典的要数 Active Support 对 ruby 的扩展,可以看到很多例子
https://github.com/rails/rails/tree/master/activesupport/lib/active_support/core_ext

Active Support 因为非常有名,所以在一定程度上降低了风险
在自己项目里面进行修改的话应该慎重考虑风险以后在进行

参考文章

huacnlee 将本帖设为了精华贴。 10月09日 16:16

看到Ruby程序员修炼之道上也介绍了这些常用的方法,但是 ta 的举例菜了点,没有涉及到实际生产中的代码,因为 ta 毕竟是本讲语法的书,放上实际的代码反而可能会让人摸不着头脑

@easy_install 有参考Ruby程序员修炼之道,实际开发中我用的最多的是 4 和 5,其他的感觉风险有点高,很少用到

需要 登录 后方可回复, 如果你还没有账号请 注册新账号