Politics, Programming and Possibilities
23 Sep
ActiveRecord gives a lot of flexibility with its 16 callbacks. I’ve never needed anything more granular than that flexibilty until today.
In trying to automate the process of saving files on Amazon S3, I needed a callback that would be triggered after the creation of my “Photo” record, but before the creation of several associated objects. (These ActiveRecord objects were being held in memory via the association “build” method, and thus had not yet been inserted in to the database.)
Delving deep in to ActiveRecord to figure out how those build-associated objects were being automatically inserted in to the database when calling “save” on my Photo object, I found out it’s a lot easier than it might seem.
Just define your “after_create” callback before the call to “has_many” in your class. For example:
class Photo < ActiveRecord::Base
# This callback must be defined before "has_many :versions" so
# that it gets called *after* the Photo is created, but *before*
# the versions are inserted in to the DB.
after_create :send_id_to_versions
has_many :versions
def send_id_to_versions
...
end
end
If you switch the order of the after_create and the has_many above, the “Version” objects will be inserted to your database before the call to “send_id_to_versions”.
Hope that saved you an hour or two! ![]()
6 Responses for "An after_create_before_association_create Callback"
Hi Duane,
I’m trying to duplicate this technique. In my case, I’m trying to use it in conjunction with (something equivalent to):
class Version
(posting error)
class Version < ActiveRecord::Base
validates_presence_of photo_id
end
But the validation fails (it works when I comment out the validation), which I take to mean that Photo#send_id_to_versions is not actually setting Version#photo_id to Photo#id.
Do you have any insight into where I’m going wrong?
Emmanuel: You won’t be able to get the id in there before the validation step occurs if you’re using after_create, because the validation step always happens before the object is saved to the database.
You may be able to do what you’re trying to do without the trickiness I posted in this article, however. For example, ActiveRecord already does some setting of foreign key ids automatically.
For example:
p = Photo.new
p.versions.build(:number => 2)
p.save!
The above code will create a new record in the ‘photos’ table, with a new record in the ‘versions’ table–and that version will have a photo_id equal to the p.id just created.
Does this help?
Hi Duane,
That helped somewhat, but mainly I misunderstood the purpose of the technique. I didn’t know that objects created by Association#build automatically receive the foreign_key of the related object, and I assumed that was why you have Photo#send_id_to_versions.
Rather OT, but: do you know if it’s possible to pass initialization parameters for related objects via the Parent.new constructor? something like:
class Photo << (screwing up the syntax here)
def after_initialize(params={})
versions.build(params[:version]) if new_record?
end
end
@photo = Photo.new(:foo=>’photo_params’, :version=>{:bar=>’version_params’})
The approaches I’ve tried all fail in Photo.new, with errors about no attribute by that name.
The after_initialize method, like all callbacks, won’t take any parameters that I’m aware of. You might be able to solve the problem this way, though:
@photo = Photo.new(:caption => ‘my photo’, :versions => [Version.new(:number => 5)])
When @photo is saved, the new version there will also get the proper photo_id set.
Duane: thanks for the time and input. Your initial post and subsequent suggestions have been very helpful. Thanks especially much for entertaining my OT question. Your hint there was helpful also.
As you can probably tell, I’m fairly new at Ruby and Rails, and I’m finding that it is easy to use, but there’s still a learning curve. Interestingly, when I struggle with something in Rails (and Ruby generally), I usually find that the solution is simpler than I imagined.
Hey, while I’m thanking you: thanks for the super-useful TextMate bundle, too
Leave a reply