Ruby on Rails order a parent model by child attribute - ruby-on-rails

How would I go about doing this? I would like to sort a selection parent model by an attribute on it's children. The parent model has a has many relationship with it's child. I'm not sure how to form the ActiveRecord query to get this.
Example: A thread has many posts. I want to grab a collection of threads ordered by the most recent post associated with it.
I've found a few solutions that convert the selection to an array and do a sort on that array, but I need the selection to stay as an ActiveRecord selection, so I can continue to chain queries onto it.

I would need more information about the layout of your database to give a better example, but you can join another table and then order by that table like so:
Thread.joins(:posts).order("posts.created_at").group("threads.id")

For your example, it would be far more efficient to simply have the children touch the parent:
class Thread < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :thread,
touch: true
end
This will change Thread's updated_at column whenever a post is created or changed, so that thread is considered updated at that time. You may need to actually access the child records in a more complicated example, but for ordering by the date of the child posts this saves you having to get the extra records out of the database when you don't actually need information from that table.

By using just plain queries, with no direct iteration over arrays, you can do something like:
Post.joins(:thread).includes(:thread).order("posts.created_at").group("thread_id").take(5)
# Maybe you can take only a few samples of your collection to show on Last Posted Topics,
# then you just replace take(5) with the number of posts you want to take.

Related

HMT or HABTM for Orders and Items

Fairly new to rails and trying to understand which relationships to use before going forward.
I have two models: orders and items. This is a many to many relationship, but I'm unsure of which relationship to use.
Orders might have delivery time, quantity of items, etc.
Lastly, what would you call the model joining orders and items if using HMT?
If you need to know anything else about the relationship of the item on a particular order, you need HMT.
If your items change price in the future, do you want to know how much they were sold for on orders in the past?
In this type of requirement, I've always had many "LineItem" records for an order, and the line_item instances belong_to to the item and order, and record the pricing and/or quantity for that order.
HMT vs HABTM? There are so few times that all you need is a many-to-many, that I'd almost always go with HMT for the extra ability to add more information to the association.
This seems like a classic case of HABTM, and the example given in the Rails Guides is perfect. The choice comes down to whether you need any other data or logic on the join model itself. If so, then use the HMT, where you will create a third active_record model to serve as the join table. You can name that anything you want. But it seems like HABTM will work for you, and all you need to setup is the join table with the default name (items_orders) in your migration, and rails will take care of everything else for you.
class Order < ActiveRecord::Base
has_and_belongs_to_many :items
end
class Item < ActiveRecord::Base
has_and_belongs_to_many :orders
end

Model with many has_one associations very slow on deleting records

I have a Notification model. Any time certain actions like Comment, Like, Mention, Follow happens this table gets a single record added to it. This table is now many millions of records long. The only index I have on it is on the user_id - which has been important as I show notifications to the current_user.
On each of these related models, I included the following line
has_one :notification, dependent: :destroy
The issue is that all these actions are reversible, so whenever someone, say, unlikes, I need to destroy the related Notification.
As such Notification#destroy action is very slow now, often taking >10secs! I'm certain this is because each time a destroy happens, it has to look up foreign keys like like_id, comment_id, or mention_id
I can add indexes on all these records, but I'm concerned given how big this table is and how often it gets INSERTS and DELETES. Should I be concerned? Is there a better way to structure this?
A possibility is considering flipping the relationship around, use belongs_to rather than has_one so you rely on only the primary key on that table.
Otherwise, you should really have indices on those; it shouldn't be too much of a burden. You'll get hammered on performance if you don't; it may even be locking the database on those deletes.
Use SHOW TABLE STATUS to see index sizes to get an idea of what you might be looking at.

How to validate parent-child relationships in Rails?

