Rails query through association limited to most recent record? Rails query through association limited to most recent record? ruby ruby

Rails query through association limited to most recent record?


If you aren't going to go with @rubyprince's ruby solution, this is actually a more complex DB query than ActiveRecord can handle in it's simplest form because it requires a sub-query. Here's how I would do this entirely with a query:

SELECT   users.*FROM     users         INNER JOIN books on books.user_id = users.idWHERE    books.created_on = ( SELECT  MAX(books.created_on)                              FROM    books                              WHERE   books.user_id = users.id)         AND books.complete = trueGROUP BY users.id

To convert this into ActiveRecord I would do the following:

class User  scope :last_book_completed, joins(:books)    .where('books.created_on = (SELECT MAX(books.created_on) FROM books WHERE books.user_id = users.id)')    .where('books.complete = true')    .group('users.id')end

You can then get a list of all users that have a last completed book by doing the following:

User.last_book_completed


This adds a little overhead, but saves complexity and increases speed later when it matters.

Add a "most_recent" column to books. Make sure you add an index.

class AddMostRecentToBooks < ActiveRecord::Migration  def self.change    add_column :books, :most_recent, :boolean, :default => false, :null => false  end  add_index :books, :most_recent, where: :most_recent  # partial indexend

Then, when you save a book, update most_recent

class Book < ActiveRecord::Base  on_save :mark_most_recent  def mark_most_recent    user.books.order(:created_at => :desc).offset(1).update_all(:most_recent => false)    user.books.order(:created_at => :desc).limit(1).update_all(:most_recent => true)  endend

Now, for your query

class User < ActiveRecord::Base  # Could also include and preload most-recent book this way for lists if you wanted  has_one :most_recent_book, -> { where(:most_recent => true) }, :class_name => 'Book'  scope :last_book_completed, -> { joins(:books).where(:books => { :most_recent => true, :complete => true })end

This allows you to write it like this and the result is a Relation to be used with other scopes.

User.last_book_completed


I recently came across a similar problem and here is how I solved it:

most_recent_book_ids = User.all.map {|user| user.books.last.id }results = User.joins(:books).where('books.id in (?) AND books.complete == ?', most_recent_book_ids, true).uniq

This way we only use ActiveRecord methods (no extra SQL) and can reuse it when considering any subset of books for users (first, last, last n books, etc...). You need the last 'uniq' cause otherwise each user would appear twice..