Импортированные из Ruby методы всегда закрыты? - PullRequest
5 голосов
/ 10 января 2012

Это лучше всего объяснить на примере:

file1.rb:

def foo
  puts 123
end

file2.rb:

class A
  require 'file1'
end
A.new.foo

выдаст ошибку "':закрытый метод "foo" называется ".

Я могу обойти это, выполнив A.new.send("foo"), но есть ли способ сделать импортированные методы общедоступными?

Редактировать: Чтобы уточнить, я не путаю include и require.Кроме того, причина, по которой я не могу использовать нормальное включение (как правильно отметили многие), заключается в том, что это является частью настройки метапрограммирования.Мне нужно разрешить пользователю добавлять функциональность во время выполнения;например, он может сказать «run-this-app --include file1.rb», и приложение будет вести себя по-разному в зависимости от кода, который он написал в file1.rb.Извините, должен был объяснить более четко.

Редактировать: Прочитав ответ Йорга, я понял, что мой код не работает точно так, как предполагалось, и он отлично отвечает на мой (ошибочный) вопрос.Я пытаюсь сделать что-то более похожее на str=(entire file1.rb as string); A.class_exec(str).

Ответы [ 3 ]

10 голосов
/ 10 января 2012

Это плохой способ сделать это в Ruby.Попробуйте использовать миксины через модули:

file1.rb:

module IncludesFoo
  def foo
    puts 123
  end
end

file2.rb:

require 'file1.rb'

class A
  include IncludesFoo
end

A.new.foo
# => 123
7 голосов
/ 10 января 2012

Глобальные процедуры в Ruby не являются действительно глобальными процедурами.Это методы, как и все остальное.В частности, когда вы определяете, как выглядит как глобальная процедура, вы на самом деле определяете метод частного экземпляра Object.Поскольку каждый фрагмент кода в Ruby оценивается в контексте объекта, это позволяет использовать эти методы, как если бы они были глобальными процедурами, поскольку self является получателем по умолчанию, а self является объектом, класс которого наследуется отObject.

Итак, это:

# file1.rb

def foo
  puts 123
end

фактически эквивалентно

# file1.rb

class Object
  private

  def foo
    puts 123
  end
end

Теперь у вас есть «глобальная процедура» под названием foo, котораяВы можете позвонить так:

foo

Причина , почему вы можете назвать это так, состоит в том, что этот вызов на самом деле эквивалентен

self.foo

иself - это объект, который включает Object в своей цепочке предков, поэтому он наследует приватный метод foo.

[Примечание: если быть точным, частные методы не могут быть вызваны с явным получателем, , даже если этот явный получатель self.Итак, чтобы быть по-настоящему педантичным, это на самом деле эквивалентно self.send(:foo), а не self.foo.]

A.new.foo в вашей file2.rb - красная сельдь: вы могли быпросто попробуйте Object.new.foo или [].foo или 42.foo и получите тот же результат.

Кстати: puts и require сами являются примерами таких "глобальных процедур", которыена самом деле приватные методы на Object (или, точнее, они являются приватными методами на Kernel, который смешан с Object).

На sidenote: это действительно плохой стильпомещать вызовы к require внутри определения класса, потому что это выглядит так, как будто код require d каким-то образом ограничен или находится в пространстве имен внутри класса, что, конечно, ложно.require просто запускает код в файле, не более того.

Так что, хотя

# file2.rb

class A
  require 'file1.rb'
end

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

# file2.rb

require 'file1.rb'

class A
end

Таким образом, читателю кода будет совершенно ясно, что file1.rb никоим образом не ограничен или не имеет пространства имен внутри A.

Кроме того, обычно предпочтительнее не указывать расширение файла, т.е. использовать require 'file1' вместо require 'file1.rb'.Это позволяет заменить файл Ruby, например, собственным кодом (для MRI, YARV, Rubinius, MacRuby или JRuby), байтовым кодом JVM в файле .jar или .class (для JRuby), байтовым кодом CIL вФайл .dll (для IronRuby) и т. Д., Без необходимости изменять какие-либо из ваших вызовов require.

Последний комментарий: идиоматический способ обойти защиту доступа состоит в использовании send, а не instance_eval, т.е. используйте A.new.send(:foo) вместо A.new.instance_eval {foo}.

0 голосов
/ 15 января 2012

А как насчет load("file1", A)?( ссылка RDoc )

...