eager loading association on a subclass - ruby-on-rails

I have the following (simplified) class hierarchy:
def Parent < ActiveRecord::Base end
def Child < Parent
belongs_to :other
end
def Other < ActiveRecord::Base end
I want to get all Parent objects and -if they are Child objects- have them eager load the :other association. So I had hoped I could do:
Parent.find(:all, :include => [:other])
But as I feared, I get the message: "Association named 'other' was not found; perhaps you misspelled it?"
What is the best way to establish eager loading in this scenario?
[Edit]
As requested, here's the more concrete example:
Parent = Event
Child = PostEvent
Other = Post
I want to log different types of events, all with their own properties (some of which are references to other objects), like the above example. At the same time I want to be able to list all Events that occurred, hence the parent class.

Can you define the belongs_to :other association in the Parent model? It won't be relevant to every Parent object, but that's the nature of STI: you will almost always have some column that's not used by every child.
If you really can't move the association to the parent, you may have to load in two steps, for example:
Parent.find(:all, :conditions => "type != 'Child'") +
Child.find(:all, :include => [:other])

Since Child inherits from Parent (and not the other way around), Parent has no knowledge of the belongs_to :other association.
I think you need to reconsider how you're modeling your app. Perhaps some specifics on your actual models would raise some answers on alternative methods of what you're trying to accomplish.

Related

Ruby parent object gain access to child attributes

So I am wondering if there is a better way of doing this. I have a parent class that has three 1 to 1 relationships, children.
class Parent
has_one :child_one
has_one :child_two
has_one :child_three
...
end
Then within each child object I state belongs_to :parent, now ChildOne has attribute1, attribute2, ..., attribute30 etc.
I am using a gem that uses yaml to build calculations, but it can only access the table or model of the Parent class. It means that all the attributes i need from the ChildOne class I'll have to pull like this
def calculation_one
cal_one = self.child_one.attribute1
end
and onward. This would mean that I would have a model that's freakin fat just linking children attributes. Is there a better way of doing this?
Update
What i am looking for is a way to basically attr_accessor a subclass?
class Parent
attr_accessor :child_one, :attribute1
end
person = Parent.new
person.attribute1 = "awesome"
person.attribute1 # => "awesome"
I think what you're looking for is the Delegate module in rails : you can call delegate to let a related model respond to a method call like that :
class Parent < ActiveRecord::Base
has_one :child_one
delegate :attribute1, to: :child_one
end
Full documentation : http://apidock.com/rails/Module/delegate

Ruby on Rails Pattern for Collecting has_many Objects in a Tree of Nodes

I have an organizational hierarchy represented via awesome nested set on a Node model. Great, works great, the updates are expensive but the finds are super efficient.
Each Node model has_many on other models, let's call them Foo and Bar.
class Node < ActiveRecord::Base
acts_as_nested_set
has_many :foos
has_many :bars
end
class Foo < ActiveRecord::Base
belongs_to :node
end
Frequently, I want to find all of the foos or bars for a given subtree, looking down from a current node. Naively, I could do:
#foos = #node.self_and_descendants.collect(&:foos).compact
I could even use an ActiveRecord .includes(:foos) to avoid N+1 queries. What I really want is just to ask for #node.all_foos so I implement something like this:
class Node < ActiveRecord::Base
def all_foos
Foo.where(node_id: self_and_descendants.pluck(:id))
end
# More efficient?
def all_foos_alternately
Foo.where(node_id: self_and_descendants.includes(:foos).pluck(:id))
end
end
But, let's say that I want to “collect” more than just foos and bars, let's say that I have half a dozen or a dozen of this models. Now I'm littering my Node class or some lib with a bunch of all_* methods.
Should I be defining class methods on the foos and bars that accept a node as an argument and return all of the foos/bars for that subtree? But then the foos and bars need to understand/be aware of node.self_and_descendants.
Or the Foo class method could accept a collection of nodes, not needing to be aware of the nested set methods, but then I lose the easy interface via node.all_foos or the like.
What's the pattern or method I'm missing here? I've played with implementing a catch-all for node.all_* via method_missing but don't like the performance hit. What I'm trying to execute here is, at its core, a database lookup and, as such, it should be efficient and elegant.
Thank you to #adamsanderson for pointing me in the right direction with regards to joins and merge. Merge allows you to filter a joined association via another scope or relation. Brilliant!
def all_foos
Foo.joins(:node).merge(self_and_descendants)
end
I verified tests on one of my all_* methods in my application, rewrote the method using this joins.merge technique, and reran my tests. All green!
This answer does not address the design issue of how to apply this method across many different has_many relations but I will leave that as an exercise for the reader (me).
See also, with regards to merge:
http://blog.mitchcrowe.com/blog/2012/04/14/10-most-underused-activerecord-relation-methods
I am going to propose a solution that I don't think is very good in order to attempt to kick off discussion...
class Node < ActiveRecord::Base
acts_as_nested_set
has_many :foos
has_many :tree_foos, :through => :children
def all_foos
# Shoot, how do I write this so I still get a relation back?
foos + tree_foos
# Nope, how about...
Foo.where(id: foo_ids + tree_foo_ids)
end
end
Blech. I don't like that. And I'll still have to repeat this code across all the has_many model associations.

