Rails: How do I submit multiple objects into strong params? - ruby-on-rails

I am making a goal tracking app. Right now outcome, purpose, action, priority, resources, and direction are all things which are part of Outcome in the database. However, I want to make purpose and action their own model objects. What I am confused about is how do I submit Outcome, Purpose, and Action, which will be 3 separate model objects, in a single HTTP request?
Should I just use multiple strong params in my controller?
app/view/outcomes/new.html.erb

You need to have model associations of outcomes with purpose and action.
Then you will need to create nested form. So that outform form can wrap purpose and action model attributes.
As you want to have different models for actions and purposes, I'm assuming outcome can has_many purposes and has_many actions. As per this type of association, below is the code you should have.
Your form will become something like:
<%= form_for #outcome do |f| %>
<%= f.label :outcome, "Outcome" %>
<%= f.text_area :outcome %>
<%= f.fields_for :purpose, #outcome.purpose.build do |p| %>
<%= p.text_area :desc, label: "Purpose" %>
<% end %>
<%= f.fields_for :action, #outcome.action.build do |p| %>
<%= p.text_area :desc, label: "Action" %>
<% end %>
<%= f.submit "submit" %>
<% end %>
Models:
# outcome.rb
has_many :purposes, :dependent => :destroy
has_many :actions, :dependent => :destroy
accepts_nested_attributes_of :purposes, :actions
-----------------------------------------
# purpose.rb
belongs_to :outcome
-----------------------------------------
# action.rb
belongs_to :outcome
Controller:
# outcomes_controller.rb
def outcome_params
params.require(:outcome).permit(:outcome, purpose_attributes:[:desc], action_attributes: [:desc])
end
SUGGESTION: You should rename your action model name to avoid unwanted conflicts with rails keyword action.
This may help you

Nestd Attributes
If the objects are associated (as below), you'll be best using the accepts_nested_attributes_for method:
#app/models/outcome.rb
Class Outcome < ActiveRecord::Base
has_many :purposes
has_many :actions
accepts_nested_attributes_for :purposes, :actions
end
#app/models/purpose.rb
Class Purpose < ActiveRecord::Base
belongs_to :outcome
end
#app/models/action.rb
Class Action < ActiveRecord::Base
belongs_to :outcome
end
accepts_nested_attributes_for means you'll be able to send the associated objects through the Outcome model - meaning you can send them all in a single HTTP request
You have to remember the way Rails is set up (MVC pattern), meaning if you send a single request; any further model objects you have will be able to be stored too.
Here's how you can set it up:
#app/controllers/outcomes_controller.rb
Class OutcomesController < ApplicationController
def new
#outcome = Outcome.new
#outcome.purposes.build
#outcoe.actions.build
end
def create
#outcome = Outcome.new(outcome_params)
#outcome.save
end
private
def outcome_params
params.require(:outcome).permit(:outcome, purpose_attributes:[:purpose], action_attributes: [:action])
end
end
Which will give you the ability to use this form:
#app/views/outcomes/new.html.erb
<%= form_for #outcome do |f| %>
<%= f.label :outcome %>
<%= f.text_area :outcome %>
<%= f.fields_for :purposes do |p| %>
<%= p.text_area :purpose %>
<% end %>
<%= f.fields_for :actions do |a| %>
<%= a.text_area :action %>
<% end %>
<%= f.submit %>
<% end %>
--
Recommendation
From the looks of it, I'd recommend you'll be able to keep all of these details in a single model - storing in multiple models seems overkill

Related

Simple Form check box for join table relationship

