newbie here stuck on nested resources. I have a image.rb model, which stores images onto the cloudinary CDN. After getting all that working, I decided I needed to categorize the images, so i created a category.rb model which just has a name really, for now anyways.
I have been slowly working through errors after adding a category model and controller/views and all was going okay, until this one. I'm stumped lol. Have been googling around for a few hours now and decided I'm at a wall... so, naturally I come here, hoping someone could enlighten me. Thanks in advance! <3 SO
Here is the error (it is currently throwing it on the "edit" link, but i think it will throw it on the "delete" as well after i get edit working, so I have been changing them there as well):
No route matches {:category_id=>nil, :id=>#<Image id: 16, title: "mario", description: "i belong to category 2!", upload_date: nil, created_at: "2014-03-15 01:13:54", updated_at: "2014-03-15 01:13:54", category_id: 2>} missing required keys: [:category_id]
<td><%= link_to "Show", [#category, image] %></td>
<% if admin? %>
<td><%= link_to "Edit", edit_category_image_path(#category, image) %></td>
<td><%= link_to "Delete", [#category, image], confirm: "Are you sure you want to delete \"#{image.title}\"?", method: :delete %></td>
<% end %>
</tr>
Here is my Image model:
class Image < ActiveRecord::Base
has_attachment :image_file, accept: [:jpg, :png, :gif]
validates :title, presence: true
belongs_to :category
end
Here is the Category model:
class Category < ActiveRecord::Base
has_many :images
end
Here are the controllers for each:
class ImagesController < ApplicationController
before_filter :authenticate_admin, except: [:index, :show]
def index
#images = Image.all
end
def show
#image = Image.find(params[:category_id])
end
def new
#category = Category.find(params[:category_id])
#image = #category.images.build(image_params)
end
def create
#category = Category.find(params[:category_id])
#image = #category.images.build(image_params)
if #image.save
render "show"
else
render "new"
end
end
def destroy
#image = Image.find(params[:id])
#image.destroy
redirect_to images_path
end
def edit
#category = Category.find(params[:category_id])
#image = #category.images.find(params[:id])
end
def update
#category = Category.find(params[:category_id])
#image = #category.images.find(params[:id])
if #image.update(image_params)
render "show"
else
render "edit"
end
end
private
def image_params
params.require(:image).permit(:title, :description, :image_file, :category_id)
end
end
and the Category controller
class CategoriesController < ApplicationController
def index
#categories = Category.all
end
def show
redirect_to category_images_path(params[:id])
end
def new
#category = Category.new
end
def create
#category = Category.new(category_params)
if #category.save
redirect_to #category
else
render "new"
end
end
def destroy
#category = Category.find(params[:id])
#category.destroy
redirect_to categories_path
end
def edit
#category = Category.find(params[:id])
end
def update
#category = Category.find(params[:id])
if #category.update(category_params)
redirect_to #category
else
render "edit"
end
end
private
def category_params
params.require(:category).permit(:title)
end
end
Here is the routes file:
Portfolio::Application.routes.draw do
devise_for :users
resources :blogs
resources :categories do
resources :images
end
root "index#index"
#attachinary
mount Attachinary::Engine => "/attachinary"
end
From googling around I kept running into people having problems with it being related to form partials so here is the _form.html.erb I am using for Image just in case it's needed:
<%= form_for([#category, #image]) do |f| %>
<%= render "error" %>
<p>
<%= f.label :title %>
<br>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :description %><br>
<%= f.text_area :description %>
</p>
<p>
<%= f.label "Upload image" %><br>
<%= f.attachinary_file_field :image_file %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
ImagesController
class ImagesController < ApplicationController
before_action :load_category
before_action :set_image, only: [:show, :edit, :update, :destroy]
before_filter :authenticate_admin, except: [:index, :show]
def index
#images = #category.images.all
end
def show
end
def new
#image = #category.images.new
end
def create
#image = #category.images.new(image_params)
if #image.save
render "show"
else
render "new"
end
end
def destroy
#image.destroy
redirect_to images_path
end
def edit
end
def update
if #image.update(image_params)
render "show"
else
render "edit"
end
end
private
def load_category
#category = Category.find(params[:category_id])
end
def set_image
#image = #category.images.find(params[:id])
end
def image_params
params.require(:image).permit(:title, :description, :image_file, :category_id)
end
end
Try to use the before_action callback to set your necessary instance variables. Here I loaded the category and the images via a private method. This saves a lot time. You can even see this when you generate scaffolding.
before_filter has been aliased as before_action to make it clearer of its function.
The root path is usually defined on the second line of routes so its clear.
Your named paths doesn't seem like the problem. It is mainly your images controller
I suggest you generate a new rails app using scaffolding just to look at it's controllers.
rails g scaffolding images
Related
Hey here! I'm kinda new to Rails and I've been trying to find some answers but no luck yet so here we go.
I've set up a basic Rails app and just trying to save a Client to my database with a validation but nothing seems to be coming together. Anyone could point me to the right direction please or let me know what I've been doing wrong in my code.
I keep getting errors like this:
NoMethodError in Clients#new
Showing /Users/******/Documents/******/*****/app/views/clients/_form.html.erb where line #1 raised:
undefined method `clients_path' for #<ActionView::Base:0x00000000064a50>
Did you mean? clients_new_path
Even if I remove #client = Client.new from the new method I can view the page but nothing gets saved.
I'm stuck really now so any help much appreciated!
Thanks!
My Routes.rb file:
Rails.application.routes.draw do
mount RailsAdmin::Engine => '/admin', as: 'rails_admin'
get 'dashboard/index'
root to: "home#index"
devise_for :users, controllers: {
sessions: 'users/sessions',
passwords: 'users/passwords',
registrations: 'users/registrations'
}
get '/clients/index'
get '/clients/new'
get '/clients/edit'
get '/clients/delete'
get '/clients/:id', to: 'clients#show'
post '/clients/new', to: 'clients#create'
end
My Dashboard file:
<% if user_signed_in? %>
<nav class="subnav">
<ul>
<li><%= link_to('My Clients', clients_index_path) %></li>
<li><%= link_to('Add Client', clients_new_path) %></li>
<li><%= link_to('Edit registration', edit_user_registration_path) %></li>
<li><%= link_to('Logout', destroy_user_session_path, :method => :delete) %></li>
</ul>
</nav>
<% else %>
<%= link_to('Register', new_user_registration_path) %>
<%= link_to('Login', new_user_session_path) %>
<% end %>
My ClientsController file:
class ClientsController < ApplicationController
def index
#clients = Client.all
end
def new
#client = Client.new
end
def show
#client = Client.find(params[:id])
end
def create
#client = Client.new(client_params)
if #client.save
redirect_to #client
else
render :new
end
end
def edit
end
def delete
end
private
def client_params
params.require(:client).permit(:name, :provider)
end
end
My form:
<%= form_with model: #client do |form| %>
<div>
<%= form.label :name %><br>
<%= form.text_field :name %>
<% client.errors.full_messages_for(:name).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
<div>
<%= form.label :provider %><br>
<%= form.text_field :provider %>
</div>
<div>
<%= form.label :business_type %><br>
<%= form.select :business_type, ["Partnership", "Sole Trader", "Limited Company"] %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>
Finally my new.html.erb file:
<h1>Clients#new</h1>
<%= render 'form', client: #client %>
clients_path is generated by resources :clients, only: :index or you probably need to give your route the name you want. Try this
get '/clients/index', as: :clients
or, if you want to specify non default paths as you're doing, your index is probably called clients_index_path, but you can check that with a rake routes or rails routes, because I'm not sure.
That said, I suggest you to go with the resources method in your routes file and use the default paths as you're trying to do. Something like
resources :clients
but now you don't have a path like /clients/index no more, just /clients for the index action.
If you're in doubts with routes try to read the guide about routing
The Rails way to declare the routes to Create, Update, Read and Destroy (CRUD) a resource is just:
Rails.application.routes.draw do
mount RailsAdmin::Engine => '/admin', as: 'rails_admin'
get 'dashboard/index'
root to: "home#index"
devise_for :users, controllers: {
sessions: 'users/sessions',
passwords: 'users/passwords',
registrations: 'users/registrations'
}
resources :clients
end
As you can see by the annotions above each method Rails does not add the "action" to the path except for the new and edit routes:
class ClientsController < ApplicationController
before_action :set_client, only: [:show, :edit, :update, :destroy]
# GET /clients
def index
#clients = Client.all
end
# GET /clients/1
def show
end
# GET /clients/new
def new
#client = Client.new
end
# POST /clients
def create
#client = Client.new(client_params)
if #client.save
redirect_to #client
else
render :new
end
end
# GET /clients/edit
def edit
end
# PATCH /clients/1
def update
if #client.update(client_params)
redirect_to #client
else
render :edit
end
end
# DELETE /clients/1
def delete
#client.destroy
redirect_to action: :index,
notice: 'Client deleted'
end
private
def set_client
#client = Client.find(params[:id])
end
def client_params
params.require(:client).permit(:name, :provider)
end
end
Thus you don't create resources with post '/clients/new' - You use POST /clients. Also when you use the "bare-bones" routing methods such as match, get, post etc Rails does not automatically add routing helper. If you actually wanted to generate the equivilent routes you would need to use:
post '/clients',
to: 'clients#create',
as: :clients
But you're much better off embracing the conventions and learning to use them to be productive.
I'm trying to create a twitter clone. I'm at the point where a user can post a tweet and it shows the content and the time it was posted. However i want it so the username is also next to the tweet of whoever tweeted it.
i'm unsure how to do this as the error is currently 'Couldn't find User without an ID' in my tweet controller create method. I'm also not sure of the syntax to display the username in index.html.erb.
thanks.
class TweetsController < ApplicationController
def index
#tweets = Tweet.all.order("created_at DESC")
#tweet = Tweet.new
# #user = User.find(params[:id])
end
def show
#tweet = Tweet.find(params[:id])
end
def new
# #tweet = Tweet.new
end
def create
#user = User.find(params[:id])
#tweet = Tweet.new(tweet_params)
#tweet.user = #user
if #tweet.save
redirect_to tweets_path
end
end
def edit
#tweet = Tweet.find(params[:id])
end
def update
#tweet = Tweet.find(params[:id])
#tweet.update(tweet_params)
redirect_to tweets_path
end
private
def tweet_params
params.require(:tweet).permit(:user_id,:content)
end
end
<h1>TWEETS</h1>
<%# #users.each do |user| %>
<%#= user.username %>
<%# end %>
<%= simple_form_for #tweet, id: "form-submit" do |f| %>
<%= f.input :content, label: "Tweet" %>
<%= f.button :submit, class: "btn btn-danger" %>
<% end %>
<br>
<% #tweets.each do |tweet| %>
<ul>
<li>
<%= tweet.created_at.strftime("%B %d %Y, %l:%M%P") %> <br>
<%= tweet.content %>
<%#= tweet.user.username %>
<%#= tweet.user.username %>
</li>
</ul>
<% end %>
You need to set the relation between your users and tweets table
in your tweet model add
class Tweet < ApplicationRecord
belongs_to :user
end
and in your user model
class User < ApplicationRecord
has_many :tweets
end
rails g migration AddUserIdToMessages and in this migration
def change
add_column :tweets, :user_id, :integer
end
After that you can user <%= tweet.user.username %>in your view to show the username
My test :
Controller
class TweetsController < ApplicationController
def index
#tweets = Tweet.all.order("created_at DESC")
end
end
Model
class Tweet < ApplicationRecord
belongs_to :user
end
View (index.html.erb)
<% #tweets.each do |tweet| %>
<ul>
<li>
<%= tweet.created_at.strftime("%B %d %Y, %l:%M%P") %> <br>
<p> Content : <%= tweet.content %></p>
Username : <%= tweet.user.username %>
</li>
</ul>
<% end %>
Route
Rails.application.routes.draw do
get 'tweet/index', to: 'tweets#index'
end
seed.rb (for testing)
User.create({username: "myName"})
Tweet.create({content: "hello",user_id: 1})
and run rake db:seed
and dont forget to add
def change
add_column :tweets, :user_id, :integer
end
In the generated migration.
I am creating a form where a user can change the images they uploaded. The problem is the form to edit the images is displaying the image metadata along with the images that have been uploaded. Alongside the images, in the HTML page it outputs the following:
[#<ActiveStorage::Attachment id: 3, name: "image", record_type: "Space", record_id: 1, blob_id: 3, created_at: "2018-08-20 00:52:57">,
How do I prevent it from displaying the above data?
Here is the relevant code
html.erb file that displays the images
<div>
<% if #space.image.attached? %>
<%= #space.image.each do |image| %>
<%= image_tag image %>
<% end %>
<% end %>
</div>
Model
class Space < ApplicationRecord
belongs_to :user
has_many_attached :image
end
Controller
class SpacesController < ApplicationController
before_action :authenticate_user!, except: [:show]
def index
#spaces = current_user.spaces
end
def new
#space = current_user.spaces.build
end
def create
#space = current_user.spaces.build(space_params)
if #space.save
redirect_to listing_space_path(#space), notice: "Saved..."
else
flash[:alert] = "Something went wrong..."
render :new
end
end
def show
end
def update
if #space.update(space_params)
flash[:notice] = "Saved!"
else
flash[:notice] = "Something went wrong. Please check your submission and try again."
end
redirect_back(fallback_location: request.referer)
end
private
def space_params
params.require(:space).permit(:name, :description, image: [])
end
end
On:
<%= #space.image.each do |image| %>
Remove the = after the <%.
On an ERB tag, the equals sign will print the result of that line. It is printing the .inspect on the image object.
For people using haml and running into this issue:
= #user.images.each do |image|
= image_tag image.variant(resize_to_limit: [300, 220])
becomes
# replace = sign
- #user.images.each do |image|
= image_tag image.variant(resize_to_limit: [300, 220])
I apologize for the newbie questions still relatively new to rails. I'm trying to show all the users who have liked my specific posts. I took a look at this similar question How to list the users who LIKE a given Post but it couldn't solve my problem. I listed below all the relevant simple code - thank you so much guys!!
Items Controller
class ItemsController < ApplicationController
before_action :authenticate_user!, only: [:new, :create]
before_action :set_item, only: [:show, :edit, :update, :destroy, :share]
def index
#items = Item.order("created_at DESC")
end
end
Index.html.erb
<% if user_signed_in? %>
<%= image_tag current_user.avatar, width: 70, class: "css-style" %>
<br>
<strong><%= link_to current_user.username, current_user, class: "profile-style" %></strong>
<ul><!--Trying to show all the users who have liked my specific posts here -->
<% item.likes.each do |like| %>
<li> <%= link_to(like.user.username, like.user) %></li>
<% end %>
</ul>
<br>
<br>
<% else %>
<%= link_to 'Login/SignUp', new_user_session_path %>
<% end %>
<% #items.each do |item| %>
<%= image_tag item.avatar.url(:medium), class: "block" %>
<div>
<%= render partial: "likes", locals: {item: item} %></span><%= item.likes_count %>
</div>
<% end %>
Items.rb
class Item < ApplicationRecord
has_many :likes, :counter_cache => true
belongs_to :user
has_attached_file :avatar, styles: { medium: "300x300>", thumb: "100x100>" }, default_url: "/images/:style/missing.png"
validates_attachment_content_type :avatar, content_type: /\Aimage\/.*\z/
end
User.rb
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable, omniauth_providers: [:facebook]
has_many :likes
has_attached_file :avatar, styles: { medium: "300x300>", thumb: "100x100>" }, default_url: "/images/:style/missing.png"
validates_attachment_content_type :avatar, content_type: /\Aimage\/.*\z/
def likes?(post)
post.likes.where(user_id: id).any?
end
end
Users Controller
class UsersController < ApplicationController
before_action :set_user, only: [:show, :edit, :update, :destroy, :share]
def index
#users = User.all
end
def show
#user = User.find(params[:id])
#items = Item.all
end
private
def set_user
#user = User.find(params[:id])
end
def item_params
params.require(:item).permit(:product, :amount, :city_id, :avatar)
end
end
Like.rb
class Like < ApplicationRecord
belongs_to :item, :counter_cache => true
belongs_to :user
end
Likes Controller
class Items::LikesController < ApplicationController
before_action :authenticate_user!
before_action :set_book
def create
#item.likes.where(user_id: current_user.id).first_or_create
respond_to do |format|
format.html {redirect_to #item}
format.js
end
end
def destroy
#item.likes.where(user_id: current_user.id).destroy_all
respond_to do |format|
format.html {redirect_to #item}
format.js
end
end
private
def set_book
#item = Item.find(params[:item_id])
end
end
Logs
Processing by ItemsController#index as HTML
Rendering items/index.html.erb within layouts/application
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT ? [["id", 1], ["LIMIT", 1]]
Item Load (0.3ms) SELECT "items".* FROM "items" ORDER BY created_at DESC
Rendered items/index.html.erb within layouts/application (287.6ms)
Completed 500 Internal Server Error in 313ms (ActiveRecord: 0.6ms)
ActionView::Template::Error (undefined local variable or method `item' for #<#:0x007f9afbfd14f0>
Did you mean? item_url
items_url
item_path
#items):
5:
6: <%= link_to current_user.username, current_user, class: "profile-style" %>
7:
8: <% item.likes.each do |like| %>
9: <%= link_to(like.user.username, like.user) %>
10: <% end %>
11:
app/views/items/index.html.erb:8:in `_app_views_items_index_html_erb___2312434021832006771_70151814680620'
Rendering /usr/local/lib/ruby/gems/2.3.0/gems/actionpack-5.0.0.1/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb within rescues/layout
Rendering /usr/local/lib/ruby/gems/2.3.0/gems/actionpack-5.0.0.1/lib/action_dispatch/middleware/templates/rescues/_source.html.erb
Rendered /usr/local/lib/ruby/gems/2.3.0/gems/actionpack-5.0.0.1/lib/action_dispatch/middleware/templates/rescues/_source.html.erb (8.3ms)
Rendering /usr/local/lib/ruby/gems/2.3.0/gems/actionpack-5.0.0.1/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb
Rendered /usr/local/lib/ruby/gems/2.3.0/gems/actionpack-5.0.0.1/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb (3.3ms)
Rendering /usr/local/lib/ruby/gems/2.3.0/gems/actionpack-5.0.0.1/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb
Rendered /usr/local/lib/ruby/gems/2.3.0/gems/actionpack-5.0.0.1/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb (1.2ms)
Rendered /usr/local/lib/ruby/gems/2.3.0/gems/actionpack-5.0.0.1/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb within rescues/layout (94.4ms)
In items/index.html.erb you are referencing #item.likes.each on line 9, but the instance variable #item has not been set in items_controller#index. You have only defined #items, hence the error you are receiving about nil not responding to #likes.
If you want to see the likes for all items, there are a number of ways to achieve this with Arel.
It's a simple mistake. I think the problem is with your partial.. you're calling the partial with locals and inside your template code you are referring to it with #item instead of item, here item is not an instance variable.
<% #items.each do |item| %>
<%= image_tag item.avatar.url(:medium), class: "block" %>
<div>
<%= render partial: "likes", locals: {item: item} %></span><%= item.likes_count %>
</div>
<% end %>
The thing is when you render a partial with locals you need to use the locals as normal variables not as instance variables.
This should work.
<% item.likes.each do |like| %>
<li> <%= link_to(like.user.username, like.user) %></li>
<% end %>
You need one more table(model) "likes" and association has-many-through. Something like this:
class Item
has_many :likes
has_many :users, through: :likes
end
class Like
belongs_to :user
belongs_to :item
end
class User
has_many :likes
has_many :items
has_many :liked_items, through: :likes
end
Really new to RoR, and ran into an issue I can't find the answer to anywhere.
I have two tables, user and books, and I'm trying to use a join table to match up the user and the books and add a rating and review column in the join table.
The problem is everything passes into the join table EXCEPT the book_id
View
<%= form_for(current_user.user_book_collections.build) do |f| %>
<div>
<%= hidden_field_tag :book_id, current_book.id %>
<%= f.label :rating %>
<%= f.text_field :rating, class:"form-control" %>
<%= f.label :review %> (optional):
<%= f.text_area :review, size: "24x8", placeholder: "Please enter a brief review... ", class:"form-control" %>
</div>
<p></p>
<%= f.submit "Add to your collection", class:"btn btn-primary" %>
<% end %>
current_user and current_book are methods where the users and books are assigned respectively.
Controller
def create
#user_book_collection = current_user.user_book_collections.build(user_book_collection_params)
if #user_book_collection.save
flash[:success] = "Added to your collection"
redirect_to mybooks_path
else
flash[:danger] = "Add was unsuccessful"
redirect_to bookcollection_path
end
end
private
def user_book_collection_params
params.require(:user_book_collection).permit( :book_id, :user_id, :review, :rating )
end
end
This is what shows up in the console:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"85gmzUO7ldQrevh/qnKwYO9mkd9lX77sG3xxJQV8Y46xZrkl5ifk665abPr79nOT91rO3oLcMSDgYL7BtR+/XQ==", "book_id"=>"6", "user_book_collection"=>{"rating"=>"s", "review"=>"asd"}, "commit"=>"Add to your collection"}
but then checking the record also in the console i can see the book_id did not pass through even though its been assigned in the parameters:
=> #<ActiveRecord::Associations::CollectionProxy [#<UserBookCollection id: 10, user_id: 7, book_id: nil, review: "asd", rating: "s", created_at: "2016-02-07 06:36:33", updated_at: "2016-02-07 06:36:33">
Any help would be appreciated, thanks.
It is because your form is constructed in a way the book_id is passed in params[:book_id], not in params[:user_book_collection][:book_id] thus not present in user_book_collection_params
Use:
f.hidden_field :book_id, value: current_book.id
<%= f.hidden_field :book_id, current_book.id %>
When using form_for, you need to bind the input method to the form object (as above).
In addition to Rich Peck and Vasfeds excellent answers I would suggest you consider what you are doing from a REST point of view.
You want users to be able to add reviews to a book. Note that here UserBookCollections is just an ugly piece of plumbing - not really a resource which you would expose with REST. And calling it UserBookCollection is a bit misleading since each UserBookCollection is really just a link between one user and one book.
Also you want to avoid calling anything Collection in Rails since ActiveRecord uses the concept to denote a collection of linked records which may or may not be loaded - you're going to confuse others.
This is one way to solve the same problem
class User
has_many :reviews
has_many :books, through: :reviews
end
class Review
belongs_to :user
belongs_to :book
end
class Book
has_many :reviews, through: :reviews
end
Nice, now we have an object that we don't even have to explain what it does. So lets turn it into a RESTful resource:
Rails.application.routes.draw do
resources :reviews, only: [:show, :edit, :update, :destroy]
resources :books do
resources :reviews, only: [:new, :create, :index], module: :books
end
resources :users do
resources :reviews, only: [:index], module: :users
end
end
This gives us routes with context:
Prefix Verb URI Pattern Controller#Action
edit_review GET /reviews/:id/edit(.:format) reviews#edit
review GET /reviews/:id(.:format) reviews#show
PATCH /reviews/:id(.:format) reviews#update
PUT /reviews/:id(.:format) reviews#update
DELETE /reviews/:id(.:format) reviews#destroy
book_reviews GET /books/:book_id/reviews(.:format) books/reviews#index
POST /books/:book_id/reviews(.:format) books/reviews#create
new_book_review GET /books/:book_id/reviews/new(.:format) books/reviews#new
user_reviews GET /users/:user_id/reviews(.:format) users/reviews#index
Cool - now we have an API for Review with a built in context. The RESTful routes themselves tell us when we are creating a review for a certain book or looking at reviews by a certain user.
module: :books tells rails to link the nested routes to a "namespaced" controller instead of shoving everything into ReviewsController.
We setup our controllers as follows:
class ReviewsController < ApplicationController
before_action :set_review, only: [:edit, :show, :update, :destroy]
# GET /reviews/:id
def show
end
# GET /reviews
def index
#reviews = Review.all
end
# GET /reviews/:id/edit
def edit
end
# PATCH /reviews/:id
def update
#review.update(review_params)
respond_with(#review)
end
# DELETE /reviews/:id
def destroy
#review.destroy
respond_with(#review)
end
private
def set_review
#review = Review.find(params[:id])
end
def review_params
params.require(:review).permit(:rating, :body) # note that you don't need book_id in here.
end
end
# app/controllers/books/reviews_controller.rb
class Books::ReviewsController < ReviewsController
before_action :set_book
# GET /books/:book_id/reviews
def index
#reviews = Review.eager_load(:user, :book).where(book_id: params[:book_id])
end
# GET /books/:book_id/reviews/new
def new
#review = #book.find(params[:book_id]).reviews.new
end
# POST /books/:book_id/reviews
def create
#review = #book.reviews.new(review_params) do |review|
review.user = current_user
end
respond_with(#review)
end
private
def set_book
#book = Book).find(params[:book_id])
end
end
# app/controllers/users/reviews_controller.rb
class Users::ReviewsController < ApplicationController
# GET /users/:user_id/reviews
def index
#reviews = Review.joins(:user, :book).where(user_id: params[:user_id])
end
end
You might question why you would want to use 3 controllers? It allows for a very clean mechanism of both code sharing and customizations. You can have different logic and views without creating a bunch of if or switch code paths. Having many branches in a single action makes testing really messy and violates the skinny controllers paradigm.
The forms would look something like this:
# app/views/reviews/_fields.html.erb
<%= f.label :rating %>
<%= f.text_field :rating, class:"form-control" %>
<%= f.label :body %> (optional):
<%= f.text_area :body, size: "24x8", placeholder: "Please enter a brief review... ", class:"form-control" %>
# app/views/books/reviews/new.erb
<%= form_for([#book, #review]) do |f| %>
<%= render partial: 'reviews/fields', f: f %>
<% end %>
# app/views/books/edit.erb
<%= form_for(#review) do |f| %>
<%= render partial: 'reviews/fields', f: f %>
<% end %>