Submit one form to 2 tables in database - ruby on rails - ruby-on-rails

I have 2 tables, landslides and sources (maybe doesn't relate to each other). I want a form which lets user to fill in information and then submit to both tables. Here's my current form without sources fields:
= form_for :landslide, :url => {:controller => 'landslides', :action => 'create'} do |f|
.form-inputs
%form#landslideForm
.form-group.row
%label.col-sm-2.col-form-label{for: "textinput"}Date
.col-sm-10
= f.date_select :start_date, :class => "form-control"
#Some fields
.form-actions
= f.button :submit, class: "btn btn-lg btn-primary col-sm-offset-5", id: "submitButton"
And parameters:
def landslide_params
params.require(:landslide).permit(:start_date, :continent, :country, :location, :landslide_type, :lat, :lng, :mapped, :trigger, :spatial_area, :fatalities, :injuries, :notes)
end
def source_params
params.require(:source).permit(:url, :text, :landslide_id)
end
Also there's a column in sources calls landslide_id which take the landslide ID from table landslides. So when a user submits a new landslide, how can I take the upcoming landslide ID (which is auto increment, user doesn't need to fill in)?
Thanks!

HTML does not allow nested <form> elements and you can't pass the id of record that has not been persisted yet through a form (because it does not have an id).
To create a nested resource in the same request you use accepts_nested_attributes_for:
class Landslide
# or has_many
has_one :source
accepts_nested_attributes_for :source
end
class Source
belongs_to :landslide
end
This means that you can do Landslide.create(source_attributes: { foo: 'bar' }) and it will create both a Landslide and a Source record and will automatically link them through sources.landslide_id.
To create the form inputs use fields_for:
# use convention over configuration
= form_for #landslide do |f|
.form-inputs
.form-group.row
# use the form builder to create labels instead
= f.label :start_date, class: 'col-sm-2 col-form-label'
.col-sm-10
= f.date_select :start_date, class: "form-control"
%fieldset
%legend Source
= f.fields_for :sources do |s|
.form-group.row
= s.label :url, class: 'col-sm-2 col-form-label'
.col-sm-10
= s.text_field :url, class: "form-control"
# ...
class LandslidesController
# ...
def new
#landslide = Landslide.new
# this is needed to seed the form with inputs for source
#landslide.source.new
end
def create
#landslide = Landslide.new(landslide_params)
if #landslide.save
redirect_to #landslide
else
#landslide.source.new unless #landslide.source.any?
render :new
end
end
private
def landslide_params
params.require(:landslide).permit(
:start_date, :continent, :country,
:location, :landslide_type,
:lat, :lng, :mapped, :trigger, :spatial_area,
:fatalities, :injuries, :notes,
source_attributes: [ :url, :text ]
)
end
end