Does an ActiveRecord object that has_many children know when a child belongs_to it?

I have an object model like:
class parent < ActiveRecord::Base
has_many: children
end
class child < ActiveRecord::Base
belongs_to: parent
end
My question is, if I do something like:
a_parent = Parent.create
child = Child.create(parent: a_parent)
Is there a clean way for the parent to be aware of the new child object so I can do something (like have the parent send out a birth announcement)? I'd rather not have the child do it (they can't even spell, let alone afford stamps). :)
I can imagine using an observer, or an after_create in the child to call a public method on the parent (calling parent.look_at_me). Ideally I'd like something that I define in the parent. Any other ideas?
You can use Association callbacks
class Parent
has_many :children, :after_add => :send_birth_announcement
def send_birth_announcement(child)
# ...
end
end
This would work in cases like this:
a_parent.children << a_child
a_parent.children.create(child_attributes)
However, ORM is leaky abstraction, so it won't be hard to miss such callback. It's always possible to create a child without instantiating parent, so perhaps after_create in child isn't that bad of an idea.
The basic answer is yes, the parent knows about its children. This is done by having a parent_id field in the child table. Whenever a parent wants to find its children all it need do is ask that table for all children with the right parent id.
This is a fundamental concept of relational databases, well worth reading more about to make sure you understand what is happening when you use model associations.
Edit:
A slight code correction for your example
a_parent = Parent.create
child = Child.create(:parent => a_parent)
You need to make sure you do the appropriate migration. See here. Given you've done that, the code above will automatically associate a_parent to its child.

Rails ActiveRecord Model Linked List

after intensive googling I will now state out a problem which seems to not occur often, but still is very basic. Linked Lists in Active Record. As far as I am now, we need two associations in the model:
class Child < ActiveRecord::Base
belongs_to :parent
belongs_to :next, :class_name => 'Child', :foreign_key => 'next_id'
belongs_to :previous, :class_name => 'Child', :foreign_key => 'previous_id'
end
So now we can get all children of a parent:
children = Child.where("parent_id = ?", parent_id)
And now to the question: I want of course to get all children from the database with one query, but I also want to go through the children in the linked order, which means first one will be the child with the previous attribute of nil, the next child will be the one which is connected by the firsts next attribute, and so on until the next attribute is nil.
Is it possible to do it like this, or do I need to query the first child, and then go from child to child without "precaching"?
The resort and ranked-model gems are other alternatives.
The first one uses an approach similar to linked lists. The second one uses a position attribute.
You should probably use the Rails acts_as_list gem. It stores the position of the item in the list, and even scopes to parent objects for lists and belongs_to. it would solve this problem as well by allowing you to query all the elements and then sort them correctly.

Rails sorting child objects and displaying

Relating to my last question here: Rails: Finding all associated objects to a parent object
Is it possible to sort multiple separate child objects in Rails by creation date, and then list them? Using the previous example I have a resume with two different has_many child objects, I would like to fetch them and then sort them based on creation date and then use that to display them.
I assume that you have two (or more) seperate models for children objects, so your Parent model looks like this:
class Parent < ActiveRecord::Base
has_many :dogs
has_many :cats
end
To sort them and get them generally as children you can write method (similar to #gertas answer):
def children
#children ||= (self.dogs.all + self.cats.all).sort(&:created_at)
end
and put it in Parent model. Then you can use it in controller:
#parent = Parent.find(params[:id])
#children = #parent.children
Now we'll try to display them in a view. I assume that you have created two partials for each model _cat.html.erb and _dog.html.erb. In view:
<h1>Children list:</h1>
<% #parent.children.each do |child| %>
<%= render child %>
<% end %>
It should automaticaly find which partial should be used, but it can be used only if you follow Rails way. If you want to name partials in different way, or store it in different directory, then you would have to write your own methods that will choose correct partial based on type od object.
You can add an accessor method on your parent model:
class Parent < ActiveRecord::Base
def sorted_children
children.scoped( :order => 'created_at DESC' )
# or, in rails3:
# children.order('created_at DESC')
end
end
If the natural order for your child model is the date field and you would like to do that everywhere, then just set a default scope on it:
class Child < ActiveRecord::Base
default_scope :order => 'created_at DESC'
end
As child objects are in different types and they are fetched separately (separate has_many) you have to do sorting in Ruby:
sorted_childs=(#resume.child1_sections.all + #resume.child2_sections.all).sort(&:created_at)
Otherwise you would need to introduce table inheritance with common columns in parent. Then it would be possible to have another has_many for all children with :order.

Resources