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

时间:2022-10-30 09:00:04

I want to be able to have methods in a module that are not accessible by the class that includes the module. Given the following example:

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

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

I want Foo.new.do_stuff to blow up because it is trying to access a method that the module is trying to hide from it. In the code above, though, Foo.new.do_stuff will work fine :(

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

Is there a way to achieve what I want to do in Ruby?

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

UPDATE - The real code

更新 - 真正的代码

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

Clearly this code is hacked out and due some serious refactoring, and the purpose of my question is to find out if there is a way I can refactor this module with impunity from accidentally overwriting some method on ActiveRecord::Base or any other module included in Place.rb.

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

3 个解决方案

#1


5  

I don't believe there's any straightforward way to do this, and that's by design. If you need encapsulation of behavior, you probably need classes, not modules.

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

In Ruby, the primary distinction between private and public methods is that private methods can only be called without an explicit receiver. Calling MyObject.new.my_private_method will result in an error, but calling my_private_method within a method definition in MyObject will work fine.

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

When you mix a module into a class, the methods of that module are "copied" into the class:

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

[I]f we include a module in a class definition, its methods are effectively appended, or "mixed in", to the class. — Ruby User's Guide

[I]如果我们在类定义中包含一个模块,它的方法可以有效地附加到类中,或者“混入”到类中。 - Ruby用户指南

As far as the class is concerned, the module ceases to exist as an external entity (but see Marc Talbot's comment below). You can call any of the module's methods from within the class without specifying a receiver, so they're effectively no longer "private" methods of the module, only private methods of the class.

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

#2


1  

This is quite an old question, but I feel compelled to answer it since the accepted answer is missing a key feature of Ruby.

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

The feature is called Module Builders, and here is how you would define the module to achieve it:

该功能称为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

Now you can include the module with:

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

class Foo
  include RecursiveTreeQueries.new
end

You need to actually instantiate the module here since RecursiveTreeQueries is not a module itself but a class (a subclass of the Module class). You could refactor this further to reduce a lot of duplication between methods, I just took what you had to demonstrate the concept.

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

#3


0  

Mark the method private when the module is included.

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

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

#1


5  

I don't believe there's any straightforward way to do this, and that's by design. If you need encapsulation of behavior, you probably need classes, not modules.

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

In Ruby, the primary distinction between private and public methods is that private methods can only be called without an explicit receiver. Calling MyObject.new.my_private_method will result in an error, but calling my_private_method within a method definition in MyObject will work fine.

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

When you mix a module into a class, the methods of that module are "copied" into the class:

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

[I]f we include a module in a class definition, its methods are effectively appended, or "mixed in", to the class. — Ruby User's Guide

[I]如果我们在类定义中包含一个模块,它的方法可以有效地附加到类中,或者“混入”到类中。 - Ruby用户指南

As far as the class is concerned, the module ceases to exist as an external entity (but see Marc Talbot's comment below). You can call any of the module's methods from within the class without specifying a receiver, so they're effectively no longer "private" methods of the module, only private methods of the class.

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

#2


1  

This is quite an old question, but I feel compelled to answer it since the accepted answer is missing a key feature of Ruby.

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

The feature is called Module Builders, and here is how you would define the module to achieve it:

该功能称为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

Now you can include the module with:

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

class Foo
  include RecursiveTreeQueries.new
end

You need to actually instantiate the module here since RecursiveTreeQueries is not a module itself but a class (a subclass of the Module class). You could refactor this further to reduce a lot of duplication between methods, I just took what you had to demonstrate the concept.

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

#3


0  

Mark the method private when the module is included.

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

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