如何在Ruby中封装包含的模块方法?

时间:2012-02-22 01:46:21

标签: ruby-on-rails ruby oop module encapsulation

我希望能够在模块中拥有包含该模块的类无法访问的方法。给出以下示例:

class Foo
  include Bar

  def do_stuff
    common_method_name
  end
end

module Bar
  def do_stuff
    common_method_name
  end

  private
  def common_method_name
    #blah blah
  end
end

我希望Foo.new.do_stuff爆炸,因为它试图访问模块试图隐藏它的方法。但是,在上面的代码中,Foo.new.do_stuff可以正常工作:(

有没有办法在Ruby中实现我想做的事情?

更新 - 真实代码

class Place < ActiveRecord::Base
  include RecursiveTreeQueries

  belongs_to :parent, {:class_name => "Place"}
  has_many :children, {:class_name => 'Place', :foreign_key => "parent_id"}
end


module RecursiveTreeQueries

  def self_and_descendants
     model_table = self.class.arel_table
     temp_table = Arel::Table.new :temp
     r = Arel::SelectManager.new(self.class.arel_engine).from(model_table).project(model_table.columns).join(temp_table).on('true').where(model_table[:parent_id].eq(temp_table[:id]))
     nr = Place.scoped.where(:id => id)
     q = Arel::SelectManager.new(self.class.arel_engine)
     as = Arel::Nodes::As.new temp_table, nr.union(r)
     arel = Arel::SelectManager.new(self.class.arel_engine).with(:recursive,as).from(temp_table).project(temp_table[:id])
     self.class.where(model_table[:id].in(arel))
   end  

  def self_and_ascendants
    model_table = self.class.arel_table
    temp_table = Arel::Table.new :temp
    r = Arel::SelectManager.new(self.class.arel_engine).from(model_table).project(model_table.columns).join(temp_table).on('true').where(temp_table[:parent_id].eq(model_table[:id]))
    nr = Place.scoped.where(:id => id)
    q = Arel::SelectManager.new(self.class.arel_engine)
    as = Arel::Nodes::As.new temp_table, nr.union(r)
    arel = Arel::SelectManager.new(self.class.arel_engine).with(:recursive,as).from(temp_table).project(temp_table[:id])
    self.class.where(model_table[:id].in(arel))
 end

end

显然,这段代码被黑了,并且由于一些严重的重构,我的问题的目的是找出是否有一种方法可以重构这个模块而不会因为意外覆盖ActiveRecord :: Base或其他任何方法而忽略某些方法模块包含在Place.rb。

3 个答案:

答案 0 :(得分:5)

我不相信有任何直接的方法可以做到这一点,这是设计的。如果需要封装行为,则可能需要类,而不是模块。

在Ruby中,私有方法和公共方法之间的主要区别是私有方法只能在没有显式接收器的情况下调用。调用MyObject.new.my_private_method会导致错误,但在my_private_method中的方法定义中调用MyObject可以正常工作。

将模块混合到一个类中时,该模块的方法被“复制”到类中:

  

[我]我们在类定义中包含一个模块,它的方法被有效地附加到或者“混入”到类中。 - Ruby User's Guide

就班级而言,该模块不再作为外部实体存在(但请参阅Marc Talbot在下面的评论)。您可以在类中调用任何模块的方法而无需指定接收器,因此它们实际上不再是模块的“私有”方法,只是该类的私有方法。

答案 1 :(得分:1)

这是一个相当古老的问题,但我觉得有必要回答它,因为接受的答案缺少Ruby的一个关键特性。

该功能称为Module Builders,以下是如何定义模块以实现它:

class RecursiveTreeQueries < Module
  def included(model_class)
    model_table = model_class.arel_table
    temp_table = Arel::Table.new :temp
    nr = Place.scoped.where(:id => id)
    q = Arel::SelectManager.new(model_class.arel_engine)
    arel_engine = model_class.arel_engine

    define_method :self_and_descendants do
      r = Arel::SelectManager.new(arel_engine).from(model_table).project(model_table.columns).join(temp_table).on('true').where(model_table[:parent_id].eq(temp_table[:id]))
      as = Arel::Nodes::As.new temp_table, nr.union(r)
      arel = Arel::SelectManager.new(arel_engine).with(:recursive,as).from(temp_table).project(temp_table[:id])
      self.class.where(model_table[:id].in(arel))
    end

    define_method :self_and_ascendants do
      r = Arel::SelectManager.new(arel_engine).from(model_table).project(model_table.columns).join(temp_table).on('true').where(temp_table[:parent_id].eq(model_table[:id]))
      as = Arel::Nodes::As.new temp_table, nr.union(r)
      arel = Arel::SelectManager.new(arel_engine).with(:recursive,as).from(temp_table).project(temp_table[:id])
      self.class.where(model_table[:id].in(arel))
    end
  end
end

现在您可以将模块包含在:

class Foo
  include RecursiveTreeQueries.new
end

你需要在这里实际实例化模块,因为RecursiveTreeQueries不是模块本身而是类(Module类的子类)。您可以进一步重构以减少方法之间的大量重复,我只是采取了您必须展示的概念。

答案 2 :(得分:0)

在包含模块时将方法标记为私有。

module Bar
  def do_stuff
    common_method_name
  end

  def common_method_name
    #blah blah
  end

  def self.included(klass)
      klass.send(:private, :common_method_name)
  end
end