I want to use a single form to send data to two tables: Questions and Answers.
To do so, I am using accepts_nested_attributes_for as outlined here. Here is my Question.rb model:
class Question < ActiveRecord::Base
has_many :answers
belongs_to :category
accepts_nested_attributes_for :answers, :allow_destroy => true
end
Then in my _form view, I am using:
<% form_for(#question, :url => question_path(#question)) do |f| %>
<% f.text_field :question_text %>
<% f.fields_for :answers do |builder| %>
<%= builder.text_field :answer_text %>
<% end %>
<% end %>
This produces a confusing error: No route matches {:action=>"show", :controller=>"questions", :id=>nil} missing required keys: [:id].
Even if I manually append an id onto the params (like http://localhost:3000/triviabuilder/new?id=1), I still receive this error.
Do I really need to specify an ID in the params, and if so, should that be done in the controller?
EDIT - Adding Routes.rb
Example::Application.routes.draw do
resources :games
resources :questions
resources :answers
resources :triviabuilder
devise_for :users
root "pages#home"
get "triviabuilder" => "triviabuilder#index"
get "new_triviabuilder" =>"triviabuilder#new"
EDIT 2 - Adding TriviaBuilder Controller
Tries to call the Question table since it includes nested_attributes
class TriviabuilderController < ApplicationController
def new
#question = Question.new
end
def create
#question = question.new(params[:question])
if #question.save
redirect_to triviabuilder_path, :notice => "Your post was saved"
else
render "new"
end
end
end
This is producing a "show" error for No route matches {:action=>"show", :controller=>"triviabuilder"} missing required keys: [:id].
Try changing following things
routes.rb
resources :triviabuilders
_form.html.erb
<%= form_for(Question.new , url: triviabuilders_path) do |f| %>
<% f.text_field :question_text %>
<% f.fields_for :answers do |builder| %>
<%= builder.text_field :answer_text %>
<% end %>
<% end %>
Controller name should be plural, change to TriviabuildersController from TriviabuilderController.
create action should be as follows:
def create
#question = Question.new(params[:question])
if #question.save
redirect_to triviabuilders_path, :notice => "Your post was saved"
else
render "new"
end
end
Remove the following line of code from form_for
:url => question_path(#question)
Also, you need to add = to your form_for tag otherwise it won't be visible, Your form_for should look like this
<%= form_for #question do |f| %>
Rails will automatically map it to the update action.
Hope that helps!
Related
My rails app has a games model, and each game has multiple players. When a game is created, a set number of players are created with a default name like so:
def create
#game = Game.new(game_params)
#game.player_number.times do
#game.players << Player.new(name: 'Santa')
end
if #game.save
redirect_to action: "players", id: #game.id
else
render 'new'
end
end
The redirect takes the user to a page that has a form with inputs for each player's name. The actions associated with this page are:
def players
#game = Game.find(params[:id])
end
def playersUpdate
#game = Game.find(params[:id])
puts player_params
if #game.players.update(player_params)
redirect_to #game
else
render 'players'
end
end
private
def player_params
params.require(players: [:name])
end
The editing page itself is:
<h2> Edit Players </h2>
<%= form_tag({:action => 'playersUpdate'},{:id => #game.id}) do %>
<%= #game.players.count %>
<% #game.players.each.with_index do |player,index| %>
<%= fields_for "players[#{index}]", player do |pl| %>
<div>
<%= pl.label :name %><br>
<%= pl.text_field :name %><br>
<%= pl.hidden_field :id %>
</div>
<% end %>
<% end %>
<div>
<%= submit_tag %>
</div>
<% end %>
Here's the routes.rb:
Rails.application.routes.draw do
get 'welcome/index'
resources :games do
collection do
match "/:id/players" => "games#players", :via => :get
match "/:id/players" => "games#playersUpdate", :via => :post
end
end
root 'welcome#index'
end
I get an error:
param is missing or the value is empty: {:players=>[:name]}
And I'm at a loss for what I could be missing. Any tips?
Here are the parameters being passed in, George is the name I'm trying to edit in, all others default to 'Santa':
Processing by GamesController#playersUpdate as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"wNwt9v2ckO/Bl8YGr/a2CDCjSsRec30E51VjZ/Qv2i5BgEnzVbH5M9DsrVfCxdLusS4Ue6Mq+aPSFOiA4K5jJg==", "players"=>{"0"=>{"name"=>"George", "id"=>"122"}, "1"=>{"name"=>"Santa", "id"=>"123"}, "2"=>{"name"=>"Santa", "id"=>"124"}, "3"=>{"name"=>"Santa", "id"=>"125"}}, "commit"=>"Save changes", "id"=>"22"}
You are not breaking any new ground here, and Rails has standard ways to do all of this. But it's easy to get "off the Rails" and make it harder than it needs to be.
Conceptually, stop thinking about updating a bunch of Players, and start thinking about updating a Game that happens to have some Players. The docs are surprisingly helpful here.
Let's go first to your Game model. You'll need to tell it that it's OK to update nested attributes for players, like this:
# models/game.rb
model Game < ApplicationRecord
has_many :players
accepts_nested_attributes_for :players
end
Your view is generating parameters that are not quite standard. Again, let Rails do the work for you. You don't need hidden fields or each_with_index. Since we're in Rails 5, let's use the new form_with helper, and we'll let fields_for do its job without our trying to tell it how to index:
# views/games/edit_players.html.erb
<h2> Edit Players </h2>
<%= form_with(model: game, local: true) do |form| %>
<div>
Game name: <%= form.text_field :name %>
</div>
<%= form.fields_for :players do |player_fields| %>
<div>
Player name: <%= player_fields.text_field :name %><br>
</div>
<% end %>
<div>
<%= form.submit %>
</div>
<% end %>
This will generate params that look something like this:
Parameters: {"game"=>
{"name"=>"Risk",
"players_attributes"=>
{"0"=>{"name"=>"Abel", "id"=>"1"},
"1"=>{"name"=>"Baker", "id"=>"2"},
"2"=>{"name"=>"Charlie", "id"=>"3"}}},
"commit"=>"Update Game",
"id"=>"1"}
Now you don't even need a custom update endpoint. Just use your standard GamesController#update action:
# controllers/games_controller.rb
class GamesController < ApplicationController
...
def edit_players
#game = Game.find(params[:id])
end
def update
#game = Game.find(params[:id])
if #game.update(game_params)
redirect_to #game
else
render :edit
end
end
private
def game_params
params.require(:game).permit(:name, players_attributes: [:id, :name])
end
end
Finally, your routes file is confusing because you are using collection (which doesn't expect an :id) instead of member. The routes file should look something like this:
# routes.rb
Rails.application.routes.draw do
resources :players
resources :games do
member { get :edit_players }
end
end
So in my rails project, I have a Patient class, which has one Treatment class. This treatment class then has many DrNotes inside of it. I am still fairly new to rails, and I am aware that nesting this deeply is not recommended in Rails, but I am proceeding with this method.
My problem is with the editing of DrNotes. Since there are many doctor notes within treatment, I am trying to only edit one specific note. I am using Form_for to pass parameters to the doctor's note. When I submit the form, it redirects me to the page that should be shown only when the update function has succeeded. However, none of the notes are actually updated, and no errors are thrown when I try to perform the update.
Here are the models in question:
patient.rb
class Patient < ApplicationRecord
has_one :treatment, dependent: :destroy
accepts_nested_attributes_for :treatment, update_only: true
end
treatment.rb
class Treatment < ApplicationRecord
belongs_to :patient
has_many :dr_notes, class_name: "DrNote",
foreign_key: "treatment_id", dependent: :destroy
accepts_nested_attributes_for :dr_notes
end
dr_note.rb
class DrNote < ApplicationRecord
belongs_to :treatment
end
In my controller I have:
Doctor Note Edit Function
def edit_dr_note
#patient = Patient.find(params[:patient_id])
#dr_note = #patient.treatment.dr_notes.find(params[:dr_id])
#dr_note.update if #dr_note.nil?
end
Doctor Note Update Function
def update_dr_note
#patient = Patient.find(params[:patient_id])
#dr_note = #patient.treatment.dr_notes.find(params[:dr_id])
if #dr_note.update(dr_note_params)
redirect_to page_path(#patient)
else
flash.now[:error] = "Cannot update Doctor's notes"
render 'edit_dr_note'
end
end
Doctor Note Params
def dr_note_params
params.require(:dr_note).permit(:id, :name, :message)
end
I have :id in the params.permit because from researching, I heard that you need to include it when updating models, but i'm not sure if it is needed here.
I have the following code in the routes.rb
get '/pages/:patient_id/treatment/edit/edit_dr_note/:dr_id', to: 'pages#edit_dr_note', as: :edit_dr_note
match "pages/:patient_id/treatment/update/update_dr_note/:dr_id" => "pages#update_dr_note", as: :update_dr_note, via: [:patch, :post]
And in the edit_dr_note.html.erb
<%= form_for #patient.treatment.dr_notes.find(params[:dr_id]), url: update_dr_note_path do |patient_form| %>
<% #patient.treatment.dr_notes.each do |doctor| %>
<% if doctor.id == #dr_note.id %> #Only displays the fields for the desired note
<%= patient_form.fields_for :dr_note, doctor do |doctor_fields| %>
Name: <%= doctor_fields.text_field :name %>
Message: <%= doctor_fields.text_field :message %>
<% end %>
<p>
<%= patient_form.submit %>
</p>
<% end %>
<% end %>
<% end %>
Any help would be greatly appreciated. Thanks!
You are mixing two approaches(the nested resources and the nested attributes). Use one to serve your purpose.
With the nested resources:
<%= form_for [:pages, #patient, #treatment, #dr_note], url: update_dr_note_path do |dr_note| %>
Name: <%= dr_note.text_field :name %>
Message: <%= dr_note.text_field :message %>
<p>
<%= dr_note.submit %>
</p>
<% end %>
The routes would be
get '/pages/:patient_id/treatment/:treatment_id/edit_dr_note/:dr_id', to: 'pages#edit_dr_note', as: :edit_dr_note
match "pages/:patient_id/treatment/:treatment_id/update_dr_note/:dr_id" => "pages#update_dr_note", as: :update_dr_note, via: [:patch, :post]
Edit the edit_dr_note to define #treatment
def edit_dr_note
#patient = Patient.find(params[:patient_id])
#treatment = #patient.treatment
#dr_note = #patient.treatment.dr_notes.find(params[:dr_id])
#dr_note.update if #dr_note.nil?
end
And finally remove accepts_nested_attribute_for from the models, you don't need it in this approach.
With the nested attributes:
Keep the accepts_nested_attributes_for in the models. And change the routes and form like below
get '/edit_dr_note/:dr_id', to: 'pages#edit_dr_note', as: :edit_dr_note
match "/update_dr_note/:dr_id" => "pages#update_dr_note", as: :update_dr_note, via: [:patch, :post]
And the form_for
<%= form_for #patient, url: update_dr_note_path do |patient| %>
<%= patient.fields_for :treatment do |t| %>
<%= t.fields_for :dr_notes, #dr_note do |dr_note| %>
Name: <%= dr_note.text_field :name %>
Message: <%= dr_notetext_field :message %>
<% end %>
<% end %>
<p>
<%= patient.submit %>
</p>
<% end %>
And change the dr_note_params method as below
def dr_note_params
params.require(:patient).permit(:id, treatment_attributes: [:id, dr_notes_attributes: [:id, :name, :message])
end
When you write the following line, you're trying to find a DrNote using the dr_id:
#dr_note = #patient.treatment.dr_notes.find(params[:dr_id])
Whereas the dr_notes relation on Treatment does not seem to define any particular behavior, and this is your problem.
You'll need to find_by doctor's id (or dr_id in your code) and thus first define the relation on DrNote.
I have been trying to fix an error associated with using the Ancestry gem for comments on my app for Rails 4. I used railscast episode 262 as a guide. However, unlike the episode, my comments model is a nested resource inside another model.Before I go further, I will supply the necessary code for reference. If you like to read the error right away, it is mentioned right after all the code snippets.
The Relevant Models:
class Comment < ActiveRecord::Base
has_ancestry
belongs_to :user
belongs_to :scoreboard
end
class Scoreboard < ActiveRecord::Base
#scoreboard model is like an article page on which users can post comments
belongs_to :user
has_many :teams, dependent: :destroy
has_many :comments, dependent: :destroy
end
Relevant code in the route file:
resources :scoreboards do
resources :comments
resources :teams, only: [:edit, :create, :destroy, :update]
end
The Scoreboards Controller Method for the page on which one can post comments:
def show
#scoreboard = Scoreboard.find_by_id(params[:id])
#team = #scoreboard.teams.build
#comment = #scoreboard.comments.new
end
The Comments Controller:
class CommentsController < ApplicationController
def new
#scoreboard = Scoreboard.find(params[:scoreboard_id])
#comment = #scoreboard.comments.new(:parent_id => params[:parent_id])
end
def create
#scoreboard = Scoreboard.find(params[:scoreboard_id])
#comment = #scoreboard.comments.new comment_params
if #comment.save
redirect_to scoreboard_url(#comment.scoreboard_id)
else
render 'new'
end
end
private
def comment_params
params.require(:comment).permit(:body, :parent_id).merge(user_id: current_user.id)
end
end
I will include the migration for the ancestry gem if any mistakes were made on that :
class AddAncestryToComments < ActiveRecord::Migration
def change
add_column :comments, :ancestry, :string
add_index :comments, :ancestry
end
end
The following code shows the view code:
Scoreboard#show View which is giving me the error in the last line:
<div class= "comment-section">
<%= form_for [#scoreboard, #comment] do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.text_area :body, class: "comment-field" %>
<%= f.hidden_field :parent_id %> #is it needed to include this here? because this form is for new comments not replies
<%= f.submit "Join the discussion...", class: " comment-button btn btn-primary" %>
<% end %>
<%= nested_comments #scoreboard.comments.reject(&:new_record?).arrange(:order => :created_at) %>
</div>
The (comments partial)_comment.html.erb View:
<div class=" comment-div">
<p> Posted by <%= link_to "#{comment.user.name}", comment.user %>
<%= time_ago_in_words(comment.created_at) %> ago
</p>
<div class="comment-body">
<%= comment.body %>
<%= link_to "Reply", new_scoreboard_comment_path(#scoreboard, comment, :parent_id => comment) %>
</div>
</div>
The helper method to render comments:
def nested_comments(comments)
comments.map do |comment, sub_comment| #the comments.map also gives me an error if I choose to render the comments without the .arrange ancestry method
render(comment) + content_tag(:div, nested_comments(sub_comment), class: "nested_messages")
end.join.html_safe
end
The new.html.erb for Comments which one is redirected to for the replies form submission:
<%= form_for [#scoreboard, #comment] do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.text_area :body, class: "comment-field" %>
<%= f.hidden_field :parent_id %>
<%= f.submit "Join the discussion...", class: " comment-button btn btn-primary" %>
<% end %>
Upon creating a scoreboard, I am redirected to the show page, where i get the following error:
undefined method `arrange' for []:Array
Even though the array of comments is empty, I get the same error if it wasnt. I have tried .subtree.arrange but that gives me the same error. Also, the ancestry documentation said that .arrange works on scoped classes only. I don't know what that means. I would appreciate some help on making the page work so the comments show properly ordered with the replies after their parent comments. If this is the wrong approach for threaded comments(replies and all), I would appreciate some guidance on what to research next.
.reject(&:new_record?) this will return an array. The error sounds like arrange is a scope on ActiveRecord. So move the reject to the end and it should work.
#scoreboard.comments.arrange(:order => :created_at).reject(&:new_record?)
In regards your comment nesting, I have implemented this before, and found the Railscasts recommendation of a helper to be extremely weak.
Passing parent_id to a comment
Instead, you're better using a partial which becomes recursive depending on the number of children each comment has:
#app/views/scoreboards/show.html.erb
<%= render #comments %>
#app/views/scoreboards/_comment.html.erb
<%= link_to comment.title, comment_path(comment) %>
<div class="nested">
<%= render comment.children if comment.has_children? %>
</div>
Routes
resources :locations, :only => [:new, :create]
Controller
class LocationsController < ApplicationController
def new
#location = Location.new
end
def create
#location = Location.new(location_params)
if #location.save
flash[:notice] = 'Created location successfully'
redirect_to new_location_path
else
flash[:notice] = 'Invalid information. Please try again'
render :new
end
end
private
def location_params
params.require(:location).permit(:name, :street, :city, :state)
end
end
Error message when I click save.
ActionController::RoutingError:
No route matches [POST] "/locations/new"
view
<%= simple_form_for :locations do |form| %>
<%= form.input :name %>
<%= form.input :street %>
<%= form.input :city %>
<%= form.input :state %>
<%= form.submit 'Create location' %>
<% end %>
Using capybara to test that when I click on save it creates a new location. I'm not quite sure why it doesn't know what the post route is because I have the new and create routes. If I put a binding.pry right underneath the create method it doesn't get called. So my create method is not being called for some reason.
EDIT:
Rake Routes
locations POST /locations(.:format) locations#
new_location GET /locations/new(.:format) locations#new
A resource normally GETs to new and POSTs to create. So your form is probably submitting back to the new actions instead of submitting to the create action.
Here's the guide for this: http://guides.rubyonrails.org/routing.html#crud-verbs-and-actions
The problem was in the views.
<%= simple_form_for :locations do |form| %>
<%= form.input :name %>
<%= form.input :street %>
<%= form.input :city %>
<%= form.input :state %>
<%= form.submit 'Create location' %>
<% end %>
I was calling on locations symbol when I should have been calling on the instance variable #location from the controller.
The actual problem was that rails 3.2.12 doesn't take private params
Using resources, the new action should be fetched with GET instead of POST. create will be POST. Check your rake routes
While learning Rails 4 I stuck while trying to add categories to posts of my simple blog. I have generated model, ran the migration and added a controller. No matter what I do now when trying to create a category, I keep running into same mistake: no route matches [POST], which is weird, as I seem to have all the code in place. Please help!
categories controller
class CategoriesController < ApplicationController
def index
#categories = Category.all
end
def new
#category = Category.new
end
def create
#category = Category.new(category_params)
#category.save
redirect_to new_category_path, alert: "Category created!"
end
def show
#category = Category.find(params[:id])
end
def destroy
#category = Category.find(params[:id])
#category.destroy
redirect_to categories_path
end
private
def category_params
params.require(:category).permit(:name)
end
end
routes.rb
Blog::Application.routes.draw do
get 'tags/:tag', to: 'posts#index', as: :tag
resources :categories
resources :posts do
resources :comments
end
root 'welcome#index'
end
category.rb
class Category < ActiveRecord::Base
validates :name, presence: true
has_many :posts
end
new.html.erb
<%= form_for :category do |f| %>
<p>
<%= f.label :name %><br>
<%= f.text_field :name %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
/categories/new
No route matches [POST] "/categories/new"
You should have in your view
<%= form_for #category do |f| %>
<p>
<%= f.label :name %><br>
<%= f.text_field :name %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
#category object is used by form_for method to figure out form url.
If you pass only Symbol to form_for method, without specifying url explicitly, form will be generated with url being the current url.
Although the #category works, if you read a bit further you will see that they will explain why the your code sends No route matches [POST] "/categories/new".
The guide actually explains that you need to specify the url: posts_path for the form to use the right route.
There's one problem with this form though. If you inspect the HTML
that is generated, by viewing the source of the page, you will see
that the action attribute for the form is pointing at /posts/new. This
is a problem because this route goes to the very page that you're on
right at the moment, and that route should only be used to display the
form for a new post.
The form needs to use a different URL in order to go somewhere else.
This can be done quite simply with the :url option of form_for.
Typically in Rails, the action that is used for new form submissions
like this is called "create", and so the form should be pointed to
that action.
Edit the form_for line inside app/views/posts/new.html.erb to look
like this:
<%= form_for :post, url: posts_path do |f| %>
In this example, the
posts_path helper is passed to the :url option. What Rails will do
with this is that it will point the form to the create action of the
current controller, the PostsController, and will send a POST request
to that route.