I'm at the end of a nested resource Rails app but I can't seem to figure out how to delete a review. I can delete an album just fine. My reviews belong to albums. Users have both albums and reviews.
I would also like to be able to make sure Users cannot delete reviews that they didn't write. I think that is where my error is coming from.
I keep getting this error:
ActiveRecord::RecordNotFound in AlbumsController#destroy
Couldn't find Album with 'id'=17
Extracted source (around line #57):
55
56
57
58
59
60
def set_album
#album = Album.find(params[:id])
end
def album_params
Here is my reviews controller:
class ReviewsController < ApplicationController
before_action :set_review, only: [:show, :delete, :edit, :update]
before_action :set_current_user, only: [:index, :new, :edit, :delete]
before_action :find_album, only: [:create, :edit]
before_action :must_login, only: [:index, :new, :create, :edit, :update, :delete]
def index
#albums = Album.with_recent_reviews
end
def show
##reviews = Review.where("album_id = ?", params[:album_id])
end
def new
if params[:album_id] && #album = Album.find_by(id: params[:client_id])
#review = #album.reviews.build
else
redirect_to albums_path
end
end
def create
#review = current_user.reviews.build(review_params)
#review.album = #album
if #review.save
redirect_to album_path(#album)
else
#album = #review.album
render :new
end
end
def edit
end
def update
if #review.update(review_params)
redirect_to album_path(params[:album_id])
else
render 'edit'
end
end
def destroy
if current_user.id == #review.user_id
#review.destroy
redirect_to album_path(params[:album_id])
else
flash[:error] = "Unable to delete your review. Please try again."
redirect_to album_reviews_path(#review)
##review.destroy
end
end
private
def set_review
#review = Review.find(params[:id])
end
def set_current_user
#user = current_user
end
def find_album
#album = Album.find(params[:album_id])
end
def review_params
params.require(:review).permit(:title, :date, :content, :user_id, :album_id, album_attributes:[:artist, :title, :user_id])
end
end
Here is my reviews index form where the delete link is located:
<% #albums.each do |album| %>
<br>
<br>
<% if album.avatar&.attached? %>
<image src="<%=(url_for(album.avatar))%>%" style="width:350px;height:350px;">
<% end %>
<br>
<%= album.artist %> - <%= album.title %>
<br>
<%= link_to "View Album", album_path(album) %>
<%= link_to "Edit Album", edit_album_path(album) %>
<br><br>
<% album.reviews.each do |review| %>
<% unless review.id.nil? %>
<small>Date written: <%= review.date %></small><br>
<small>Written by: <%= review.user.name %></small><br>
<strong>Title: <%= review.title %></strong><br>
Review: <%= review.content %><br>
<%= link_to "Edit Review", edit_album_review_path(album_id: album.id, id: review.id) %>
<%= link_to 'Delete', album_path(album_id: album.id, id: review.id), method: :delete, data: { confirm: 'Are you sure?' } %>
<br><br>
<% end %>
<% end %>
<br>
<% end %>
Albums controller:
class AlbumsController < ApplicationController
before_action :set_album, only: [:show, :edit, :update, :destroy]
before_action :must_login, only: [:new, :show, :create, :edit, :update, :destroy]
def index
#albums = Album.all
#user = current_user
end
def show
#review = #album.reviews.build
#review.user = current_user
# If you want to have some flag to indicate its status
##review.draft = true
#review.save
#reviews = Review.recent #scope
end
def new
#album = Album.new
#user = current_user
end
def create
#user = User.find(current_user.id)
#album = current_user.albums.build(album_params)
#album.user_id = current_user.id
if #album.save
redirect_to album_path(#album)
else
render :new
end
end
def edit
#user = current_user
end
def update
##album = current_user.albums.build(album_params)
#album.user_id = current_user.id
if #album.update(album_params)
redirect_to album_path(#album), notice: "Your album has been updated."
else
render 'edit'
end
end
def destroy
#album.delete
redirect_to albums_path
end
private
def set_album
#album = Album.find(params[:id])
end
def album_params
params.require(:album).permit(:artist, :title, :avatar, :user_id, review_attributes:[:title, :date, :content, :user_id, :album_id])
end
end
You have this issue because you are not passing an album_id to the destroy action of the review controller. The reason you need an album_id is because your routes probably look like this (since you said they were nested):
http://localhost:3000/albums/1/reviews
You can see in your route that you need an album_id (the 1 after the album). Try changing your review destroy action to this:
def destroy
if current_user.id == #review.user_id
#album.reviews.find(params[:id]).destroy
redirect_to album_path(params[:album_id])
else
flash[:error] = "Unable to delete your review. Please try again."
redirect_to album_reviews_path(#review)
end
end
You'll also have to change your before_action in the review controller to this, since you need the album_id to destroy the review:
before_action :find_album, only: [:create, :update, :destroy]
Related
So I have a reviews show page that is nested under albums routes. On a reviews show page, the URL was http://localhost:3000/albums/2/reviews/19, I changed 19 to a random number of a review i don't have. That led me to an error "couldn't find Review with 'id'=25". I would like to redirect that user to my not_found page I created, but I'm not sure how to do that.
Here is my error:
ActiveRecord::RecordNotFound in ReviewsController#show
Couldn't find Review with 'id'=25
Extracted source (around line #59):
57
58
59
60
61
62
def set_review
#review = Review.find(params[:id])
end
def set_current_user
Reviews controller:
class ReviewsController < ApplicationController
before_action :set_review, only: [:show, :edit, :update, :destroy]
before_action :set_current_user, only: [:index, :show, :new, :edit, :destroy]
before_action :find_album, only: [:show, :create, :edit, :update, :destroy]
before_action :must_login, only: [:index, :show, :new, :create, :edit, :update, :destroy]
def index
#albums = Album.with_recent_reviews #scope
end
def show
##reviews = Review.where("album_id = ?", params[:album_id])
end
def new
if params[:album_id] && #album = Album.find_by(id: params[:user_id])
#review = #album.reviews.build
else
redirect_to albums_path
end
end
def create
#review = current_user.reviews.build(review_params)
##review.album = #album
if #review.save
redirect_to album_path(#album)
else
##review.album
render :new
end
end
def edit
end
def update
if #review.update(review_params)
redirect_to album_path(params[:album_id])
else
render 'edit'
end
end
def destroy
if current_user.id == #review.user_id
#album.reviews.find(params[:id]).destroy
redirect_to album_path(params[:album_id])
else
flash[:error] = "Unable to delete your review. Please try again."
redirect_to album_reviews_path(#review)
end
end
private
def set_review
#review = Review.find(params[:id])
end
def set_current_user
#user = current_user
end
def find_album
#album = Album.find(params[:album_id])
end
def review_params
params.require(:review).permit(:title, :date, :content, :album_id, album_attributes:[:artist, :title, :user_id])
end
end
REviews show.html.erb:
<h6><strong>
Showing "<%= #review.title %>" for
<%= #album.artist %> -
<%= #album.title %>
</strong></h6>
<br>
<% if #album.avatar.attached? %>
<image src="<%=(url_for(#album.avatar))%>%" style="width:350px;height:350px;">
<% end %>
<br>
<br>
<%= link_to "Edit Album", edit_album_path(#album), class: "waves-effect waves-light btn" %>
<%= link_to 'Delete Album', album_path(#album), method: :delete, class: "waves-effect waves-light btn" %>
<br><br>
<hr>
<% unless #review.id.nil? %>
<small>Date written: <%= #review.date.strftime("%d %B %Y") %></small><br>
<small>Written by: <%= #review.user.name %></small><br>
<br>
<strong>Title: <%= #review.title %></strong><br>
<%= #review.content %><br>
<br>
<% if current_user.id == #review.user_id %>
<%= link_to "Edit Review", edit_album_review_path(album_id: #album.id, id: #review.id), class: "waves-effect waves-light btn" %>
<%= link_to 'Delete', album_review_path(album_id: #album.id, id: #review.id), method: :delete, data: { confirm: 'Are you sure?' }, class: "waves-effect waves-light btn" %><br><br>
<% end %>
<br><br>
<% end %>
<br>
<strong>
<%= link_to "Write a New Review", album_path(#album), class: "waves-effect waves-light btn" %></strong>
<br><br>
rake routes:
GET /auth/:provider/callback(.:format) sessions#omniauth
auth_failure_path GET /auth/failure(.:format) redirect(301, /)
signup_path GET /signup(.:format) users#new
POST /signup(.:format) users#create
signin_path GET /signin(.:format) sessions#new
POST /signin(.:format) sessions#create
signout_path GET /signout(.:format) sessions#destroy
logout_path POST /logout(.:format) sessions#destroy
not_found_path GET /not_found(.:format) pages#not_found
album_reviews_path POST /albums/:album_id/reviews(.:format) reviews#create
new_album_review_path GET /albums/:album_id/reviews/new(.:format) reviews#new
edit_album_review_path GET /albums/:album_id/reviews/:id/edit(.:format) reviews#edit
album_review_path GET /albums/:album_id/reviews/:id(.:format) reviews#show
PATCH /albums/:album_id/reviews/:id(.:format) reviews#update
PUT /albums/:album_id/reviews/:id(.:format) reviews#update
DELETE /albums/:album_id/reviews/:id(.:format) reviews#destroy
pages controller for not_found path
class PagesController < ApplicationController
before_action :current_user
def not_found
end
private
def current_user
super() #looks for same method on what i'm inheriting from (applicationcontroller)
end
end
albums controller:
class AlbumsController < ApplicationController
before_action :set_current_user, only: [:new, :index, :new, :edit]
before_action :set_album, only: [:show, :edit, :update, :destroy]
before_action :must_login, only: [:new, :show, :create, :edit, :update, :destroy]
def index
#albums = Album.all.order("artist ASC")
end
def show
#review = #album.reviews.build
#review.user = current_user
#reviews = Review.recent #scope shows recent reviews
end
def new
#album = Album.new
#review = #album.reviews.build
end
def create
#album = current_user.albums.build(album_params)
#album.reviews.each { |r| r.user ||= current_user }
if #album.save
redirect_to album_path(#album)
else
render :new
end
end
def edit
end
def update
#album.user_id = current_user.id
#album.avatar.purge # or album.avatar.purge_later
#album.avatar.attach(params[:avatar])
if #album.update(album_params)
redirect_to album_path(#album), notice: "Your album has been updated."
else
render 'edit'
end
end
def destroy
#album.destroy #destroy is for associated objects
#album.avatar.purge
redirect_to albums_path
end
private
def set_current_user
#user = current_user
end
def set_album
#album = Album.exists?(params[:id]) ? Album.find(params[:id]) : nil
redirect_to not_found_path if #album.nil?
end
def album_params
params.require(:album).permit(:artist, :title, :avatar, :user_id, reviews_attributes:[:title, :date, :content])
end
end
You can use a rescue block at the top of your ReviewsController like this:
rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
Then, at the bottom of your file, you can create your record_not_found method:
private
def record_not_found
render file: "#{Rails.root}/public/404", layout: true, status: :not_found
end
The render file: in this case is going to the public directory and displaying the 404.html view.
I am trying to fetch booking controller's new form, from the cab index page. how can i fetch it? how can access the new form from cab index page where i have shown all the cabs which are added..
cab controller
class CabsController < ApplicationController
before_action :find_cab, only: [:show, :edit, :update, :destroy]
def index
#cabs = Cab.all.order("created_at DESC")
end
def new
#cab = Cab.new
end
def show
#reviews = Review.where(cab_id: #cab.id).order("created_at DESC")
if #reviews.blank?
#avg_review=0
else
#avg_review=#reviews.average(:rating).round(2)
end
end
def edit
end
def create
#cab = Cab.new(cab_params)
if #cab.save
redirect_to #cab
else
render 'new'
end
end
def update
if #cab.update(cab_params)
redirect_to #cab
else
render 'edit'
end
end
def destroy
#cab.destroy
redirect_to root_path
end
private
def find_cab
#cab = Cab.find(params[:id])
end
def cab_params
params.require(:cab).permit(:name, :number_plate, :seat, :image)
end
end
booking controller
class BookingsController < ApplicationController
before_action :find_booking, only: [:show, :edit, :update, :destroy]
before_action :find_cab
def index
#bookings = Booking.where(cab_id: #cab.id).order("created_at DESC")
end
def new
#booking = Booking.new
end
def show
end
def edit
end
def create
#booking = Booking.new(booking_params)
#booking.user_id = current_user.id
#booking.cab_id = #cab.id
if #booking.save
redirect_to cab_booking_path(#cab, #booking)
else
render 'new'
end
end
def update
end
def destroy
end
private
def find_booking
#booking = Booking.find(params[:id])
end
def find_cab
#cab = Cab.find(params[:cab_id])
end
def booking_params
params.require(:booking).permit(:date, :address, :start_destination, :destination, :start_date, :end_date, :contact_no)
end
end
routes
resources :cabs do
resources :bookings
end
cab/index.html.erb
<div class="container">
<h2>All Cabs</h2>
<div class="row">
<% #cabs.each do |cab| %>
<div class="col-sm-6 col-md-3">
<div class="thumbnail">
<%= link_to image_tag(cab.image.url(:medium), class: 'image'), cab %><br>
Cab Name : <h4><%= cab.name %></h4>
<%= link_to "Book Now", new_cab_booking_path(#cab, #booking) %> # i wanted to create this link
</div>
</div>
<% end %>
</div>
<%= link_to "Add Cab", new_cab_path, class: 'btn btn-default' %>
the error i am getting is
No route matches {:action=>"new", :cab_id=>nil, :controller=>"bookings"} missing required keys: [:cab_id]
No route matches {:action=>"new", :cab_id=>nil,
:controller=>"bookings"} missing required keys: [:cab_id]
The problem is with this line
<%= link_to "Book Now", new_cab_booking_path(#cab, #booking) %>
which should be
<%= link_to "Book Now", new_cab_booking_path(cab) %>
I've read other SO articles relating to UrlGenerationError's which seem to point to singularization or plurization of a word, but I don't think that's the issue here.
It works when I remove from valuations/_form.html.erb:
<%= render "comments/comments" %>
<%= render "comments/form" %>
Submit the _form with :name & :tag_list, readd
<%= render "comments/comments" %>
<%= render "comments/form" %>
and then refresh. What's the deal when nil?
routes
resources :valuations do
resources :comments
end
comments_controller
class CommentsController < ApplicationController
before_action :load_commentable
before_action :set_comment, only: [:show, :edit, :update, :destroy]
before_action :logged_in_user, only: [:create, :destroy]
def index
#comments = #commentable.comments
end
def new
#comment = #commentable.comments.new
end
def create
#comment = #commentable.comments.new(comment_params)
if #comment.save
#comment.create_activity :create, owner: current_user
redirect_to #commentable, notice: "comment created."
else
render :new
end
end
def edit
#comment = current_user.comments.find(params[:id])
end
def update
#comment = current_user.comments.find(params[:id])
if #comment.update_attributes(comment_params)
redirect_to #commentable, notice: "Comment was updated."
else
render :edit
end
end
def destroy
#comment = current_user.comments.find(params[:id])
#comment.destroy
#comment.create_activity :destroy, owner: current_user
redirect_to #commentable, notice: "comment destroyed."
end
private
def set_comment
#comment = Comment.find(params[:id])
end
def load_commentable
resource, id = request.path.split('/')[1, 2]
#commentable = resource.singularize.classify.constantize.find(id)
end
def comment_params
params.require(:comment).permit(:content, :commentable)
end
end
valuations_controller
class ValuationsController < ApplicationController
before_action :set_valuation, only: [:show, :edit, :update, :destroy]
before_action :logged_in_user, only: [:create, :destroy]
def index
if params[:tag]
#valuations = Valuation.tagged_with(params[:tag])
else
#valuations = Valuation.order('RANDOM()')
end
end
def show
#valuation = Valuation.find(params[:id])
#commentable = #valuation
#comments = #commentable.comments
#comment = Comment.new
end
def new
#valuation = current_user.valuations.build
#commentable = #valuation
#comments = #commentable.comments
#comment = Comment.new
end
def edit
end
def create
#valuation = current_user.valuations.build(valuation_params)
if #valuation.save
redirect_to #valuation, notice: 'Value was successfully created'
else
#feed_items = []
render 'pages/home'
end
end
def update
if #valuation.update(valuation_params)
redirect_to #valuation, notice: 'Value was successfully updated'
else
render action: 'edit'
end
end
def destroy
#valuation.destroy
redirect_to valuations_url
end
private
def set_valuation
#valuation = Valuation.find(params[:id])
end
def correct_user
#valuation = current_user.valuations.find_by(id: params[:id])
redirect_to valuations_path, notice: "Not authorized to edit this valuation" if #valuation.nil?
end
def valuation_params
params.require(:valuation).permit(:name, :private_submit, :tag_list, :content, :commentable, :comment)
end
end
comments/_form.html.erb
<%= form_for [#commentable, #comment] do |f| %>
<% if #comment.errors.any? %>
<div class="error_messages">
<h2>Please correct the following errors.</h2>
<ul>
<% #comment.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="america">
<div class="form-group">
<%= f.text_area :content, rows: 4, class: 'form-control', placeholder: 'Enter Comment' %>
</div>
<div class="america2">
<%= button_tag(type: 'submit', class: "btn") do %>
<span class="glyphicon glyphicon-plus"></span> Comment
<% end %>
</div>
<% end %>
Thank you so much for your time.
When you have a nested resource like that, the url for creating a comment looks like /valuations/123/comments where 123 is the id of the valuation - without a valuation id this url cannot be generated.
On your Valuations#new page, the valuation (i.e. #commentable) is an unsaved object, so it has no id yet, hence the error about a missing valuation_id. In addition having one form nested within another is invalid html and will lead to odd behaviour. On your show page on the other hand, the valuation does have an id and so things should work as they are.
If you want to create a valuation and its initial comment(s) in one go then you should use accepts_nested_attributes_for and fields_for to add the fields for the comments to your valuation form (There are other ways, but accepts_nested_attributes_for is what is built into rails)
I have two classes of users: "users" and "shops". I want the admin to be a "user" and be able to delete shops, and am running in to trouble.
I was able to get the delete button to show up in shops/index.html.erb if the logged in user was an admin (shown below), but when I try to delete a shop object I get the error The action 'destroy' could not be found for ShopsController
shops/index.html.erb
<% provide(:title, 'All shops') %>
<h1>All Shops</h1>
<ul class="center hero-unit col-md-6 col-md-offset-3 shops">
<% #shops.each do |shop| %>
<li>
<div class= "shop-name pull-left">
<%= link_to shop.name, shop %>
<% if current_user.admin? && !current_shop?(shop) %>
| <%= link_to "(Delete Shop)", shop, method: :delete,
data: { confirm: "You sure?" } %>
<% end %>
</div>
<div class= "shop-address pull-right">
<p><%= shop.address %> <br> <%= shop.city %>, <%= shop.state %> <%= shop.zip %> <br> <%= shop.phone %></p>
</div>
</li>
<% end %>
</ul>
The destroy action is in the shops controller though:
class ShopsController < ApplicationController
before_action :logged_in_shop, only: [:edit, :update]
before_action :logged_in_user, only: :destroy
before_action :correct_shop, only: [:edit, :update]
before_action :admin_user, only: :destroy
def index
#shops = Shop.all
end
def show
#shop = Shop.find(params[:id])
end
def new
#shop = Shop.new
end
def create
#shop = Shop.new(shop_params)
if #shop.save
shop_log_in #shop
flash[:success] = "Thank you for signing up, welcome to ensage!"
redirect_to shop_home_path
else
render 'new'
end
end
def edit
#shop = Shop.find(params[:id])
end
def update
#shop = Shop.find(params[:id])
if #shop.update_attributes(shop_params)
flash[:success] = "Profile updated"
redirect_to #shop
else
render 'edit'
end
end
def destroy
Shop.find(params[:id]).destroy
flash[:success] = "Shop deleted"
redirect_to shops_url
end
private
def shop_params
params.require(:shop).permit(:name, :address, :city, :state, :zip, :email, :phone, :password,
:password_confirmation, :picture)
end
def correct_shop
#shop = Shop.find(params[:id])
redirect_to(root_url) unless current_shop?(#shop)
end
def admin_user
redirect_to(root_url) unless current_user.admin?
end
end
How can I allow an admin user to delete a shop?
You have set your destroy method as private, so it is not accessible.
before_action :logged_in_shop, only: [:edit, :update, :destroy]
You probably want to remove :destroy from here, or it will expect that you are logged in as shop in order to destroy it. I'm not sure how your access control works but be careful not to make this publicly accessible.
You have put action destroy in private section, this is why it is invisible. Try moving it up over private keyword
First move destroy action out of private, secondly I cannot see admin_shop method anywhere, either remove this line or define admin_shop method
before_action :admin_shop, only: :destroy
To add admin_shop method
def admin_shop
unless current_user.admin?
flash[:error] = "Not authorized"
redirect_to shops_url
end
end
In your destroy action
def destroy
user = User.find(current_user.id)
Shop.find(params[:id]).destroy
sign_in user, bypass: true
flash[:success] = "Shop deleted"
redirect_to shops_url
end
This is my first ror app.
I have main page: home.html.erb
I have form there.
<%= form_for(#lead ,:html => {:class => 'check_form'}) do |f| %>
<%= f.text_field :phone, placeholder: 'phone' %>
<%= f.submit "Check car status", class: "btn btn-large btn-primary" %>
<% end %>
Backstory: a customer(I call him Lead can input his phone number and check status of his car which is being repaired now.)
Right now this view home.html.erbis served by static_pages_controller
class StaticPagesController < ApplicationController
def home
end
def help
end
def about
end
def contact
end
end
I have also LeadsController
class LeadsController < ApplicationController
#before_action :signed_in_user, only: [:index, :edit, :update]
#before_action :correct_user, only: [:edit, :update]
before_action :admin_user, only: [:index, :edit, :update, :destroy]
def index
#leads = Lead.paginate(page: params[:page])
end
def destroy
Lead.find(params[:id]).destroy
flash[:success] = "record deleted."
redirect_to leads_url
end
def show
#lead = Lead.find(params[:id])
end
def new
#lead = Lead.new
end
def create
#lead = Lead.new(lead_params)
if #lead.save
#sign_in #lead
flash[:success] = "Request successfully created!"
redirect_to #lead
else
render 'new'
end
end
def edit
#lead = Lead.find(params[:id])
end
def update
#lead = Lead.find(params[:id])
if #lead.update_attributes(status: params[:status])
flash[:success] = "Information updated"
redirect_to leads_url
else
render 'edit'
end
end
private
def lead_params
params.require(:lead).permit(:name, :email, :phone, :car_type,
:car_year, :car_manufacturer, :car_model, :photo1, :photo2, :coords )
end
# Before filters
def signed_in_user
unless signed_in?
store_location
redirect_to signin_url, notice: "Please sign in."
end
end
def correct_user
#lead = Lead.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
end
def admin_user
redirect_to(root_url) unless current_user.admin?
end
end
What I want to do when user inputs his phone number to find lead in database with the same phone number and show repair status to user.
But it generates an error:
First argument in form cannot contain nil or be empty
So I got that form_for(#lead.. here #lead is empty.
But what should I do to get rid of this error.?
First argument in form cannot contain nil or be empty
As your view(home.html.erb) is served by static_pages#home, you should initialize #lead in the static_pages#home action
def home
#lead = Lead.new
end