The goal is simple, create a document with a name and description an attached CSV file.
I have the following migration:
class CreateKeywords < ActiveRecord::Migration
def change
create_table :keywords do |t|
t.string :name, null: false, unique: true
t.string :description, null: false
t.string :keys, null: false
t.timestamps null: false
end
end
end
Model:
class Keyword < ActiveRecord::Base
mount_uploader :keys, KeywordsUploader
validates :name, :description, :keys, presence: true
validates :name, uniqueness: true
end
Controller:
class KeywordsController < ApplicationController
def index
#keywords = Keyword.all
end
def new
#keyword = Keyword.new
end
def create
puts "Keyword params"
pp keyword_params
#keyword = Keyword.new(keyword_params)
if #keyword.save
flash[:success] = "New server created!"
redirect_to #keyword
else
render 'new'
end
end
private
def keyword_params
params.permit(:keyword).permit(:name, :description, :keys)
end
end
And finally view:
<% provide(:title, 'Create keywords set') %>
<h1>New keywords set</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for #keyword, :html => {:multipart => true } do |f| %>
<%= render 'shared/error_messages' %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :description %>
<%= f.text_field :description %>
<%= f.label :keywords %>
<%= f.file_field :keys %>
<%= f.submit "Submit", class: "btn btn-primary" %>
<% end %>
</div>
</div>
When I'm trying to submit a form with all required fields I'm getting the following error in a view:
1) Name can't be blank
2) Description can't be blank
3) Keys can't be blank
In Rails console I can see the following during the form submission:
Started POST "/keywords" for ::1 at 2015-10-09 17:21:40 +0300
Processing by KeywordsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"QO69fZ9xx1+mXRTL1TqZQLs9kYriQj4sqxV0t8P8XG2nu1FvKmOw6fISpmvi70VlWrD9bCJg7bCqwLwUfvwGRQ==", "keyword"=>{"name"=>"asd", "description"=>"asd"}, "commit"=>"Create keywords"}
Keyword params
Unpermitted parameters: utf8, authenticity_token, keyword, commit
{}
Unpermitted parameters: utf8, authenticity_token, keyword, commit
As you can see, the strong params function returns an empty hash, and
that is why it's impossible to create an instance of a model "Keyword".
What can be a problem?
Resolved!
Instead of using:
def keyword_params
params.permit(:keyword).permit(:name, :description, :keys)
end
I should been using:
def keyword_params
params.require(:keyword).permit(:name, :description, :keys)
end
The issue was caused by incorrect keyword_params method.
Related
I am building a flight booking app with Rails that lets you select airports, date and number of passengers. Once you select the airports and dates, it gives you radio buttons to select which flight you want and, once you click submit, you are taken to a booking confirmation page where you are asked to provide passenger info.
The confirmation page(bookings#new) has a nested form to include passengers in the booking object. To do this, I have first set the following models and associations:
class Passenger < ApplicationRecord
belongs_to :booking
end
class Booking < ApplicationRecord
belongs_to :flight
has_many :passengers
accepts_nested_attributes_for :passengers
end
And the relevant migrations that result in the following schema tables:
create_table "bookings", force: :cascade do |t|
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "flight_id"
t.integer "passenger_id"
end
create_table "passengers", force: :cascade do |t|
t.string "name"
t.string "email"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "booking_id"
t.index ["booking_id"], name: "index_passengers_on_booking_id"
end
From what I understand, the flow goes like this:
User selects flight -> User submits flight, goes to Booking#new through the #new method on my controller:
class BookingsController < ApplicationController
def new
#booking = Booking.new
#flight = Flight.find(params[:flight_id])
params[:passengers_number].to_i.times do #params passed from select flight page
#booking.passengers.build
end
end
Then, the form I built takes over on new.html.erb:
<%= form_with model: #booking, url: bookings_path do |f| %>
<%= f.hidden_field :flight_id, value: #flight.id %>
<% #booking.passengers.each_with_index do |passenger, index| %>
<%= f.fields_for passenger, index: index do |form| %>
<h4><%= "Passenger #{index+1}"%> <br> </h4>
<%= form.label :name, "Full name:" %>
<%= form.text_field :name %>
<%= form.label :email, "Email:" %>
<%= form.email_field :email %>
<% end %>
<% end %>
<%= f.submit "Confirm details"%>
<% end %>
I fill it in with names and emails, click 'Confirm details' and I get this error on my terminal:
Unpermitted parameter: :passenger
Why? I have set accepts_nested_attributes_for :passengers on my Booking model, my booking_params method is:
def booking_params
params.require(:booking).permit(:flight_id,
:passengers_attributes => [:name, :email, :passenger_id, :created_at, :updated_at])
end
and my #create method is:
def create
#booking = Booking.new(booking_params)
respond_to do |format|
if #booking.save
format.html { redirect_to #booking, notice: "booking was successfully created." }
format.json { render :show, status: :created, location: #booking }
else
format.html { redirect_to root_path, alert: "booking failed, #{#booking.errors.full_messages.first}" , status: :unprocessable_entity }
format.json { render json: #booking.errors, status: :unprocessable_entity }
end
end
end
Is there something I am not permitting properly? Note that if I set booking_params as params.require(:booking).permit! it gives me an unknown attribute 'passenger' for Booking error. But I have defined associations and database on passenger and booking, at least to my knowledge.
Thanks in advance
Edit: The server log that generates the error is:
Started POST "/bookings" for ::1 at 2021-08-27 17:52:17 +0300
Processing by BookingsController#create as HTML
Parameters: {"authenticity_token"=>"[FILTERED]", "booking"=>{"flight_id"=>"5", "passenger"=>{"0"=>{"name"=>"Jason Smason", "email"=>"jason#ymail.com"}, "1"=>{"name"=>"Joe Smith", "email"=>"Joe#smith.com"}}}, "commit"=>"Confirm details"}
Unpermitted parameter: :passenger
Edit2: I followed PCurell's advice and changed my fields_for to <%= f.fields_for :passengers, passenger, index: index do |form| %> and my booking params to :passenger => [:name, :email, :passenger_id, :created_at, :updated_at])
That generated a different error: Unpermitted parameter: passengers_attributes'. So I changed my controller's booking_params` to:
def booking_params
params.require(:booking).permit(:flight_id,
:passengers_attributes => [:name, :email, :passenger_id, :created_at, :updated_at],
:passenger => [:name, :email, :passenger_id, :created_at, :updated_at])
end
With that, I successfully managed to create a booking with 2 passengers. However, the passengers are blank; their name and email are nil. The log says:
Parameters: {"authenticity_token"=>"[FILTERED]", "booking"=>{"flight_id"=>"1", "passengers_attributes"=>{"0"=>{"0"=>{"name"=>"John Smith", "email"=>"John#smith.com"}}, "1"=>{"1"=>{"name"=>"Burger King", "email"=>"bk#bk.com"}}}}, "commit"=>"Confirm details"}
Unpermitted parameter: :0
Unpermitted parameter: :1
I might be able to hardcode :0 and :1 to pass, but surely that's not the Rails way. Is there a way to dynamically let them in? Or am I doing the whole thing wrong?
Your usage of fields_for is incorrect. When you look at the example in the guide you will notice that there is no need to wrap it with an .each if used for a collection.
10.2 Nested Forms
The following form allows a user to create a Person and its
associated addresses.
<%= form_with model: #person do |form| %>
Addresses:
<ul>
<%= form.fields_for :addresses do |addresses_form| %>
<li>
<%= addresses_form.label :kind %>
<%= addresses_form.text_field :kind %>
<%= addresses_form.label :street %>
<%= addresses_form.text_field :street %>
...
</li>
<% end %>
</ul>
<% end %>
When an association accepts nested attributes fields_for renders its block once for every element of the association. In particular,
if a person has no addresses it renders nothing. A common pattern is
for the controller to build one or more empty children so that at
least one set of fields is shown to the user. The example below would
result in 2 sets of address fields being rendered on the new person
form.
def new
#person = Person.new
2.times { #person.addresses.build }
end
When applying this to your code, removing the .each wrapper and changing passenger into :passengers should do the trick. You can access the index through the FormBuilder instance (form) passed to the fields_for block.
<%= form_with model: #booking, url: bookings_path do |f| %>
<%= f.hidden_field :flight_id, value: #flight.id %>
<%= f.fields_for :passengers do |form| %>
<h4>Passenger <%= form.index + 1 %></h4>
<%= form.label :name, "Full name:" %>
<%= form.text_field :name %>
<%= form.label :email, "Email:" %>
<%= form.email_field :email %>
<% end %>
<%= f.submit "Confirm details"%>
<% end %>
As #Rockwell Rice mentioned in his comment:
The problem here is that you are permitting the wrong param.
def booking_params
params.require(:booking).permit(:flight_id,
:passenger => [:name, :email, :passenger_id, :created_at, :updated_at])
end
Should work.
Although you might encounter another error.
I think that you are constructing your fields_for wrong.
This should be what you are looking for (and you will not have to change the booking_params)
<%= form_with model: #booking, url: bookings_path do |f| %>
<%= f.hidden_field :flight_id, value: #flight.id %>
<% #booking.passengers.each_with_index do |passenger, index| %>
<%= f.fields_for :passengers, passenger, index: index do |form| %>
<h4><%= "Passenger #{index+1}"%> <br> </h4>
<%= form.label :name, "Full name:" %>
<%= form.text_field :name %>
<%= form.label :email, "Email:" %>
<%= form.email_field :email %>
<% end %>
<% end %>
<%= f.submit "Confirm details"%>
<% end %>
The above should work.
If you don't care too much about the index this would work as well:
<%= form_with model: #booking, url: bookings_path do |f| %>
<%= f.hidden_field :flight_id, value: #flight.id %>
<%= f.fields_for #booking.passengers do |form| %>
<%= form.label :name, "Full name:" %>
<%= form.text_field :name %>
<%= form.label :email, "Email:" %>
<%= form.email_field :email %>
<% end %>
<%= f.submit "Confirm details"%>
<% end %>
I have my parameters whitelisted and when I look at the output of party_params I see that they are permitted but when I got to save the instance into the database it gives me a rollback transaction in the console. I've tried just create, create then save, new then save. Is there something I am missing?
#controller
class PartiesController < ApplicationController
def new
#party = Party.new
end
def create
#party = Party.create(party_params)
redirect_to party_path(#party)
end
private
def party_params
params.require(:party).permit(
:name,
:trainer_id,
:pokemon1_id
)
end
end
#model
class Party < ApplicationRecord
belongs_to :trainer
belongs_to :pokemon
validates :name, presence: true
end
#view
<h1>Create a New Pokemon Party</h1>
<p>Select 6 Pokemon</p>
<%= form_for(#party) do |f| %>
<label>Party Name:</label>
<%= f.text_field :name %><br>
<label>Trainer Name:</label>
<%= collection_select(:party, :trainer_id, Trainer.order(:id), :id, :name, include_blank: true) %><br>
<label>Pokemon:</label>
<%= collection_select(:party, :pokemon1_id, Pokemon.order(:id), :id, :nickname, include_blank: false) %><br>
<%= f.submit %>
<% end %>
#schema
create_table "parties", force: :cascade do |t|
t.string "name"
t.integer "pokemon1_id"
t.integer "trainer_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
Lets start with the models. If you want a party to be able to include multiple pokemon you need to place the foreign key in the other model:
class Party < ApplicationRecord
belongs_to :trainer
has_many :pokemons # references the pokemons.party_id column
end
class Pokemon < ApplicationRecord
belongs_to :party # pokemons needs a `party_id` column
end
class AddPartyToPokemons < ActiveRecord::Migration
def change
add_reference :pokemons, :party, null: false, foreign_key: true
remove_column :parties, :pokemon1_id
end
end
This is very simplefied and assumes that Pokemon is an individual Pokemon and not the entire species and can only belong to a single party. Otherwise you need a many-to-many assocation with a join table/model.
In your controller you need to check if creating the record was actually successful and respond accordingly:
class PartiesController < ApplicationController
def new
#party = Party.new
end
def create
#party = Party.new(party_params)
if #party.save
redirect_to #party
else
render :new
end
end
private
def party_params
params.require(:party).permit(
:name,
:trainer_id,
pokemon_ids: []
)
end
end
If the user input is invalid this will render the app/parties/new.html.erb view and respond with it.
While you could use pry or byebug to step into the controller and check the errors you want to display the validation errors to the user in the view anyways so that they know what to actually do to correct the form:
<h1>Create a New Pokemon Party</h1>
<p>Select 6 Pokemon</p>
<%= form_for(#party) do |f| %>
<% if #party.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#party.errors.count, "error") %> prohibited this party from being saved:</h2>
<ul>
<% #article.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<%
# group labels and inputs in an element instead of abusing BR tags
# this lets you style the content with CSS
-%>
<div class="field">
<%# use f.label as it sets the `for=` attribute which is important for screen readers -%>
<%= f.label :name, 'Party Name:' %>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :trainer_id, 'Trainer Name:' %>
<%# call the helper on the form builder to bind the input to the model -%>
<%= f.collection_select(:trainer_id, Trainer.order(:id), :id, :name, include_blank: true) %>
</div>
<div class="field">
<%= f.label :pokemon_ids, 'Pokemon:' %>
<%# call the helper on the form builder to bind the input to the model -%>
<%= f.collection_select(:pokemon_ids, Pokemon.order(:id), :id, :nickname, include_blank: false, multiple: true) %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Note f.collection_select(:pokemon_ids, ...). This is a special setter/getter is generated by has_many :pokemons.
So I have this challenge that I'm having trouble figuring out how to declare the controllers so it works properly, I've been having a diverse kind of errors but my major issue is that, the way it is now I can't save a new recipe or update the 'Tipo de Receita' which is associated with the recipe_type model used on a drop down, bear in mind that recipe_type.name will be populated beforehand
that's the edit.hmtl.erb that do the editing
<%= form_for #recipe do |f| %>
<%= f.label :title, 'Título' %>
<%= f.text_field :title %>
<%= f.label :recipe_type, 'Tipo da Receita' %>
<%= collection_select(:recipe, :recipe_type_id, RecipeType.all, :id, :name) %>
<%= f.label :cuisine, 'Cozinha' %>
<%= f.text_field :cuisine %>
<%= f.label :difficulty, 'Dificuldade' %>
<%= f.text_field :difficulty %>
<%= f.label :cook_time, 'Tempo de Preparo' %>
<%= f.number_field :cook_time %>
<%= f.label :ingredients, 'Ingredientes' %>
<%= f.text_area :ingredients %>
<%= f.label :cook_method, 'Como Preparar' %>
<%= f.text_area :cook_method %>
<%= f.submit 'Enviar' %>
<% end %>
similarly that's the new.hmtl.erb that registers the new recipes
<%= form_for #recipe do |f| %>
<%= f.label :title, 'Título' %>
<%= f.text_field :title %>
<%= f.label :recipe_type, 'Tipo da Receita' %>
<%= collection_select(:recipe, :recipe_type_id, RecipeType.all, :id, :name) %>
<%= f.label :cuisine, 'Cozinha' %>
<%= f.text_field :cuisine %>
<%= f.label :difficulty, 'Dificuldade' %>
<%= f.text_field :difficulty %>
<%= f.label :cook_time, 'Tempo de Preparo' %>
<%= f.number_field :cook_time %>
<%= f.label :ingredients, 'Ingredientes' %>
<%= f.text_area :ingredients %>
<%= f.label :cook_method, 'Como Preparar' %>
<%= f.text_area :cook_method %>
<%= f.submit 'Enviar' %>
<% end %>
models/recipe_type which is the one used in the drop down
class RecipeType < ApplicationRecord
has_many :recipes
validates :name, presence: true
end
models/recipe which receives the recipe_type model
class Recipe < ApplicationRecord
belongs_to :recipe_type
validates :title, :cuisine, :difficulty, :cook_time,
:ingredients, :cook_method, presence: true
def cook_time_min
"#{cook_time} minutos"
end
end
recipes.controller.rb
class RecipesController < ApplicationController
def index
#recipes = Recipe.all
end
def show
#recipe = Recipe.find(params[:id])
end
def new
#recipe = Recipe.new
#recipe_type = RecipeType.all
end
def create
#recipe_type = RecipeType.all
#recipe = Recipe.new(recipe_params)
if #recipe.save
redirect_to #recipe
else
flash[:alert] = 'Você deve informar todos os dados da receita'
render :new
end
end
def edit
#recipe_type = RecipeType.all
#recipe = Recipe.find(params[:id])
#recipe_type = RecipeType.all
end
def update
#recipe = Recipe.find(params[:id])
if #recipe.update(recipe_params)
redirect_to #recipe
else
flash[:alert] = 'Você deve informar todos os dados da receita'
render :edit
end
end
private
def recipe_params
params.require(:recipe).permit(:title, :cuisine, :difficulty,
:cook_time, :ingredients, :cook_method, :name)
end
end
and that's the schema
ActiveRecord::Schema.define(version: 2020_03_26_013134) do
create_table "recipe_types", force: :cascade do |t|
t.string "name"
t.integer "recipe_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["recipe_id"], name: "index_recipe_types_on_recipe_id"
end
create_table "recipes", force: :cascade do |t|
t.string "title"
t.string "cuisine"
t.string "difficulty"
t.integer "cook_time"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.text "ingredients"
t.text "cook_method"
t.integer "recipe_type_id"
t.index ["recipe_type_id"], name: "index_recipes_on_recipe_type_id"
end
end
One problem is that your not building the input from the form builder. You want to use:
<%= f.label :recipe_type_id, 'Tipo da Receita' # key has to match input for accessibility %>
<%= f.collection_select(:recipe_type_id, RecipeType.all, :id, :name) %>
collection_select is a bare bones input helper that will just create a select tag that's not bound to a form builder and thus not to your model instance. So if the record is invalid or you are updating an existing record it won't have anything selected.
f.collection_select is a method on the form builder and will properly set the recipe_type_id value from the model instance wrapped by the form.
You also need to permit the param in your whitelist:
def recipe_params
params.require(:recipe)
.permit(
:title, :cuisine, :difficulty,
:cook_time, :ingredients, :cook_method,
:name, :recipe_type_id
)
end
you need to add recipe_type_id in recipe_params:
def recipe_params
params.require(:recipe).permit(:recipe_type_id ...)
end
I am new in ror and when I submit my form:
<%= form_for :project, url: projects_path, html: {id:'form'} do |f| %>
<%= f.text_field :text, placeholder: 'Новая задача' %>
<%= link_to 'Отмена', '', id:'cancel_link' %>
<%= link_to 'Отправить', projects_path, id:'submit_link' %>
<% end %>
Have error:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"OR2HWCi3zVz9gB5VAmnzbEuzIwFGE58JlLrWQdNcws6FVTzqh5Cu0zvUJTUEv2O/sCvU9HuadJYr3mfA40ehGA==", "project"=>{"text"=>"NEW ITEM"}} Unpermitted parameter: :text
Have two models:
class Project < ApplicationRecord
has_many :todos
validates :title, presence: true
accepts_nested_attributes_for :todos
end
class Todo < ApplicationRecord
belongs_to :project, required: false
end
The Todo model has a text attribute in which our todo should be located
Controller
class ProjectsController < ApplicationController
def index
#projects = Project.all
end
def create
#project = Project.new(project_params)
if #project.save
redirect_to root_path
end
end
def update
end
private
def project_params
params.require(:project).permit(:title, todos_attributes: [:id, :text])
end
end
Project db
class CreateProjects < ActiveRecord::Migration[5.2]
def change
create_table :projects do |t|
t.string :title
t.string :todos
t.timestamps
end
Todo db
class CreateTodos < ActiveRecord::Migration[5.2]
def change
create_table :todos do |t|
t.text :text
t.boolean :isCompleted
t.integer :project_id
t.timestamps
end
I'm requesting the todo attributes using accepts_nested_attributes_for: todos, the controller is also registered on the guides, in project_params I request todos_attributes. But when sending a form to the database, the value is text. He does not save in db. Can u help please
In order to save text field in Todo model, you have to create nested form. Use nested_form gem for this purpose.
A vague example to show how it works:
<%= nested_form_for :project, url: projects_path, html: { id: 'form' } do |f| %>
<%= f.text_field :title, placeholder: 'Новая задача' %>
<%= f.fields_for :todos do |todo_form| %>
<%= todo_form.text_field :text %>
<%= todo_form.link_to_remove "Remove this todo" %>
<% end %>
<p><%= f.link_to_add "Add a todo", :todos %></p>
<%= link_to 'Отмена', '', id:'cancel_link' %>
<%= link_to 'Отправить', projects_path, id:'submit_link' %>
<% end %>
In controller, to have the functionality of removing a todo in case of editing a project:
def project_params
params.require(:project).permit(:title, todos_attributes: [:id, :text, _destroy])
end
In the migration CreateProjects < ActiveRecord::Migration[5.2], I do not think that you require todos as a string.
The form which you created is wrong, you need to create a nestead_form
It is giving you and Unpermitted parameter error because the text is not a field of project model you can check this on your migration file. You need to change it to title because the title is the field of project model.
And for to create a nested form you need to do some changes in your form
<%= form_for :project, url: projects_path, html: {id:'form'} do |f| %>
<%= f.text_field :title, placeholder: 'Новая задача' %>
<%= f.fields_for :todos do |todo| %>
<%= f.text_field :text %>
<% end %>
<%= link_to 'Отмена', '', id:'cancel_link' %>
<%= link_to 'Отправить', projects_path, id:'submit_link' %>
<% end %>
I'm trying to add a user profile sub module to a user module but having some problems.
Routes:
resources :users do
resources :userprofiles
end
userprofiles_controller.rb:
class UserprofilesController < ApplicationController
def edit
#user = current_user
#user.UserProfile ||= UserProfile.new
#userprofile = #user.UserProfile
end
def update
#user = current_user
#user.UserProfile ||= UserProfile.new
#userprofile = #user.UserProfile
if #userprofile.update_attributes(:userprofile => params[:userprofile])
redirect_to #user
flash[:notice] = "Changes saved."
else
render 'edit'
flash[:notice] = "Error."
end
end
end
user_profile.rb:
class UserProfile < ActiveRecord::Base
attr_accessible :first_name, :last_name, :summary
belongs_to :user
end
Error:
Can't mass-assign protected attributes for UserProfile: userprofile
Line:
if #userprofile.update_attributes(:userprofile => params[:userprofile])
EDIT
Form:
<%= form_for([#user, #userprofile], url: user_userprofile_path(#user, #userprofile)) do |form| %>
<%= form.label :first_name %>
<%= form.text_field :first_name %>
<%= form.label :last_name %>
<%= form.text_field :last_name %>
<%= form.label :summary %>
<%= form.text_area :summary %>
<%= form.submit "Update", class: "btn btn-block btn-primary" %>
<% end %>
Table:
create_table "user_profiles", force: true do |t|
t.string "last_name"
t.string "first_name"
t.text "summary"
t.integer "user_id", null: false
t.datetime "created_at"
t.datetime "updated_at"
end
You just want
#userprofile.update_attributes(params[:userprofile])
That's a hash with keys :first_name, :last_name, and :summary, which are allowed attributes. When you try to update :userprofile => params[:userprofile], the model checks to see if the key :userprofile is allowed - and it isn't.
I also had this problem. The issue is that you still have attr_accessible in your model controller. Since you don't need them anymore with Rails 4 remove them, add your strong parameters to the controller, and you'll be able to mass-assign without issue.