How do I apply the Law of Demeter to this?

291 views Asked by At

I have an admittedly ugly query to do, to find a particular role related to the current role. This line produces the correct result:

@person_event_role.event_role.event.event_roles.
  joins(:mission_role).where(:mission_roles => {:title => 'Boss'}).
  first.person_event_roles.first.person

(You can infer the associations from the plurality of those calls)

The only way to get this information requires a ton of knowledge of the structure of the database, but to remove the coupling... It would require filling in a bunch of helper functions in each step of that chain to give back the needed info...

1

There are 1 answers

1
Mark Tabler On BEST ANSWER

I think the thing to do here is to create the helper functions where appropriate. I'm unclear what the beginning of your association chain is here, but I'd probably assign it a method #event that returns event_role.event. From there, an event has an #boss_role, or whatever makes sense semantically, and that method is

event_roles.joins(:mission_role).where(:mission_roles => {:title => 'Boss'}).first 

Finally, also on the Event model, there's a #boss method, which gets

boss_roles.first.person_event_roles.first.person

So, your original query becomes

@person_event_role.event.boss

Each leg of the chain is then self-contained and easy to understand, and it doesn't require the beginning of your chain to be omniscient about the end of it. I don't fully comprehend the full reach of these associations, but I'm pretty sure that just breaking it into three or four model methods will give you the clean reading and separation of concerns you're looking for. You might even break it down further for additional ease of reading, but that becomes a question of style.

Hope that helps!

The following is by the original questioner

I think I followed this advice and ended up with:

@person_event_role.get_related_event_roles_for('Boss').first.filled_by.first

#person_event_role:
def get_related_event_roles_for(role)
  event.event_roles_for(role)
end

def event
 event_role.event
end

#event:
def event_roles_for(role)
  event_roles.for_role(role)
end

#event_role:
scope :for_role, lambda {|role| joins(:mission_role).where(:mission_roles => {:title => role})}
def filled_by
  person_event_roles.collect {|per| per.person}
end