views:

203

answers:

4

I have a bunch of methods like this in view helper

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

I'd like these methods exposed to the view, but I'd prefer the helper to be a bit more concise. What's the best way to turn a hash, say, into methods (or something else)?

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

Though I don't think trading this bit of conciseness for the readability of your first approach is necessarily a good idea.

If you want to generalize this, you can add the following method to the Module class:

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

And then in your helper call methods_from_hash(:background => ...).

sepp2k
Actually it would be cleaner if you use a variable for the hash and the monkey patch to the module class. I quite like it. Thanks.
Yar
@sepp2k I remixed your answer a bit in my "answer" below... though I need to figure out which version is better in the context of the original question (a helper).
Yar
+3  A: 

If you create constants in a namespace, then you can easily whip up accessors for those constants:

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

The include Values mixes the constants into Foo for the convenience of Foo's own methods. It is not needed for this pattern to work.

Wayne Conrad
Ruby knows they are constants by their case, or is the case merely a convention?
Yar
@yar, Ruby knows we are defining a constant because the identifier starts with a capital letter. How it keeps track of what is a constant and what is not, I do not know.
Wayne Conrad
It's an ironic sad fact about Ruby: so much of it is written in non-Ruby, that it's hard to know all this stuff unless you want to get into the C beneath. Thanks for your answer.
Yar
@yar: you say it like it is a bad feature of ruby. Actually in most languages you do not need to know how things are handled underneath. How is a const in C handled? in C++? java? You know by declaring it const it is a constant. But i do not need to know more. Likewise in ruby: declare a capitalized variable, and it is treated as a constant. But there is heaps of documentation around if that is what you ment.
nathanvda
@nathanvda, it is a negative comment about Ruby, though probably not in this case. As you say, in Java `static final` is a language feature, and therefore not written in Java. So you're right in this case.
Yar
A: 

Here is my remix of sepp2k's answer. It's a bit more OO and works even in irb. Not sure whether to patch Object or Hash.

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

Test code

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

OpenStruct: thanks to sepp2k again! I didn't know this existed.

Here is yet another version using 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

I'm sure I could get the code tighter (if I knew how).

Yar
Patching Object makes no sense as this will only ever work on an object that has at least an each method (so at least Enumerable). Also note that if all you want is a hash where you can get the value for a key by doing `hash.key` instead of `hash[key]`, you can just use OpenStruct (or monkey-patch Hash with `method_missing`).
sepp2k
Thanks @sepp2k, I implemented with method_missing, but I don't know how to elegantly handle keys being symbols OR strings.
Yar
+1  A: 

Actually, ruby has something called OpenStruct, which is quite awesome and really useful for when you want hash but do not want to use it like one.

require 'ostruct'

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

p colors.background #=> "0x00FF00"
vava
Thanks vava, I learned about this yesterday thanks to sepp2k's comment on my answer (below somewhere).
Yar