Включите модуль для определения метода динамического класса

Я играю с Ruby и написал следующий код:

module IdAndNameRedefine
  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def use_id_and_name_from_module(use_attribute)
      class_eval <<CODE
        def id_and_name
          "\#{id}-\#{#{use_attribute}.downcase}"
        end
CODE
    end
  end
end


class Model
  include IdAndNameRedefine

  attr_reader :id, :name1, :name2

  def initialize(id, name1, name2)
    @id = id
    @name1 = name1
    @name2 = name2
  end

  def id_and_name
    "#{id}-#{name1}"
  end

  use_id_and_name_from_module :name2
end

model = Model.new(1, "TesT", "Test number TWO")
puts model.id_and_name

Когда я пытаюсь сделать здесь, это переопределить метод класса id_and_name в классе Model методом, динамически вставленным модулем IdAndNameRedefine. Когда этот модуль включен, он создает «статический» метод (на самом деле, метод класса Model.class, как я его понимаю), а когда вызывается use_id_and_name_from_module, он создает метод класса в модели, который переопределяет id_and_name для использования любого атрибута модели попросили.

Мой вопрос... Есть ли лучший способ сделать это, или это "правильный" способ сделать это? Я не совсем уверен, нравится ли мне, что class_eval принимает строку, в которой мне нужно экранировать значения и т. д., чтобы это работало.


person Thorbjørn Hermansen    schedule 18.06.2009    source источник


Ответы (1)


Вам не нужно передавать строку в class_eval. Вместо этого он может взять блок. На самом деле, я не могу вспомнить ни одного случая, когда мне приходилось передавать строку в class_eval. Таким образом, мы можем переписать модуль ClassMethods следующим образом:

module ClassMethods
  def use_id_and_name_from_module(use_attribute)
    class_eval do 
      define_method(:id_and_name) {"#{id}-#{send(use_attribute).downcase}"}
    end
  end
end

Но в данном конкретном случае мы просто сообщаем self class_eval, что означает, что мы уже находимся в контексте этого класса. Так что на самом деле его можно сократить до:

module ClassMethods
  def use_id_and_name_from_module(use_attribute)
    define_method(:id_and_name) {"#{id}-#{send(use_attribute).downcase}"}
  end
end

(Я просто хотел показать, как на самом деле работает class_eval, так как вас больше всего интересовала эта часть.)

person Chuck    schedule 18.06.2009
comment
Спасибо! Я думал, что это выглядит глупо, но я не мог придумать лучшего способа сделать это. Я действительно считаю, что я тоже пробовал define_method, но в этот момент у меня был вызов use_id_and_name_from_module в верхней части определения класса, поэтому он не работал (я думаю, он перезаписывается объявлением позже в классе). Просто из любопытства; Есть ли способ разместить use_id_and_name_from_module в верхней части класса и убедиться, что его нельзя переопределить в более поздних объявлениях? - person Thorbjørn Hermansen; 18.06.2009
comment
Вы можете переопределить method_added и повторно добавить свою версию всякий раз, когда кто-то пытается ее заменить. Хотя кажется немного запутанным. Лучше просто не переопределять метод в первую очередь. - person Chuck; 18.06.2009