Nested attributes wouldn't save in database - ruby-on-rails

I have two models one Topic and Topic_Content.
With the following code
Route
resources :topics do
resources :topic_contents
end
Topic
class Topic < ActiveRecord::Base
has_one :topic_content
accepts_nested_attributes_for :topic_content
end
TopicContent
class TopicContent < ActiveRecord::Base
belongs_to :topics
end
Controller
class TopicsController < ApplicationController
def new
#topic = Topic.new
end
def create
# render text: params[:topic].inspect
#topic = Topic.new(topic_params)
#topic.save
end
private
def topic_params
params.require(:topic).permit(:title, topic_content_attributes: [:text])
end
end
View
<%= form_for #topic do |f| %>
<%= f.label 'Topic:' %>
<%= f.text_field :title %>
<%= f.fields_for :topic_contents do |tf| %>
<%= tf.label :text %>
<%= tf.text_area :text %> 
<% end %>  
<%= f.submit %>
<% end %>
The title will be saved correct in the topic table but the topic_content(text) wouldn't saved in the database, and I couldn't find the problem.

I'm not a Rails expert, but I'm certain you need to build the association in your controller.
In your new and edit actions you need to have:
def new
#topic = Topic.new
#topic_content = #topic.build_topic_content
end
Because this is a has_one/belongs_to you need to have it look that way. If it was a many association you'd build it with something like #topic_content = #topic.topic_contents.build.
I'm pretty sure it's just a matter of building the association in the right controller, which, I believe, for you, is the topic controller.

Your view should be as follow:
f.fields_for :topic_content do |content_fields|
^

Related

Rails, populate database with associated models on one form

