Reputation: 729
Question: Is there a concise way in Ruby (and/or Rails) to merge two objects together?
Specifically, I'm trying to figure out something akin to jQuery's $.extend()
method, whereas the first object you pass in will have its properties overridden by the second object.
I'm working with a tableless model in Rails 3.2+. When a form submission occurs, the parameters from the submission are used to dynamically populate a User object. That user object is persisted between page requests using Ruby's PStore class, marshalling objects to flat files which can be easily retrieved in the future.
Relevant code:
module Itc
class User
include ActiveModel::Validations
include ActiveModel::Conversion
include ActionView::Helpers
extend ActiveModel::Naming
def set_properties( properties = {} )
properties.each { |k, v|
class_eval("attr_reader :#{k.to_sym}")
self.instance_variable_set("@#{k}", v)
} unless properties.nil?
end
end
end
Creation of a user object occurs like this:
user = Itc.User.new( params[:user] )
user.save()
The save()
method above is not ActiveRecord's save method, but a method I wrote to do persistence via PStore.
If I have a user object loaded, and I have a form submission, I'd like to do something like this:
merged = existingUserObject.merge(User.new(params[:user])
and have the outcome of merged
be a user object, with only properties that were changed in the form submission be updated.
If anyone has any ideas about a better way to do this in general, I'm all ears.
Upvotes: 12
Views: 35537
Reputation: 59
This is how I achieved similar thing with 1 of my model
# merge other_config.attrs into self.attrs, only for nil attrs
def merge!(other_object)
return if other_object.nil? || other_object.class != self.class
self.assign_attributes(self.attributes.slice ('id').merge(other_object.attributes.slice!('id')){|key, oldval, newval|
oldval.nil? ? newval: oldval
})
end
Upvotes: 0
Reputation: 889
Is Hash#merge
not what you're looking for? http://www.ruby-doc.org/core-1.9.3/Hash.html#method-i-merge. Seems like you could just go
merged = existingUserObject.merge(params[:user])
I don't think you need to create an entirely new User
object since presumably that's what existingUserObject is and you just want to overwrite some properties.
Upvotes: 19
Reputation: 160551
Do it by piggybacking on hash's behaviors. Create a class that takes a hash for the parameter of the new()
method, and then a to_h
method that takes an object and generates a hash from the current state of the instance:
class Foo
def initialize(params={})
@a = params[:a]
@b = params[:b]
end
def to_h
{
a: @a,
b: @b
}
end
end
instance_a = Foo.new(a: 1, b:2)
instance_b = Foo.new(a: 1, b:3)
instance_c = Foo.new(instance_a.to_h.merge(instance_b.to_h))
Dumping it into IRB:
irb(main):001:0> class Foo
irb(main):002:1> def initialize(params={})
irb(main):003:2> @a = params[:a]
irb(main):004:2> @b = params[:b]
irb(main):005:2> end
irb(main):006:1>
irb(main):007:1* def to_h
irb(main):008:2> {
irb(main):009:3* a: @a,
irb(main):010:3* b: @b
irb(main):011:3> }
irb(main):012:2> end
irb(main):013:1> end
nil
irb(main):014:0>
irb(main):015:0* instance_a = Foo.new(a: 1, b:2)
#<Foo:0x1009cfd00
@a = 1,
@b = 2
>
irb(main):016:0> instance_b = Foo.new(a: 1, b:3)
#<Foo:0x1009ead08
@a = 1,
@b = 3
>
irb(main):017:0>
irb(main):018:0* instance_c = Foo.new(instance_a.to_h.merge(instance_b.to_h))
#<Foo:0x100a06c60
@a = 1,
@b = 3
>
Upvotes: 4