As I said in my last post, I can't help but to think there is a better way to persist object graphs in Rails applications. The solution I outlined last time seems to stray from the DRY principle (Don't Repeat Yourself,) and just seems to leave a bad taste in my mouth. Don't you wish there was some way to leverage the meta-programming capabilities of Ruby and Rails to solve this problem in a generic way? Me too. Consider the following sketch of a solution:
def update_object_graph(root_entity, entities_seen = [])
# return if we've processed this entity already
return if entities_seen.include?(root_entity)
entities_seen << root_entity
errors = []
parameter_name = root_entity.class.name.underscore
# if paramaters are given for the root entity, then we update it
if params[parameter_name]
keys = params[parameter_name].keys
index = keys.index(root_entity.id.to_s)
root_entity.attributes = params[parameter_name].values[index]
errors += root_entity.errors.full_messages unless root_entity.save
end
forward_associations =
root_entity.class.reflect_on_all_associations -
root_entity.class.reflect_on_all_associations(:belongs_to)
forward_associations.each do |association|
# ignore acts_as_versioned association tables
next if(/::Version$/ =~ association.class_name)
raise unless root_entity.respond_to?(association.name)
# Deal with has_one associations
if(association.macro == :has_one)
assoc_entity = root_entity.send(association.name)
new_errors = update_object_graph(assoc_entity, entities_seen)
errors = errors + new_errors if new_errors
# Deal with has_many and habtm associations
else
association_entities = root_entity.send(association.name)
association_entities.each do |assoc_entity|
new_errors = update_object_graph(assoc_entity, entities_seen)
errors = errors + new_errors if new_errors
end
end
end
errors
end
The idea here is that we first update the current entity with any corresponding values in the parameters hash. Then we attempt to
save the entity and collect any errors. Remember, since the save
method is called on the actual instance of
the entity in question, it's erorrs collection will be populated and used when rendering the page. Now things get interesting - we discover the
associations of the entity, and then use reflection to obtain references to the actual objects that represent those associations. Now we recursively call persist_object_graph
on each of these objects.
Makes perfect sense right? Let me first say that this code doesn't actually work: it is just a sketch of a solution and has lots of oversights (no support for composed_of aggregations, it makes assumptions about the names of things, etc.) However, I was hopeful that I could take this idea and run with it. Unfortunately, this solution hits a brick wall when we back up and take a real close look at the Active Record design pattern. Can anyone spot the subtle but major flaw here?