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

heroyct · October 08, 2018 · Last by heroyct replied at October 31, 2018 · 3908 hits
Topic has been selected as the excellent topic by the admin.

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 mark as excellent topic. 09 Oct 16:16

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

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

You need to Sign in before reply, if you don't have an account, please Sign up first.