让Rails中的委托方法成为私有方法

71 阅读2分钟

最近,我发现了一些使用Railsdelegate 方法的旧代码。看看下面这个例子,我把几个方法委托给一个实例变量,但想把它们变成私有的。

class UserDecorator
  def initialize(user)
    @user = user
  end

  def full_name
    "#{first_name} #{last_name}"
  end

  private

  delegate :first_name, :last_name, to: :@user
end

我的意图是把first_namelast_name 方法变成私有的。只有full_name 应该是公开的。 让我们看看这是否按预期的那样工作。

user = User.new(first_name: 'John', last_name: 'Doe')
decorated_user = UserDecorator.new(user)

decorated_user.full_name #=> John Doe
decorated_user.first_name #=> John
decorated_user.last_name #=> Doe

decorated_user.methods - Object.instance_methods
#=> [:full_name, :last_name, :first_name]

正如你所看到的,我们可以调用这两个委托方法。 它们应该是私有的!这里发生了什么?

问题是把delegate 放在private 作用域下没有任何效果。Ruby 中的方法在从类中调用时没有办法知道private 的可见性作用域被设置了。delegate 使用module_eval 来定义这些名字的新方法,但它不知道它必须把这些方法变成私有的。

那么,如果我们希望委托一个方法而不使其成为类的公共接口的一部分,我们该怎么做呢? 让我们深入了解一下delegate 。在这样的情况下,我喜欢使用pry控制台来交互地检查类的行为。

pry> cd UserDecorator
# This pry command puts us within the scope of the UserDecorator class.

pry> delegate :first_name, :last_name, to: :@user
#=> [:first_name, :last_name]

pry> instance_methods
#=> [:full_name, :last_name, :first_name]

pry> private :first_name, :last_name

pry> instance_methods
#=> [:full_name]

delegate 返回委托方法名称的列表。 我们可以用这些方法名称调用 ,使其成为私有的。这意味着我们可以像这样明确地使这些方法成为私有。private

class UserDecorator
  # def initialize...

  delegate :first_name, :last_name, to: :@user
  private :first_name, :last_name
end

现在,我们怎样才能避免方法名列表的重复呢? 由于delegate 返回方法名列表,我们可以使用splat操作符将列表传递给private

class UserDecorator
  # def initialize...

  private *delegate(:first_name, :last_name, to: :@user)
end

虽然这可以避免重复,但我觉得这种语法不是很直观。 幸运的是,Rails 6正在引入一个新的private: true 关键字参数,给我们提供一个更简洁的语法。

# Rails 6+
delegate :first_name, :last_name, to: :@user, private: true

然而,在我写这篇文章的时候,Rails 6还没有出来。 如果你使用的是Rails 5.2或以下版本,你现在必须使用private *delegate(...) 语法。

链接