I'm working on a small demo app to help myself understand how to implement cascading selects on Rails 4, but I've run into an interesting problem:
When I fill out a form and submit it to the database, the parameters seem to be passed, but the values are not being saved in the database because the SQL skips the field names and values, as demonstrated in this snippet from the console:
Started POST "/prop_sub_types" for ::1 at 2015-01-23 18:54:05 -0800
Processing by PropSubTypesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"7mGqF/MR+IKrKKdAKBYBI/E7pl7PCJasHDLN5T1/ohF/qEuXyhwsm8d87xDvfmcxk590hp/2XuenQBDaGhI+IA==", "prop_sub_type"=>{"name"=>"sub foo", "prop_type_id"=>"1"}, "commit"=>"Create Prop sub type"}
(0.1ms) begin transaction
SQL (0.3ms) INSERT INTO "prop_sub_types" ("created_at", "updated_at") VALUES (?, ?) [["created_at", "2015-01-24 02:54:05.218673"], ["updated_at", "2015-01-24 02:54:05.218673"]]
(2.2ms) commit transaction
Redirected to http://localhost:3000/prop_sub_types/1
Completed 302 Found in 7ms (ActiveRecord: 2.6ms)
This is the relevant bit of schema.rb that demonstrates that the columns are there:
create_table "prop_sub_types", force: :cascade do |t|
t.string "name"
t.integer "prop_type_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Model:
class PropSubType < ActiveRecord::Base
attr_accessor :name, :prop_type_id
belongs_to :prop_type
has_many :appraisals
end
Controller (relevant portions for the sake of brevity -- I can post more if needed):
def create
#prop_sub_type = PropSubType.new(prop_sub_type_params)
respond_to do |format|
if #prop_sub_type.save
format.html { redirect_to #prop_sub_type, notice: 'Prop sub type was successfully created.' }
format.json { render :show, status: :created, location: #prop_sub_type }
else
format.html { render :new }
format.json { render json: #prop_sub_type.errors, status: :unprocessable_entity }
end
end
end
...
private
# Use callbacks to share common setup or constraints between actions.
def set_prop_sub_type
#prop_sub_type = PropSubType.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def prop_sub_type_params
params.require(:prop_sub_type).permit(:name, :prop_type_id)
end
app/views/prop_sub_types/_form.html.erb:
<%= form_for(#prop_sub_type) do |f| %>
<% if #prop_sub_type.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#prop_sub_type.errors.count, "error") %> prohibited this prop_sub_type from being saved:</h2>
<ul>
<% #prop_sub_type.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :prop_type_id %><br>
<%= f.number_field :prop_type_id %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
I'm adapting some Rails 3 code, so I thought I messed up the strong parameters portion, but the code actually looks correct to me and compares well with an unrelated, but working Rails 4 application.
What did I do wrong to make the INSERT ignore my parameters?
I believe it is because of your attr_accessor :name, :prop_type_id in model. Those accessors definitely are redundant and override the right ones: all fields for the DB table's fields are created as a part of initializing AR:Base class
Related
I wanted to display my time variables to see when pass has started and when will it end but don't know how and couldn't find solution.
I tried something very simple but it doesn't work:
show.html.erb
<h1><%= #pass.name %></h1>
<p><%= #pass.valid_from %></p>
<p><%= #pass.valid_until %></p>
create_passes.rb migration
class CreatePasses < ActiveRecord::Migration[7.0]
def change
create_table :passes do |t|
t.time :valid_from
t.time :valid_until
t.boolean :is_time_limited
t.integer :entries_left
t.string :name
t.timestamps
end
end
end
passes_controller.rb
class PassesController < ApplicationController
def index
#passes = Pass.all
end
def show
#pass = Pass.find(params[:id])
end
def new
#pass = Pass.new
end
def create
#pass = Pass.new(pass_params)
if #pass.save
redirect_to #pass
else
render :new, status: :unprocessable_entity
end
end
def edit
#pass = Pass.find(params[:id])
end
def update
#pass = Pass.find(params[:id])
if #pass.update(pass_params)
redirect_to #pass
else
render :edit, status: :unprocessable_entity
end
end
def destroy
#pass = Pass.find(params[:id])
#pass.destroy
redirect_to root_path, status: :see_other
end
private
def pass_params
params.require(:pass).permit(:name, :is_time_limited, :valid_from, :valid_until, :entries_left)
end
end
new/edit pass form view
<%= form_with model: #pass do |form| %>
<div>
<%= form.label :name %><br>
<%= form.text_field :name %>
<% #pass.errors.full_messages_for(:name).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
<div>
<%= form.label :valid_from %><br>
<%= form.date_field :valid_from %>
<% #pass.errors.full_messages_for(:name).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
<div>
<%= form.label :valid_until %><br>
<%= form.date_field :valid_until %>
<% #pass.errors.full_messages_for(:name).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>
rails server log
Started GET "/passes/2" for ::1 at 2022-08-10 18:18:18 +0200
Processing by PassesController#show as HTML
Parameters: {"id"=>"2"}
(0.1ms) SELECT sqlite_version(*)
↳ app/controllers/passes_controller.rb:7:in `show'
Pass Load (0.2ms) SELECT "passes".* FROM "passes" WHERE "passes"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
↳ app/controllers/passes_controller.rb:7:in `show'
Rendering layout layouts/application.html.erb
Rendering passes/show.html.erb within layouts/application
Rendered layouts/_navbar.html.erb (Duration: 1.0ms | Allocations: 724)
Rendered passes/show.html.erb within layouts/application (Duration: 2.8ms | Allocations: 1670)
Rendered layout layouts/application.html.erb (Duration: 6.9ms | Allocations: 4039)
Completed 200 OK in 31ms (Views: 7.5ms | ActiveRecord: 0.5ms | Allocations: 5333)
Also, is it better to use time or datatime type for dates and time?
Ok I believe I know why you are not getting any values.
You setup your DB to use a time field
t.time :valid_from
t.time :valid_until
But you are using a date_field in the view:
form.date_field :valid_from
You need to change this to:
form.time_field :valid_from
The difference between time or datetime is datetime includes a DATE and a TIME, where TIME ONLY has a time so it depends on what your use case is. You can also use just a date and then it would be
form.date_field :valid_from
If you need dates you will need to change your model and DB to use either t.date or t.datetime. If you don't have a lot of information in your DB you can do a rails db:rollback and modify your migration file would be the quickest and easiest way to fix it. IF you DO have data you will have to create a new migration to change the fields.
I create an application, which is basically a character creator for an RPG with interactive and dynamic forms. I use Rails 5.0.0.1, and I cannot update my form properly. The base model updates well, but all nested don't.
So, I have
class Character < ApplicationRecord
has_many :attr4characters, autosave: true
accepts_nested_attributes_for :attr4characters, allow_destroy: true
end
and
class Attr4character < ApplicationRecord
belongs_to :character
end
which represent a character and a set of his attributes. Each record in Attr4character is a different attribute.
The Show view is simple:
...
<div class="field">
<%= f.label :description %>
<%= f.text_area :description %>
</div>
<div class="field">
<%= f.label :character_type %>
<%= f.select(:character_type, options_for_select(["Zhong Lung", "Shih", "Hsien", "Garou", "Technocrat"])) %>
</div>
<% f.object.attr4characters.each do |attr| %>
<%= f.fields_for attr do |attr_f| %>
<div class="field">
<%= attr_f.label "Field name" %>
<%= attr_f.text_field :field_name %>
</div>
<div class="field">
<%= attr_f.label "Field value" %>
<%= attr_f.text_field :field_value %>
</div>
<% end %>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
...
And finally my characters_controller:
def update
respond_to do |format|
if #character.update(character_params)
format.html { redirect_to #character, notice: 'Character was successfully updated.' }
format.json { render :show, status: :ok, location: #character }
else
format.html { render :edit }
format.json { render json: #character.errors, status: :unprocessable_entity }
end
end
end
private
def set_character
#character = Character.find(params[:id])
end
def character_params
params.require(:character).permit(:id, :name, :player, :description, :character_type, attr4characters_attributes: [:id, :character_id, :field_name, :field_value])
end
So, I have a form, which correctly display a character and all set of his attributes. When I update a character field (like :description), it is normally updated. When I update any nested field, Rails says that character is successfully updated and nothing changes! I searched with Google, and I found a lot of problems with nested attributes in Rails forms, but none of the recipes worked for me. I even encountered opinions that it is a bug in Rails 4, but I use 5th version...
Please, advice on the topic. Is it a bug really? Or am I doing something wrong? I'm new on Rails, so I don't exclude that possibility. :)
By the way, in the server's log I found that there is warning about attr4characters.
Processing by CharactersController#update as HTML
Parameters: {"utf8"=>"\u2713", "authenticity_token"=>"xeYyIRc13YiOk29v18rFM6Oh5OHRRuPpSKEQuIHE/U4uhANEF7TwMp8mb6hv6L7mUAm5MngAuyFayHcWV/Vvbw==", "character"=>{"name"=>"Ray", "player"=>"111", "description"=>"A Zhong Lung suicider", "character_type"=>"Hsien", "attr4character"=>{"field_name"=>"Gift1", "field_value"=>"Sense Wyrm"}}, "commit"=>"Update Character", "id"=>"3"}
Character Load (0.2ms) SELECT "characters".* FROM "characters" WHERE "characters"."id" = ? LIMIT ? [["id", 3], ["LIMIT", 1]]
Unpermitted parameter: attr4character
(0.1ms) begin transaction
SQL (0.9ms) UPDATE "characters" SET "character_type" = ?, "updated_at" = ? WHERE "characters"."id" = ? [["character_type", "Hsien"], ["updated_at", 2016-09-13 14:39:10 UTC], ["id", 3]]
(24.3ms) commit transaction
But attr4characters are permitted in the characters_controller...
The warning is telling you that it is ignoring all the attr4character attributes. Your permit code is correct as is your model, but your view doesn't match them. You should be doing
f.fields_for :attr4characters do |attr_f|
...
end
And let rails handle iterating over the association. This will also ensure that attributes are named correctly (so they will be allowed through by your whitelist)
I've been struggling on this for the last couple of day and some help would be greatly appreciated. A Rails app that has two models:
Accounts - name[string], credit[boolean], active[boolean]
Balances - balance[decimal], date[date], account_id[integer]
When creating a new balance at http://localhost:3000/balances/new, the form looks like this:
How can I enable users to be able to create multiple balances at the same time? There should only be one textbox for the date field which should be used for all the balances records created but there should be multiple account dropdown lists and balance textboxes on a form.
I've tried looking up nested forms but I'm struggling.
CODE
schema
create_table "accounts", force: :cascade do |t|
t.string "name"
t.boolean "credit"
t.boolean "active"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "balances", force: :cascade do |t|
t.decimal "balance"
t.date "date"
t.integer "account_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
account.rb
class Account < ActiveRecord::Base
has_many :balances
validates :name, presence: true, length: { maximum: 250 },
uniqueness: { case_sensitive: false }
end
balance.rb
class Balance < ActiveRecord::Base
belongs_to :account
#default_scope -> { order(date: :desc) }
validates :account, presence: true, length: { maximum: 250 }
end
accounts_controller.rb
....
def new
#account = Account.new
end
def create
#account = Account.new(account_params)
respond_to do |format|
if #account.save
format.html { redirect_to accounts_path, notice: 'Account was successfully created.' }
format.json { render :show, status: :created, location: #account }
else
format.html { render :new }
format.json { render json: #account.errors, status: :unprocessable_entity }
end
end
end
....
balances_controller.rb
....
def new
#balance = Balance.new
end
def create
#balance = Balance.new(balance_params)
respond_to do |format|
if #balance.save
format.html { redirect_to balances_path, notice: 'Balance was successfully created.' }
format.json { render :show, status: :created, location: #balance }
else
format.html { render :new }
format.json { render json: #balance.errors, status: :unprocessable_entity }
end
end
end
....
balances/_form.html.erb
<%= form_for(#balance) do |f| %>
<% if #balance.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#balance.errors.count, "error") %> prohibited this balance from being saved:</h2>
<ul>
<% #balance.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :date %><br>
<%= f.text_field :date %>
</div>
<div class="field">
<%= f.label :account_id %><br>
<%= f.collection_select(:account_id, Account.all.where(active: true).order('name ASC'), :id, :name,{})%>
</div>
<div class="field">
<%= f.label :balance %><br>
<%= f.text_field :balance %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
So, it seems to me there's two parts to your challenge.
Creating a form with multiple balances, and
Handling the multiple balances in your controller.
I'm not going to go into a lot of detail. Hopefully, this will point you in the right direction...
On your balances form, use form_tag instead of form_for which will allow you to represent multiple balances (instead of binding to a single balance instance). Create a balances partial (that includes the account name (as plain text, not a drop down), a balance input, and an account.id hidden field). Iterate on all active accounts, using the balances partial, to create the appropriate inputs. The date field will be outside the iterator so that you get your one date input. You can fiddle with all that so your balances all end up in one params hash.
In your controller, grab the params hash for balances, and iterate on that to create the balances using the one date value for each new balance.
I know that's very high level. And I apologize. But, writing out all the code is more than I have time for at the moment. Hope this helps you with the approach.
I have 3 models with a has_many through relationship: Food (eg: Chocolate), Sub (Chocolate food substitute), Joint (joint table).
Say #food = Food.find(1); The has_many through relationship allows me to do #subs = #food.subs which return all substitutes associated with #food. This work fine, however only the Sub id is saved and not its attributes which are :name and :description as you can see it returned nil when trying to save #food.subs in my create action in my controller:
=> #<ActiveRecord::Associations::CollectionProxy [#<Sub id: 28,name:nil,description:nil,created_at:
"2015-01-07 00:40:35", updated_at: "2015-01-07 00:40:35">]>
I guess the issue lies with my create action in my food controller and perhaps something to do with my nested form as well. I spent countless hours trying to figure this out I am so desperate to find an answer. I really do not know where to look anymore.
I am new to rails so thanks a lot for your help and your time, I really appreciate it. Please if possible adapt your answer to my beginner level :-) .
Down below are samples of my controller, form and relevant information.
Here are my models:
class Food < ActiveRecord::Base
has_many :joints
has_many :subs, :through => :joints
accepts_nested_attributes_for :subs
end
class Sub < ActiveRecord::Base
has_many :joints
has_many :foods, :through => :joints
accepts_nested_attributes_for :foods
end
class Joint < ActiveRecord::Base
belongs_to :food
belongs_to :sub
end
Here is my db-schema FYI:
create_table "foods", force: true do |t|
t.string "name"
t.text "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "joints", force: true do |t|
t.integer "food_id"
t.integer "sub_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "subs", force: true do |t|
t.string "name"
t.text "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Here is my foods_controller:
def new
#food = Food.new
#sub = Sub.new
end
def create
#food = Food.new(food_params)
#food.subs.build(params[:subs])
#food.save
respond_to do |format|
if #food.save
format.html { redirect_to #food, notice: 'Food was successfully created.' }
format.json { render :show, status: :created, location: #food }
else
format.html { render :new }
format.json { render json: #food.errors, status: :unprocessable_entity }
end
end
end
private
def food_params
params.require(:food).permit(:name, :description, subs_attributes: [:name, :description])
end
end
Here is my views/foods/_form:
<%= form_for(#food) do |f| %>
<% if #food.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#food.errors.count, "error") %> prohibited this food from being saved:</h2>
<ul>
<% #food.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :description %><br>
<%= f.text_area :description %>
</div>
<div>
<%= f.fields_for(#sub) do |sub| %>
<div class="field">
<%= sub.label :name %>
<%= sub.text_field :name %>
</div>
<div class="field">
<%= sub.label :description %>
<%= sub.text_area :description %>
</div>
<% end %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
My routes in case it helps:
resources :foods
resources :subs
resources :joints
root "foods#index"
Thank you very much !
Antoine.
In your new action:
def new
#food = Food.new
#food.subs.build
end
and in your view:
<%= f.fields_for :subs do |sub| %>
When you're passing directly an object, this object becomes the new form_builder's object - rails have no idea it is in any way connected with original object so it will result in different field names.
When you pass a symbol, rails will first try to find if your current object defines subs_attributes method. If so it will loop over subs association and build the fields for each associated model.
Reference here.
UPDATE - answer to comment:
Firstly - #subs is not a symbol, it is an instance variable. Symbols start with a colon like :subs. When fields_for receives an argument, it checks whether it is a symbol or object. In former case it search an object associated with form builder (f.object) to find out if it defines <passed_symbol>_attributes=. That way it knows that the model accepts nested attributes for this association so it can behave accordingly (the new form builder is created for each associated object with a correct name - <symbol>_attributes).
When object is passed, rails has no way of detecting if this is in ay way connected to the current object - you could have two associations for the same type of objects, or even it might have absolutely nothing to do with the original object. In that case fields_for acts like it was a nested form_for - resulting form builder will carry the model name of the object (f.object.class.model_name.singular)
I created a simple application that has a Product and an Image model. Product has_many Images and Images has an attached file attribute (paperclip).
I created a simple_form for creating/editing Products and it works fine on creation. However, when editing a Product that has N images, rails inserts more N files - empty files.
I have set up a Simple Form custom input that tests if the image attachment exists in which case instead of rendering the builders input, it only renders an image_tag().
I see the html generated and it show something strange, a hidden tag:
<input id="product_images_attributes_0_id" name="product[images_attributes][0][id]" type="hidden" value="14">
And in the rails server console I see:
Processing by ProductsController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"asdfasdfaasdf=", "product"=>{"reference"=>"Y1112CYL.E2", "images_attributes"=>{"0"=>{"id"=>"14"}}}, "commit"=>"Update Product", "id"=>"20"}
Product Load (0.6ms) SELECT "products".* FROM "products" WHERE "products"."id" = $1 LIMIT 1 [["id", "20"]]
Unpermitted parameters: id
(0.2ms) BEGIN
SQL (1.0ms) INSERT INTO "images" ("created_at", "imageable_id", "imageable_type", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["created_at", Tue, 18 Feb 2014 05:05:13 UTC +00:00], ["imageable_id", 20], ["imageable_type", "Product"], ["updated_at", Tue, 18 Feb 2014 05:05:13 UTC +00:00]]
(0.6ms) COMMIT
Here is the code to my implementation. If someone could help I would be very happy! Please pardon me if I left out any import part of the code, I will gladly edit the question to include it.
_form.html.erb
<%= simple_form_for(#product) do |f| %>
<%= f.error_notification %>
<div class="inputs">
<%= f.input :reference %>
<h3>Images</h3>
<div id='images'>
<%= f.simple_fields_for :images do |image| %>
<%= render 'image_fields', :f => image %>
<% end %>
<div class='links'>
<%= link_to_add_association 'New image', f, :images %>
</div>
</div>
</div>
<div class="actions">
<%= f.button :submit %>
</div>
<%end%>
_image_fields.html.erb
<%= content_tag :div, class: "nested-fields images-fields" do %>
<%= content_tag :div, id: "new-image" do %>
<% if f.object.photo.exists? %>
<% f.template.image_tag(f.object.photo.url(:thumb)) %>
<% else %>
<% f.input :photo, :as => :photo %>
<% end %>
<% end %>
<% end %>
app/inputs/photo_input.erb
class PhotoInput < SimpleForm::Inputs::FileInput
def input
out = '' # the output string we're going to build
# check if there's an uploaded file (eg: edit mode or form not saved)
if object.send("#{attribute_name}?")
# append preview image to output
# <%= image_tag #user.avatar.url(:thumb), :class => 'thumbnail', id: 'avatar' %>
out << template.image_tag(object.send(attribute_name).url(:thumb), :class => 'thumbnail', id: 'photo')
else
# append file input. it will work accordingly with your simple_form wrappers
(out << #builder.file_field(attribute_name, input_html_options)).html_safe
end
end
end
ProductsController#update
def update
respond_to do |format|
if #product.update(product_params)
format.html { redirect_to #product, notice: 'Product was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #product.errors, status: :unprocessable_entity }
end
end
end
For posterity, #omarvelous' comment seems to have solved it:
I added the reject if all blank to my accepts_nested_attributes_for
accepts_nested_attributes_for :images, :allow_destroy => true, :reject_if => :all_blank
Edit:
It turns out there is an issue with this solution. When reject_if => :all_blank is it stops the destroy from working properly. See this post that shows a workaround -- that I did not manage to get to work.