I can't figure this out for the life of me but here are my models:
class User < ApplicationRecord
has_many :user_stores
has_many :stores, through: :user_stores
end
class UserStore < ApplicationRecord
belongs_to :user
belongs_to :store
end
class Store < ApplicationRecord
has_many :user_stores
has_many :users, through: :user_stores
end
So I have a join table, I'm trying to make a form, which would have selected checkboxes next to the store names that the user has selected (this information would come from the join table relationship) and open checkboxes for the remaining stores (coming from the Store model). How do I show that in the view/make it work in the controller as well. Would I use collections instead? ( I am using Devise and Simple Form gem )
This is what I have so far:
<h1>Add Favorite Stores</h1>
<%= simple_form_for(#user, html: { class: 'form-horizontal' }) do |f| %>
<%= f.fields_for :stores, #user.stores do |s| %>
# not sure if this is the right way or not
<% end %>
<%= f.button :submit %>
<% end %>
Store Controller:
class StoresController < ApplicationController
...
def new
#user = current_user
#stores = Store.all
# #user.stores => shows user's stores (from join table)
end
end
When you set up a one or many to many relationship in rails the model gets a _ids setter:
User.find(1).store_ids = [1,2,3]
This would for example setup a relation between user 1 and the stores with ids 1,2 and 3.
The built in Rails collection form helpers make use of this:
<%= form_for(#user) do |f| %>
<% f.collection_check_boxes(:store_ids, Store.all, :id, :name) %>
<% end %>
This creates a list of checkboxes for each store - if an association exists it will already be checked. Note that we are not using fields_for since it is not a nested input.
SimpleForm has association helpers which add even more sugar.
<h1>Add Favorite Stores</h1>
<%= simple_form_for(#user, html: { class: 'form-horizontal' }) do |f| %>
<%= f.association :stores, as: :check_boxes %>
<%= f.button :submit %>
<% end %>

Create 2 types of resources using the same view

I have 2 models (and resources) - Institute and Admin.
I want to have a view with 1 submit button that creates 2 types of resources. Would I need to have 2 separate forms? An example would be great!
Also, what naming convention should this view use (given that it creates 2 types resources).
There is a "has-many through" association between Institute and Admin.
What you want is a design pattern called Form Object.
https://robots.thoughtbot.com/activemodel-form-objects
With a Form Object, you can create a class that represents the form, validate the data and then persist to the resource (or resources) that you need.
There's also a gem called Virtus for that. For me, it's a overkill if what you want is simple. You could just create a ActiveModel model and do your stuff.
Would I need to have 2 separate forms?
Answer is Non. you can make one form nested.
So example : Gessing your "has many through" association like this: One institue has many admins throuth mettings
Models :
class Institute < ActiveRecord::Base
has_many :mettings
has_many :admins, :through => :mettings
accepts_nested_attributes_for :mettings
end
class Admin < ActiveRecord::Base
has_many :mettings
has_many :institues, :through => :mettings
accepts_nested_attributes_for :mettings
end
class Metting < ActiveRecord::Base
belongs_to :institue
belongs_to :admin
accepts_nested_attributes_for :institues
end
Controller :
def new
#institue= Institue.new
#metting= #institue.mettings.build
#admin = #metting.build_admin
end
def create
Institue.new(institue_params)
end
def institue_params
params.require(:institue).permit(:id, mettings_attributes: [:id, :metting_time, admin_attributes: [:id ] )
end
Views can be called _form.erb.rb included in edit.erb.rb:
<% form_for(#institue) do |institue_form| %>
<%= institue_form.error_messages %>
<p>
<%= institue_form.label :name, "Institue Name" %>
<%= institue_form.text_field :name %>
</p>
<% institue_form.fields_for :mettings do |metting_form| %>
<p>
<%= metting_form.label :metting_date, "Metting Date" %>
<%= metting_form.date_field :metting_date %>
</p>
<% metting_form.fields_for :admin do |admin_form| %>
<p>
<%= admin_form.label :name, "Admin Name" %>
<%= admin_form.text_field :name %>
</p>
<% end %>
<% end %>
<p>
<%= institue_form.submit 'Create' %>
</p>
<% end %>
<%= link_to 'Back', institues_path %>

How to update child record in nested form in rails

How would I update a collection of child 'Product' rows when submitting the form below.
Many thanks
class User < ActiveRecord::Base
has_many :products, :class_name => 'Product', :inverse_of => :user
accepts_nested_attributes_for :products
end
class Product < ActiveRecord::Base
belongs_to :user, :inverse_of => :products
end
The View
<%= form_for(#user) do |f| %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<%= f.fields_for :products do |builder| %>
<% builder.text_field :name %>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Here is somewhat minimal update action:
# app/controller/users_controller.rb
def update
# Get the user in param
#user = User.find(params[:id])
# Update attributes
if #user.update_attribute(params[:user])
# Redirect to :show for this user.
redirect_to :show
else
# update_attriubtes failed. Render the edit view.
render :edit
end
end
Secondly, you also need to ensure the attributes can be mass assigned such that the call to #user.update_attributes succeeds. Since you've not tagged your question with the appropriate Rails version, following is what you can do for Rails 3 and Rails 4:
Rails 3 - Define each attribute you want to mass assign as attr_accessible
Rails 4 - Permit parameters in controllers params.require(:user).permit(...).
Note that in your view you are not outputting the result of the helper text_field on this line:
<% builder.text_field :name %>
You need to use <%=...%> (note the equals = sign) in order to output the result of expressions within <%=...%>. This might have just been a typo here as you've done this correctly on all other elements. Replace the line with:
<%= builder.text_field :name %>
Also, suggest reading the "Action Controller Overview" of guides for details on Controllers.

Nested model form with mutliple has_many/belongs_to associations

I have three models:
class Rate < ActiveRecord::Base
attr_accessible :user_id, :car_id, :rate
belongs_to :user
belongs_to :car
end
class User < ActiveRecord::Base
attr_accessible :name
has_many :rates
accepts_nested_attributes_for :rates
end
class Car < ActiveRecord::Base
attr_accessible :name
has_many :rates
accepts_nested_attributes_for :rates
end
And one controller:
class UsersController < ResourceController
def new
# Assume user is loaded
#user.rates.build
end
end
I'm trying to build a nested form that will associate a list of users/cars and their associated rates.
Something like:
<% form_for #user do |f| %>
<%= #user.name %><br />
<% Car.all.each do |car| %>
<%= car.name %><br />
<%= f.fields_for :rates do |r| %>
<%= r.number_field :rate %>
<% end %>
<% end %>
<% end %>
The problem is that I would like the Rate model to store data as follows:
USER_ID CAR_ID RATE
1 1 10
1 2 20
1 3 30
2 1 40
3 2 50
I cannot figure out how to properly build the fields_for helper to build the proper params for both the user_id and the car_id.
Something like:
user[car=1][rate]
user[car=2][rate]
I've tried being more explicit with the fields_for like this:
<%= r.fields_for 'user[car][rate]' %>
But it still doesn't build out the nested parameters properly. The car parameter is not correctly identified.
Any help would be appreciated! Thanks.
EDIT:
The controller action has to be under user. The example above has been shortened for brevity but other user-related attributes are available through the form so it has to use the users controller.
ANSWER:
I figured out a way to do it. I've added my own answer that explains it.
<% form_for #user do |f| %>
<%= #user.name %><br />
<%= f.fields_for :rates do |r| %>
<% Car.all.each do |car| %>
<%= car.name %><br />
<%= r.number_field :rate %>
<% end %>
<% end %>
<% end %>
This may be solution of your problem. Just check it.
The form is going to create a new rate instead of a new user, so the method should be in RatesController instead of UsersController.
With this logic the problem seems solved. You can write field_for rate[user] and field_for rate[car]
I think I've got it figured out.
In my controller, I've modified the build method as follows:
Car.all.each { |c| #user.rates.build(car_id: c.id) } if #user.rates.count == 0
Then, in my model, I need the following:
attr_accessible :rates_attributes
Finally, the fields_for block should look like this (remember, this is in the #user form object f):
<%= f.fields_for :rates do |r| %>
<%= r.hidden_field :car_id %>
<%= r.object.car.name %><br />
<%= r.number_field :rate %>
<% end %>
This builds the params hash properly and create the rate model entries when the form is submitted.
The check on existing user rates in the controller will ensure that the existing values are used in the form and new ones are not built (which I thought build took into consideration... ?).

Conditional nested forms with simple_form

I have 2 models, clients and client_prices, I would like to have 2 client prices nested forms sections in the client's show page. 1 nested form would be for non-custom prices and another for custom prices. The non-custom (custom == false) prices would only have the "custom" attribute available to edit. The "Custom Prices" would have all of the attributes available to edit.
I've tried a few different approaches, but I don't know where to put the conditional logic to make this work correctly. I'm using simple_form to generate the forms, but I'm not married to it.
Controllers
class Client < ActiveRecord::Base
attr_accessible :name, :client_prices_attributes
has_many :client_prices
accepts_nested_attributes_for :client_prices, :allow_destroy => true
end
class ClientPrice < ActiveRecord::Base
attr_accessible :client_id, :price, :visit_type, :id, :custom
belongs_to :client
belongs_to :default_price
end
Client Show page
<%= simple_nested_form_for #client do |f| %>
<%= f.fields_for :client_prices do |def_price_form| %>
non-custom prices form here
<%end%>
<%= f.fields_for :client_prices do |def_price_form| %>
custom prices form here
<%end%>
<%end%>
Try the following:
<%= simple_nested_form_for #client do |f| %>
<%= f.fields_for :client_prices do |def_price_form| %>
<%= if def_price_form.object.custom %>
Here you fields for custom form
<% end %>
<% end %>
<% end %>

Resources