A better way to fill a hash with values

Go To StackoverFlow.com

0

Given I have an object which responds so some methods. I want to collect some of them in a Hash, I am always writing a snipped like this.

class Person
  # ...

  def some_selected_attributes
    attrs = {}
    [:first_name, :last_name, :zip].each do |attr|
      attrs[attr] = self.__send__(attr)
    end
    attrs
  end
end

I belive there is a more elegant way to do this.

:wq!

2012-04-05 23:30
by iblue


1

If you really want a hash, LBg has given you some fine patterns. But you may want to consider making small struct objects instead of primitive hashes. Structs behave like hashes for indexing and enumeration but also have accessor methods so they can be used with send and dot notation.

class Person
  # ...
  SomeSelectedPersonAttrs = Struct.new :first_name, :last_name, :zip

  def some_selected_attributes
    SomeSelectedPersonAttrs[ * SomeSelectedPersonAttrs.members.map{|a| send a } ]
  end

end

pa = person.some_selected_attributes
pa.first_name    # => "Joe"
pa[:first_name]  # => "Joe"
pa['first_name'] # => "Joe"
p.zip = 12345    # sets zip to 12345
p[:zip] = 12345
p['zip'] = 12345

pa.values       # => ["Joe","Blow",12345]
pa.each_pair {|k,v| ... }
2012-04-06 02:29
by dbenhur


2

As there's only three attributes, I see there's no reason to don't write it directly:

def some_selected_attributes
  {
    first_name: first_name,
    last_name: last_name,
    zip: zip
  }
end

You can also do it with inject:

def some_selected_attributes
  [:first_name, :last_name, :zip].inject({}) do |hash, attr|
    hash[attr] = __send__ attr
    hash
  end
end

Another option, using Hash[]:

def some_selected_attributes
  Hash[[:first_name, :last_name, :zip].map {|attr| [attr, __send__ attr] }]
end
2012-04-05 23:41
by Guilherme Bernal
I also wanted to mention inject. But even as I knew what it does, I'll always find it tricky to read. So i often come up with h = {}; ...;return h; just for readabilit - Deradon 2012-04-05 23:43
Thank you! The first not DRY enough. inject goes in the right direction, but is hard to read. Any other ideas - iblue 2012-04-05 23:51
See the last edit - Guilherme Bernal 2012-04-06 00:16
You can also use each_with_object instead of inject to get rid of the line with only hash on it: [:first_name, :last_name, :zip].each_with_object({}){|attr,hash| hash[attr] = __send__ attr}Abe Voelker 2012-04-06 01:12


-1

I think that there is a better way to accomplish what you're trying to do. It doesn't make much sense to write a method to return a hash of arguments that are already in the class. You could just make the instance variables or methods accessible using attr_accessor:

class Person
  attr_accessor :first_name, :last_name, :zip

end

Then you don't need the some_selected_attributes() method. You could just access the variables or methods directly:

p = Person.new
p.first_name  # => value of first_name

If you wanted a hash, you could just create it:

p = Person.new
hash = {:first_name => p.first_name, :last_name => p.last_name, :zip => p.zip }

I'm not sure why you'd want to build this hash either. It seems like the're a more "object oriented" way to accomplish whatever you're trying to do (by passing the whole object as an argument).

Instead of working with a hash of selected fields, I'd suggest working with whole objects.

2012-04-06 03:29
by user1182000
Ads