How can I encapsulate the included module methods in Ruby?

I want to have methods in a module that are not accessible to the class that the module includes. In 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 explode because it is trying to access the method that the module is trying to hide from it. In the above code, Foo.new.do_stuff will work fine: (

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

UPDATE - 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 

Obviously, this code is hacked and caused by serious refactoring, and the purpose of my question is to find out if there is a way to reorganize this module with impunity from accidentally rewriting any method on ActiveRecord :: Base or any other module included in Place .rb.

+6
source share
3 answers

I don’t think there is an easy way to do this, and this is by design. If you need encapsulation of behavior, you probably need classes, not modules.

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

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

[I] f we include the module in the definition of the class, its methods are effectively added or "mixed" with the class. - Ruby User Guide

As for the class, the module ceases to exist as an external object (but see Marc Talbot's comment below). You can call any of the module methods from the class without specifying a receiver, so they are actually not "private" methods of the module, but only private methods of the class.

+5
source

This is a pretty old question, but I have to answer it, because the accepted answer lacks a key Ruby feature.

This function is called Module Builders , and here is how you would define a module to achieve it:

 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 enable the module with:

 class Foo include RecursiveTreeQueries.new end 

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

+1
source

Mark the private method when the module is enabled.

 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 
0
source

Source: https://habr.com/ru/post/909036/


All Articles