Use form_for for non-attribute values - ruby-on-rails

I have a form_for like so:
<%= form_for(#post) do |f| %>
I would like to submit a parameter that is not an attribute of the model
<%= f.text_field :label%>
I have a Label model (labels has_many :posts and posts has_many :labels) and in the create action of the posts_controller I want to create a new Label object based off the label text_field. With the above text_field I get:
undefined method `label'for #<Object>
How can I go about achieving this?
Thanks in advance!

If your attribute doesn't exist into database table, and still you want to use it, then you have specify that attribute into model by following way
attr_accessor :name, :email, :content

You can try fields_for method for the creation of associated objects.
<%= form_for(#post) do |f| %>
<%= fields_for #post.labels do |label| %>
<%= label.text_field :name %>
<%= label.text_area :description %>
<% end %>
<% end %>
To display default number of labels you need to build the associated objects in the controller.
In your posts controller add
def new
#post = Post.new
#To build 2 labels use
2.times do
#post.labels.build
end
end
To accept the nested attributes add below code in your Post model.
class Post < ActiveRecord::Base
accepts_nested_attributes_for :labels
end
Someone posted this then deleted...

Related

Pre-populated association for nested form rendered empty

My PlanList has_many :items.
I want my plan_list#new to have a nested form, where I can pre-populate items.
I tried
# View
<%= form_for #plan_list do |f| %>
<%= f.fields_for :items do |item| %>
<%= item.text_field :quantity %>
<% end %>
with
# Controller
def new
#plan_list = PlanList.new
#plan_list.items.build(quantity:1)
#plan_list.items.build(quantity:2)
end
However I only see empty inputs for items.
I also tried <%= f.fields_for #plan_list.items do |item| %> but it will only show one item (the last one with quantity 2). How can I achieve my goal?
You should use the build method of associations as:
def new
#plan_list = PlanList.new
#plan_list.items << #plan_list.items.build(quantity:1)
#plan_list.items << #plan_list.items.build(quantity:2)
end
I did not have accepts_nested_attributes_for :items.
I thought I could add this later when I implement the create method, but it is essential for both form creation and attribute mass assignment
Yep, this is must and should resolve the issue accepts_nested_attributes_for :items
<%= form_for #plan_list do |f| %>
<%= f.fields_for :items do |item| %>
<%= item.text_field :quantity %>
<% end %>
def new
#plan_list = PlanList.new
#plan_list.items.build(quantity:1)
#plan_list.items.build(quantity:2)
end

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

How to get Rails build and fields_for to create only a new record and not include existing?

I am using build, fields_for, and accepts_nested_attributes_for to create a new registration note on the same form as a new registration (has many registration notes). Great.
Problem: On the edit form for the existing registration, I want another new registration note to be created, but I don't want to see a field for each of the existing registration notes.
I have this
class Registration < ActiveRecord::Base
attr_accessible :foo, :bar, :registration_notes_attributes
has_many :registration_notes
accepts_nested_attributes_for :registration_notes
end
and this
class RegistrationsController < ApplicationController
def edit
#registration = Registration.find(params[:id])
#registration.registration_notes.build
end
end
and in the view I am doing this:
<%= form_for #registration do |r| %>
<%= r.text_field :foo %>
<%= r.text_field :bar %>
<%= r.fields_for :registration_notes do |n| %>
<%= n.text_area :content %>
<% end %>
<% end %>
and it is creating a blank text area for a new registration note (good) and each existing registration note for that registration (no thank you).
Is there a way to only create a new note for that registration and leave the existing ones alone?
EDIT: My previous answer (see below) was bugging me because it's not very nice (it still loops through all the other registration_notes needlessly). After reading the API a bit more, the best way to get the behaviour the OP wanted is to replace:
<%= r.fields_for :registration_notes do |n| %>
with:
<%= r.fields_for :registration_notes, #registration.registration_notes.build do |n| %>
fields_for optionally takes a second parameter which is the specific object to pass to the builder (see the API), which is built inline. It's probably actually better to create and pass the new note in the controller instead of in the form though (just to move the logic out of the view).
Original answer (I was so close):
Just to clarify, you want your edit form to include a new nested registration note (and ignore any other existing ones)? I haven't tested this, but you should be able to do so by replacing:
<%= r.fields_for :registration_notes do |n| %>
with:
<%= r.fields_for #registration.registration_notes.build do |n| %>
EDIT: Okay, from a quick test of my own that doesn't work, but instead you can do:
<%= r.fields_for :registration_notes do |n| %>
<%= n.text_area :content if n.object.id.nil? %>
<% end %>
This will only add the text area if the id of the registration note is nil (ie. it hasn't been saved yet).
Also, I actually tested this first and it does work ;)
If you want to create a new registration form on your edit action, you can just instantiate a new registration_note object. Right now, your form is for the existing registration object.
I believe this is what you want:
class RegistrationsController < ApplicationController
def edit
#new_registration_note = RegistrationNote.new
#registration = Registration.find(params[:id])
#registration.registration_notes.build
end
end
In your view, you should pass a hidden param that references the registration record id:
<%= form_for #new_registration_note do |r| %>
<%= r.hidden_field :registration_id, :value => #registration.id %>
<%= r.text_area :content %>
<% end %>
Now, you can create your new registration note that belongs to #registration. Make sure you have a column in your registration_notes table to point to the registration. You can read more about associations here: http://guides.rubyonrails.org/association_basics.html
Thank you so much for your help as I said in my post the only problem with the approach from "Zaid Crouch"(I don't know how to make a reference to a user hehe) is that if the form has error fields the form will be clear and boom after the page reloading you'll have nothing filled in your form and can you imagine if you form is like 20 or 30 fields that would be a terrible user experience of course
Here is my solution that works with validation models:
class Registration < ActiveRecord::Base
attr_accessible :foo, :bar, :registration_notes_attributes
has_many :registration_notes
has_one :new_registration, class_name: 'RegistrationNote'
accepts_nested_attributes_for :new_registration
end
class RegistrationsController < ApplicationController
def edit
#registration = Registration.find(params[:id])
#registration.build_new_registration
end
end
<%= form_for #registration do |r| %>
<%= r.text_field :foo %>
<%= r.text_field :bar %>
<%= r.fields_for :new_registration do |n| %>
<%= n.text_area :content %>
<% end %>
<% end %>
I'm using simple_form in my example if you want to see the same working with validations and transaction take a look at the complete post here:
http://elh.mx/ruby/using-simple_form-for-nested-attributes-models-in-a-has_many-relation-for-only-new-records/
As Heriberto Perez correctly pointed out the solution in the most upvoted answer will simply discard everything if there's a validation error on one of the fields.
My approach is similar to Heriberto's but nevertheless a bit different:
Model:
class Registration < ActiveRecord::Base
has_many :registration_notes
accepts_nested_attributes_for :registration_notes
# Because 0 is never 1 this association will never return any records.
# Above all this association don't return any existing persisted records.
has_many :new_registration_notes, -> { where('0 = 1') }
, class_name: 'RegistrationNote'
accepts_nested_attributes_for :new_registration_notes
end
Controller:
class RegistrationsController < ApplicationController
before_action :set_registration
def edit
#registration.new_registration_notes.build
end
private
def set_registration
#registration = Registration.find(params[:id])
end
def new_registration_params
params.require(:registration).permit(new_registrations_attributes: [:content])
end
end
View:
<%= form_for #registration do |r| %>
<%= r.text_field :foo %>
<%= r.text_field :bar %>
<%= r.fields_for :new_registration_notes do |n| %>
<%= n.text_area :content %>
<% end %>
<% end %>

Update fails for nested attributes when nested object is ActiveRecord subclass

I have ActiveRecord with a subclass and its associated with another ActiveRecord object.
I am able to create my object with nested attributes with a form with nested attributes no problem for a new object (following Ryan Bates rails cast - Thanks by the way :)). However when i do an update it fails to save the changes to either the main object or the related object when submitted
I have the following Activerecord classes and sub class.
class Room < ActiveRecord::Base
attr_accessible :name, :type, room_headers_attributes
has_many :room_headers, dependent: :destroy
accepts_nested_attributes_for :room_headers , :allow_destroy => true
end
And the sub class is
class BigRoom < Room
end
And the related class is
class RoomHeader < ActiveRecord::Base
attr_accessible :key, :room_id, :value
belongs_to :room
end
In my room controller I created the nested objects. note that i'm using :type to specify the subclass type
def new
#room = current_user.passes.build(params[:room])
#room.type = params[:type]
3.times do
room_header = #room.room_headers.build
end
....
end
....
def edit
#room = Room.find(params[:id])
end
def update
#room = Room.find(params[:id])
if #room.update_attributes(params[:room])
...
The form used for creating and editing is the same
<%= form_for(#room) do |f| %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<%= f.fields_for :room_headers do |builder| %>
<%= render 'room_header_fields', f: builder %>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end &>
And the _room_headers.html.erb partial is
<p class="fields">
<%= f.label :key, "Key" %>
<%= f.text_field :key %>
<%= f.label :value, "Value" %>
<%= f.text_field :value %>
<%= f.check_box :_destroy %>
<%= f.label :_destroy, "Remove Header" %>
</p>
To recap on the problem. I can successfully create a new BigRoom. In the new form when i create the BigRoom and I can successfully set values for the RoomHeader class and these are all saved successfully.
However when i Edit the the record and submit changes for update, nothing is saved. Either for changes for the Bigroom attributes or to the associated RoomHeader records.
first try by
if #room.update_attribute(params[:room])
rather
if #room.update_attributes(params[:room])
if this works then their are some errors with your validdations
Ok, nested attributes were a red herring. The problem is with STI
The Rails form helper guide says you can’t rely on record identification with STI.
In the form_for we need to coearce the ids to be the base type id otherwise the edit fails
so
<%= form_for(#room) do |f| %>
should be
<%= form_for(#room.becomes(Room) do |f| %>
if you look at the difference in the html output
the problem html would create ids like big_room_fieldname when in edit mode
when using .becomes we get ids like room_fieldname. in whihc case it saves and updates ok

How to use fields for a nested form outside of the nested form to act as a "global field"?

If I have the models with the following associations:
class Business
has_many :products
class Product
belongs_to :business
And I generate 3 products in the controller:
def new
#business = Business.new
3.times do
#business.products.build
end
end
Making my form look like this:
<%= form_for #business do |f| %>
<% f.text_field :business_name %>
<%= f.fields_for :products do |pf| %> # x2 more products generated
<% pf.text_field :name %>
<% pf.text_field :price %>
<% pf.text_field :date %>
<% end %>
If I want one of the fields to act as a global field for the rest of the products how could I take a field like the :price and put it outside of the f.fields_for :products to have it be the :price for all of the products?
Thank you.
If you need to initialize the price, do it in the controller. But if you need a field that doesn't map to a model directly, use the regular form helpers:
<%= text_field_tag 'global_price' %>
and then in the controller on the create action, it is available as
params[:global_price]
Alternately, you could define a method in your Business model:
def global_price=
#do something with the global price, such as updating child object...
# I'm not sure if the child form objects have been instantiated yet though
end
and then you can use it in your business form:
<%= f.text_field :global_price %>
If you need to update the child objects, you might have to do that at a later time; instead of that method, make it
attr_accessor :global_price
Which makes it an instance variable. Then you can use a before_save filter to update the child objects.
before_save :update_global_price
def update_global_price
#do something with #global_price
end

Resources