Rails5. Nested attributes are not going to database - ruby-on-rails

class User < ApplicationRecord
has_one :address
accepts_nested_attributes_for :address
end
class Address < ApplicationRecord
belongs_to :user
end
<%= form_for #user do |f| %>
.... // some filed here everything fine
<%= f.fields_for :address do |a| %>
<%= a.text_field :city %> // this field is not appear
<% end %>
<% end %>
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.valid?
#user.save
else
redirect_to root_path
end
end
private
def user_params
params.require(:user).permit(:id, :name, :email, :password, :password_confirmation, :status, :image, :address_attributes => [:id, :city, :street, :home_number, :post_code, :country])
end
end
So like you can see above I have two classes and one form, when I am trying display fields for Address class I can not do it in that way. I took this example from https://apidock.com/rails/ActionView/Helpers/FormHelper/fields_for
I was trying different combination like for example using User.new and Address.new in form definition it not working as well, I was able display all fields in that situation but I wasn't able to save Address data to table, because of "unpermited address".
Can someone explain what I am doing wrong? Or at least give me please some hints.
[SOLVED]
I should learn how to read documentations properly. Excalty like #Srack said I needed just use build_address method. I checked documentation rails api again and on the end of page there was examples says to create User class like this:
class User < ApplicationRecord
has_one :address
accepts_nested_attributes_for :address
def address
super || build_address
end
end
and that solved my issue.
Thank you.

You'll have to make sure there's an address instantiated for the user in the new view. You could do something like:
def new
#user = User.new
#user.build_address
end
You should then see the address fields on the form.
The nested_fields_for show the fields for a record that's been initialised and belong to the parent. I think the latter is why your previous attempts haven't worked.
FYI build_address is an method generated by the belongs_to association: http://guides.rubyonrails.org/association_basics.html#methods-added-by-belongs-to

Related

Ruby on Rails: ActiveRecord::AssociationTypeMismatch

