0

I have a database schema with a few levels of nesting. Let's say it has a User model which has_many Foo, and Foo has_many Bar, and Bar has_many Baz. Now let's say I want to return all of the Baz belonging to a given User. Of course, there is a way to get that with just the associations above, but to my knowledge, it's circuitous.

For convenience, I've just been saying that User has_many Foo, Bar, and Baz. Then I can bring up any records belonging to a User easily. Furthermore, anytime I find myself regularly needing to access an association that's a few levels of nesting away, I just go ahead and create a direct association. It seems to have been working fine so far.

I can't really find much about this practice on the web, so my fear is that it's not done much and maybe isn't a good one. Is this OK? Is there a better way to move nested through nested associations like this?

Davigor
  • 373
  • 3
  • 15

2 Answers2

1

I think it's ok if it makes sense for those things to belong to each other in that way.

For example, I see no better way to do:

Author has_many books has_many pages has_many words has_many letters

You can link them by doing:

Author has_many pages, through: :books

Rails will figure out the connections and it'll make the code more readable.

You'd need to link all the way through the models to be able to do Author has_many letters, through: :books, for example:

Books has_many words, through: :pages

Pages has_many letters, through: :words

  • 1
    This seems like a more elegant solution than what I'm doing. And you still get the automatic helper methods for the deeply nested objects this way, so you could call `author.letters`? – Davigor Sep 08 '18 at 21:23
  • Yep, author.letters will be all the letters Rails can find through books for that author – Steve Brewer Sep 09 '18 at 15:55
0

I would use joins, and create a helper method that would do a deeply nested find, collecting the records in the way I needed them. You can read more about the deeply nested includes at Rails - Nested includes on Active Records? . I can't say myself if its best practice.

class User
  def self.find_bazzes(user_id)
    bazs = []
    self.includes(foo: [{bar: [:baz]}]).find(user_id).foos.do |foo|
      foo.bars.each{|bar| bazs += bar.bazs}
    end
    bazs
  end
end
Billy Kimble
  • 798
  • 3
  • 9
  • 1
    This should raise error `Undefined method 'includes' for <#User ...>` – Jagdeep Singh Sep 07 '18 at 06:30
  • 1
    Instead of `bar.bazs.each { ... }`, you could do `bazs |= bar.bazs` – Jagdeep Singh Sep 07 '18 at 06:32
  • Also, what is the use of making it a class method? It could as easily be an instance method. – Jagdeep Singh Sep 07 '18 at 06:32
  • Thanks for the feedback. I fixed the order of operation issue. Re |=, that will only add unique Bazes to the array. If he had two of the same bars in there, then a given baz would show up twice, but |= would ensure it only appeared once. Depending on his case, he may want += instead, to append all of the array elements.Updating the response with that. – Billy Kimble Sep 07 '18 at 15:52
  • Regarding a class method vs instance method, one of them will execute one query, and the other, two. You don't always want to include the join tables. If it was an instance method, that assumes you already have a user that you've found, and for the helper to work, you either need to re-query the dependent records to get to the bazs, or you would have had to have this data already sitting in you user record, which is likely not optimal. This highly depends on his use case, but he said "I want all of the bazes for a given user" in the question. – Billy Kimble Sep 07 '18 at 15:56