My project is a website for a book club that allows users to share books and discuss them. Each review/comment has an option to add a heart or a favorite
.
The objective was to add a button to my book display page that displays the book with the highest favorite
count.
My schema looks like this:
Check out my repo for more context.
Solution
In the front I added a variable favoriteBook to state, a JSX button, and the onClick with the following fetch request:
const handleFavoriteClick = () => {
fetch('/books/favorite')
.then(res => res.json())
.then(res => setFavoriteBook(res))
}
From there, I added a custom route in routes.rb.
get '/books/favorite', to: 'books#favorite'
Next, in my Book model I added a method to determine the number of favorited reviews on a book instance.
def favorite_count
count = 0
self.reviews.each do |review|
if review.favorite == true
count += 1
end
end
count
end
self
above refers to an instance of Book in the class Book. I started the count
variable at zero, then iterated through each review to see if the favorite boolean was true. If it was, I added one to the count
.
In book_serializer.rb I added :favorite_count
to my attributes in order for it to be available to the frontend.
Finally, in books_controller.rb I was able to utilize the favorite_count method. I set the highest_count
variable to zero and a case 'No books have been favorited' incase of a scenario where there are no favorites. I iterated through each book and determined the book with highest number of favorited reviews.
def favorite
highest_count = 0
favorite_book = 'No books have been favorited.'
Book.all.each do |book|
if book.favorite_count > highest_count
highest_count = book.favorite_count
favorite_book = book
end
end
render json: favorite_book, status: :ok
end
Other Considerations
Routing
When I was initially fetching to the custom route I kept getting back content for books#index. I later determined that in order for my custom route to work on books_controller.rb, I needed to remove resources :books, only: [:show, :create]
This only:
was blocking the custom route. I ended up listing out the :show
and :create
routes along with :favorite
.
get '/books/show', to: 'books#show'
post '/books', to: 'books#create'
get '/books/favorite', to: 'books#favorite'
Controller Clean-Up
The #favorite action in books_controller.rb was originally made with only a highest_count
variable, and I was trying to save the book there.
def favorite
highest_count = ""
Book.all.each do |book|
if book.favorite_count > highest_count
highest_count = book
end
end
render json: favorite_book, status: :ok
end
This failed by the second iteration because I was comparing an integer to an instance of book.
The solution I made was to create two variables, one for the count and another for the book itself:
def favorite
highest_count = 0
favorite_book = 'No books have been favorited.'
Book.all.each do |book|
if book.favorite_count > highest_count
highest_count = book.favorite_count
favorite_book = book
end
end
render json: favorite_book, status: :ok
end
This was a success, but I thought could be cleaner.
Another solution would be as follows:
def favorite
favorite_book = Book.new(title: 'No books have been favorited.')
Book.all.each do |book|
if book.favorite_count > favorite_book.favorite_count
favorite_book = book
end
end
render json: favorite_book, status: :ok
end
Here, I'm comparing the favorite_count of the current highest book to the favorite_count of the book in the current iteration. I created an unsaved instance of Book with no reviews (and therefore no favorites) in order to make the first comparison.
Tips for Rails Live Coding
Determine where in the schema the information you're looking for is. In my scenario, my
favorite
attribute was on every individual review nested under books. I needed to calculate a count for each instance of Book before I could compare to find the highest favorite_count.Follow the call stack. First I needed a button to press, then the routing, then an empty controller action. From there I had to determine the connection between all my ruby files (controllers, serializers, and models) to ensure I was utilizing them properly. Remember the models represent an instance of a table item. Serializers are for data display to frontend. Controller is sending out the response.
Read error messages carefully. They're helpful if you take the time to understand the feedback. Pop in a debugger or binding.pry to take a closer look at whats happening.
Happy coding!
Top comments (0)