2012-02-22 21 views
6

Quiero poder tener métodos en un módulo que no sean accesibles para la clase que incluye el módulo. Teniendo en cuenta el siguiente ejemplo:¿Cómo encapsulo los métodos de módulo incluidos en 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 

Quiero Foo.new.do_stuff para hacer estallar, ya que está intentando acceder a un método que el módulo está tratando de esconderse de ella. En el código anterior, sin embargo, Foo.new.do_stuff trabajará muy bien :(

¿Hay una manera de lograr lo que quiero hacer en Ruby

ACTUALIZACIÓN -? El código real

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 

Está claro que este código está bloqueado y debido a una seria refactorización, y el propósito de mi pregunta es averiguar si hay alguna manera de refactorizar este módulo impunemente al sobrescribir accidentalmente algún método en ActiveRecord :: Base o cualquier otro módulo incluido en Place.rb.

+0

En cuanto al primer ejemplo, 'Bar # do_stuff' es esencialmente una interfaz pública para' common_method_name', por lo que no hay ninguna lógica de que el código se rompa. Debería romperse en caso de que haga 'Foo.new.common_method_name'. –

Respuesta

5

No creo que haya ninguna manera directa de hacerlo, y eso es por diseño. Si necesita encapsulación de comportamiento, probablemente necesite clases, no módulos.

En Ruby, la distinción principal entre los métodos privados y públicos es que los métodos privados solo se pueden llamar sin un receptor explícito. Llamar a MyObject.new.my_private_method dará como resultado un error, pero llamar a my_private_method dentro de una definición de método en MyObject funcionará bien.

Cuando se mezcla un módulo en una clase, los métodos de ese módulo se "copian" en la clase:

[S] i se incluye un módulo en una definición de clase, sus métodos se adjunta con eficacia , o "mezclado", a la clase. - Ruby User's Guide

En lo que se refiere a la clase, el módulo deja de existir como una entidad externa (pero consulte el comentario de Marc Talbot a continuación). Puede llamar a cualquiera de los métodos del módulo desde dentro de la clase sin especificar un receptor, por lo que ya no son métodos "privados" del módulo, solo métodos privados de la clase.

+1

Estrictamente hablando, eso no es del todo cierto. El módulo SI EXISTE como una entidad separada, aunque una que está en la cadena de herencia de la clase. La clase en sí no sabe nada sobre los métodos que están en el módulo: cuando se hace una llamada a un método de módulo en una clase, la clase básicamente dice: "No tengo idea de cómo hacer eso, tal vez una de mis clases base "y lo arroja por la cadena (en la que se mezcla el módulo). –

+0

@MarcTalbot Gracias. Enmendé mi respuesta. – Brandan

0

Mar k el método privado cuando el módulo está incluido.

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

Esto no impide que una clase incluida llame '' common_method_name'. Simplemente marca ese método como privado, que ya es el caso en el código del OP. – Brandan

+0

Sí, tienes toda la razón ... Creo que leí un poco la pregunta. En este caso, todo el propósito detrás de eso es un poco confuso para mí, y no estoy seguro de cómo hacerlo. :( – Veraticus

+1

El objetivo es habilitar la encapsulación de los métodos auxiliares en el módulo. De modo que incluir dos o más módulos con los mismos nombres de métodos privados no introduce ningún comportamiento extraño. –

1

Esta es una pregunta bastante antigua, pero me siento obligado a responderla ya que a la respuesta aceptada le falta una característica clave de Ruby.

La función se denomina Module Builders, y aquí es cómo definiría el módulo para lograrlo:

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 

Ahora se puede incluir el módulo con:

class Foo 
    include RecursiveTreeQueries.new 
end 

Es necesario realidad instanciar el módulo aquí desde RecursiveTreeQueries no es un módulo en sí mismo sino una clase (una subclase de la clase Module). Podrías refactorizar esto para reducir la duplicación entre métodos, solo tomé lo que tuviste para demostrar el concepto.

+0

¡Gran respuesta! ted! –

Cuestiones relacionadas