Dry Way to Assign Hash Values to an Object

DRY way to assign hash values to an object

Best bet is probably to simply define a method like update_attributes which takes a hash and does it inside an instance method of the class.

Expanding on what others have written and what you seem to need I think your best bet would be:

hash.keys.each do |key|
m = "#{key}="
obj.send( m, hash[key] ) if obj.respond_to?( m )
end

This will account for:

  • not having all the attributes of the class in the hash at all times, and
  • any number of keys in the hash (not just :name, etc)

DRY way to assign hash values to an object

Best bet is probably to simply define a method like update_attributes which takes a hash and does it inside an instance method of the class.

Expanding on what others have written and what you seem to need I think your best bet would be:

hash.keys.each do |key|
m = "#{key}="
obj.send( m, hash[key] ) if obj.respond_to?( m )
end

This will account for:

  • not having all the attributes of the class in the hash at all times, and
  • any number of keys in the hash (not just :name, etc)

How to assign hash values to an object while creating each key-value pair as a new object in Ruby

Edit

I started riffing on an answer below, but it got me thinking "there's usually a built-in way to do this in Ruby/Rails", I would check out SimpleDelgator and if anyone wants to take a crack at using that to create a model like PersonAttributor < SimpleDelgator and do your transformations inside that, I'd upvote it.


Not sure if this is the direction you're looking to go but you could do something with JSON serialization. If you happen to be using Postgres you could use the JASONB column type and store the data as JSON that can be queried directly. But plain serialization could happen like:

person.attributes = {'one' => 'something', 'two' => 'some_other_thing'}.to_json
=> "{\"one\":\"something\",\"two\":\"some_other_thing\"}"
person.save

JSON(person.attributes)
=> {"one"=>"something", "two"=>"some_other_thing"}

JSON(person.attributes)["one"]
=> "something"

This is a very simple, off the cuff example. It puts all of your key/value pairs into a column without creating a ton of attr_accessors. You could do the clean-up when the hash is first assigned to the person.

This doesn't cover your idea of moving those attributes into classes of their own. If you are looking to create these objects on the fly, I'm not sure what you're saving yourself by doing that. If you want them to be accessible as methods you could use OpenStruct in some way, here's one:

In your person model you could have a method:

class Person < ApplicationRecord
...
def attr_object
OpenStruct.new(self.attributes)
end
...

You'd then get an object with all of your keys as methods and your values as the return of those methods...

os_obj = person.attr_object
=> #<OpenStruct one='something', two='something_else'....>

You now have an object that gives you the methods one and one=. You can apply all of your transformations to that object using the methods...

os_obj.one
=> 'something'
os_obj.one = 'something new'
=> 'something new'

You could create a model that inherits from Person and creates the OpenStruct object. It could also store the updated key/value pairs from the OpenStruct object by serializing it as JSON:

person.attr_store = os_obj.to_h.to_json
"{\"one\":\"something new\"....}"

new_obj = Openstruct.new(JSON(person.attr_store))
=> #<OpenStruct one='something_new', two='something_else'....>

Hope that leads to something useful for you.

DRY way to assign function values to an object

This seems to be the best option so far. The first answer to the question started me on this route, but the poster seems to have removed it…

  def assign_factors_to_score
factors.each do |factor|
@score.public_send("#{factor}=", self.public_send(factor))
end
end

def factors
%i{factor_a factor_b factor_c factor_d}
end

Creating an object for a hash key

Change 'def' to 'class'

class CompositeKey
...
end

What's the best way to assign an array of values to Hash.values


row.keys.zip([5, 6, 7]){|kv| row.store(*kv)}

DRY Ruby Initialization with Hash Argument

You don't need the constant, but I don't think you can eliminate symbol-to-string:

class Example
attr_reader :name, :age

def initialize args
args.each do |k,v|
instance_variable_set("@#{k}", v) unless v.nil?
end
end
end
#=> nil
e1 = Example.new :name => 'foo', :age => 33
#=> #<Example:0x3f9a1c @name="foo", @age=33>
e2 = Example.new :name => 'bar'
#=> #<Example:0x3eb15c @name="bar">
e1.name
#=> "foo"
e1.age
#=> 33
e2.name
#=> "bar"
e2.age
#=> nil

BTW, you might take a look (if you haven't already) at the Struct class generator class, it's somewhat similar to what you are doing, but no hash-type initialization (but I guess it wouldn't be hard to make adequate generator class).

HasProperties

Trying to implement hurikhan's idea, this is what I came to:

module HasProperties
attr_accessor :props

def has_properties *args
@props = args
instance_eval { attr_reader *args }
end

def self.included base
base.extend self
end

def initialize(args)
args.each {|k,v|
instance_variable_set "@#{k}", v if self.class.props.member?(k)
} if args.is_a? Hash
end
end

class Example
include HasProperties

has_properties :foo, :bar

# you'll have to call super if you want custom constructor
def initialize args
super
puts 'init example'
end
end

e = Example.new :foo => 'asd', :bar => 23
p e.foo
#=> "asd"
p e.bar
#=> 23

As I'm not that proficient with metaprogramming, I made the answer community wiki so anyone's free to change the implementation.

Struct.hash_initialized

Expanding on Marc-Andre's answer, here is a generic, Struct based method to create hash-initialized classes:

class Struct
def self.hash_initialized *params
klass = Class.new(self.new(*params))

klass.class_eval do
define_method(:initialize) do |h|
super(*h.values_at(*params))
end
end
klass
end
end

# create class and give it a list of properties
MyClass = Struct.hash_initialized :name, :age

# initialize an instance with a hash
m = MyClass.new :name => 'asd', :age => 32
p m
#=>#<struct MyClass name="asd", age=32>


Related Topics



Leave a reply



Submit