I am getting an ActiveRecord::AssociationTypeMismatch error when trying to submit a record.
Subject(#88982676) expected, got String(#20223000)
View:
<%= f.collection_select :subject, Subject.order(:subject), :subject, :subject, {prompt: "Select a subject"}, {class: "form-control"} %>
Controller:
def create
#homework = current_user.homeworks.build(homework_params)
if #homework.save
redirect_to homeworks_path
else
render 'new'
end
end
...
def homework_params
params.require(:homework).permit(:subject, :description, :date, :completed_at)
end
Model: Homework.rb
class Homework < ActiveRecord::Base
validates :subject, presence:true
belongs_to :subject
def completed?
!completed_at.blank?
end
end
Subject.rb
class Subject < ActiveRecord::Base
has_many :homeworks
def to_s
subject
end
end
This use to work but suddenly doesn't. I did change the name of the table to "subject" and changed the views and controller accordingly. It appears to be looking for id now? Subject is a string. Any advice? Thanks.
According to your association models, your homework attributes should look like this:
subject_id:integer description:string date:datetime completed_at:datetime
Therefore, you should permit subject_id, instead of subject in your homework_params
As for collection_select method, it should be something like this:
f.collection_select :subject_id, Subject.order(:subject), :id, :subject

How can get array values using has_and_belongs_to_many?

Can't save params selected on select box.
Table users:
1id| |name|
1 CR7
2 Messi
Table ejecutives:
1id| |name|
1 Mourinho
2 Guardiola
Table user_ejecutives:
|id| |user_id| |ejecutive_id|
1 1 1
2 2 2
Controller users_controller.rb:
def new
#obj_user = User.new
end
def create
#user = User.new user_params
#user.save
end
def show
#user = User.find(params[:id])
end
private
def user_params
params.require(:user).permit(:name, user_ejecutive_ids: [])
end
Models:
#User.rb
has_many :ejecutives, :through => :user_ejecutives
has_many :user_ejecutives
has_and_belongs_to_many :user_ejecutives, class_name: "User", join_table: "user_ejecutives"#, foreign_key: :user_id, association_foreign_key: :ejecutive_id
#Ejecutive.rb
has_many :user_ejecutives
has_many :users, :through => :user_ejecutives
#UserEjecutive.rb
belongs_to :user
belongs_to :ejecutive
View new.html.erb:
<%= form_for #user do |f| %>
<%= form.text_field :name %>
<%= f.collection_select :user_ejecutive_ids, Ejecutive.all, :id, :name, multiple: true %>
<% end %>
View show.html.erb
<% #user.ejecutives.each do |ejecutive| %>
<%= ejecutive.name %></label>
<% end %>
I'm not getting results on the view show and it show on logs:
SystemStackError (stack level too deep):
If you're just trying to populate the join table (user_ejecutives), you'll want to populate the singular_colletion_ids method:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new user_params
#user.save
end
private
def user_params
params.require(:user).permit(:name, user_ejecutive_ids: [])
end
end
#app/views/users/new.html.erb
<%= form_for #user do |f| %>
<%= f.collection_select :user_ejecutive_ids, User.all, :id, :name, multiple: true %>
<%= f.submit %>
<% end %>
This will assign new user_ejecutives for each new #user you create.
PS User.all is valid in this instance as you're dealing with a new (uncreated) #user record, hence it won't appear in the db.
If you wanted to create new user_ejecutives with each new #user, you'll want to use accepts_nested_attributes_for, which I can explain if required.
Update
So your error is as follows:
Unpermitted parameter: user_ejecutive_ids
... you also have another error...
NoMethodError (undefined method `each' for nil:NilClass):
This is exactly why I don't like your code. Because it doesn't fit to convention, you've go to evaluate whether the params are present etc.
You'll need to use the controller code I posted - it will populate the other table for you, and fix this NilClass error.
--
Join Table
Your user_ejecutives table is a join table.
Your User model should have the following:
#app/models/user.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :user_ejecutives, class_name: "User", join_table: "user_ejecutives", foreign_key: :user_id, association_foreign_key: :ejecutive_id
end
You'll have to remove the id column from your user_ejecutives table (as per the definition here). The importance of this is that it gives you the ability to populate the singular_collection_ids method (in your case user_ejective_ids), as per my recommended code.
Try the following code.
params.require(:user).permit(:name, :user_ejecutives => [])
Hey, I think you have "ejecutive_id" column declared as integer but when loop through "user_ejecutives" you are getting each value as string, May be this is causing the issue, Kindly update your create action to below.
def create
obj_user = User.new(user_params)
if obj_user.save
params[:user_ejecutives].each do |ejecutive|
user_ejecutive = UserEjecutive.create(user_id: obj_user.id, ejecutive_id: ejecutive.to_i)
user_ejecutive.save
end
end
end

Setting attribute in join model when creating two new parents

I have two models: User and Company. A company can have many users and a user can have many companies. As you might suggest, this is the perfect place to use a join table. I'm actually using a full blown model to join User and Company so that I can specify the role that each user has. The table, companies_users, therefore has the following columns: user_id, company_id and company_role.
The situation I'm trying to negotiate is one in which I'm creating both a Company and a User and would like to specify the company_role while doing so.
My new method is as follows:
def new
#user=User.new
#company=#user.companies.build
end
This creates an entry in the companies_users join table but (obviously) does so in leaving the company_role blank.
How might I add this bit of info?
Thanks in advance!
You can pass the attributes through the build / create methods:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def new
#user = User.new
#user.company_users.build.build_company
end
def create
#user = User.new user_params
#user.save
end
private
def user_params
params.require(:user).permit(company_users_attributes: [company_attributes:[:name]])
end
end
#app/views/users/new.html.erb
<%= form_for #user do |f| %>
<%= f.fields_for :company_users do |cu| %>
<%= cu.text_field :company_role %>
<%= cu.fields_for :company do |c| %>
<%= c.text_field :name %>
<% end %>
<% end %>
<%= f.submit %>
<% end %>
The above looks complicated, I'll explain in a second.
You need the following models:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :company_users
has_many :companies, through: :company_users
accepts_nested_attributes_for :company_users
end
#app/models/company_user.rb
class CompanyUser < ActiveRecord::Base
belongs_to :company
belongs_to :user
accepts_nested_attributes_for :company
end
#app/models/company.rb
class Company < ActiveRecord::Base
has_many :company_users
has_many :users, through: :company_users
end
If you want to create a company and company_user, you'll have to pass params for both. Although it looks messy, all you're doing is passing each nested object to their respective models.
If you want to set the "role", you have to pass the attributes to company_users. If you want to also create a new company (rather than just assigning an existing one), you need to also pass the respective params for that too.
You could explicitly reference the join table...
def new
#user = User.new
#company = Company.new
#companies_user=#user.companies_user.build(company: #company, company_role: 'default role')
end

Ruby on Rails Saving in two tables from one form

I have two models Hotel and Address.
Relationships are:
class Hotel
belongs_to :user
has_one :address
accepts_nested_attributes_for :address
and
class Address
belongs_to :hotel
And I need to save in hotels table and in addresses table from one form.
The input form is simple:
<%= form_for(#hotel) do |f| %>
<%= f.text_field :title %>
......other hotel fields......
<%= f.fields_for :address do |o| %>
<%= o.text_field :country %>
......other address fields......
<% end %>
<% end %>
Hotels controller:
class HotelsController < ApplicationController
def new
#hotel = Hotel.new
end
def create
#hotel = current_user.hotels.build(hotel_params)
address = #hotel.address.build
if #hotel.save
flash[:success] = "Hotel created!"
redirect_to #hotel
else
render 'new'
end
end
But this code doesn't work.
ADD 1
Hotel_params:
private
def hotel_params
params.require(:hotel).permit(:title, :stars, :room, :price)
end
ADD 2
The main problem is I don't know how to render form properly. This ^^^ form doesn't even include adress fields (country, city etc.). But if in the line
<%= f.fields_for :address do |o| %>
I change :address to :hotel, I get address fields in the form, but of course nothing saves in :address table in this case. I don't understand the principle of saving in 2 tables from 1 form, I'm VERY sorry, I'm new to Rails...
You are using wrong method for appending your child with the parent.And also it is has_one relation,so you should use build_model not model.build.Your new and create methods should be like this
class HotelsController < ApplicationController
def new
#hotel = Hotel.new
#hotel.build_address #here
end
def create
#hotel = current_user.hotels.build(hotel_params)
if #hotel.save
flash[:success] = "Hotel created!"
redirect_to #hotel
else
render 'new'
end
end
Update
Your hotel_params method should look like this
def hotel_params
params.require(:hotel).permit(:title, :stars, :room, :price,address_attributes: [:country,:state,:city,:street])
end
You should not build address again
class HotelsController < ApplicationController
def new
#hotel = Hotel.new
end
def create
#hotel = current_user.hotels.build(hotel_params)
# address = #hotel.address.build
# the previous line should not be used
if #hotel.save
flash[:success] = "Hotel created!"
redirect_to #hotel
else
render 'new'
end
end
Bottom line here is you need to use the f.fields_for method correctly.
--
Controller
There are several things you need to do to get the method to work. Firstly, you need to build the associated object, then you need to be able to pass the data in the right way to your model:
#app/models/hotel.rb
Class Hotel < ActiveRecord::Base
has_one :address
accepts_nested_attributes_for :address
end
#app/controllers/hotels_controller.rb
Class HotelsController < ApplicationController
def new
#hotel = Hotel.new
#hotel.build_address #-> build_singular for singular assoc. plural.build for plural
end
def create
#hotel = Hotel.new(hotel_params)
#hotel.save
end
private
def hotel_params
params.require(:hotel).permit(:title, :stars, :room, :price, address_attributes: [:each, :address, :attribute])
end
end
This should work for you.
--
Form
Some tips for your form - if you're loading the form & not seeing the f.fields_for block showing, it basically means you've not set your ActiveRecord Model correctly (in the new action)
What I've written above (which is very similar to that written by Pavan) should get it working for you

Associating (has_one) multiple models in form

I'm unable to associate the foreign key before saving the data (the field 'user_id' for the Network model is blank when stored in the database). I'm new to RoR so please excuse me if my code is sloppy :-)
Models:
class Network < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_one :network, :foreign_key => "user_id",
:dependent => :destroy
Controller:
class UsersController < ApplicationController
def new
#user = User.new
#network = #user.build_network
#title = "Sign Up"
end
def create
#user = User.new(params[:user])
if (#user.save)
#network = Network.new(params[:network])
if (#network.save)
sign_in #user
flash[:success] = "Welcome!"
redirect_to #user
else
#title = "Sign up"
render 'new'
end
else
#title = "Sign up"
render 'new'
end
end
I'm able to get all of the user input from the view without issues (using form_for and fields_for). Do I have to explicitly define the has_one association in addition to using #user.build_network ?
Per the suggestion of using accepts_nested_attributes_for, I cleaned up my controller to be
def new
#user = User.new
#user.build_network
#title = "Sign Up"
end
def create
#user = User.new(params[:user])
if (#user.save)
sign_in #user
flash[:success] = "Welcome!"
redirect_to #user
else
#title = "Sign up"
render 'new'
end
end
The updated association in the User Model:
has_one :network, :class_name => 'Network',
:foreign_key => "user_id",
:dependent => :destroy
accepts_nested_attributes_for :network,
:reject_if => :all_blank,
:allow_destroy => true
However, the network_attributes are all blank when I submit the form. I have followed the directions in the links provided as well as http://ryandaigle.com/articles/2009/2/1/what-s-new-in-edge-rails-nested-attributes (and many more)
I don't think it's an issue with the view/form since I'm able to access the attributes using Network.new(params[:network]
Any thoughts/suggestions ?
This is a perfect situation for using accepts_nested_attributes_for
Have a look at http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html as well as http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-fields_for to see how you can easily implement it using form_for
(post-question update)
If you can access the attributes for Network through params[:network], then you have (unfortunately!) missed one of the more subtle parts of nested attributes, which is the invocation of fields_for
I'm assuming you have something along the lines of the following (using HAML syntax for speed):
= form_for #user do |f|
= f.text_field :name
= f.text_field :email_address
- fields_for #network do |n|
= n.text_field :name
The problem with this is that Rails isn't seeing any explicit connection in the form between your User and Network fields. (It's smart, but not that smart)
The way you explicitly state that Network is nested within User (for the sake of updating nested attributes is to make sure you call the fields_for #network function on the User form builder, not by itself:
= form_for #user do |f|
= f.text_field :name
= f.text_field :email_address
- f.fields_for #network do |n|
= n.text_field :name
That way, when you submit your form and inspect params, you'll notice that you've got both params[:user] and params[:user][:network] - which Rails will recognise as nested attributtes and should then save them and link them together.
It's possible you need to set network as attr_accessible, along with other attributes of User model, like this:
attr_accessible :name, :age, :network_attributes, ...
Please note you'll also need to put all attributes you want to 'mass assign' in the list, just like :name and :age in the above example.

Resources