2012-02-22 54 views
6

我希望能够在模块中拥有包含该模块的类无法访问的方法。考虑下面的例子:如何在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。

+0

至于第一个例子 - 'Bar#do_stuff'本质上是一个到'common_method_name'的公共接口,所以没有任何逻辑代码应该被破坏。如果你做了'Foo.new.common_method_name',它应该会中断。 – 2014-06-06 17:41:08

回答

5

我不认为有任何直接的方法来做到这一点,这是通过设计。如果你需要封装行为,你可能需要类,而不是模块。

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

当您混合模块插入类,该模块的方法被“复制”到类:

[I F的我们包括在一个类定义的模块,它的方法是有效地追加,或者“混入”到课堂上。 - Ruby User's Guide

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

+1

严格地说,这不是真的。模块DOES作为一个单独的实体存在,尽管一个在继承链中类本身对模块中的方法一无所知 - 当一个模块方法调用是在一个类上进行时,该类基本上说“我不知道该怎么做,也许我的一个基类会做“并把它扔到链上(这个模块混合在一起) – 2012-02-22 02:27:15

+0

@MarcTalbot谢谢,我修改了我的答案, – Brandan 2012-02-22 02:47:41

0

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 
+2

这并不妨碍包含类调用'common_method_name'。它只是将该方法标记为私有,这在OP的代码中已经是这种情况。 – Brandan 2012-02-22 02:33:38

+0

是的,你是完全正确的......我想我有点误解了这个问题。在这种情况下,它背后的全部目的对我来说有点混乱,我不知道该怎么做。 :( – Veraticus 2012-02-22 02:35:48

+1

目的是为了在模块中封装helper方法,所以包含两个或多个具有相同私有方法名称的模块不会引入任何奇怪的行为 – 2012-02-22 05:50:44

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类的子类)。你可以进一步重构这个方法来减少很多方法之间的重复,我只是拿出你所要展示的概念。

+0

很好的回答!泰德! – 2017-08-22 00:17:09