Reputation: 18670
I have a model Book
with a has_many relationship to State
.
class Book < ActiveRecord::Base
has_many :states
...
end
State
sets the visibility of the book to "private", "restricted" or "public" through a name
attribute. For auditing purposes, we keep a record of all state changes so to get the current state of a book I use
> book = Book.first
> book.states.last.name
> "public"
Now I need to query on all Book
objects where the current state is public.
Something along the lines of:
Book.joins(:visibility_states).where(states: { state: "public"})
The problem is that the query above returns all books that are currently public, or have been public in the past. I only want it to return books that are currently "public" (i.e. book.states.last.name == "public").
I know I can do it with select but that's generating one query for each record:
Book.all.select { |b| b.states.last.name == "public" }
Is there a way to do it using ActiveRecord only?
Upvotes: 4
Views: 1233
Reputation: 3042
I will do something with better performance.
If you want to save the historical state changes, is it ok. But try to avoid to introduce more complexity to your application because of this.
Why don't you add a current_state attribute in your Book model?
It will be much faster, and easier to develop.
class Book < ActiveRecord::Base
def set_new_current_state!(new_state)
self.current_state = new_state # e.g State.public
self.stats << State.create(new_state)
end
end
And your query will be just this:
Book.where(current_state: 'public')
Upvotes: 1
Reputation: 1596
You can use window function:
Book.joins(:visibility_states)
.where(states: { state: "public"})
.where("visibility_states.id = FIRST_VALUE(visibility_states.id)
OVER(PARTITION BY books.id ORDER BY visibility_states.created_at DESC))")
Or may be in your situation it would be better to save current state in Book model
Upvotes: 4