Метод создания методов: простое метапрограммирование Ruby

У меня есть куча таких методов в представлении помощника

  def background
    "#e9eaec"
  end
  def footer_link_color
    "#836448"
  end

Я бы хотел, чтобы эти методы отображались в представлении, но я бы предпочел, чтобы помощник был немного более кратким. Как лучше всего превратить хэш, скажем, в методы (или что-то еще)?


person Dan Rosenstark    schedule 17.05.2010    source источник


Ответы (4)


module MyHelper
  {:background => "#e9eaec", :footer_link_color => "#836448"}.each do |k,v|
    define_method(k) {v}
  end
end

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

Если вы хотите обобщить это, вы можете добавить следующий метод в класс модуля:

class Module
  def methods_from_hash(hash)
    hash.each do |k,v|
      define_method(k) {v}
    end
  end
end

А затем в своем помощнике вызовите methods_from_hash(:background => ...).

person sepp2k    schedule 17.05.2010
comment
На самом деле было бы чище, если бы вы использовали переменную для хеша и патч обезьяны для класса модуля. Мне это очень нравится. Спасибо. - person Dan Rosenstark; 18.05.2010
comment
Я немного изменил ваш ответ в своем ответе ниже ... хотя мне нужно выяснить, какая версия лучше в контексте исходного вопроса (помощник). - person Dan Rosenstark; 22.05.2010

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

class Foo

  module Values
    FOO = 1
    BAR = 'bar'
    BAZ = :baz
  end
  include Values

  Values.constants.each do |name|
    define_method(name.downcase) do
      Values.const_get(name)
    end
  end

end

foo = Foo.new
p foo.foo    # => 1
p foo.bar    # => "bar"
p foo.baz    # => :baz

include Values смешивает константы в Foo для удобства собственных методов Foo. Для работы этого шаблона это не требуется.

person Wayne Conrad    schedule 17.05.2010
comment
Руби знает, что они константы по своему регистру, или это просто соглашение? - person Dan Rosenstark; 18.05.2010
comment
@yar, Ruby знает, что мы определяем константу, потому что идентификатор начинается с заглавной буквы. Как он отслеживает, что является константой, а что нет, я не знаю. - person Wayne Conrad; 18.05.2010
comment
Печальный и ироничный факт о Ruby: так много всего написано не на Ruby, что все это сложно понять, если только вы не хотите углубиться в C ниже. Спасибо за Ваш ответ. - person Dan Rosenstark; 18.05.2010
comment
@yar: ты так говоришь, как будто это плохая особенность рубина. На самом деле в большинстве языков вам не нужно знать, как обрабатываются вещи внизу. Как обрабатывается константа в C? на С++? Ява? Вы знаете, объявляя его const, это константа. Но мне не нужно знать больше. Аналогично в ruby: объявите переменную с заглавной буквы, и она будет рассматриваться как константа. Но есть куча документации, если вы об этом. - person nathanvda; 19.05.2010
comment
@nathanvda, это негативный комментарий о Руби, хотя, вероятно, не в этом случае. Как вы говорите, в Java static final — это языковая функция, и поэтому она не написана на Java. Так что вы правы в этом случае. - person Dan Rosenstark; 19.05.2010

На самом деле, в ruby ​​есть что-то под названием OpenStruct, что очень здорово и действительно полезно для когда вы хотите хеш, но не хотите использовать его как один.

require 'ostruct'

colors = OpenStruct.new({:background => "0x00FF00", :color => "0xFF00FF"})

p colors.background #=> "0x00FF00"
person vava    schedule 23.05.2010
comment
Спасибо, вава, я узнал об этом вчера благодаря комментарию sepp2k к моему ответу (где-то ниже). - person Dan Rosenstark; 23.05.2010

Вот мой ремикс ответа sepp2k. Это немного больше OO и работает даже в irb. Не уверен, нужно ли исправлять Object или Hash.

class Hash
  def keys_to_methods()
    each do |k,v|
      self.class.send(:define_method, k,  Proc.new {v});
    end
    length
  end
end

Тестовый код

hash = {:color_one=>"black", :color_two=>"green"}
hash.keys_to_methods
has.color_one # returns black

OpenStruct: еще раз спасибо sepp2k! Я не знал, что это существует.

Вот еще одна версия с использованием method_missing

class Hash
  def method_missing(method_id)
    key = method_id.id2name
    if has_key?(key)
      return self[key]
    elsif has_key?(key.to_sym)
      return self[key.to_sym]
    else
      super.method_missing(method_id)
    end
  end
end

hash = {:color_one=>"black", :color_two=>"green"}
hash.color_one

Я уверен, что мог бы сделать код более жестким (если бы знал как).

person Dan Rosenstark    schedule 21.05.2010
comment
Исправление объекта не имеет смысла, так как это будет работать только с объектом, который имеет хотя бы метод each (по крайней мере, Enumerable). Также обратите внимание, что если все, что вам нужно, это хеш, где вы можете получить значение для ключа, выполнив hash.key вместо hash[key], вы можете просто использовать OpenStruct (или хеш-патч обезьяны с method_missing). - person sepp2k; 22.05.2010
comment
Спасибо @ sepp2k, я реализовал метод method_missing, но я не знаю, как элегантно обрабатывать ключи, являющиеся символами ИЛИ строками. - person Dan Rosenstark; 23.05.2010