I posted this in my tumblelog too, but it's just so cool, I have to post it here in case anybody misses it:
model_first.attributes.each {|attr, value| eval("new_model.#{attr}= first_model.#{attr}")}
Awesome.
If you need to clone a model from another model, this is all you need to do. It can throw validation errors in some cases, where it violates validates_uniqueness_of constraints, but you can check the model and adjust accordingly. In fact, you can do that programmatically, so this could be added to ActiveRecord::Base. And if you add it to Base, it works. I know because I've done it.
So cool!
Update: this'll be available in plugin form soon. Probably after this coming weekend. I'm mostly just doing it for fun, but I could see this being useful for people. I created it because I need it for a project.
Wednesday, June 20, 2007
Subscribe to:
Post Comments (Atom)













Isn't this the same as:
ReplyDeletenew_model.attributes = old_model.attributes
Or am I missing something?
Marcel,
ReplyDeleteYour way will ignore any attr_protected attributes, while Giles' implementation will clone them as well. Depends on what behavior is desired then, I suppose.
One thing I've found with stuff like this is often times I don't want to clone foreign keys. It'd be trivial to write an attr_not_cloneable macro though of course.
Also the cool kids would use
ReplyDeleteself.send("#{attr}="), model.send(attr)
instead of eval
Any chance you might adjust your feed to show a few blank lines before the Feedburner switch notice? It looks like a part of the last paragraph, and that's a wee bit annoying. :)
ReplyDeleteAlso, is there a reason to show that notice when I've already switched to your Feedburner feed?
rytmis - sorry about the Feedburner lameness. Feedburner pulls that notice from the original feed, so I'm just turning off the switch notice in the original feed. Thanks for switching, by the way.
ReplyDeletemarcel - I would have said yes, except for that attr_protected thing Pat mentioned. I don't know exactly what that is, though, so I'm going to have to settle for maybe.
pat - yeah, cloning foreign keys is bad, also unique attributes obviously if you're cloning two models of the same class. thanks for telling me how my code would have looked if I was cool. :-p
attr_protected protects attributes from mass assignment.
ReplyDeleteclass Employee < ActiveRecord::Base
attr_protected :salary
end
>> e = Employee.new :name => "Pat Maddox", :salary => "10000000"
=> #<Employee:0x35e4964 @attributes={"name"=>"Pat Maddox", "salary"=>nil}, @new_record=true>
See how the salary is nil? We have to set it using the accessor:
>> e.salary = 10000000
=> 10000000
>> e
=> #<Employee:0x35e4964 @attributes={"name"=>"Pat Maddox", "salary"=>10000000}, @new_record=true>
attr_protected also kicks in with Marcel's example of self.attributes = other.attributes:
>> e2 = Employee.new
=> #<Employee:0x35d3aec @attributes={"name"=>nil, "salary"=>nil}, @new_record=true>
>> e2.attributes = e.attributes
=> {"name"=>"Pat Maddox", "salary"=>10000000}
>> e2.name
=> "Pat Maddox"
>> e2.salary
=> nil
However when we do it your way, we're not doing a mass assignment - rather we're iterating over the attributes and using the individual accessors:
>> e2 = Employee.new
=> #<Employee:0x35b5f4c @attributes={"name"=>nil, "salary"=>nil}, @new_record=true>
>> e2.clone_from e
=> {"name"=>"Pat Maddox", "salary"=>10000000}
>> e2.name
=> "Pat Maddox"
>> e2.salary
=> 10000000
Hope that makes sense.
Ah yes. See, that's exactly what I was thinking of at the time. That's why I didn't use attributes.
ReplyDeleteLooks to me that this does not work for models which have has_and_belongs_to_many relationships. I keep getting complaints about duplicate keys. I'm currently copying like this:
ReplyDeleteresult.dishes << original.dishes