2013-09-17 2 views
2

Я создал драгоценный камень, который по существу является плагином/расширением существующего приложения Ruby. Приложение имеет некоторое отношение к этому, используя bundler; он автоматически запускает Bundle.require :misc при запуске.Как я могу автоматически запускать код при активации моего драгоценного камня?

Я добавил свой драгоценный камень в группу :misc в Gemfile, и мой драгоценный камень добавляется к пути загрузки, как ожидалось. Задача состоит в том, что для того, чтобы мое расширение работало правильно, мне нужно, чтобы обезьяна исправляла некоторые существующие классы.

Весь мой код патча обезьяны содержится в рубиновом файле в камне (скажем, lib/mygem/patch.rb). Если я вручную отредактирую источник базового приложения для вызова require 'mygem/patch' после существующей строки для Bundle.require :misc, тогда все будет отлично. Однако это небрежно и требует редактирования установленного драгоценного камня для базового приложения каждый раз, когда я переустанавливаю драгоценные камни или перехожу на новую машину.

# Currently I can load my gem and execute the monkey patch in 2 lines 
gem 'mygem' 
require 'mygem/patch' 

# Or with bundler (mygem is in the :misc group) 
Bundle.require :misc 
require 'mygem/patch' 

# I want to achieve the same result in only one line 
gem 'mygem' 

# or 
Bundle.require :misc 

Я хочу, чтобы автоматически запустить код из внутри моего расширение драгоценного камня, когда она активируется из базового приложения, без необходимости вручную требуют файла. Решение должно работать как при самопроизвольном самообразовании с помощью инструкции gem 'mygem', либо как часть группы пакетов, как в Bundle.require :misc.

Возможно ли это? Есть ли лучший шаблон для решения проблем с обезглавливанием из-за «плагина/расширения»?

Спасибо!

ответ

1

Непонятно, когда вы хотите, чтобы ваш код запускался. Вы хотите, чтобы он запускался, когда Ruby запускает программу, или когда Bundler строит список драгоценных камней для приложения?

Если вы говорите о том, когда Руби загружает код, чтобы запустить его ...

Рубин поддерживает BEGIN {...} и END {...} блоки, которые получают, прежде чем запускать переменные инициализируются. Они запускаются, как они видны, но, очевидно, END {...} будет работать в конце программы непосредственно перед выходом переводчика.

См "Programming Ruby":

Начинайте и заканчивайте Блоки

Каждый рубин исходный файл может объявить блоки кода, который будет работать как файл загружается (Бегин блоков) и после завершения программы (блоки END).

BEGIN { 
    begin code 
} 


END { 
    end code 
} 

Программа может включать в себя несколько начинаются и заканчиваются блоки. Блоки BEGIN выполняются в том порядке, в котором они встречаются. Блоки END выполняются в обратном порядке.

Вы также можете добавить код в файл gem, class или module, который будет запущен при загрузке файла, чтобы инициализировать переменные/константы, которые динамически определяются. Для этого нет особых требований, просто не помещайте его в class или def. Код на главном уровне будет работать, как ожидалось, в том числе, если он находится в модуле - это важная часть «основного уровня».

Выполняйте любое из этих действий, потому что вы не хотите воздействовать на нагрузку или время выхода драгоценного камня, что отрицательно сказывается на программе.

Кроме того, будьте очень осторожны в отношении обезглавливания существующих классов в Ruby. Если вы измените функциональность существующего метода, вы можете сильно сломать приложение Ruby пользователя. Если вы добавите имя метода, которое конфликтует с именем, которое пользователь определил, тогда может возникнуть такая же ситуация, и в любом случае это может быть трудно отследить и исправить.


Выработать:

Когда Руби требует файл класса или модуль, он загружает его, а затем запускает код он находит там. Любые классы или константы инициализируются и запускается любой код на основном уровне. В нижней части файла Ruby перейдет к следующему запросу «require».

Рассмотрим файл, содержащий:

class Foo 
    def initialize 
    puts "Inside Foo.new()" 
    end 
end 

Running, что не будет возвращать ничего, и не будет ничего выполняться. Класс будет анализироваться, но поскольку ничего не вызывает Foo.new, мы даже не видим выход. Вот почему мы не внедряем код, который мы хотим запустить автоматически внутри класса, потому что он не будет запускаться, если только это явно не вызывает его. Требование этого будет иметь тот же результат.

Изменение кода к следующему и работает он выводит «Inside Foo.new()»:

class Foo 
    def initialize 
    puts "Inside Foo.new()" 
    end 
end 

Foo.new() 
# >> Inside Foo.new() 

Это явным образом вызывая класс инициализатор.

Если первый код был необходим другому файлу, то при его запуске ничего не произойдет, потому что класс не вызывается до тех пор, пока что-то в требуемом коде или загруженный впоследствии файл не сообщит Ruby о его запуске. Имея класс инициализатор запустить результаты:

Inside Foo.new() 

В любом случае, метод или определение класса не будет работать, пока он не вызывается foo или Foo.new(), который затем вызывает новый экземпляр будет определен, что позволяет инициализатору бежать.

Если ОП хочет что-то запустить в своем собственном драгоценном камне, поместить его внутрь def или class скроет его. Он должен быть на основном уровне для Ruby для запуска. И, опять же, это предполагает вопрос о запуске кода, когда Ruby загружает скрипт, а НЕ о коде Rubygems или Bundler может работать во время установки.

+1

Трусливый лев хотел бы, чтобы вы уточнили: «Просто не кладите его в класс или модуль ...», но слишком боится спросить. –

+0

"Трусливый лев"? В мире литературы и кино есть более одного персонажа под названием «Оловянный человек». Чтобы уточнить, что я сказал, я думаю, мне придется добавить пример кода для объяснения. –

+0

CL утверждает, что его вопрос был общим, о запуске кода при загрузке скрипта и указывает, что вы можете вызывать методы класса во время создания класса (например, 'class Cat; def self.speak; p« Meow », ; end; speak; end => «Meow»). –

1

К сожалению, эта функция не поддерживается.

Однако, поскольку каталог вашего драгоценного камня lib добавлен в путь загрузки, вы можете комбинировать его по другому, добавив файл с таким же именем в свой каталог lib.

В качестве примера, если класс, которым вы управляете monkeypatching, зависит от другого класса, который требует erb, затем создайте файл lib/erb.rb. Так как erb находится в стандартной библиотеке, вместо этого вам понадобится файл erb.rb, потому что драгоценные камни появляются в пути загрузки до стандартной библиотеки.

Вам необходимо убедиться, что вам также нужен оригинальный файл.Таким образом, вы можете установить содержимое erb.rb на следующее:

filename = File.basename(__FILE__) 
$LOAD_PATH.map { |path| 
    Dir.glob("#{path}/*#{filename}").map { |file|  
    require file unless file.include? GEM_NAME 
    } 
} 

puts 'Custom file within gem was required' 

Затем построить/переустановка камень. Когда угнанный require называется:

$ bundle exec irb 
2.0.0 :001 > require 'erb' 
Custom file within gem was required 
=> true 

Этот подход может работать только тогда, когда есть require что вы можете захватить, прежде чем ваш патч должен вступить в силу (и для файла, который существует в пути загрузки после того, как свой собственный драгоценный камень).

 Смежные вопросы

  • Нет связанных вопросов^_^