I have looked at various answers to similar questions and haven't quite cracked it.
A wine model is defined with has_one :register, :dependent => :destroy and rightly or wrongly I have added accepts_nested_attributes_for :register. A register is defined with belongs_to :wine.
The code within wines_controller.rb for create is:
def new
#wine = Wine.new
#register = Register.new
def create
#wine = Wine.new(wine_params)
#register = #wine.registers.build(register_params)
respond_to do |format|
if #wine.save
#success
else
format.json { render json: #wine.errors, status: :unprocessable_entity }
format.json { render json: #register.errors, status: :unprocessable_entity }
end
end
end
My form for creating a new wine has the following code:
<%= simple_form_for #wine do |f| %>
# various working elements
<div class="field">
<% f.fields_for :register do |r| %>
<%= r.label :short_name %>
<%= r.text_field :short_name %>
<%= r.label :barcode %>
<%= r.text_field :barcode %>
<% end %>
</div>
When this form is called up no fields are created from the f.fields_for command but this block is executed because I can add test buttons within it to prove it is accessed.
If I try to create a wine I get the following error message:
undefined method `registers' for #<Wine:0x007f1204375330> Did you mean? register register= register_id
I believe that using .build is there to ensure data integrity: I don't want to create a wine that does not have a corresponding register. I have tried thinking about it nested attributes but that seems to be considered a bad plan by many. This current approach feels correct but I think I am missing some understanding of syntax at the very least.
At a later date it will be necessary to have other models linked to register that will not be associated to wines. I was considering a similar approach but I am happy to be told to rethink!
If I understand you correctly you have 2 issues:
Firstly fields for register aren't being displayed - this is partly because #wine.register is nil.
You should change your new action to:
def new
#wine = Wine.new
#wine.register = Register.new
In addition because you are using simple_form_for you will need to use simple_fields_for instead of fields_for
Your second issue that results in the exception tells you everything... you are trying to access #wine.registers, and not #wine.register
Change in your create method to:
#register = #wine.register.build(register_params)
This will fix that issue ... however ... all you really need to do is build the #wine object from your params - your params should be configured to permit the right nested attributes - if it is set up correctly the register object will also be built when building the #wine object.
Your model is already set to accept_nested_attributes and thus will also validate and save the register object when calling #wine.save - no need to explicitly save the register object.
You should have something like:
def wine_params
params.require(:wine).permit(
:attribute1, :attribute2,
register_attributes: [:id, :short_name, :barcode])
end
Try this
Wine and Register models
class Wine < ApplicationRecord
has_one :register, inverse_of: :wine, :dependent => :destroy
accepts_nested_attributes_for :register
end
class Register < ApplicationRecord
belongs_to :wine, inverse_of: :register
validates_presence_of :wine
end
Wines Controller
class WinesController < ApplicationController
def new
#wine = Wine.new
#wine.build_register
end
def create
#wine = Wine.new(wine_params)
if #wine.save
redirect_to #wine
else
render :new
end
end
private
def wine_params
params.require(:wine).permit(:name, register_attributes: [:simple_name])
end
end
My wine_params are specific for
rails g model wine name:string
rails g model register name:string wine_id:integer
Lastly wine form should look like this
<%= form_for #wine do |f|%>
<p>
<%= f.label :name%>
<%= f.text_field :name%>
</p>
<%= f.fields_for :register do |r|%>
<p>
<%= r.label :simple_name%>
<%= r.text_field :simple_name%>
</p>
<% end %>
<%= f.submit %>
<% end %>
So you can modify wine_params and form partial for your application specifics

save multiple columns in one form

I want a User to be able to answer all questions that are assigned to them, in an Answer model. Now I'm trying to create a form that allows me to loop through the questions a User have assigned to them, and answer them in an Answer model.
In the answer model I save the reply, and the question id. However this requires multiple saves in one form, which I'm unable to do.
Model associations look like this:
User
has_many :answers
has_many :questions, through: :question_participants
Answer
belongs_to :user
belongs_to :question
now I'm trying to create an Answer#new form like this:
<%= form_for #answer do |f| %>
<% #questions.each do |question| %>
<h3><%= question.name %></h3>
<%= f.hidden_field :question_id, value: question.id %>
<%= f.text_field :reply, class: 'form-control' %>
<% end %>
<%= f.submit 'Send inn', class: 'btn btn-success' %>
<% end %>
and thus hoping it will allow me to save multiple columns in one, but that doesn't work. It only saves the last column, no matter what.
My answers controller:
class AnswersController < ApplicationController
def new
#questions = current_user.questions
#answer = current_user.answers.new
end
def create
#questions = current_user.questions
#answer = current_user.answers.new(answer_params)
if #answer.save
redirect_to answers_path
else
render 'new'
end
end
private
def answer_params
params.require(:answer).permit(:reply, :question_id)
end
end
What you're looking for is accepts_nested_attributes_for:
This should work:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :answers
has_many :questions, through: :answers
accepts_nested_attributes_for :answers
#this will have to be populated on user create
before_create :build_answers
private
def build_answers
questions = Question.find [1,3,4,6]
questions.each do |question|
user.build_answer(question: question)
end
end
end
#app/models/answer.rb
class Answer < ActiveRecord::Base
#columns id | user_id | question_id | response | created_at | updated_at
belongs_to :user
belongs_to :question
end
#app/models/question.rb
class Question < ActiveRecord::Base
has_many :answers
has_many :users, through: :answers
end
This will give you the ability to do the following:
#config/routes.rb
resources :answers, only: [:edit, :update]
#app/controllers/answers_controller.rb
class AnswersController < ApplicationController
def edit
#questions = current_user.questions
end
def update
#answers = current_user.answers.update answer_params
end
private
def answer_params
params.require(:answer).permit(:response) #-> question_id and user_id set on create, don't need to be changed
end
end
This will allow you to use the following form:
#app/views/answers/edit.html.erb
<%= form_tag answers_update_path, method: :patch do |f| %>
<% #questions.each do |question| %>
<%= f.fields_for "answers[]", question do |qf| %>
<%= qf.label question.title %>
<%= qf.text_field :response %>
<% end %>
<% end %>
<%= f.submit %>
<% end %>
--
Typically, you'd use the accepts_nested_attributes_for with the nested model. However, since you just want multiple answer responses, you can use the above.
The bugs in this would likely be in the strong params, or in the form declaration (IE current_user.questions). If you reply with information, I'll write some upates
Ref: Multiple objects in a Rails form

company and user create in one form

Im trying to make a sign up form where you can create a user and a company at the same time. I have a user table and a company table.
user model
belongs_to :companies
accepts_nested_attributes_for :companies
company model
has_many :users
users controller
def new
#users = User.new
#companies = Company.new
end
def create
#companies = Company.create(company_params)
#users = #companies.user.create(user_params)
#users.save
redirect_to :back
end
private
def user_params
params.require(:user).permit(:name)
end
def company_params
params.require(:user).permit(:name)
end
Routes
resources :users
resources :companies
new.html.erb
<%= form_for(#users) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.fields_for #companies do |build| %>
<%= build.text_field :name %>
<% end %>
<%= f.submit %>
<% end %>
So this create a company and a user at the same time. But i want the user to be assigned a company_id there belongs to the company they just created. I have the company_id field in my users table. Any ideas?
UPDATE
def create
#companies = Company.create(company_params)
c = Company.last
#users = c.users.create(user_params)
#users.save
redirect_to :back
end
I did add this to the user controller at it works, but it doesn't seems like the best solution. Please correct me if i'm wrong.
Thanks in advance
I would do in the following way:
Your User Model
belongs_to :companies,:inverse_of => :users
accepts_nested_attributes_for :companies
Your controller
def new
#users = User.new
#users.build_company
end
def create
#users = User.new(user_company_params)
#users.save
end
def user_company_params
params.require(:user).permit(:name, companies_attributes: name)
end
Your View
<%= f.fields_for :companies do |build| %>
<%= build.text_field :name %>
<% end %>
It works for me :)
Source:
http://guides.rubyonrails.org/association_basics.html

Ruby on Rails: create records for multiple models with one form and one submit

I have a 3 models: quote, customer, and item. Each quote has one customer and one item. I would like to create a new quote, a new customer, and a new item in their respective tables when I press the submit button. I have looked at other questions and railscasts and either they don't work for my situation or I don't know how to implement them.
quote.rb
class Quote < ActiveRecord::Base
attr_accessible :quote_number
has_one :customer
has_one :item
end
customer.rb
class Customer < ActiveRecord::Base
attr_accessible :firstname, :lastname
#unsure of what to put here
#a customer can have multiple quotes, so would i use has_many or belongs_to?
belongs_to :quote
end
item.rb
class Item < ActiveRecord::Base
attr_accessible :name, :description
#also unsure about this
#each item can also be in multiple quotes
belongs_to :quote
quotes_controller.rb
class QuotesController < ApplicationController
def index
#quote = Quote.new
#customer = Customer.new
#item = item.new
end
def create
#quote = Quote.new(params[:quote])
#quote.save
#customer = Customer.new(params[:customer])
#customer.save
#item = Item.new(params[:item])
#item.save
end
end
items_controller.rb
class ItemsController < ApplicationController
def index
end
def new
#item = Item.new
end
def create
#item = Item.new(params[:item])
#item.save
end
end
customers_controller.rb
class CustomersController < ApplicationController
def index
end
def new
#customer = Customer.new
end
def create
#customer = Customer.new(params[:customer])
#customer.save
end
end
my form for quotes/new.html.erb
<%= form_for #quote do |f| %>
<%= f.fields_for #customer do |builder| %>
<%= label_tag :firstname %>
<%= builder.text_field :firstname %>
<%= label_tag :lastname %>
<%= builder.text_field :lastname %>
<% end %>
<%= f.fields_for #item do |builder| %>
<%= label_tag :name %>
<%= builder.text_field :name %>
<%= label_tag :description %>
<%= builder.text_field :description %>
<% end %>
<%= label_tag :quote_number %>
<%= f.text_field :quote_number %>
<%= f.submit %>
<% end %>
When I try submitting that I get an error:
Can't mass-assign protected attributes: item, customer
So to try and fix it I updated the attr_accessible in quote.rb to include :item, :customer but then I get this error:
Item(#) expected, got ActiveSupport::HashWithIndifferentAccess(#)
Any help would be greatly appreciated.
To submit a form and it's associated children you need to use accepts_nested_attributes_for
To do this, you need to declare it at the model for the controller you are going to use (in your case, it looks like the Quote Controller.
class Quote < ActiveRecord::Base
attr_accessible :quote_number
has_one :customer
has_one :item
accepts_nested_attributes_for :customers, :items
end
Also, you need to make sure you declare which attributes are accessible so you avoid other mass assignment errors.
If you want add info for diferent models i suggest to apply nested_model_form like this reference: http://railscasts.com/episodes/196-nested-model-form-part-1?view=asciicast.
This solution is very simple and cleanest.

Associating two records after create in Rails

I'm working on an association between two models:
class Person < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_one :person
end
Many person records exist in the system that don't necessarily correspond to a user, but when creating a user you need to either create a new person record or associate to an existing one.
What would be the best way to associate these two models when the person record already exists? Do I need to manually assign the user_id field, or is there a Rails way of doing that?
Where #user is a recently created user, and #person is an existing person.
#user.person = #person
#user.save
Alternately:
User.new :person => #person, ... #other attributes
or in params form:
User.new(params[:user].merge({person => #person}))
As far as forms go:
<% form_for #user do |f| %>
...
<% fields_for :person do |p| %>
<%= p.collection_select, :id, Person.all, :id, :name, :include_blank => "Use fields to create a person"%>
<%= p.label_for :name%>
<%= p.text_field :name %>
...
<% end %>
<% end %>
And in the user controller:
def create
#user = User.create(params[:user])
#person = nil
if params[:person][:id]
#person = Person.find(params[:person][:id])
else
#person = Person.create(params[:person])
end
#user.person = #person
...
end
If you don't want to create/alter a form for this, you can do
#person_instance.user = #user_instance
For has_many relationships, it would be:
#person_instance.users << #user_instance
You first have to do a nested form :
<% form_for #user do |user| %>
<%= user.text_field :name %>
<% user.fields_for user.person do |person| %>
<%= person.text_field :name %>
<% end %>
<%= submit_tag %>
<% end %>
In your User model :
class User < ActiveRecord::Base
accepts_nested_attributes_for :person
end
If you want the person deleted when the user is :
class User < ActiveRecord::Base
accepts_nested_attributes_for :person, :allow_destroy => true
end
And in your controller do nothing :
class UserController < ApplicationController
def new
#user = User.new
#find the person you need
#user.person = Person.find(:first)
end
def create
#user = User.new(params[:user])
#user.save ? redirect_to(user_path(#user)) : render(:action => :new)
end
end

Resources