ActionController::UrlGenerationError in Valuations#new - ruby-on-rails

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)

Related

Rails RecordNotFound couldnt find album with id

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]

How to Edit a Pic Comment without redirecting to an Edit view page in Rails

I am building an app for practice,(Instagram replica), and I am having a really hard time getting a feature to work.
What I want to happen is, a user is able to edit or delete their own comments about a picture. I was able to get the delete feature to work, but I am unable to figure out the 'Edit comment' feature. I want the user to be able to edit the comment from within the picture show page. Code is below.
pics_controller.rb
class PicsController < ApplicationController
before_action :find_pic, only: [:show, :edit, :update, :destroy, :upvote]
before_action :authenticate_user!, except: [:index, :show]
before_action :require_same_user, only: [:edit, :update, :destroy]
def index
#pics = Pic.all.order("created_at DESC")
end
def show
end
def new
#pic = current_user.pics.build
end
def create
#pic = current_user.pics.build(pic_params)
if #pic.save
redirect_to #pic, notice: "Your pic has been posted!"
else
render :new
end
end
def edit
end
def update
if #pic.update(pic_params)
redirect_to #pic, notice: "Awesome! Your Pic was updated!"
else
render :edit
end
end
def destroy
if #pic.destroy
redirect_to root_path
end
end
def upvote
#pic.upvote_by current_user
redirect_back fallback_location: root_path
end
private
def pic_params
params.require(:pic).permit(:title, :description, :image)
end
def find_pic
#pic = Pic.find(params[:id])
end
def require_same_user
if current_user != #pic.user
flash[:danger] = "You can only edit or delete your own pictures"
redirect_to root_path
end
end
end
comments_controller.rb
class CommentsController < ApplicationController
def create
#pic = Pic.find(params[:pic_id])
#comment = #pic.comments.create(params[:comment].permit(:name, :body))
redirect_to pic_path(#pic)
end
def edit
#pic = Pic.find(params[:pic_id])
#comment = #pic.comments.find(params[:id])
redirect_to #pic
end
def update
#comment = #pic.comments.find(params[:id])
#comment.update_attributes(comment_params)
if #comment.save
redirect_to #pic
end
end
def destroy
#pic = Pic.find(params[:pic_id])
#comment = #pic.comments.find(params[:id])
#comment.destroy
redirect_to pic_path(#pic)
end
def show
#pic = Pic.find(params[:pic_id])
end
private
def comment_params
params.require(:comment).permit(:body)
end
end
And here is the (_comment.html.erb) partial being called from the show page
<div class="card" style="width: 18rem;">
<div class="card-header">
<span class="badge badge-dark"><%= comment.name %></span>
</div>
<ul class="list-group list-group-flush">
<p><%= comment.body %></p>
</ul>
</div>
<% if user_signed_in? && comment[:body].present? %>
<p><%= link_to 'Delete Comment', [comment.pic, comment], method: :delete, class: "btn btn-danger",
data: { confirm: "Are you sure?" } %></p>
<p><%= link_to 'Edit Comment', edit_pic_comment_url(#pic, comment), class: 'btn btn-primary' %></p>
<% end %>
Any help is greatly appreciated. TIA
You need to ajaxify for that scenario. The steps will be:
Create the edit link with data-remote=true option. It'll enable the link to hit on the server with a ajax request. Also, add a editable div.
Edit
<div id="comment_#{comment.id}_form"></div>
In the edit method respond the request using js. Like:
def edit
respond_to do |format|
format.js # actually means: if the client ask for js -> return edit.js
end
end
Create edit.js file. In there, render the comment _form.html.erb on the defined div with ID - comment_4_form (Lets assume you're now editing comment with id 4).
It's not elaborated I know. The solution will be much bigger if I elaborate. But you are good to go if you understand the cycle.

I'm having an issue with my form_for

I'm currently running into a problem with my form.
First argument in form cannot contain nil or be empty
in my comments/new which is a partial as _new.html.erb. This is the file:
<% form_for #comment do |f| %>
<%= f.label :body %>
<%= f.text_field :body, placeholder: "Write Message here.." %>
<% end %>
What I'm trying to do is render add a comment to an Article#Show page.
My code:
Comments Controller
class CommentsController < ApplicationController
before_action :find_comment, only: [:show, :edit, :update, :destroy]
def index
#comments = Comment.all
end
def new
#comment = Comment.new
end
def create
#comment = current_user.comments.build(comment_params)
if #comment.save
redirect_to :back
else
redirect_to '/'
end
end
def show
end
def edit
end
def update
if #comment.update(comment_params)
redirect_to '/'
else
redirect_to :back
end
end
def destroy
#comment.destroy
redirect_to "/"
end
private
def find_comment
#comment = Comment.find(params[:id])
end
def comment_params
params.require(:comment).permit(:body)
end
end
Articles#Show
<%= render '/comments/new' %>
If there is additional code required on my part. Please ask and I'll edit/update the question with additional files.
All help/explanation is appreciated. Thank you in advance.
First question:
Add into Article#Show
def show
#comment = Comment.new
end

why has_one association with nested routes gives access to all possible routes in the url search bar?

I am confused with the way you can get every url such as deals/:id/properties/:id despite having a one-to-one association btw a deal and a property. How come is that possible that I can type and get deals/2/properties/14 (or any combination) in the browser url when the property associated with deal 2 is property 6 in my database. The links I have in the views do work and I get the right association deals/2/properties/6 using the links but my question is there something I did wrong in the setup (or everywhere else) or is that just Rails allowing every possible combination to be tested in the browser.... If yes is there a way to prevent this to happen? Thanks a lot
I have a one-to-one association => 1 deal has_one property and a property belongs_to deal. I have nested routes as follows
resources :deals, only: [:index, :show, :create, :update, :destroy] do
scope '/siteadmin' do
resources :properties
end
end
scope '/siteadmin' do
resources :deals, except: [:index, :show]
end
deal.rb
class Deal < ApplicationRecord
has_one :property, dependent: :destroy
accepts_nested_attributes_for :property
end
property.rb
class Property < ApplicationRecord
belongs_to :deal
validates :full_address, presence: true
validates_uniqueness_of :deal_id
end
deals_controller.rb
class DealsController < ApplicationController
before_action :set_deal, only: [:show, :edit, :update, :destroy]
def index
#deals = Deal.all
end
def show
#property = #deal.property
end
def new
#deal = Deal.new
end
def create
#deal = Deal.new(deal_params)
if #deal.save
redirect_to deals_path, notice: 'Deal was successfully created'
else
render :new
end
end
def edit
end
def update
if #deal.update(deal_params)
redirect_to #deal, notice: 'Deal was successfully updated'
else
flash.now[:alert] = "Deal has not been updated."
render :edit
end
end
def destroy
#deal.destroy
redirect_to deals_path, notice: 'Deal was successfully deleted'
end
private
def deal_params
params.require(:deal).permit(:description, :kind, :address, :image_url, :occupancy, :yield)
end
def set_deal
#deal = Deal.find(params[:id])
rescue ActiveRecord::RecordNotFound
flash[:alert] = "The deal you were looking for could not be found."
redirect_to deals_path
end
end
properties_controller.rb
class PropertiesController < ApplicationController
before_action :set_deal
before_action :set_property, only: [:show, :edit, :update, :destroy]
def index
#properties = Property.all.order(id: :asc)
end
def show
end
def new
#property = #deal.build_property
end
def create
#property = #deal.build_property(property_params)
if #property.save
flash[:notice] = "Property has been created."
redirect_to [#deal, #property]
else
flash.now[:alert] = "Property has not been created."
render "new"
end
end
def edit
end
def update
if #property.update(property_params)
flash[:notice] = "Property has been updated."
redirect_to [#deal, #property]
else
flash.now[:alert] = "Property has not been updated."
render "edit"
end
end
def destroy
#property.destroy
flash[:notice] = "Property has been deleted."
redirect_to #deal
end
private
def property_params
params.require(:property).permit(:genre, :surface, :nb_rooms, :nb_bedrooms, :city, :district, :full_address)
end
def set_property
#property = Property.find(params[:id])
end
def set_deal
#deal = Deal.find(params[:deal_id])
end
end
deals show.html.erb
<h2>Property in the deal:</h2>
<ul class="list-unstyled text-justify">
<li>Property id #<%= #deal.property.try(:id) %> - <%= link_to #deal.property.try(:full_address), [#deal, #property] %></li>
<li>Adresse of the property: <%= #deal.property.try(:full_address) %></li>
<li><%= link_to "see all the properties", deal_properties_path(#deal) %></li>
<%= link_to "add deal", new_deal_path, {class: "btn btn-primary"} %>
</ul>
properties index.html.erb
<header>
<h2>All properties</h2>
<ul id="properties in the db">
<% #properties.each do |property| %>
<li>Property id #<%= property.id %> - <%= link_to property.full_address, deal_property_path(property.deal, property) %></li>
<li><%= link_to "See our Deal", deal_path(property.deal) %></li>
<li><%= link_to "Edit Property", edit_deal_property_path(property.deal, property) %></li>
<li>Deal id #<%= property.deal.id %></li>
<% end %>
<br>
</ul>
<ul class="actions">
<li><%= link_to "Add Property", new_deal_property_path(#deal),
class: "new" %></li>
<li><%= link_to "Back to deals", deals_path%></li>
</ul>
</header>
properties show.html.erb
<header>
<h2>Property id #<%= #property.id %> <%= #property.full_address %></h2>
</header>
<h3>This is the property #<%= #property.id %> of the Deal #<%= #deal.id %> - <%= #deal.address %></h3>
<p>This property is located at <%= #property.full_address %></p>
<li><%= link_to "See all the properties", deal_properties_path(#deal)%></li>
the routes handles just the format of the query string not the values in it.
what I like to do is scope the data in the controller like this
class PropertiesController < ApplicationController
before_action :set_deal
before_action :set_property, only: [:show, :edit, :update, :destroy]
def index
#properties = Property.all.order(id: :asc)
end
def show
end
def new
#property = #deal.build_property
end
def create
#property = #deal.build_property(property_params)
if #property.save
flash[:notice] = "Property has been created."
redirect_to [#deal, #property]
else
flash.now[:alert] = "Property has not been created."
render "new"
end
end
def edit
end
def update
if #property.update(property_params)
flash[:notice] = "Property has been updated."
redirect_to [#deal, #property]
else
flash.now[:alert] = "Property has not been updated."
render "edit"
end
end
def destroy
#property.destroy
flash[:notice] = "Property has been deleted."
redirect_to #deal
end
private
def property_params
params.require(:property).permit(:genre, :surface, :nb_rooms, :nb_bedrooms, :city, :district, :full_address)
end
def set_property
#property = #deal.property #.find(params[:id]) # you don't need to find it because there is only 1
end
def set_deal
#deal = Deal.find(params[:deal_id])
end
end

Nested attributes undefined user_comments_path error

I'm trying to follow Ryan Bates polymorphic models tutorial (Rails 3), to implement comments in my photo model. I'm having trouble when trying to create a new comment for photos since I'm in Rails 4 and I have to deal with :comment strong parameters.
Error: undefined method `user_comments_path' for #<#:0x00000107c8a200>
I have nested resources
#Nesting Resources
resources :users do
resources :photos do
resources :comments
resources :tags
end
end
So the route should be /users/friendly-id/photos/friendly-id/comments, but it's getting badly constructed, I have <%= form_for [#user, #commentable, #comment] do |f| %> in my form.
Photo Controller
#create
def show
#photo = Photo.friendly.find(params[:id])
#user = #photo.user
#tag = Tag.new
#tag.photo_id = #photo.id
#category = Category.all
#commentable = #photo
#comments = #commentable.comments
#comment = Comment.new
#zone = Zone.all
respond_to do |format|
format.html #show.html.erb
format.json {render json: #photo}
end
end
Form
<%= form_for [#user, #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 %>
Comments controller
class CommentsController < ApplicationController
before_filter :load_commentable
def index
#comments = #commentable.comments
end
def new
#comment = #commentable.comments.new
end
def create
#comment = #commentable.comments.new(comment_params)
if #comment.save
redirect_to #commentable, notice: "Comment created."
else
render :new
end
end
private
def load_commentable
resource, id = request.path.split('/')[1, 2]
#commentable = resource.singularize.classify.constantize.find(id)
end
def comment_params
require(:comment).permit(:photo_id, :user_id, :content)
end
###Error 2: When I put friendly.id instead of only id in `load_commentable` method I get a Forbidden Attribute error.
end
Please someone help! Thank you

Resources