如何在 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 可以正常工作:(
有没有办法在 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 中包含的任何其他模块上的某些方法。
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 :(
Is there a way to achieve what I want to do in 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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我不认为有任何直接的方法可以做到这一点,这是设计使然的。如果您需要行为的封装,您可能需要类,而不是模块。
在 Ruby 中,私有方法和公共方法之间的主要区别是私有方法只能在没有显式接收者的情况下调用。调用
MyObject.new.my_private_method
将导致错误,但在MyObject
的方法定义中调用my_private_method
则可以正常工作。当您将模块混合到类中时,该模块的方法将被“复制”到类中:
就类而言,模块不再作为外部实体存在(但请参阅下面 Marc Talbot 的评论)。您可以从类内部调用模块的任何方法,而无需指定接收者,因此它们实际上不再是模块的“私有”方法,而只是类的私有方法。
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 callingmy_private_method
within a method definition inMyObject
will work fine.When you mix a module into a class, the methods of that module are "copied" into the class:
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.
这是一个相当老的问题,但我觉得有必要回答它,因为接受的答案缺少 Ruby 的一个关键特性。
该功能称为模块构建器,这里是如何定义模块来实现它:
现在您可以包含该模块:
您需要在此处实际实例化该模块,因为
RecursiveTreeQueries
不是一个模块它本身而是一个类(Module
类的子类)。您可以进一步重构它,以减少方法之间的大量重复,我只是用您所需要的来演示这个概念。This is quite an old question, but I feel compelled to answer it since the accepted answer is missing a key feature of Ruby.
The feature is called Module Builders, and here is how you would define the module to achieve it:
Now you can include the module with:
You need to actually instantiate the module here since
RecursiveTreeQueries
is not a module itself but a class (a subclass of theModule
class). You could refactor this further to reduce a lot of duplication between methods, I just took what you had to demonstrate the concept.当包含模块时,将方法标记为私有。
Mark the method private when the module is included.