Как мне инкапсулировать включенные методы модуля в Ruby? - PullRequest
6 голосов
/ 22 февраля 2012

Я хочу иметь возможность иметь методы в модуле, которые не доступны классу, который включает этот модуль.В следующем примере:

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 ::База или любой другой модуль, включенный в Place.rb.

Ответы [ 3 ]

5 голосов
/ 22 февраля 2012

Я не верю, что есть какой-то прямой способ сделать это, и это сделано специально.Если вам нужна инкапсуляция поведения, вам, вероятно, нужны классы, а не модули.

В Ruby основное различие между закрытыми и открытыми методами заключается в том, что частные методы можно вызывать только без явного получателя.Вызов MyObject.new.my_private_method приведет к ошибке, но вызов my_private_method в определении метода в MyObject будет работать нормально.

Когда вы смешиваете модуль в класс, методы этого модуля "копируются""в класс:

[I] Если мы включаем модуль в определение класса, его методы эффективно добавляются или" смешиваются "в классе.- Руководство пользователя Ruby

Что касается класса, модуль перестает существовать как внешняя сущность (но см. Комментарий Марка Тэлбота ниже).Вы можете вызывать любой из методов модуля из класса без указания получателя, так что они больше не являются «частными» методами модуля, а только частными методами класса.

1 голос
/ 21 августа 2017

Это довольно старый вопрос, но я вынужден ответить на него, поскольку в принятом ответе отсутствует ключевая особенность 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 голосов
/ 22 февраля 2012

Отметьте метод как приватный, когда модуль включен.

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