Лучший способ вызова методов в экземпляре

У моего вопроса есть пара слоев, поэтому, пожалуйста, потерпите меня? Я создал модуль, который добавляет рабочие процессы из гема Workflow в экземпляр, когда вы вызываете метод для этого экземпляра. Он должен иметь возможность получить описание в виде хэша или некоторой базовой структуры данных, а затем превратить его в нечто, что помещает описанный рабочий процесс в класс во время выполнения. Так что все должно происходить во время выполнения. Немного сложно объяснить, для чего нужны все сумасшедшие требования, но, надеюсь, это все еще хороший вопрос. В любом случае, лучшее, что я могу сделать, чтобы быть кратким для контекста, вот это:

  1. Создайте класс и включите этот модуль, который я построил.
  2. Создайте экземпляр своего класса.
  3. Позвоните inject_workflow(some_workflow_description) Метод по экземпляру. Все должно быть динамично.

Самое сложное для меня в том, что когда я использую public_send() или же eval() или же exec()Мне все еще нужно отправить несколько вложенных вызовов методов, и кажется, что они используют 2 разные области видимости - класс и рабочий процесс (гем). Когда кто-то использует гем Workflow, он вручную записывает эти вызовы методов в своем классе, чтобы он все правильно определял. Гем получает доступ к классу, для которого он создает методы. Как я пытаюсь это сделать, пользователь не пишет вручную методы класса, они добавляются в класс с помощью метода, показанного здесь. Так что я не смог заставить его работать с использованием блоков, потому что мне нужно делать вложенные вызовы блоков, например

workflow() do # first method call
  # first nested method call.  can't access my scope from here
  state(:state_name) do 
    # second nested method call.  can't access my scope
    event(:event_name, transitions_to: :transition_to_state)
  end
end

Одна из вещей, которые я пытаюсь сделать, это позвонить Workflow#state() метод n количество раз, в то время как вложение Workflow#event(with, custom_params)0..n раз. Мне кажется, что проблема заключается в том, что я не могу получить правильную область видимости, когда вкладываю подобные методы.

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

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

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

# Call this as soon as you can, after .new()
def inject_workflow(description)
  public_send :workflow do 
    description[:workflow][:states].each do |state|
      state.map do |name, event|
        public_send name.to_sym do # nested call occurs in Workflow gem
          # nested call occurs in Workflow gem
          public_send :event, event[:name], transitions_to: event[:transitions_to]
        end
      end
    end
  end
end

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

В любом случае, вот моя реализация. Я был бы очень признателен, если бы кто-то указал мне правильное направление!

module WorkflowFactory
# ...
  def inject_workflow(description)       
      # Build up an array of strings that will be used to create exactly what 
      # you would hand-write in your class, if you wanted to use the gem.
      description_string_builder = ['include Workflow', 'workflow do']
      description[:workflow][:states].each do |state|
        state.map do |name, state_description|
          if state_description.nil? # if this is a final state...
            description_string_builder << "state :#{name}"
          else # because it is not a final state, add event information too.
            description_string_builder.concat([
              "state :#{name} do",
              "event :#{state_description[:event]}, transitions_to: :#{state_description[:transitions_to]}",
              "end"
            ])
          end
        end
      end
      description_string_builder << "end\n"
      begin
        # Use class_eval to run that workflow specification by 
        # passing it off to the workflow gem, just like you would when you use 
        # the gem normally.  I'm pretty sure this is where everyone's head pops...
        self.class.class_eval(description_string_builder.join("\n"))
        define_singleton_method(:has_workflow?) { true }
      rescue Exception => e
        define_singleton_method(:has_workflow?) { !!(puts e.backtrace) }
      end
    end 
  end
end


# This is the class in question.
class Job
  include WorkflowFactory
  # ... some interesting code for your class goes here
  def next!
    current_state.events.#somehow choose the correct event
  end
end

# and in some other place where you want your "job" to be able to use a workflow, you have something like this...
job = Job.new
job.done?
# => false
until job.done? do job.next! end
# progresses through the workflow and manages its own state awareness

Я начал этот вопрос с 300000 строк текста, клянусь. Спасибо, что повесил там! Вот еще больше документации, если вы еще не спите. модуль в моей жемчужине


0 ответов

Другие вопросы по тегам