¿Rails query a través de asociación limitada al logging más reciente?

class User has_many :books 

Necesito una consulta que devuelva:

Usuarios cuyo libro más reciente tiene: complete => true. es decir, si el libro más reciente de un usuario tiene: complete => false, no los quiero en mi resultado .

Lo que tengo hasta ahora

 User.joins(:books).merge(Book.where(:complete => true)) 

que es un comienzo prometedor pero no me da el resultado que necesito. He intentado agregar un .order("created_on desc").limit(1)
hasta el final de la consulta anterior, pero luego termino con un solo resultado cuando estoy esperando muchos.

¡Gracias!

Si no vas a utilizar la solución ruby ​​de @ rubyprince, esta es en realidad una consulta DB más compleja que ActiveRecord puede manejar en su forma más simple porque requiere una sub consulta. Así es como haría esto completamente con una consulta:

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

Para convertir esto en ActiveRecord, haría lo siguiente:

 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 

A continuación, puede get una list de todos los usuarios que tienen un último libro completo haciendo lo siguiente:

 User.last_book_completed 

Esto agrega un poco de sobrecarga, pero ahorra complejidad y aumenta la velocidad más adelante cuando es importante.

Agregue una columna "most_recent" a los libros. Asegúrese de agregar un índice.

 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 index end 

Luego, cuando guarde un libro, actualice 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) end end 

Ahora, para tu consulta

 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 

Esto le permite escribirlo así y el resultado es una relación que se utilizará con otros ámbitos.

 User.last_book_completed 

Recientemente me encontré con un problema similar y así es como lo resolví:

 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 

De esta forma, solo usamos los methods ActiveRecord (sin SQL adicional) y podemos reutilizarlo cuando consideramos cualquier subset de libros para los usuarios (primero, último, último n libros, etc.). Necesita la última causa 'uniq', de lo contrario cada usuario aparecería dos veces ..

No puedo pensar en una forma de hacerlo en una sola consulta, pero puedes hacer lo siguiente:

 User.all.select { |user| user.books.order("created_at desc").limit(1).complete } 

deberías usar ámbitos aquí: harán tu vida mucho más sencilla (este es un ejemplo de rails3)

En su model book.rb

 scope :completed, where("complete = ?", true) scope :recent, order("created_on ASC") 

Luego puede llamar a User.books.recent.completed para get lo que desea

basado en la respuesta de Pan pero con una combinación de la izquierda en lugar de una combinación interna, y sin agrupamiento:

 scope :with_last_book_complete, -> { subquery = Book.select(:id) .where("books.user_id = users.id") .order(created_at: :desc).limit(1) joins(<<-SQL).where(books: { complete: true }) LEFT JOIN books ON books.user_id = users.id AND books.id = (#{subquery.to_sql}) SQL }