Nested model form with mutliple has_many/belongs_to associations - ruby-on-rails

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... ?).

Related

Rails/ActiveRecord - association not saving

I can't get my CheckIn record to save because the associated Tenancy isn't saving.
I have three models with associations:
class Property < ApplicationRecord
has_many :tenancies
end
class Tenancy < ApplicationRecord
belongs_to :property
has_many :check_ins
end
class CheckIn < ApplicationRecord
belongs_to :tenancy
accepts_nested_attributes_for :tenancy
end
I want the CheckIn new action to create both the CheckIn and the associated Tenancy:
def new
#check_in = CheckIn.new
#check_in.build_tenancy.property_id = params[:property_id]
end
I have to include the property_id part otherwise the Tenancy won't save.
The form in check_ins/new.html.erb:
<%= form_for #check_in, url: property_check_ins_path do |f| %>
<%= f.label :date_time %>
<%= f.datetime_select :date_time, {minute_step: 15} %>
<%= f.label :tenancy %>
<%= f.fields_for :tenancy do |i| %>
<%= i.date_select :start_date %>
<% end %>
<%= f.submit "Create Check In" %>
<% end %>
I've added tenancy attributes to the strong params in the CheckInsController:
def check_in_params
params.require(:check_in).permit(:tenancy_id, :date_time, tenancy_attributes: [:start_date])
end
It's worth noting that the check_ins routes are nested in properties:
resources :properties do
resources :check_ins, only: [:new, :create]
end
So the problem is that by the time I get to the create action in the CheckInsController, the tenancy that I built has disappeared. I'm not sure how and when each of the records should be being saved and the slight complexity of what I'm trying to achieve has made it quite difficult to find relevant help so any ideas?
I'm using Rails 5.
The problem was that the property attached to the tenancy was being forgotten. I removed the property attachment from the new action:
def new
#check_in = CheckIn.new
#check_in.build_tenancy
end
Added a hidden field for property_id to the form (as well as adding :property_id to the strong params):
<%= f.fields_for :tenancy do |i| %>
<%= i.date_select :start_date %>
<%= i.hidden_field :property_id, value: params[:property_id] %>
<% end %>
And saved the tenancy in the CheckIn create action, prior to saving the check in itself:
def create
#check_in = CheckIn.new(check_in_params)
#check_in.tenancy.save
if #check_in.save
redirect_to property_check_in_path(#check_in.tenancy.property.id, #check_in)
else
render :new
end
end
I'd certainly be interested if anyone could pick holes in this solution or offer a better one.
Using nested resources (check_ins depends from properties) you create a namespaces routes. form_for helper ( rails guides - form helpers ) when you build your form, need a Property reference also.
I try to explain me better with an example:
#checks_controller.rb
def new
#property = Property.new
#check_in = #property.build_check_ins
#check_in.build_tenancy
end
#check_ins/new.html.erb
<%= form_for [#property, #check_in], url: property_check_ins_path do |f| %>
<%= f.label :date_time %>
<%= f.datetime_select :date_time, {minute_step: 15} %>
<%= f.label :tenancy %>
<%= f.fields_for :tenancy do |i| %>
<%= i.date_select :start_date %>
<% end %>
<%= f.submit "Create Check In" %>
<% end %>
I haven't tried this code, but I hope this give you at least a way to follow to solve your problem.

Rails: How do I submit multiple objects into strong params?

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

Rails 4 - checkboxes for has_and_belongs_to_many association

I recently had a problem getting checkboxes to work for a has_and_belongs_to_many (HABTM) association in Rails 4. I was able to find the information on how to get it working correctly in a few disparate places, but thought it would be good to document the few simple steps necessary to get it working correctly in one place here on StackOverflow.
As a setup assume a model of Kennel with a HABTM association to Handler.
class Kennel
has_and_belongs_to_many :handlers
end
This is all you need to do for the form: Don't do it manually when there is a built in helper.
<%= form_for #kennel do |f| %>
<%= f.collection_check_boxes(:handler_ids, Handler.all, :id, :to_s) %>
<% end %>
The form should have something like this:
<%= form_for(#kennel) do |form| %>
...
<div class="field">
<div class="field_head">Handlers</div>
<%= hidden_field_tag("kennel[handler_ids][]", nil) %>
<% Handler.order(:name).each do |handler| %>
<label><%= check_box_tag("kennel[handler_ids][]", id, id.in?(#kennel.handlers.collect(&:id))) %> <%= handler.name %></label>
<% end %>
</div>
...
<% end %>
The hidden_field_tag allows the user to uncheck all the boxes and successfully remove all the associations.
The controller needs to allow the parameter through strong parameters in the permitted_params method:
params.permit(kennel: [:city, :state
{handler_ids: []},
:description, ...
])
References:
http://railscasts.com/episodes/17-habtm-checkboxes
https://coderwall.com/p/_1oejq
I implement has_and_belongs_to_many association this way:
model/role
class Role < ActiveRecord::Base
has_and_belongs_to_many :users
end
model/user
class User < ActiveRecord::Base
has_and_belongs_to_many :roles
end
users/_form.html.erb
---
----
-----
<div class="field">
<% for role in Role.all %>
<div>
<%= check_box_tag "user[role_ids][]", role.id, #user.roles.include?(role) %>
<%= role.name %>
</div>
<% end %>
</div>
users_controller.rb
def user_params
params.require(:user).permit(:name, :email, { role_ids:[] })
end
Intermediate table_name should be roles_users and there should be two fields:
role_id
user_id

At least one text_field must be filled

I have 3 text_fields in my view in which I enter students name. Of course you can enter one student or three students but I want to make sure that at least one student was provided because a project must have a student assigned to it.
Here is my view:
<%= form_for #project, url: projects_path do |f| %>
<p>
<%= f.label :name, "Name" %>
<%= f.text_field :name %>
</p>
<p>
<%= f.fields_for :students do |s| %>
<%= s.label :name %>
<%= s.text_field :name %>
<% end %>
</p>
<p>
<%= f.submit "Submit" %>
</p>
<% end %>
And new method from Projects controller:
def new
#project = Project.new()
3.times do
student = #project.students.build
end
end
What I want to achieve is to check if at least one student was provided and if not just show alert or disable submiting.
Edit
Models used in this project:
class Student < ActiveRecord::Base
belongs_to :project
end
class Project < ActiveRecord::Base
has_many :students
accepts_nested_attributes_for :students
validate :validate_student_count
def validate_student_count
errors.add(:students, "at least one is required") if students.count < 1
end
end
Lots of very similar questions on the internet. Here's some examples: Validate the number of has_many items in Ruby on Rails and Validate that an object has one or more associated objects
Just add a custom validation rule as:
validate :validate_student_count
def validate_student_count
errors.add(:students, "at least one is required") if students.count < 1
end

Ruby on rails: fields_for do nothing if defined submodel_attributes=

I have such code in new.erb.html:
<% form_for(#ratification) do |f| %>
<%= f.error_messages %>
<% f.fields_for :user do |fhr| %>
<p>
<%= fhr.label :url %><br />
<%= fhr.text_field_with_auto_complete :url %>
</p>
<% end %>
<% end %>
If i have empty Ratification.rb it is ok, fields_for works ok.
But if I wrote:
class Ratification < ActiveRecord::Base
belongs_to :user
accepts_nested_attributes_for :user
end
or
class Ratification < ActiveRecord::Base
belongs_to :user
def user_attributes=(attr)
...
end
end
f.fields_for yields nothing! Why!?
Rails: 2.3.8
Plugin for autocomplete: repeated_auto_complete
just add an = (EQUAL) after <% in the f.fields_for
like this:
<%= f.fields_for :user do |fhr| %>
<p>
<%= fhr.label :url %><br />
<%= fhr.text_field_with_auto_complete :url %>
</p>
<% end %>
obs: you have to do the same in Rails 3
I believe you need to build a user in your controller, like
# controller
def new
#ratification = Ratification.new
#ratification.build_user
end
What about
<% f.fields_for :user, #ratification.user do |fhr| %>
# ...
<% end %>
?
I believe if you use
<% f.fields_for :user do |fhr| %>
you should have #user as instance variable. But in your case you have #ratification.user.
You cannot redefine user_attributes since you will overwrite the standar behaviour that ActiveRecord specifies for it. If still knowing this you need to redefine user_attributes try using alias_method_chain.
Aren't you doing this wrong? If Ratification belongs to a user, then the user model should accept nested attributes for ratifications, not the other way around.
So if users have many ratifications, and if you want to submit multiple ratifications for a user in a single form, then you would use accept nested attributes for ratification in the user model.
And you would do somewhere in the users controller
#user = User.new
2.times { #user.ratifications.build } # if you want to insert 2 at a time
I tried to do something similar in the console:
#user = User.new
#user.ratifications.build # this works
But if I did
#ratification = Ratification.new
#ratification.user.build # this fails
I just encountered the same problem and was able to fix it. The problem was that fields_for takes :user as an argument while the argument should be #ratification.user
Hence, replace
<% f.fields_for :user do |fhr| %>
with
<% f.fields_for #ratification.user do |fhr| %>
That's it.

Resources