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
Inserting an Array Using Sequel Gem in Postgresql
Ruby, Remove Last N Characters from a String
Allow Public Connections to Local Ruby on Rails Development Server
Ruby | Find a Way to Find an Exception on the Same Word to Capitalize
Ruby Class Inheritance: What Is '<<' (Double Less Than)
Ruby Ternary Operator Without Else
What Does the Equal ('=') Symbol Do When Put After the Method Name in a Method Definition
What's the Best/Easiest Gui Library for Ruby
How to Run a .Rb File from Irb
Breaking Up Long Strings on Multiple Lines in Ruby Without Stripping Newlines
Determining If a Variable Is Within Range
Switch Theme in an Existing Jekyll Installation