如何在Ruby中封装包含的模块方法?
我希望能够在模块中拥有包含该模块的类无法访问的方法。考虑下面的例子:如何在Ruby中封装包含的模块方法?
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将正常工作:(
有没有办法达到我想要的红宝石做
UPDATE - 真正的代码
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或任何其他模块的一些方法in Place.rb。
我不认为有任何直接的方法来做到这一点,这是通过设计。如果你需要封装行为,你可能需要类,而不是模块。
在Ruby中,私有和公共方法的主要区别是私有方法只能在没有明确接收方的情况下被调用。调用MyObject.new.my_private_method
将导致错误,但在MyObject
的方法定义内调用my_private_method
将正常工作。
当您混合模块插入类,该模块的方法被“复制”到类:
[I F的我们包括在一个类定义的模块,它的方法是有效地追加,或者“混入”到课堂上。 - Ruby User's Guide
就类而言,该模块不再作为外部实体存在(但请参阅下面的Marc Talbot的注释)。您可以在类中调用模块的任何方法而无需指定接收器,因此它们实际上不再是模块的“私有”方法,而只是该类的私有方法。
严格地说,这不是真的。模块DOES作为一个单独的实体存在,尽管一个在继承链中类本身对模块中的方法一无所知 - 当一个模块方法调用是在一个类上进行时,该类基本上说“我不知道该怎么做,也许我的一个基类会做“并把它扔到链上(这个模块混合在一起) – 2012-02-22 02:27:15
@MarcTalbot谢谢,我修改了我的答案, – Brandan 2012-02-22 02:47:41
Mar k包含模块时私有方法。
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
这是一个相当古老的问题,但我觉得不得不回答它,因为接受的答案是缺少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
类的子类)。你可以进一步重构这个方法来减少很多方法之间的重复,我只是拿出你所要展示的概念。
很好的回答!泰德! – 2017-08-22 00:17:09
至于第一个例子 - 'Bar#do_stuff'本质上是一个到'common_method_name'的公共接口,所以没有任何逻辑代码应该被破坏。如果你做了'Foo.new.common_method_name',它应该会中断。 – 2014-06-06 17:41:08