Background
I'm implementing a feature in my application that allow users to rate and review pictures.
My problem stems from nesting Reviews resources inside Pictures resources, specifically in my Review#Edit function. The error message specifically declares missing required keys: [:id]
Error Message
ActionController::URLGenerationError in Reviews#Edit
No route matches {:action=>"show", :controller=>"reviews", :format=>nil, :picture_id=>#<Review id: 4, username: "DarkMouse", body: "Updating review", picture_id: 11, created_at: "2014-08-14 03:26:52", updated_at: "2014-08-14 03:55:29">, :id=>nil} missing required keys: [:id]
Undefined method 'reviews_path' for #<#<Class:0x45c1b00>:0x39ae810>
Extracted source (Around line #7):
4 <div class = 'center'>
5 <div class = 'center'>
6
7 <% form_for [:picture, #review] do |f| %>
8
9 <p>
10 <%= f.label :username, :class => 'marker' %><br />
I searched for answers on Stack Overflow.com(My references are given at the bottom) and was advised to do this.
<td><%= link_to 'Edit', edit_picture_review_path(#picture, review) %></td>
Every single solution I came across said to edit the path to include the specific object. If this is any indication, it looks like it really should work.
Parameters
{"picture_id"=>"11",
"id"=>"4"}
Also, my path_url is as follows
http://localhost:3000/pictures/11/reviews/4/edit
The URL path looks similar to my defined routes as well
edit_picture_review_path GET /pictures/:picture_id/reviews/:id/edit(.:format) reviews#edit
I am using a Posts/Comments relationship model for a Pictures/Reviews relationship.
Models
class Review < ActiveRecord::Base
belongs_to :picture
end
class Picture < ActiveRecord::Base
has_many :reviews
end
Above, I established a one-to-many relationship between pictures and reviews.
Routes
favorite_picture_path PUT /pictures/:id/favorite(.:format) pictures#favorite
picture_reviews_pat GET /pictures/:picture_id/reviews(.:format) reviews#index
POST /pictures/:picture_id/reviews(.:format) reviews#create
new_picture_review_path GET /pictures/:picture_id/reviews/new(.:format) reviews#new
edit_picture_review_path GET /pictures/:picture_id/reviews/:id/edit(.:format) reviews#edit
pictures_review_path GET /pictures/:picture_id/reviews/:id(.:format) reviews#show
PATCH /pictures/:picture_id/reviews/:id(.:format) reviews#update
PUT /pictures/:picture_id/reviews/:id(.:format) reviews#update
DELETE /pictures/:picture_id/reviews/:id(.:format) reviews#destroy
ReviewsController
class ReviewsController < ApplicationController
before_action :set_review, only: [:show, :edit, :update, :destroy]
before_filter :find_picture, only: [:index, :create, :edit, :update, :destroy]
def edit
#review = Review.find(params[:id])
end
def update
#review = Review.find(params[:id])
if #review.update_attributes(params[:review])
flash[:notice] = "Review updated"
redirect_to #picture
else
flash[:error] = "There was an error updating your review"
redirect_to #picture
end
end
private
def set_review
#review = Review.find(params[:id])
end
def find_picture
#picture = Picture.find(params[:picture_id])
end
def review_params
params.require(:review).permit(:username, :body, :post_id)
end
end
The problem might lie with the review_params
post_id #should be
review_id
Reviews#Index Page
<h3>Reviews for <%= "#{#picture.title}" %></h3>
<table class = "reviews-table">
<thead>
<tr>
<th>Review</th>
<th>Username</th>
<th><th>
</tr>
</thead>
<tbody>
<% #picture.reviews.each do |review| %>
<tr>
<td><%= review.body %></td>
<td><%= review.username %></td>
<td><%= link_to 'Edit', edit_picture_review_path(#picture, review) %></td>
</tr>
<% end %>
<div class = 'center'>
<p><%= link_to 'New Review', new_reviews_path(#review), :class => "btn btn-info" %></p>
<p><%= link_to 'Back', picture_path, :class => "btn btn-info" %></p>
</div>
Reviews#Edit Page
<h3>Edit Review</h3>
<div class = 'center'>
<div class = 'center'>
<%= form_for [:picture, #review] do |f| %>
<p>
<%= f.label :username, :class => 'marker' %><br />
<%= f.text_field :username %>
</p>
<p>
<%= f.label :body, "Review", :class => 'marker' %><br />
<%= f.text_area :body, :rows => "10", :cols => "10" %>
</p>
<p>
<%= f.submit "Submit Review", :class => 'btn btn-success' %>
</p>
<% end %>
routes.rb
resources :pictures do
put :favorite, on: :member
resources :reviews
end
As you can see above, I am using nested resources.
References and External Links
Missing required keys
ActionController::UrlGenerationError missing required keys: [:id]
No route matches missing required keys: [:id]
ActionController::UrlGenerationError: missing required keys: [:id]
No routes matches "missing required keys: [:id, :_id]"
All of the above links and more consulted. Every solution tried but none worked. This is why I am re-opening this question. I hope this guide can serve as a template for this problem moving forward.
Thanks in advance. This is not an easy question. Good luck.
A few observations, off the cuff.
Your error message indicates that No route matches {:action=>"show", :controller=>"reviews".
Your ReviewsController does seem to be missing the show action, but that does not seem relevant.
So that leaves us with the offending line:
<% form_for [:picture, #review] do |f| %>
Note, your error is not with this line:
<td><%= link_to 'Edit', edit_picture_review_path(#picture, review) %></td>
Looking at the docs, under Resource-oriented style, we can see the following instruction:
If your resource has associations defined, for example, you want to
add comments to the document given that the routes are set correctly:
<%= form_for([#document, #comment]) do |f| %>
...
<% end %>
For you, this translates to:
<% form_for [#picture, #review] do |f| %>
The same page also advises:
In the examples just shown, although not indicated explicitly, we
still need to use the :url option in order to specify where the form
is going to be sent. However, further simplification is possible if
the record passed to form_for is a resource, i.e. it corresponds to a
set of RESTful routes, e.g. defined using the resources method in
config/routes.rb. In this case Rails will simply infer the appropriate
URL from the record itself
Although you shouldn't need it, for you this translates to something like:
<% form_for [#picture, #review], url: edit_picture_review_path(#picture, #review) do |f| %>
Related
So I'm getting the error stated in the title. What code would I have to write in my Postscontroller to fix this? I'm not sure what I would have to do here, would I have to define rsvp in my post controller? If thats the case how would I go about doing that?
class RsvpController < ApplicationController
def create
rsvp = current_user.rsvps.build({post_id: params[:id]})
if rsvp.save
end
end
end
Show.html.erb
<p>
<strong>Date:</strong>
<%= #post.date %>
</p>
<p>where:<%=#post.longitude %>, <%=#post.latitude%></p>
<p>
<strong>Name:</strong>
<%= #post.name %>
</p>
<p>
<strong>User_id:</strong>
<%= #post.user_id %>
</p>
<p>
<strong>Description:</strong>
<%= #post.description %>
</p>
<% if current_user == #post.user %>
<%= link_to 'Edit', edit_post_path(#post) %> |
<%end%>
<%= link_to 'Back', posts_path %>
<%= button_to "Rsvp now", rsvp_post_path(#post), class: "btn btn-primary" %>
routes.rb
Rails.application.routes.draw do
mount RailsAdmin::Engine => '/admin', as: 'rails_admin'
resources :posts
devise_for :users
root 'home#index'
get 'home/ruby_meetup'
resources :posts do
post 'rsvp', on: :member
end
Also I want it so it shows the list of people who already rsvped for the event. How would I go about doing that? I would appreciate some help as I'm still learning rails and this is my first project.
By the following line, you define the route:
resources :posts do
post 'rsvp', on: :member
end
By this line you're requesting this route:
To process the request, you need a controller action rsvp, which actually the error states.
Thus, just define the action (method) and process the request:
class RsvpController < ApplicationController
def create
rsvp = current_user.rsvps.build({post_id: params[:id]})
if rsvp.save
end
end
def rsvp
# process the request here...
end
end
I'm building my second-ever basic Ruby on Rails application and having fun doing it, but have gotten stuck at precisely the same place that gave me trouble (and was never solved) on my last effort: the PUT or PATCH request.
My application has two models: entries and users. A logged-in user should be able to edit only those entries that were originally created by that user.
CONTROLLER
class EntriesController < ApplicationController
# authenticate user (Devise)
before_action :authenticate_user!, :except => [:index, :show]
# set entry upon page load
before_action :set_entry, :only => [:show, :edit, :update, :destroy]
# GET request - display all entries
def index
#all_entries = Entry.all
end
# GET request - display an individual entry
def show
# nothing required here because entry identified with before_action :set_entry on line 2 above
end
# GET request - access form to create a new entry
def new
#entry = Entry.new
#user = User.find(current_user[:id])
end
# GET request - access form to update an existing entry
def edit
if #entry[:user_id] != current_user[:id]
redirect_to root_path
else
redirect_to edit_entry_path
end
end
# POST request - make a new entry/save new data into db
def create
user = current_user[:id]
Entry.create({
entry_title: params[:entry][:entry_title],
book_title: params[:entry][:book_title],
text: params[:entry][:text],
img_url: params[:entry][:img_url],
tag: params[:entry][:tag],
created_at: params[:entry][:created_at],
user_id: user
})
redirect_to entries_path
end
# PUT request - save changes to an existing entry
def update
if #entry.update(entry_params)
redirect_to entry_path
else
render :new
end
end
# DELETE request - delete an existing entry from db
def destroy
#entry.destroy
redirect_to entries_path
end
private
def set_entry
#entry = Entry.find(params[:id])
end
def entry_params
params.require(:entry).permit(:email, :text, :tag)
end
end
VIEW (show.html.erb - shows a single entry and includes links allowing the logged-in user who originally authored the entry to edit or delete it)
<h3>Selected Entry</h3>
<div class="row">
<div class="col-md-2"></div>
<div class="col-md-6">
<div>Entry title: <%= #entry.entry_title %></div>
<div>Book title: <%= #entry.book_title %></div>
<div>Text: <%= #entry.text %></div>
</div>
<div class="col-md-4">
<div><%= #entry.created_at.strftime("%b %d, %Y") %></div>
<div>Submitted by: <i><%= #entry.user.email %></i></div>
<div>File under: <i><%= #entry.tag %></i></div>
<% if current_user %>
<%= link_to 'Edit', #entry, :method => 'update' %>
<%= link_to 'Delete', #entry, :method => 'delete' %>
<% end %>
</div>
</div>
ROUTES.RB - At first my routes were the commented-out lines, but then I had a thought that was either madness or sudden realization - should only the GET routes lead with "get"? So that's the non-commented-out attempt you see. Somehow the app works (except for the issue at hand) both ways.
In researching I've come across routes defined using a much more elaborate syntax than that I'm using here. I've been unable to figure out whether a given way of doing things is different convention, outdated, or just inadequate to the task.
Rails.application.routes.draw do
devise_for :users
resources :entries
# root 'entries#index'
# get '/entries' => 'entries#index'
# get '/users' => 'users#index'
# get '/entries/:id' => 'entries#show'
# get '/entries/:id' => 'entries#update'
# get '/entries/new' => 'entries#new'
# get '/entries/:id/edit' => 'entries#edit'
# get '/users/:id' => 'users#show'
# get '/about' => 'pages#index'
root 'entries#index'
get '/entries' => 'entries#index'
get '/entries/new' => 'entries#new'
post '/entries' => 'entries#create'
get '/entries/:id' => 'entries#show'
get '/entries/:id/edit' => 'entries#edit'
put '/entries/:id' => 'entries#update'
delete '/entries/:id' => 'entries#destroy'
get '/users' => 'users#index'
get '/users/:id' => 'users#show'
get '/about' => 'pages#index'
end
Thanks in advance for any insight. If additional context is needed I'm happy to provide.
Edited to add:
PARTIAL (_form.html.erb)
<div class="row">
<div class="col-md-2"></div>
<div class="col-md-6" id="form-container">
<%= form_for #entry do |form| %>
<br>
<%= form.text_field :entry_title, :size => 59, :placeholder => "Entry Title"%>
<br><br>
<%= form.text_field :book_title, :size => 59, :placeholder => "Book Title"%>
<br><br>
<%= form.text_field :img_url, :size => 59, :placeholder => "Image URL"%>
<br><br>
<%= form.text_area :text, :placeholder => "Text" %>
<br><br>
<%= form.text_field :tag, :placeholder => "Tag" %>
<br><br>
<%= form.submit %>
<% end %>
</div>
<div class="col-md-4"></div>
</div>
To edit a record you
first, should use a GET request to get the edit form
second, should submit that form using a PUT/PATCH request
To get to the edit form you should link to the edit path for your entry
<%= link_to 'Edit', edit_entry_path(#entry) %>
The Rails form helpers will automatically set the form to submit with the proper method, PUT OR PATCH.
:method in link_to helpers refers to HTML verb (get, post, etc), while controllers methods naming convention is action.
link_to
You need something as
<%= link_to 'Edit', #entry, :method => 'put' %>
or
<%= link_to 'Edit', #entry, :action => 'update' %>
At a glance you are trying to post with the edit link. Remember new/edit are get methods to render form, so just just delete method part in your links. Like from
<%= link_to 'Edit', #entry, :method => 'update' %>
to
<%= link_to 'Edit', edit_entry_path(#entry) %>
I'm building my second-ever basic Ruby on Rails application
Congrats! You need at least 3 more before it all starts to make sense
To add to the existing answers, you'll be best looking at the resources directive to clean the routes up:
#config/routes.rb
root 'entries#index'
devise_for :users
resources :entries
resources :pages, only: [:index], path_names: { index: "about" }
resources :users, only: [:index,:show]
--
A logged-in user should be able to edit only those entries that were originally created by that user.
This is known as authorization.
Authentication = is user logged in?
Authorization = can user do this?
Although people confuse Devise with being able to handle authorization, it only handles authentication. Whilst you have a simple implementation of this in your controller, you should check out either the CanCanCan or Pundit gems:
#Gemfile
gem "cancancan"
#app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
can :manage, Post, user_id: user.id
end
end
#app/controllers/entries_controller.rb
class EntriesController < ApplicationController
def edit
authorize! :edit, #entry
end
end
--
Finally, to answer your question directly, you're calling the update method (which doesn't exist) to access the edit view:
<% if current_user %>
<%= link_to 'Edit', #entry, :method => 'update' %>
<%= link_to 'Delete', #entry, :method => 'delete' %>
<% end %>
You should read up about http verbs - this is what the "method" option invokes with the link. As mentioned above, you don't need to set the method for edit as it uses GET. Update uses put/patch, which I can explain later.
A much better way to achieve what you want would be the following:
<%= link_to "Edit", edit_entry_path(#entry) if can? :edit, #entry %>
<%= link_to "Delete", #entry, method: :delete, if can? :destroy, #entry %>
The above uses the CanCanCan authorization method can?
I have made my form:
<tbody>
<% #player.bases.each do |basis| %>
<td><%= basis.id %></td>
<td><%= image_tag(basis.image_url(:thumb), class: 'thumbnail') %></td>
<td><%= link_to basis.name, basis %></td>
<td><%= basis.cost %></td>
<td><%= basis.short_info %></td>
<td>
<%= form_for #player, url: {:controller => 'players', :action => :remove_detail} do |f| %>
<%= f.hidden_field :type, :value => 'basis' %>
<%= f.hidden_field :detail_id, :value => basis.id %>
<%= f.submit 'Remove',class: 'btn btn-danger' %>
<% end %>
</td>
<% end %>
</tbody>
In my routes, I have added this:
resources :players do
collection do
get 'search'
post 'remove_detail'
end
end
I have remove_detail in my players_controller.rb, and I have added this action to before_action to get current player. However when I press on my Remove button, it throws me error and tries to run update action of my controller. Why?
My before_action:
before_action :set_player, only: [:show, :edit, :update, :destroy, :remove_detail]
My remove_detail:
def remove_detail
type = params['type']
id = params['detail_id']
if type == 'basis'
basis = Basis.find(id)
name = basis.name
#player.bases.delete(basis)
end
redirect_to #player, notice: "#{name} detail is removed"
end
To fix that, try as follows:
First of all, I'd redefine your routes as follows:
resources :players do
member do
delete 'remove_detail'
end
collection do
get 'search'
end
end
This will generate proper url for deleting a detail for a "single Player":
/players/:id/remove_detail
Because of REST-y nature of Rails, we defined the url to be accessible by performing delete request.
Your form change accordingly:
<%= form_for #player, { url: { action: "remove_detail" }, method: :delete } do |f| %>
Changing your routes to use delete method is more to keep the convention of Rails. Post would make your application work too, but - its just Rails-y way.
Good luck!
I'm getting a curious error after submitting my form. Been trying to solve this for several hours..
No route matches {:action=>"show", :controller=>"items", :item_id=>"141", :matter_id=>"3"} missing required keys: [:id]
The parameters are:
{"utf8"=>"✓",
"authenticity_token"=>"w0D7XmX2X2/ZMU19T6RlMvWCEClXnCFFOR+4EdIFvWg=",
"comment_item"=>{"item_id"=>"",
"name"=>"kaljdf",
"body"=>"yet another comment test"},
"commit"=>"Post Comment",
"matter_id"=>"3",
"item_id"=>"141"}
I have the following models:
class Matter < ActiveRecord::Base
has many :discoveries
delegate :items, to: :discoveries
end
class Discovery < ActiveRecord::Base
belongs_to :matter
scope :items, -> { where(type: 'Item') }
end
class Item < Discovery
has_many :comment_items
end
class CommentItem < ActiveRecord::Base
belongs_to :item
end
Controllers:
class ItemsController < DiscoveriesController
def show
#item = Item.find(params[:id])
#comment_item = CommentItem.new
end
def edit
#item = Item.find(params[:id])
end
def new
#item = Item.new
end
end
class CommentItemsController < ApplicationController
before_action :set_comment_item, only: [:show, :edit, :update, :destroy]
def new
#item = Item.find(params[:item_id])
#comment_item = #item.comment_item.new
end
def create
#item = Item.find(params[:item_id])
#comment_item = #item.comment_items.new(comment_item_params)
if #comment_item.save
flash[:notice] = 'Comment was successfully created'
redirect_to matter_item_url(matter_id: params[:matter_id])
else
flash[:notice] = "Error creating comment: #{#comment.errors}"
redirect_to matter_item_url(#matter, #item)
end
end
def destroy
#comment_item = CommentItem.find(params[:id])
#comment_item.destroy
redirect_to(#comment_item.item)
end
private
def set_comment_item
#comment_item = CommentItem.find(params[:id])
end
def comment_item_params
params.require(:comment_item).permit(:name, :body, :item_id, :matter_id)
end
end
The show action for the item resource:
<p>
<strong>Matter:</strong>
<%= #item.matter_id %>
</p>
<p>
<strong>Content:</strong>
<%= #item.content %>
</p>
<hr />
<%= form_for #comment_item, url: matter_item_comment_items_path(matter_id: #item.matter, item_id: #item.id) do |f| %>
<% if #comment_item.errors.any? %>
<ul>
<% #comment_item.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
<% end %>
<%= f.hidden_field :item_id %>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<p>
<%= f.label :body %><br />
<%= f.text_area :body %>
</p>
<p>
<%= f.submit "Post Comment" %>
</p>
<% end %>
<%= render :partial => 'comment_item', :collection => #item.comment_items %>
<%= link_to 'Edit', edit_matter_item_path(id: #item.id) %> |
<%= link_to 'Back', matter_items_path %>
Routes
resources :items do
resources :comment_items
end
resources :matters do
resources :items do
resources :comment_items
end
end
When looking at CommentItems in the console, I see that the comments are in fact being added to the model with their correct ID's, but they don't seem to be passed as parameters.. What am I missing?
I've reviewed Rails 4 form_for double nested comments and Rails 3.2 - Nested Resource Passing ID but I didn't have much luck..
I'd really appreciate your help!
No route matches {:action=>"show", :controller=>"items", :item_id=>"141", :matter_id=>"3"} missing required keys: [:id]
your request is going to ItemsController instead of CommentItemsController
see :controller => "items"
I'm new to Rails and I'm building a quizz application.
I have a has_many and belongs_to association set up between two models: Level and Question.
#models/level.rb
class Level < ActiveRecord::Base
has_many :questions
end
#models/question.rb
class Question < ActiveRecord::Base
belongs_to :level
attr_accessible :level_id
end
I have an action 'startlevel' in my LevelController that simply lists all the levels.
class LevelsController < ApplicationController
def startlevel
#levels = Level.all
end
and the view with links to go to the first question of the level. I want to add the id of the level as a parameter in the link. I noticed the 1 in the url of the view. I have no idea why how it came there and if it is part of my problem.
#controller/levels/startlevel/1
<h2>which level would you like to play</h2>
<table>
<tr>
<th>level</th>
</tr>
<% #levels.each do |level| %>
<tr>
<td> level <%=level.number %></td>
<td><%= link_to '<> play this level', :controller => "questions", :action => "answer",:level_id=> level.id%></td>
</tr>
<% end %>
</table>
When I follow the the link, I want to go to the first question with a level_id that matches the id parameter in the link_to, so i tried to do this:
class QuestionsController < ApplicationController
def answer
#question = Question.find_by_level_id(params[:level_id])
end
with this view
<p>
<b>Question:</b>
<%=h #question.word %>
</p>
<p>
<b>1:</b>
<%=h #question.ans1 %>
</p>
<p>
<b>2:</b>
<%=h #question.ans2 %>
</p>
<p>
<b>3:</b>
<%=h #question.ans3 %>
</p>
<p>
<b>4:</b>
<%=h #question.ans4 %>
</p>
<%= form_tag(:action => "check", :id => #question.id) do %>
<p>
<b>the correct answer is number: </b>
<%=text_field :ans, params[:ans]%>
</p>
<p><%= submit_tag("check")%></P>
<% end %>
unfortunately, whatever i tried, all i got for the last couple of hours was:
undefined method `word' for nil:NilClass
(word is an attribute of a question)
I want to cry. what am I doing wrong?
p.s. my idea is to add a link_to_unless in the 'answer'view which goes to the next question of the same level unless the next one is nil, so i think i need to group the questions with the same reference key somehow?
It works right now, however i'm not sure if this is the prettiest solution. Views/levels/play is empty since it only redirects to the first question of the level.
class LevelsController < ApplicationController
def startlevel
#levels = Level.all
end
def play
#level = Level.find(params[:id])
#question = Question.find_by_level_id(params[:id])
redirect_to controller: 'questions', action: 'answer', id: #question.id
end
#views/levels/startlevel
<h2>Which level would you like to play</h2>
<table>
<tr>
<th>level</th>
</tr>
<% #levels.each do |level| %>
<tr>
<td> level <%=level.number %></td>
<td>
<%= link_to '<> Play this level', :action => "play", :id=>level.id %></td>
</tr>
<% end %>
</table>
QuestionsController
class QuestionsController < ApplicationController
def answer
#question= Question.find(params[:id])
end
edit: Routes:
quizz::Application.routes.draw do
resources :questions
resources :levels
get "home/index"
root :to => 'home#index'
match ':controller/:action/:id', via: [:get, :post]
match ':controller/:action/:id.:format', via: [:get, :post]