Всегда ли импортированные методы Ruby являются частными?

Лучше всего это пояснить на примере:

файл1.rb:

def foo
  puts 123
end

файл2.rb:

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

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

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

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

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


person alexloh    schedule 10.01.2012    source источник
comment
Вы пробовали A.new.instance_eval{foo}? У меня не работает (ruby 1.9.2).   -  person knut    schedule 10.01.2012
comment
Вы путаете require with include`?   -  person Andrew Grimm    schedule 11.01.2012
comment
@AndrewGrimm К сожалению, нет. Я ищу дешевый способ расширить программу динамически. Все приложение довольно сложное, но в основном строка file1.rb вводится пользователем.   -  person alexloh    schedule 13.01.2012


Ответы (3)


Глобальные процедуры в 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).

На заметку: это действительно плохой стиль помещать вызовы require внутри определения класса, потому что это создает впечатление, что код required каким-то образом ограничен областью действия или пространством имен внутри класса, что, конечно же, неверно. 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}.

person Jörg W Mittag    schedule 10.01.2012
comment
Благодарю вас! Этот ответ действительно полный и устраняет недоразумения, которые у меня были --- что требование было похоже на то, что C включает этот скопированный текст. Есть ли способ динамически добавить содержимое file1.rb в качестве методов класса A? Как содержимое, так и местоположение файла 1 известны только во время выполнения, и пользователь не знает об A. Или я могу найти список методов или модулей, определенных в файле? - person alexloh; 13.01.2012

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

файл1.rb:

module IncludesFoo
  def foo
    puts 123
  end
end

файл2.rb:

require 'file1.rb'

class A
  include IncludesFoo
end

A.new.foo
# => 123
person Michelle Tilley    schedule 10.01.2012
comment
И 'file1.rb', и его содержимое поступают из пользовательского ввода и загружаются динамически. По сути, пользователь может ввести путь во время выполнения, и приложение загрузит соответствующий файл ruby, который изменит существующую функциональность без перезапуска и т. д. Извините, нужно было сделать это яснее. - person alexloh; 13.01.2012

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

person Andrew Grimm    schedule 15.01.2012