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?