You need to use accept_nested_attributes_for and nest your form accordingly:
(With reservation in regards to what form should be nested in which, I use the example of Sources submitted via landslide-form.)
in landslide.rb
accept_nested_attributes_for :sources
In your view (I don't know haml but anyways)
<%= form_for :landslide do |f|%>
<%= f.select :start_date %>
<%= fields_for :sources do |s| %>
<%= s.input :your_column %>
<% end %>
<%= f.button :submit %>
<% end %>
Btw, there are a lot of questions on this already, it's called 'Nested Forms'
Nested forms in rails - accessing attribute in has_many relation
Rails -- fields_for not working?
fields_for in rails view

Related

Rails - Unpermitted nested children parameters [duplicate]

This question already has an answer here:
Unpermitted parameters nested attributes - rails
(1 answer)
Closed 5 years ago.
The parent is saved but the children isn't. If I add landslide.sources.create, it does create a row in sources table with the correct landslide_id but all the other columns are null. Here's the files:
landslide_controller.rb
def new
#landslide = Landslide.new
#landslide.sources.build
end
def create
landslide = Landslide.new(landslide_params)
landslide.save
end
def landslide_params
params.require(:landslide).permit(:start_date, :continent, :country, :location, :landslide_type, :lat, :lng, :mapped, :trigger, :spatial_area, :fatalities, :injuries, :notes, source_attributes: [ :url, :text ])
end
sources_controller.rb
def new
source = Source.new
end
def create
source = Source.new(source_params)
source.save
end
def source_params
params.require(:source).permit(:url, :text)
end
_form.html.haml
= form_for :landslide, :url => {:controller => 'landslides', :action => 'create'} do |f|
.form-inputs
%form#landslideForm
#Fields
%fieldset
%legend Source
= f.fields_for :sources do |s|
.form-group.row
= s.label :url, class: 'col-sm-2 col-form-label'
.col-sm-10
= s.text_field :url, class: "form-control"
.form-group.row
= s.label :text, class: 'col-sm-2 col-form-label'
.col-sm-10
= s.text_field :text, class: "form-control"
.form-actions
= f.button :submit, class: "btn btn-lg btn-primary col-sm-offset-5", id: "submitButton"
landslide.rb and source.rb
class Source < ApplicationRecord
belongs_to :landslide, inverse_of: :sources
end
class Landslide < ApplicationRecord
has_many :sources, dependent: :destroy, inverse_of: :landslide
accepts_nested_attributes_for :sources
** routes.rb **
resources :landslides do
resources :sources
end
According to your code it is expected to create source with null field. Because of landslide.sources.create here you are creating source with out any attribute values.
To successfully save source follow the following step.
build source on controller's new method
def new
#landslide = Landslide.new
#landslide.sources.build
end
User #landslide (declared on new) on the form
= form_for #landslide and other thing will remain same.
remove landslide.sources.create from your landslide_controller.rb because source will save automatically after saving landslide.
Hope above changes will solve your problem.

Update nested object in rails 4

I looked for two days on the web but I am still blocked to update a child object of a parent object.
My parent:
class Pass < ActiveRecord::Base
has_many :fields
attr_accessor :fields_attributes
accepts_nested_attributes_for :fields, :allow_destroy => true, :update_only => true
My child:
class Field < ActiveRecord::Base
belongs_to :pass
My form view:
<%= f.fields_for :fields do |field|%>
<div class="control-group">
<%= field.label :id, :class => 'control-label' %>
<%= field.label :value, :class => 'control-label' %>
<div class="controls">
<%= field.text_field :value, :class => 'text_field' %>
</div>
</div>
<% end %>
I also define permitted parameters thanks to:
def pass_params params.require(:pass).permit(:pass,
:description,
:organization_name,
:logo_upload,
:icon_upload,
:strip_upload,
fields_attributes: [:id,:value])
#params.require(:pass).permit!
end
I have no problem to create a pass with 5 fields in my passes_controller
def new
#pass = Pass.new
5.times {#pass.fields.build}
##fields = #pass.fields
end
My problem happens is that child fields of my pass are not updated after an edit of the pass. I always get initial values (at the creation of the pass) of fields.
I tried to update using different ways without success
if #pass.update_attributes(pass_params)
if #pass.update_attributes[params[:pass][:fields_attributes]]
if #pass.update_attributes(params[:fields_attributes])
When I update my pass, the pass_params looks like this:
{"description"=>"Test22gg", "organization_name"=>"Toto", "fields_attributes"=>{"0"=>{"id"=>"30", "value"=>"testf"}, "1"=>{"id"=>"29", "value"=>"test"}, "2"=>{"id"=>"28", "value"=>"test"}, "3"=>{"id"=>"27", "value"=>"test"}, "4"=>{"id"=>"26", "value"=>"test"}}}
I don't see which requirement or thing I forget to update these fields!

How do I reference an existing instance of a model in a nested Rails form?

I'm attempting to build a recipe-keeper app with three primary models:
Recipe - The recipe for a particular dish
Ingredient - A list of ingredients, validated on uniqueness
Quantity - A join table between Ingredient and Recipe that also reflects the amount of a particular ingredient required for a particular recipe.
I'm using a nested form (see below) that I constructed using an awesome Railscast on Nested Forms (Part 1, Part 2) for inspiration. (My form is in some ways more complex than the tutorial due to the needs of this particular schema, but I was able to make it work in a similar fashion.)
However, when my form is submitted, any and all ingredients listed are created anew—and if the ingredient already exists in the DB, it fails the uniqueness validation and prevents the recipe from being created. Total drag.
So my question is: Is there a way to submit this form so that if an ingredient exists whose name matches one of my ingredient-name fields, it references the existing ingredient instead of attempting to create a new one with the same name?
Code specifics below...
In Recipe.rb:
class Recipe < ActiveRecord::Base
attr_accessible :name, :description, :directions, :quantities_attributes,
:ingredient_attributes
has_many :quantities, dependent: :destroy
has_many :ingredients, through: :quantities
accepts_nested_attributes_for :quantities, allow_destroy: true
In Quantity.rb:
class Quantity < ActiveRecord::Base
attr_accessible :recipe_id, :ingredient_id, :amount, :ingredient_attributes
belongs_to :recipe
belongs_to :ingredient
accepts_nested_attributes_for :ingredient
And in Ingredient.rb:
class Ingredient < ActiveRecord::Base
attr_accessible :name
validates :name, :uniqueness => { :case_sensitive => false }
has_many :quantities
has_many :recipes, through: :quantities
Here's my nested form that displays at Recipe#new:
<%= form_for #recipe do |f| %>
<%= render 'recipe_form_errors' %>
<%= f.label :name %><br>
<%= f.text_field :name %><br>
<h3>Ingredients</h3>
<div id='ingredients'>
<%= f.fields_for :quantities do |ff| %>
<div class='ingredient_fields'>
<%= ff.fields_for :ingredient_attributes do |fff| %>
<%= fff.label :name %>
<%= fff.text_field :name %>
<% end %>
<%= ff.label :amount %>
<%= ff.text_field :amount, size: "10" %>
<%= ff.hidden_field :_destroy %>
<%= link_to_function "remove", "remove_fields(this)" %><br>
</div>
<% end %>
<%= link_to 'Add ingredient', "new_ingredient_button", id: 'new_ingredient' %>
</div><br>
<%= f.label :description %><br>
<%= f.text_area :description, rows: 4, columns: 100 %><br>
<%= f.label :directions %><br>
<%= f.text_area :directions, rows: 4, columns: 100 %><br>
<%= f.submit %>
<% end %>
The link_to and link_to_function are there to allow the addition and removal of quantity/ingredient pairs on the fly, and were adapted from the Railscast mentioned earlier. They could use some refactoring, but work more or less as they should.
Update: Per Leger's request, here's the relevant code from recipes_controller.rb. In the Recipes#new route, 3.times { #recipe.quantities.build } sets up three blank quantity/ingredient pairs for any given recipe; these can be removed or added to on the fly using the "Add ingredient" and "remove" links mentioned above.
class RecipesController < ApplicationController
def new
#recipe = Recipe.new
3.times { #recipe.quantities.build }
#quantity = Quantity.new
end
def create
#recipe = Recipe.new(params[:recipe])
if #recipe.save
redirect_to #recipe
else
render :action => 'new'
end
end
You shouldn't put the logic of ingredients match into view - it's duty of Recipe#create to create proper objects before passing 'em to Model. Pls share the relevant code for controller
Few notes before coming to code:
I use Rails4#ruby2.0, but tried to write Rails3-compatible code.
attr_acessible was deprecated in Rails 4, so strong parameters are used instead. If you ever think to upgrade your app, just go with strong parameters from the beginning.
Recommend to make Ingredient low-cased to provide uniform appearance on top of case-insensitivity
OK, here we go:
Remove attr_accessible string in Recipe.rb, Quantity.rb and Ingredient.rb.
Case-insensitive, low-cased Ingredient.rb:
class Ingredient < ActiveRecord::Base
before_save { self.name.downcase! } # to simplify search and unified view
validates :name, :uniqueness => { :case_sensitive => false }
has_many :quantities
has_many :recipes, through: :quantities
end
<div id='ingredients'> part of adjusted form to create/update Recipe:
<%= f.fields_for :quantities do |ff| %>
<div class='ingredient_fields'>
<%= ff.fields_for :ingredient do |fff| %>
<%= fff.label :name %>
<%= fff.text_field :name, size: "10" %>
<% end %>
...
</div>
<% end %>
<%= link_to 'Add ingredient', "new_ingredient_button", id: 'new_ingredient' %>
We should use :ingredient from Quantity nested_attributes and Rails will add up _attributes-part while creating params-hash for further mass assignment. It allows to use same form in both new and update actions. For this part works properly association should be defined in advance. See adjusted Recipe#new bellow.
and finally recipes_controller.rb:
def new
#recipe = Recipe.new
3.times do
#recipe.quantities.build #initialize recipe -> quantities association
#recipe.quantities.last.build_ingredient #initialize quantities -> ingredient association
end
end
def create
#recipe = Recipe.new(recipe_params)
prepare_recipe
if #recipe.save ... #now all saved in proper way
end
def update
#recipe = Recipe.find(params[:id])
#recipe.attributes = recipe_params
prepare_recipe
if #recipe.save ... #now all saved in proper way
end
private
def prepare_recipe
#recipe.quantities.each do |quantity|
# do case-insensitive search via 'where' and building SQL-request
if ingredient = Ingredient.where('LOWER(name) = ?', quantity.ingredient.name.downcase).first
quantity.ingredient_id = quantity.ingredient.id = ingredient.id
end
end
end
def recipe_params
params.require(:recipe).permit(
:name,
:description,
:directions,
:quantities_attributes => [
:id,
:amount,
:_destroy,
:ingredient_attributes => [
#:id commented bc we pick 'id' for existing ingredients manually and for new we create it
:name
]])
end
In prepare_recipe we do the following things:
Find ID of ingredient with given name
Set foreign_key quantity.ingredient_id to ID
Set quantity.ingredient.id to ID (think what happens if you don't do that and change ingredient name in Recipe)
Enjoy!

Rails - simple_form, include a named blank object in a collection on new and edit

I have a simple_form which I am trying to get to always include a blank item in it, as a 'nil' value in that field has a special meaning in this database.
In order to make it more obvious for end users, I also want to title it with something along the lines of "(select if none)".
I'm currently doing this, but it only inserts the 'blank' item when creating a new object, not when editing one.
# _child_form.html.erb
<%= simple_form_for #child do |f| %>
<%= f.input :first_name %>
<%= f.input :last_name %>
<%= f.association :parent, :collection => #parents, :prompt => "(select if none)" %>
<%= f.button.submit %>
<% end %>
.
# child_controller.rb
def new
#child = Child.new
#parents = Parent.all
end
def edit
#child = Child.find(params[:id])
#parents = Parent.all
end
You want to use :include_blank, not :prompt
<%= f.association :parent, :collection => #parents, :include_blank => "(select if none)" %>
The documentation

rails ActiveAdmin nested form has_one accepts_attributes_for formtastic issue

I am using ActiveAdmin and Rails 3.1 -- having problem understanding whether the following is a bug, or if there is some way to do it correctly that I am not understanding. I am trying to use a nested model with a has one relationship, so that I can create a page and fill out it's meta data in 1 step. --
(page has_one meta_data, accepts_nested_attributes_for meta_data)
Example 1)
in this example, when I click new page, meta data section is there but there are no input fields -- also, if I edit the record, it shows up correctly, however the fieldset is duplicated in the second section... and if I remove the f.inputs wrapping semantic_field_for (which would make sense), then it breaks completely and shows nothing in the meta data area...
form do |f|
f.inputs "Page Information" do
f.input :name
f.input :uri
f.input :view
f.input :body, :as => :text
f.input :active
end
f.inputs "Meta Data" do
f.semantic_fields_for :meta_data do |meta_form|
meta_form.inputs :title, :description, :keywords, :name => "Meta Information"
end
end
end
I understand the meta data probably isn't being instantiated, but I am not sure how I am supposed to do that in the form block? (or if I can even do it) -- The only way I am able to get this to work is by doing using a custom form, and building the meta data in the view, which looks like this
2) How I am working around it, but seems hacky
<%= semantic_form_for [:admin, #page] do |f| %>
<% #page.build_meta_data %>
<%= f.inputs :name => "Page Information" do %>
<%= f.input :name %>
<%= f.input :uri %>
<%= f.input :view %>
<%= f.input :body, :as => :text %>
<%= f.input :active %>
<% end %>
<%= f.semantic_fields_for :meta_data do |meta_form| %>
<%= meta_form.inputs :title, :description, :keywords, :name => "Meta Information" %>
<% end %>
<%= f.buttons %>
<% end %>
Thanks in advance for any help or clarification.
(note to moderators I started another thread on this but was not as clear and didn't have the workaround solution I do now yet, so if one of the questions should be deleted please delete the other)
I found a better solution for you. You can use :for option in inputs helper.
f.inputs "Meta Data", for: [:meta_data, f.object.meta_data || MetaData.new] do |meta_form|
meta_form.input :title
meta_form.input :description
meta_form.input :keywords
end
I think this might work too, but I didn't check
f.inputs :title, :desctiption, :keywords,
name: "Meta Data",
for: [:meta_data, f.object.meta_data || MetaData.new]
In rails 4, this is something that works, with a nice design
e.g.,
A customer has one account
model/customer.rb
accepts_nested_attributes_for :account
admin/customer.rb
form do |f|
f.inputs do
f.input :user, input_html: { disabled: true }
f.input :name
f.input :address
f.input :city
f.input :country, as: :string
end
f.buttons
f.inputs "Account Information", for: [:account, f.object.account] do |s|
s.input :active, as: :boolean
s.input :subscription, as: :boolean
s.input :expires_on, as: :datepicker
s.actions
end
end
controller do
def permitted_params
params.permit!
end
end
end
i was having the same problem, i worked in your hack and got it working.
i then moved <% #page.build_meta_data %> to a custom new method like this
controller do
def new
#tenant = Tenant.new
#tenant.build_tenant_configurable
end
end
hope this helps

Resources