How to use nested forms in Rails 3? - ruby-on-rails

I have a question on rails3 nested_form.
These are my two models:
class User
belongs_to :shop
accepts_nested_attributes_for :shop
end
class Shop
has_many :users
end
In my register view(i am using Devise):
form_for(resourse,:url => registration(resource_name)) do |f|
=f.fields_for :shop do |s|
=s.text_fields :name
but i get nothing for this form. What should i do?

You need to add some objects first to it. Use build method on model in controller.
Example:
#shop = Shop.new
3.times { #shop.users.build }
More informations at Railscasts. AJAX is used in second part of this video.

Related

Rails 4+ Edit multiple objects in one form BEST PRACTICE

I have a User, Drinks, Gyms, Foods Model.
class User < ActiveRecord::Base
has_many :drinks
has_many :foods
has_many :gyms
end
I track the number of drinks a user had during the day and save it in the database.I do the same with Foods and Gyms.
I have a User and a Session (for login) controller. So far I haven't needed a controller for my "passiv" Models (Drink, Food, Gym).
Now I have one page with a form on which the User can change the entries of all tables(Drink, Food, Gym) of the previous day.
I think I need to use fields_for in the form to edit objects of multiple Models in one form.
However I don't know how many controllers I need and where I should put in all the business logic... I don't want to do anything quick and dirty, but rather follow certain Best Practices.
My approach so far:
A lot of forms on one page
<%= form_for :running, url: data_update_user_path do |f| %>
<%= form_for :drinks, url: data_update_user_path do |f| %>
<%= form_for :food, url: data_update_user_path do |f|
One DataController who handles all the different updates (It's basically a big if elsif)
class DataController
def update
if params[:drinks]
#update drinks
elsif params[:foods]
#update foods
elsif params[:gyms]
#update gyms
end
end
end
So my question: What is the best practice in such a situation?
Use nested form with only one controller; the users controller, the update action for the user will update it's related models as well when you use accepts_nested_attributes_for so basically your user model will be
class User < ActiveRecord::Base
has_many :foods
has_many :drinks
has_many :gyms
accepts_nested_attributes_for :foods, :drinks, :gyms
end
And the for for user will contain fields_for foods, drinks and gyms
Don't forget in your users controller if you are using strong_parameters to permit the attributes of the nested models
def user_params
params.require(:user).permit(:id, .... , foods_attributes: [:id, :destroy, ....], gyms_attributes: [:id, :_destroy, ....], drinks_attributes: [:id, :_destroy, ....])
end

Rails has_many relationship with prefilled views

I have a pretty basic Rails 4 app, and am using Cocoon's nested forms to manage the has_many... :through model association.
class Student < ActiveRecord::Base
has_many :evaluations
has_many :assessments, through: :evaluations
# ... etc
end
class Evaluation < ActiveRecord::Base
belongs_to :student
belongs_to :assessment
# ... etc
end
class Assessment < ActiveRecord::Base
has_many :evaluations
has_many :students, through: :evaluations
accepts_nested_attributes_for :evaluation, reject_if: :all_blank
# ... etc
end
When I use Cocoon in the View, I want to use the New Assessment view to pre-fill all the Student records in order to create a new Evaluation for each one. I don't want to have to do some hacky logic on the controller side to add some new records manually, so how would I structure the incoming request? With Cocoon I see that requests have some number in the space where the id would go (I've replaced these with ?? below).
{"utf8"=>"✓", "authenticity_token"=>"whatever", "assessment"=>{"description"=>"quiz 3", "date(3i)"=>"24", "date(2i)"=>"10", "date(1i)"=>"2015", "assessments_attributes"=>{"??"=>{"student_id"=>"2", "grade" => "A"}, "??"=>{"student_id"=>"1", "grade" => "B"}, "??"=>{"student_id"=>"3", "grade"=>"C"}}, }}, "commit"=>"Create Assessment"}
I see in the Coccoon source code that this is somehow generated but I can't figure out how it works with the Rails engine to make this into a new record without an ID.
What algorithm should I use (or rules should I follow) to fill in the id above to make a new record?
"??"
Never a good sign in your params.
With Cocoon I see that requests have some number in the space where the id would go
That ID is nothing more than the next ID in the fields_for array that Rails creates. It's not your record's id (more explained below).
From your setup, here's what I'd do:
#app/models/student.rb
class Student < ActiveRecord::Base
has_many :evaluations
has_many :assessments, through: :evaluations
end
#app/models/evaluation.rb
class Evaluation < ActiveRecord::Base
belongs_to :student
belongs_to :assessment
end
#app/models/assessment.rb
class Assessment < ActiveRecord::Base
has_many :evaluations
has_many :students, through: :evaluations
accepts_nested_attributes_for :evaluations, reject_if: :all_blank
end
This will allow you to do the following:
#app/controllers/assessments_controller.rb
class AssessmentsController < ApplicationController
def new
#assessment = Assessment.new
#students = Student.all
#students.each do
#assessment.evaluations.build
end
end
end
Allowing you:
#app/views/assessments/new.html.erb
<%= form_for #assessment do |f| %>
<%= f.fields_for :evaluations, #students do |e| %>
<%= e.hidden_field :student_id %>
<%= e.text_field :grade %>
<% end %>
<%= f.submit %>
<% end %>
As far as I can tell, this will provide the functionality you need.
Remember that each evaluation can connect with existing students, meaning that if you pull #students = Student.all, it will populate the fields_for accordingly.
If you wanted to add new students through your form, it's a slightly different ballgame.
Cocoon
You should also be clear about the role of Cocoon.
You seem like an experienced dev so I'll cut to the chase - Cocoon is front-end, what you're asking is back-end.
Specifically, Cocoon is meant to give you the ability to add a number of fields_for associated fields to a form. This was discussed in this Railscast...
Technically, Cocoon is just a way to create new fields_for records for a form. It's only required if you want to dynamically "add" fields (the RailsCast will tell you more).
Thus, if you wanted to just have a "static" array of associative data fields (which is I think what you're asking), you'll be able to use fields_for as submitted in both Max and my answers.
Thanks to #rich-peck I was able to figure out exactly what I wanted to do. I'm leaving his answer as accepted because it was basically how I got to my own. :)
assessments/new.html.haml (just raw, no fancy formatting)
= form_for #assessment do |f|
= f.fields_for :evaluations do |ff|
.meaningless-div
= ff.object.student.name
= ff.hidden_field :student_id, value: ff.object.student_id
= ff.label :comment
= ff.text_field :comment
%br/
assessments_controller.rb
def new
#assessment = Assessment.new
#students = Student.all
#students.each do |student|
#assessment.evaluations.build(student: student)
end
end

Nested Resource Creation in Parent Form

I have a class called Quote which has_many :line_items, as: :line_itemable (line_items are polymorphic). A quote must have at least one line_item upon creation, so in my Quote creation form I have a section dedicated to adding line items. My routes look like this:
resources :quotes, shallow: true do
resources :line_items
end
which means my routes look like this:
POST /quotes/:quote_id/line_items(.:format) line_items#create
new_quote_line_item GET /quotes/:quote_id/line_items/new(.:format) line_items#new
In the line items section of the quote form I have a button that, when clicked, links to the new_quote_line_item controller action to render a line_item creation modal. My issue is that since the quote hasn't been created yet it doesn't have :quote_id to use in the path. How can I go about achieving this the Rails Way™? I was considering using ajax but I'm not sure if that is overkill for this situation. Thanks for your help!
You should user accepts_nested_attributes_for method in your model to accept attributes for LineItem and fields_for helper
Your model should looks like:
class Quote < ActiveRecord::Base
accepts_nested_attributes_for :line_item
...
end
And you template like:
form_for #quote do |f|
f.fields_for :line_items do |f2|
...
end
...
end
Ajax
You wouldn't need ajax functionality for this - Ajax only allows you to pull data from the server asynchronously, which essentially means you don't have to reload the page.
--
Nested Attributes
What you're looking for, as alluded to by atomAltera sounds like accepts_nested_attributes_for - which allows you to create dependent models from the parent
It sounds to me that you'll need to create a quote before you try and populate line_items, which is actually quite simple using ActiveRecord:
#app/models/quote.rb
Class Quote < ActiveRecord::Base
has_many :line_items
accepts_nested_attributes_for :line_items
end
#app/controllers/quotes_controller.rb
Class QuotesController < ApplicationController
def new
#quote = Quote.new
#quote.line_items.build
end
def create
#quote = Quote.new(quote_params)
#quote.save
end
private
def quote_params
params.require(:quote).permit(:quote, :attributes, :new, line_items_attributes: [:line, :items, :attributes])
end
end
--
If you need any further information, please let me know!!

Updating a join model relationship in Rails

Basically I have a Shop, Category and a join model ShopCategory with additional attributes
class Shop
has_many :shop_categories
has_many :categories, through: :shop_categories
class Category
has_many :shop_categories
has_many :shops, through: :shop_categories
class ShopCategory
belongs_to :shop
belongs_to :category
I have a shop form which I'd like to create or update the shop through it.
My first thought is to create a virtual attribute called :categories and to have the model handle the setter and getter through it, something like this (pseudocode for simplicity):
def categories=(cats)
cats.each do |c|
check if a ShopCategory exists with this shop (self) and that category.
if doesn't exist, create one, if exists ignore
for all the categories in self that weren't touched, delete that ShopCategory
end
end
but I feel this would cause problems in the long run because of the connection of 3 models and not though a controller
However, I can't seem to think of a simple way to have a create and update methods in the shops_controller for handling this
def update
#shop = Shop.find params[:id]
cats = params[:shop].delete :categories
#shop.update_attributes(shop_params)
## should I have a category update method here? How would I handle errors? This gets complicated
end
It sounds like you want a nested model form, for editing both a Shop and its associated ShopCategories.
Basically, what it entails is on the form for your Shop, you can simply iterate over the associated ShopCategories and print out fields for them, to edit them all together. Rails will automatically handle it all, as long as the parameters are structured correctly.
https://github.com/nathanvda/cocoon is a gem for making nested model forms easier.
There is also a tutorial on Railscasts:
http://railscasts.com/episodes/196-nested-model-form-revised
Collections
I don't know how experienced you are with Ruby on Rails, but you may wish to look at some of the documentation pertaining to collections
What you're looking at is how to populate your collections - which is actually relatively simple:
#app/controllers/shops_controller.rb
Class ShopsController < ApplicationController
def create
#shop = Shop.new(shop_params)
#shop.save
end
private
def shop_params
params.require(:shop).permit(:your, :attributes, category_ids: [])
end
end
This will allow you to use the following form:
#app/views/shops/new.html.erb
<%= form_for #shop do |f| %>
<% Category.all.each do |category| %>
<%= f.check_box :category_ids, category.id %>
<% end %>
<% end %>
--
Modularity
In terms of validating your collections for uniqueness, you will be best using DB, or Association-level validation:
class Shop
has_many :categories, -> { uniq }, through: :shop_categories
This will essentially create only unique categories for your shop, which you can populate with the method described above.

How to maintain the ordering for nested attributes when using accepts_nested_attributes_for in a Rails application

Here is the parent model:
class TypeWell < ActiveRecord::Base
...
has_many :type_well_phases, :dependent => :destroy
accepts_nested_attributes_for :type_well_phases, :reject_if => lambda { |a| a[:phase_id].blank? }, :allow_destroy => true
...
end
Here is the nested model:
class TypeWellPhase < ActiveRecord::Base
belongs_to :type_well
belongs_to :phase
end
Here is the Phase model:
class Phase < ActiveRecord::Base
...
has_many :type_well_phases
...
end
I add nested records in child table (TypeWellPhases) by copying ALL records from my phases (Phase model) table in the parent model's controller as shown below:
class TypeWellsController < ResourceController
...
def new
#new_heading = "New Type Well - Computed"
#type_well = TypeWell.new
initialize_phase_fields
end
private
def initialize_phase_fields
Phase.order("id").all.each do |p|
type_well_phase = #type_well.type_well_phases.build
type_well_phase.phase_id = p.id
type_well_phase.gw_heat_value = p.gw_heat_value
end
end
...
end
I do this because I want to maintain a specific order by the children fields that are added. The part of the code Phase.order("id") is for that since the phases table has these records in a specific order.
After this I use the simple_form_for and simple_fields_for helpers as shown below in my form partial:
= simple_form_for #type_well do |f|
...
#type_well_phases
= f.simple_fields_for :type_well_phases do |type_well_phase|
= render "type_well_phase_fields", :f => type_well_phase
Everything works as desired; most of the times. However, sometimes the ordering of Child rows in the form gets messed up after it has been saved. The order is important in this application that is why I explicitly do this ordering in the private method in the controller.
I am using the "cocoon" gem for adding removing child records. I am not sure as to why this order gets messed up sometimes.
Sorry for such a long post, but I wanted to provide all the pertinent details up front.
Appreciate any pointers.
Bharat
I'll explain you in a more generic way. Say, you have Product and Order models:
= form_for #product do |f|
...
= f.fields_for :orders do |order_fields|
= order_fields.text_field :name
If you want your orders to be sorted by name then just sort them :)
Instead of:
= f.fields_for :orders do |order_fields|
put:
= f.fields_for :orders, f.object.orders.order(:name) do |order_fields|
As you see, the f variable that is a parameter of the block of form_for has method object. It's your #product, so you can fetch its orders via .orders and then apply needed sorting via .order(:name) (sorry for this little confusion: order/orders).
The key to your solution that you can pass sorted orders as the second parameter for fields_for.
P.S. Your using the simple_form gem doesn't affect my solution. It'll work if you add 'simple_' to helpers. Just wanted my answer to be more helpful for others and not too task-related.
If you are using Rails 2.3.14 or older you have to use:
f.fields_for :orders, f.object.orders.all(:order => :name) do |order_fields|
I use this way:
class League < ActiveRecord::Base
has_many :rounds, -> { sort_by_number }, dependent: :destroy
end
class League::Round < ActiveRecord::Base
belongs_to :league
scope :sort_by_number, -> { order('league_rounds.number ASC') }
end
In the view
= form_for league do |f|
= f.fields_for :rounds do |round_form|
# Here rounds are sorted by sort_by_number
This approach allows the use of any scope defined in the model. This approach allows the creation of several differently sorted associations.

Resources