I have a Category model which can have multiple parent and child categories. I have modelled this using a Hierarchy model which contains parent_category and child_category attributes.
I can validate that the rows are unique using
validates_uniqueness_of :parent_category_id, scope: :child_category_id
This (and the corresponding unique index in database) takes care that there are not multiple rows representing the same parent-child relationship.
But, I want to prevent someone assigning parent of a child as it's child. For ex. if Category A is a parent of Category B, assigning Category A as a child of Category B should result in a validation error.
The only approach I can think of is querying the database in a validate method.
def child_parent_messup
unless Hierarchy.where(child_category_id: parent_category_id, parent_category_id: child_category_id).blank?
errors[:base] << "This child is also a parent of the same class."
end
end
How can this be improved?
What you are referring to is a 'cyclic' relationship - and, IMHO, there is no default validator built into rails for this.
To make it simpler, you can do the following:
Add a collection with has_many relation say, parents, which returns its parent as well its parent's parent
Check whether the children_id is included in the above parents collection
Your design seems little complicated, see whether you can make it simpler. May be SIngle Table Inheritance makes sense here, including for better validation handling. I might be wrong as I don't have much idea about the requirement.

Rails Order by Contained Objects 2 Levels Deep?

How can I include ordering in an 'order' ActiveRelation call that's more than one level deep?
That is, I understand the answer when it's only one level deep (asked and answered at Rails order by associated data). However, I have a case where the data on which I want to sort is two levels deep.
Specifically, in my schema a SongbookEntry contains a Recording, which contains an Artist and a Song. I want to be able to sort SongbookEntry lists by song title.
I can go one level deep and sort Recordings by song title:
#recordings = Recording.includes(:song).order('songs.title')
...but don't know how to go two levels deep. In addition, it would be great if I could sort on the recording (that is, the song title and the artist name) -- is this possible without descending into SQL?
Thanks for any help,
Keith
If you model the association between SongbookEntry and Song as such:
class SongbookEntry < ActiveRecord::Base
# ...
has_one :song, through: :recording
end
you will be able to access #songbookentry.song and SongbookEntry.joins(:song) using your existing schema.
Edit:
Applying the same idea for Artist, a possible query would be:
SongbookEntry.joins(:song,:artist).order('songs.title','artists.name')
Note that this may not be the most efficient operation (multiple joins involved) even though it looks Rails-ish, so later on you may want to denormalize the tables as Ryan suggested, or find another way to model the data.
I would advise storing the artist name (and possibly the song title too) on the recording itself, so you don't have to "descend into SQL".
Try this
SongbookEntry.includes(:recording=>[:artist,:song]).order('songs.title, artists.name')
You can use joins in place of includes if you don't want to use associated tables fields in views

Nested association/join in rails

I have a seat object that has a car object that has a owner that has a name. I want to display the car brand and the car's owner's name together. How do I do this in one query?
eg:
class Seat < ActiveRecord::Base
belongs_to :car
def description
"I am in a #{car.brand} belonging to #{car.owner.name}"
# --> how do I replace this with one query?
end
end
I'll note that this is a highly contrived example to simplify my question. I'm doing this thousands of times in a row, hence the need for more efficiency.
Let us say you are trying to query the Seat model, and you want to eager load the car and owner objects, you can use the includes clause.
Seat.includes(:car => :owner).where(:color => :red).each do |seat|
"I am in a #{seat.car.brand} belonging to #{seat.car.owner.name}"
end
Use default_scope
class Seat
default_scope includes([:car])
end
class Car
default_scope includes([:owner, :seats])
end
For multi-table joins that are often used in my application, I create a View in MySQL. Then create an ActiveRecord Rails model based on the view.
Depending on the SQL statement that powers the view, MySQL may even let the View be read/write. But I just go the simple route and always treat the view as being read-only. You can set the AR model as read only.
By using the Active Record model which uses the view, you get quick single query reads of the database. And they're even faster than normal since MySQL computes the SQL "plan" once for the view, enabling faster use of it.
Remember to also check that your foreign keys are all indexed. You don't want any table scans.

Resources