Creating nested models in Rails 4 forum app - ruby-on-rails

Hello I am making a Forum application in Rails 4. It can have numerous forums, each with numerous topics. Each topic can have many posts. When creating a new topic, one must also create the initial post, much like Stack Overflow itself. Therefore, I have a text area in the "New Topic" form that allows this with a fields_for method. The Problem is, when you click the "Create Topic" button after filling out the form (including the "post" field), the transaction is rolled back. The following validation error appears:
3 errors prohibited this topic from being saved:
Posts forum must exist
Posts topic must exist
Posts user must exist
This is my form: app/views/topics/_form.html.erb
<%= form_for([ #forum, topic ]) do |f| %>
<% if topic.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(topic.errors.count, "error") %> prohibited this topic from being saved:</h2>
<ul>
<% topic.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :title %><br>
<%= f.text_field :title %>
</div>
<div class="field">
<%= f.fields_for :posts do |builder| %>
<%= builder.label :content %><br>
<%= builder.cktext_area :content, class: 'ckeditor' %>
<% end %>
</div>
<div class="actions">
<%= f.submit 'Create Topic', class: "btn btn-l btn-success" %>
</div>
<% end %>
Models: forum.rb
class Forum < ApplicationRecord
has_many :topics, dependent: :destroy
has_many :posts, through: :topics
def most_recent_post
topic = Topic.last
return topic
end
end
topic.rb
class Topic < ApplicationRecord
belongs_to :forum
belongs_to :user
has_many :posts, dependent: :destroy
accepts_nested_attributes_for :posts
end
post.rb
class Post < ApplicationRecord
belongs_to :forum
belongs_to :topic
belongs_to :user
validates :content, presence: true
end
The controller for topics, app/controllers/topics_controller.rb
class TopicsController < ApplicationController
before_action :get_forum
before_action :set_topic, only: [:show, :edit, :update, :destroy]
# GET /topics
# GET /topics.json
def index
#topics = #forum.topics
end
# GET /topics/1
# GET /topics/1.json
def show
end
# GET /topics/new
def new
#topic = #forum.topics.build
#topic.posts.build
end
# GET /topics/1/edit
def edit
# #topic.posts.build
end
# POST /topics
# POST /topics.json
def create
#topic = #forum.topics.build(topic_params.merge(user_id: current_user.id))
#topic.last_poster_id = #topic.user_id
respond_to do |format|
if #topic.save
format.html { redirect_to forum_topic_path(#forum, #topic), notice: 'Topic was successfully created.' }
format.json { render :show, status: :created, location: #topic }
else
format.html { render :new }
format.json { render json: #topic.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /topics/1
# PATCH/PUT /topics/1.json
def update
respond_to do |format|
if #topic.update(topic_params)
format.html { redirect_to forum_topic_path(#forum, #topic), notice: 'Topic was successfully updated.' }
format.json { render :show, status: :ok, location: #topic }
else
format.html { render :edit }
format.json { render json: #topic.errors, status: :unprocessable_entity }
end
end
end
# DELETE /topics/1
# DELETE /topics/1.json
def destroy
#topic.destroy
respond_to do |format|
format.html { redirect_to forum_path(#forum), notice: 'Topic was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def get_forum
#forum = Forum.find(params[:forum_id])
end
def set_topic
#topic = Topic.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def topic_params
params.require(:topic).permit(:title, :last_poster_id, :last_post_at, :tags, :forum_id, :user_id, posts_attributes: [:id, :content])
end
end
As you see I've added the posts_attributes to the strong parameters for topic. These are the only fields that posts have besides the foreign key references (:forum_id, :topic_id, :user_id). And I've tried putting those attributes in, but I get the same error.
Finally, this is my routes.rb
Rails.application.routes.draw do
resources :forums do
resources :topics do
resources :posts
end
end
resources :sessions
resources :users
mount Ckeditor::Engine => '/ckeditor'
end
I should also mention that I have tried adding hidden_fields inside of fields_for, with the id criteria for #forum, #topic, and current_user. That throws the same validation error.
What am I missing? I feel like it's something in the controller. Like I'm not saving it properly. Every tutorial I've seen has it this way. Except for the Rails <=3 versions, which are way different because of no strong_params.
Any ideas? Thanks for the help!
EDIT Here is the log output when I try to submit a topic entitled "I am a title" and the content "I am some content"...
Started POST "/forums/1/topics" for 127.0.0.1 at 2016-01-31 09:03:33 -0500
Processing by TopicsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"pYt842XQHiOKqNjPHBO8lNP2z92gHF7Lpt24CppbuvHR/cFHky3FVCpBs77p7WFRKmYBHgeZQjx0sE+DI+Q+sQ==", "topic"=>{"title"=>"I am a title", "posts_attributes"=>{"0"=>{"content"=>"<p>I am some content</p>\r\n"}}}, "commit"=>"Create Topic", "forum_id"=>"1"}
Forum Load (0.6ms) SELECT "forums".* FROM "forums" WHERE "forums"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
User Load (0.6ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
(0.3ms) BEGIN
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
(0.4ms) ROLLBACK

This is not a direct answer; too long for comment.
One of the issues you have with your routes is that you're nesting too many resources:
Resources should never be nested more than 1 level deep...
resources :x do
resources :y
end
--
Although you can do what you're doing, it would perhaps be better to use a scope:
#config/routes.rb
scope ':forum' do
resources :topics do
resources :posts
end
end
The issue you're facing is that things can get very complicated, very quickly. Although the
This way, you could make the forums CRUD accessible in its own set of functionality:
#config/routes.rb
resources :forums #-> only accessible to admins?
scope ...
Either way, you'd still need to define your routes with the forum present:
<%= link_to "Test", [#forum, #topic, #post] %>

Related

Rails 5 Independent View Nested Resource - param is missing or the value is empty

I have a nested resource called PracticeQuestion, which is a child of PracticeQuiz. I want users to be able to go through one question at a time when they are at a PracticeQuiz. For example: foo.com/practice_quizzes/1/practice_questions/1..n
I got the practice quizzes working, but when I try to add a new practice question, I get a rails error that says that the param is missing or empty, but I don't see what i'm doing wrong. Please help
practice_quiz.rb
class PracticeQuiz < ApplicationRecord
belongs_to :user, optional: true
validates :user, presence: true
has_many :practice_questions, dependent: :destroy
end
practice_question.rb
class PracticeQuestion < ApplicationRecord
belongs_to :user, optional: true
belongs_to :practice_quiz
end
practice_questions_controller.rb
class PracticeQuestionsController < ApplicationController
before_action :set_practice_question, only: [:show, :edit, :update, :destroy]
def index
#practice_questions = PracticeQuestion.all
end
def show
end
# GET /practice_questions/new
def new
#practice_quiz = PracticeQuiz.friendly.find(params[:practice_quiz_id])
#practice_question = PracticeQuestion.new
end
# GET /practice_questions/1/edit
def edit
end
def create
#practice_quiz = PracticeQuiz.friendly.find(params[:practice_quiz_id])
#practice_question = PracticeQuestion.new(practice_question_params)
respond_to do |format|
if #practice_question.save
format.html { redirect_to #practice_question, notice: 'Practice question was successfully created.' }
format.json { render :show, status: :created, location: #practice_question }
else
format.html { render :new }
format.json { render json: #practice_question.errors, status: :unprocessable_entity }
end
end
end
private
def set_practice_question
#practice_question = PracticeQuestion.find(params[:id])
end
def practice_question_params
params.require(:practice_question).permit(:question, :explanation, :flagged)
end
end
views/practice_questions/_form.html.erb
<%= form_with(url: practice_quiz_practice_questions_path, local: true) do |form| %>
<% if practice_question.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(practice_question.errors.count, "error") %> prohibited this practice_question from being saved:</h2>
<ul>
<% practice_question.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= form.label :question %>
<%= form.text_field :question %>
</div>
<div class="field">
<%= form.label :explanation %>
<%= form.text_field :explanation %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
routes.rb
resources :practice_quizzes do
resources :practice_questions
end
I set the practice_questions controller's new method to find the id of the parent resource, but I get this error. I'm pretty sure I'm following rails naming conventions fine too.
ActionController::ParameterMissing at /practice_quizzes/23535/practice_questions
param is missing or the value is empty: practice_question
Update: here's the results from the rails server window
Processing by PracticeQuestionsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"7dpxBB7jjZWzicCYWGA8yeTPSc9UeaqNDOavKQai2vMISryPBiMZ9Zo4LLS3DgZQI8IJc7rLh2TXd9Fj8PAjiA==", "question"=>"235235", "explanation"=>"25235232", "commit"=>"Save ", "practice_quiz_id"=>"23535"}
User Load (0.8ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 1], ["LIMIT", 1]]
PracticeQuiz Load (0.9ms) SELECT "practice_quizzes".* FROM "practice_quizzes" WHERE "practice_quizzes"."slug" = $1 ORDER BY "practice_quizzes"."id" ASC LIMIT $2 [["slug", "23535"], ["LIMIT", 1]]
Completed 400 Bad Request in 102ms (ActiveRecord: 1.7ms)
ActionController::ParameterMissing - param is missing or the value is empty: practice_question:
app/controllers/practice_questions_controller.rb:76:in `practice_question_params'
app/controllers/practice_questions_controller.rb:30:in `create'
Update 2:
views/practice_questions/new.html.erb
<h1>New Practice Question</h1>
<%= render 'form', practice_question: #practice_question %>
Here
def practice_question_params
params.require(:practice_question).permit(:question, :explanation, :flagged)
end
you are using rails strong parameters. See this answer https://stackoverflow.com/a/30826895/2627121
Basically, params.require(:practice_question) means that you must have practice_question parameter.
Here
Parameters: {"utf8"=>"✓", "authenticity_token"=>"", "question"=>"235235", "explanation"=>"25235232", "commit"=>"Save ", "practice_quiz_id"=>"23535"}
you have question and explanation as root parameters, when according to your strong parameters declaration you must have
"practice_question" => { "question"=>"235235", "explanation"=>"25235232" }
You should edit form fields to have name as practice_question[question]

Create data through a form edit from another controller via nested attributes

I'm trying through an edit form to add values ​​to actions in other controllers. I have the following models:
class Day < ApplicationRecord
belongs_to :goal
has_many :day_salesmen, dependent: :destroy
has_many :salesmen, through: :day_salesmen
validates_presence_of :date_day, :goal_id
accepts_nested_attributes_for :day_salesmen
end
class Salesman < ApplicationRecord
belongs_to :company
has_many :goal_salesmen, dependent: :destroy
has_many :goals, through: :goal_salesmen
has_many :day_salesmen, dependent: :destroy
has_many :days, through: :day_salesmen
end
class DaySalesman < ApplicationRecord
belongs_to :day
belongs_to :salesman
accepts_nested_attributes_for :salesman
end
In other words, I have a day that can have many employees and many employees can be part of a day.
When I edit the day I want it to be possible to add employee and associate them to my day through the day_salesman table.
I'm trying to do this, but I get the following error log:
ActionController::ParameterMissing (param is missing or the value is empty: salesman):
app/controllers/salesmen_controller.rb:49:in `params_salesman'
app/controllers/salesmen_controller.rb:17:in `create'
Rendering /box/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb within rescues/layout
Rendering /box/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_source.html.erb
Rendered /box/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_source.html.erb (32.8ms)
Rendering /box/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb
Rendered /box/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb (28.5ms)
Rendering /box/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb
Rendered /box/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb (53.3ms)
Rendered /box/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb within rescues/layout (216.0ms)
Started POST "/companies/3/salesmen" for 172.24.0.1 at 2017-10-31 15:21:09 +0000
Cannot render console from 172.24.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by SalesmenController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"/5ULp1WOehoaJIZL0SaCSDYU9MssS7ZQ5EfyTkmZCyFSvogj6lOtxOTuNTx8AjdeRjAnkkd3XhD5V30/QAXijg==", "day"=>{"value"=>"400", "day_salesman"=>{"salesman"=>{"name"=>"Denis"}}},"commit"=>"Create","company_id"=>"3"}
[1m[36mOwner Load (3.5ms)[0m [1m[34mSELECT "owners".* FROM "owners" WHERE "owners"."id" = $1 ORDER BY "owners"."id" ASC LIMIT $2[0m [["id", 1], ["LIMIT", 1]]
Completed 400 Bad Request in 108ms (ActiveRecord: 12.7ms)
My controllers are:
class SalesmenController < ApplicationController
before_action :find_salesman, only: [:edit, :update, :destroy, :show]
def index
#salesman = current_owner.companies.find(params[:company_id]).salesman
end
def new
end
def show
end
def create
#salesman = Salesman.new(params_salesman)
if #salesman.save
flash[:notice] = "Salesman saved!"
else
flash.now[:error] = "Cannot create salesman!"
render :new
end
end
def edit
end
def update
if #salesman.update(params_salesman)
flash[:notice] = "salesman updated!"
else
flash.now[:error] = "Could not update salesman!"
render :edit
end
end
def destroy
#salesman.destroy
end
private
def find_salesman
#salesman = Salesman.find(params[:id])
end
def params_salesman
params.require(:salesman).permit(:name).merge(company_id: params[:company_id])
end
end
DaysController:
class DaysController < ApplicationController
before_action :find_day, only: [:show, :edit, :update]
def index
#day = current_owner.companies.find(params[:company_id]).goal.find(params[:goal_id]).days
end
def show
end
def edit
#dayup = Day.new
#day_salesmen = #dayup.day_salesmen.build
#salesman = #day_salesmen.build_salesman
end
def update
if #day.update(params_day)
flash[:notice] = "Day updated!"
redirect_to company_salesman_path(:id => #day.id)
else
flash.now[:error] = "Could not update day!"
render :edit
end
end
private
def find_day
#day = Day.find(params[:id])
end
def params_day
params.require(:day).permit(:value, day_salesman_attributes: [:id, salesman_attributes:[:name]]).merge(goal_id: params[:goal_id])
end
end
My view edit for controller days:
<%= form_for(#dayup, url: company_salesmen_path) do |f| %>
<%= f.label :value_of_day %>
<%= f.number_field :value %>
<%= f.fields_for :day_salesman do |ff| %>
<%= ff.fields_for :salesman do |fff| %>
<%= fff.label :names_of_salesmen %>
<%= fff.text_field :name %>
<% end %>
<% end %>
<%= f.submit "Create" %>
<% end %>
My routes are:
Rails.application.routes.draw do
root to: 'companies#index'
resources :companies do
resources :salesmen
resources :goals do
resources :days
end
end
devise_for :owners, :controllers => { registrations: 'registrations' }
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
I'm trying to use nested attributes, but it seems like I'm applying the wrong way, can someone help you get a salesman through the edit form of the days and relate them?
In salesmen_controller.rb in should be like this, i m not sure what are you using in strong parameter. please have a try and modify it accordinly
def params_salesman
params.require(:day).require(:day_salesman).require(:salesman).permit(:name).merge(company_id: params[:company_id])
end
I would try and replace
<%= f.fields_for :salesman do |fff| %>
With:
<%= ff.fields_for :salesman do |fff| %>
to get proper form builder.

Why Rails 5 is not saving nested attributes because parent model is not saving first

I am using Rails 5 and everything at its newest stable versions. So I get the following :
You have your association set to required but it's missing.
Associations are set to required by default in rails 5 so if you want
to keep one empty you need to set optional:true on your association in
mode
This is great and I understand what is going on however for the life of me I cannot figure out how to get the parent model to save first so the user_id is translated the nested models record. I see the same answer above everywhere however no one explains a work around other than turning the default in the initializer from true to false. THIS DOES NOT SOLVE THE PROBLEM, because the record sure does save but it does not include the user_id.
Below is what I have for my code base, I would ask rather than responding with the above quote, could someone enlighten me on HOW to get the USER_ID field into the nested attributes while saving. I refuse to disable validation and manually handle the insertion, as this is not the ruby way and breaks from standards!
Thanks in advance for anyone who can answer this question directly and without vague explanations that digress from the ruby way of things!
###Models
#Users
class User < ApplicationRecord
has_one :profile, inverse_of: :user
accepts_nested_attributes_for :profile, allow_destroy: true
end
#Profiles
class Profile < ApplicationRecord
belongs_to :user, inverse_of: :profile
end
###Controller
class UsersController < ApplicationController
before_action :set_user, only: [:show, :edit, :update, :destroy]
# GET /users
# GET /users.json
def index
#users = User.all
end
# GET /users/1
# GET /users/1.json
def show
end
# GET /users/new
def new
#user = User.new
#user.build_profile
end
# GET /users/1/edit
def edit
#user.build_profile
end
# POST /users
# POST /users.json
def create
#user = User.new(user_params)
respond_to do |format|
if #user.save
format.html { redirect_to #user, notice: 'User was successfully created.' }
format.json { render :show, status: :created, location: #user }
else
format.html { render :new }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /users/1
# PATCH/PUT /users/1.json
def update
respond_to do |format|
if #user.update(user_params)
format.html { redirect_to #user, notice: 'User was successfully updated.' }
format.json { render :show, status: :ok, location: #user }
else
format.html { render :edit }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
# DELETE /users/1
# DELETE /users/1.json
def destroy
#user.destroy
respond_to do |format|
format.html { redirect_to users_url, notice: 'User was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_user
#user = User.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def user_params
params.require(:user).permit(:username, :password, :user_type_id, profile_attributes: [:user_id, :first_name, :middle_name, :last_name, :phone_number, :cell_number, :email])
end
end
##View
<%= form_for(#user) do |f| %>
<% if user.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(user.errors.count, "error") %> prohibited this user from being saved:</h2>
<ul>
<% user.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
<!--<li><%= debug f %></li>-->
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :username %>
<%= f.text_field :username %>
</div>
<div class="field">
<%= f.label :password %>
<%= f.text_field :password %>
</div>
<div class="field">
<% if params[:trainer] == "true" %>
<%= f.label :user_type_id %>
<%= f.text_field :user_type_id, :readonly => true, :value => '2' %>
<% else %>
<%= f.label :user_type_id %>
<%= f.text_field :user_type_id, :readonly => true, :value => '1' %>
<% end %>
</div>
<h2>Account Profile</h2>
<%= f.fields_for :profile do |profile| %>
<%#= profile.inspect %>
<div>
<%= profile.label :first_name %>
<%= profile.text_field :first_name %>
</div>
<div>
<%= profile.label :middle_name %>
<%= profile.text_field :middle_name %>
</div>
<div>
<%= profile.label :last_name %>
<%= profile.text_field :last_name %>
</div>
<div>
<%= profile.label :email %>
<%= profile.text_field :email %>
</div>
<div>
<%= profile.label :phone_number %>
<%= profile.telephone_field :phone_number %>
</div>
<div>
<%= profile.label :cell_phone %>
<%= profile.telephone_field :cell_number %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<%= debug params %>
<%= debug user %>
<%= debug user.profile %>
<% end %>
UPDATE
For starters I have figured out that you need to include autosave: true to the relationship like so
class User < ApplicationRecord
has_one :profile, inverse_of: :user, autosave: true
accepts_nested_attributes_for :profile, allow_destroy: true
end
Then the parent record gets saved before the child. Now comes another gotcha that I am just not sure about and is odd when the form is submitted you will notice in the console output I pasted below that the INSERT INTO profiles statement includes the user_id column and the value of 1. It passees validation and looks like it runs properly from the output, however the user_id column in the profiles table is still null. I am going to keep digging, hopefuly one of my fellow rubyiests out there will see this and have some ideas on how to finish fixing this. I love Rails 5 improvements so far but it wouldn't be ROR without small interesting gotchas! Thanks again in advance!
Started POST "/users" for 192.168.0.31 at 2017-03-12 22:28:14 -0400
Cannot render console from 192.168.0.31! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by UsersController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"YA7kQnScvlIBy5OiT+BmOQ2bR7J00ANXId38FqNwX37Cejd+6faUyD3rMF4y0qJNKBUYGaxrRZqcLrXonL6ymA==", "user"=>{"username"=>"john", "password"=>"[FILTERED]", "user_type_id"=>"1", "profile_attributes"=>{"first_name"=>"john", "middle_name"=>"r", "last_name"=>"tung", "email"=>"thegugaru#gmail.com", "phone_number"=>"8033207677", "cell_number"=>"8033207677"}}, "commit"=>"Create User"}
(0.1ms) BEGIN
SQL (0.3ms) INSERT INTO `users` (`username`, `password`, `user_type_id`, `created_at`, `updated_at`) VALUES ('john', '0000', 1, '2017-03-13 02:28:14', '2017-03-13 02:28:14')
SQL (0.4ms) INSERT INTO `profiles` (`user_id`, `email`, `first_name`, `middle_name`, `last_name`, `phone_number`, `cell_number`, `created_at`, `updated_at`) VALUES (1, 'thegu#gmail.com', 'john', 'r', 'tung', '8033207677', '8033207677', '2017-03-13 02:28:14', '2017-03-13 02:28:14')
(10.8ms) COMMIT
Redirected to http://192.168.0.51:3000/users/1
Completed 302 Found in 24ms (ActiveRecord: 11.5ms)
Ok, I am answering my own question because I know many people are struggling with this and I actually have the answer and not a vague response to the documentation.
First we will just be using a one to one relationship for this example. When you create your relationships you need to make sure that the parent model has the following
inverse_of:
autosave: true
accepts_nested_attributes_for :model, allow_destroy:true
Here is the Users model then I will explain,
class User < ApplicationRecord
has_one :profile, inverse_of: :user, autosave: true
accepts_nested_attributes_for :profile, allow_destroy: true
end
in Rails 5 you need inverse_of: because this tells Rails that there is a relationship through foreign key and that it needs to be set on the nested model when saving your form data.
Now if you were to leave autosave: true off from the relationship line you are left with the user_id not saving to the profiles table and just the other columns, unless you have validations off and then it won't error out it will just save it without the user_id.
What is going on here is autosave: true is making sure that the user record is saved first so that it has the user_id to store in the nested attributes for the profile model.
That is it in a nutshell why the user_id was not traversing to the child and it was rolling back rather than committing.
Also one last gotcha is there are some posts out there telling you in your controller for the edit route you should add #user.build_profile like I have in my post. DO NOT DO IT THEY ARE DEAD WRONG, after assessing the console output it results in
Started GET "/users/1/edit" for 192.168.0.31 at 2017-03-12 22:38:17 -0400
Cannot render console from 192.168.0.31! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by UsersController#edit as HTML
Parameters: {"id"=>"1"}
User Load (0.4ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
Profile Load (0.5ms) SELECT `profiles`.* FROM `profiles` WHERE `profiles`.`user_id` = 1 LIMIT 1
(0.1ms) BEGIN
SQL (0.5ms) UPDATE `profiles` SET `user_id` = NULL, `updated_at` = '2017-03-13 02:38:17' WHERE `profiles`.`id` = 1
(59.5ms) COMMIT
Rendering users/edit.html.erb within layouts/application
Rendered users/_form.html.erb (44.8ms)
Rendered users/edit.html.erb within layouts/application (50.2ms)
Completed 200 OK in 174ms (Views: 98.6ms | ActiveRecord: 61.1ms)
If you look it is rebuilding the profile from scratch and resetting the user_id to null for the record that matches the current user you are editing.
So be very careful of this as I have seen tons of posts making this suggestion and it cost me DAYS of research to find a solution!

Form With Nested Attributes Not Saving Rails 5

I have a Client and Office Address Model, I want to create the office address when the client is created, so to do this I have decided to go the nested attribute direction.
When I try to create the Client with the Office Address I get this in the server output, doesn't give me much to go on and not sure how to proceed.
Started POST "/clients" for 127.0.0.1 at 2016-10-26 21:57:06 -0600
Processing by ClientsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"oC4Bwgw8zQrQCGU6RVGXXVwgWGIbOGmyP9gmJYUbyKXVXzgdeRGrp/wMnsmbF6spSeNxTpcHLJx+ZceBKjHxvQ==", "client"=>{"account_id"=>"", "name"=>"Test Client", "client_type"=>"Corp", "client_ident"=>"1234567890", "office_address_attributes"=>{"client_id"=>"", "unit_number"=>"317", "street_number"=>"1717", "street_name"=>"60 st SE", "city"=>"Clagary", "prov_state"=>"Alberta", "postal_zip"=>"T2A7Y7", "country"=>"CA"}}, "commit"=>"Create Client"}
Account Load (0.1ms) SELECT "public"."accounts".* FROM "public"."accounts" WHERE "public"."accounts"."subdomain" = $1 LIMIT $2 [["subdomain", "shawnwilson"], ["LIMIT", 1]]
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 1], ["LIMIT", 1]]
(0.1ms) BEGIN
(0.1ms) ROLLBACK
Rendering clients/new.html.erb within layouts/application
Rendered clients/_form.html.erb (32.8ms)
Rendered clients/new.html.erb within layouts/application (34.4ms)
Rendered shared/_signed_in_nav.html.erb (0.7ms)
Completed 200 OK in 109ms (Views: 102.0ms | ActiveRecord: 1.2ms)
So when I create the client I want to associate the client to the account and I want to associate the OfficeAddress to the Client.
My Client Model
class Client < ApplicationRecord
belongs_to :account, required: true
has_one :office_address
validates :office_address, presence: true
accepts_nested_attributes_for :office_address
end
My Office Address Model
class OfficeAddress < ApplicationRecord
belongs_to :client, required: true
end
My Client Controller
class ClientsController < ApplicationController
before_action :set_client, only: [:show, :edit, :update, :destroy]
# GET /clients
# GET /clients.json
def index
#clients = Client.all
end
# GET /clients/1
# GET /clients/1.json
def show
end
# GET /clients/new
def new
#client = Client.new
#client.build_office_address
end
# GET /clients/1/edit
def edit
end
# POST /clients
# POST /clients.json
def create
#client = Client.new(client_params)
respond_to do |format|
if #client.save
format.html { redirect_to #client, notice: 'Client was successfully created.' }
format.json { render :show, status: :created, location: #client }
else
format.html { render :new }
format.json { render json: #client.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /clients/1
# PATCH/PUT /clients/1.json
def update
respond_to do |format|
if #client.update(client_params)
format.html { redirect_to #client, notice: 'Client was successfully updated.' }
format.json { render :show, status: :ok, location: #client }
else
format.html { render :edit }
format.json { render json: #client.errors, status: :unprocessable_entity }
end
end
end
# DELETE /clients/1
# DELETE /clients/1.json
def destroy
#client.destroy
respond_to do |format|
format.html { redirect_to clients_url, notice: 'Client was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_client
#client = Client.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def client_params
params.require(:client).permit(:account_id, :name, :client_type, :client_ident, office_address_attributes: [:unit_number, :street_number, :street_name, :city, :prov_state, :postal_zip, :country, :client_id])
end
end
My Form
<%= simple_form_for(#client) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :account_id %>
<%= f.input :name %>
<%= f.input :client_type %>
<%= f.input :client_ident %>
</div>
<%= f.fields_for :office_address do |oa| %>
<%= oa.input :client_id %>
<%= oa.input :unit_number %>
<%= oa.input :street_number %>
<%= oa.input :street_name %>
<%= oa.input :city %>
<%= oa.input :prov_state %>
<%= oa.input :postal_zip %>
<%= oa.input :country %>
<% end %>
<div class="form-actions">
<%= f.button :submit %>
</div>
<% end %>
Any assistance here would be much appreciated!
EDIT # 1 - Adds Byebug Errors
(byebug) #client.errors
#<ActiveModel::Errors:0x007fb249813488 #base=#<Client id: nil, account_id: nil, name: "Test Client", client_type: "Corp", client_ident: "1234567890", created_at: nil, updated_at: nil>, #messages={}, #details={}>
(byebug)
If you don't want to turn off validation on related models ( which is not ideal in some cases ) then you should set inverse_of like so
has_one :office_address, inverse_of: :client
inverse_of is really worth knowing about, this blog explains it well:
https://www.viget.com/articles/exploring-the-inverse-of-option-on-rails-model-associations
Please change you association as per below:
class OfficeAddress < ApplicationRecord
belongs_to :client, optional: true
end
Rails 5 association belongs to filed validate your client id, so your entry is getting rollback.
I fixed this issue by adding update_attributes to the create method of the client controller. like so:
def create
#client = Client.new(client_params)
respond_to do |format|
if #client.save
### This is what I added ###
#client.update_attributes!(account_id: #current_account.id)
#client.office_address.update_attributes!(client_id: #client.id)
format.html { redirect_to #client, notice: 'Client was successfully created.' }
format.json { render :show, status: :created, location: #client }
else
puts #client.errors
format.html { render :new }
format.json { render json: #client.errors, status: :unprocessable_entity }
end
end
end
My not be the best solution, however it gets it working.

Rails assign id in model

I would like to assign the event_option_id to the registration. I can easily do it in the view by adding this to the form:
<%= f.text_field :event_option_id, value: #event_option.id %>
I would like to do it in the model not in the view. For security I'm doing the same for the registration price. Setting the price from the model is working but doing the same thing for the event_option_id is not.
Registration Model:
class Registration < ActiveRecord::Base
belongs_to :event_option
belongs_to :order_item
belongs_to :order
before_save :set_event_options
def order_present
if order.nil?
errors.add(:order, "is not a valid order.")
end
end
def registration_price
self[:price] = event_option.price
end
def event_option_id
self.event_option_id = event_option
end
private
def set_event_options
self[:price] = registration_price
self.event_option_id = event_option_id
end
end
EventOptions model:
class EventOption < ActiveRecord::Base
belongs_to :event
has_many :registrations
end
Create Method in the Registrations controller:
def create
#event_option = EventOption.find(params[:id])
#order = current_order
#registration = #order.registrations.build(registration_params)
##registration = Registration.new(registration_params)
#order_id = current_order.id
respond_to do |format|
if #registration.save
format.html { redirect_to #registration, notice: 'Registration was successfully created.' }
format.json { render :show, status: :created, location: #registration }
format.js {}
#order.save
session[:order_id] = #order.id
else
format.html { render :new }
format.json { render json: #registration.errors, status: :unprocessable_entity }
end
end
Error in log:
Started POST "/registrations" for 127.0.0.1 at 2016-01-04 21:16:06 -0500
Processing by RegistrationsController#create as JS
Parameters: {"utf8"=>"âo"", "registration"=>{"name"=>"saasas", "lastname"=>"asas"}, "commit"=>"Create Registration"}
EventOption Load (0.0ms) SELECT "event_options".* FROM "event_options" WHERE "event_options"."id" = ? LIMIT 1 [["id", nil]]
Completed 404 Not Found in 8ms (ActiveRecord: 0.0ms)
ActiveRecord::RecordNotFound (Couldn't find EventOption with 'id'=):
app/controllers/registrations_controller.rb:27:in `create'
Rendered C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/actionpack-4.2.4/lib/action_dispatch/middleware/templates/rescues/_source.erb (0.0ms)
Rendered C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/actionpack-4.2.4/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb (0.0ms)
Rendered C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/actionpack-4.2.4/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb (1.0ms)
Rendered C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/actionpack-4.2.4/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb (44.0ms)
Rendered C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/web-console-2.2.1/lib/web_console/templates/_markup.html.erb (1.0ms)
Rendered C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/web-console-2.2.1/lib/web_console/templates/_inner_console_markup.html.erb within layouts/inlined_string (0.0ms)
Rendered C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/web-console-2.2.1/lib/web_console/templates/_prompt_box_markup.html.erb within layouts/inlined_string (0.0ms)
Rendered C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/web-console-2.2.1/lib/web_console/templates/style.css.erb within layouts/inlined_string (0.0ms)
Rendered C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/web-console-2.2.1/lib/web_console/templates/console.js.erb within layouts/javascript (48.0ms)
Rendered C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/web-console-2.2.1/lib/web_console/templates/main.js.erb within layouts/javascript (0.0ms)
Rendered C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/web-console-2.2.1/lib/web_console/templates/error_page.js.erb within layouts/javascript (0.0ms)
Rendered C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/web-console-2.2.1/lib/web_console/templates/index.html.erb (105.0ms)
I'm reading this part of the rails documentation:
http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html,
but still can't figure out whats going on.
Update:
Routes:
Rails.application.routes.draw do
resource :cart, only: [:show]
resources :orders
resources :order_items
resources :registrations
resources :event_options
resources :events
resources :charges
root 'events#index'
Registration form - inside event_option show.html.erb:
<%= form_for(#registration, remote: true) do |f| %>
<% if #registration.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#registration.errors.count, "error") %> prohibited this registration from being saved:</h2>
<ul>
<% #registration.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :lastname %><br>
<%= f.text_field :lastname %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
I think your controller's create function is missing params[:id].
The easiest way to fix is adding it into your form:
<%= form_for(#registration, remote: true) do |f| %>
<%= hidden_field_tag :id, your_event_option_id %>
### your other stuffs
The error clearly states that Rails is unable to find an EventOption without an id:
def create
#event_option = EventOption.find params[:id] #-> :id is not passed to create action
To fix it, just use the param that's submitted as part of the form:
#app/controllers/registrations_controller.rb
class RegistrationsController < ApplicationController
def create
#order = current_order
#registration = #order.registrations.new registration_params
#registration.save
end
private
def registration_params
params.require(:registration).permit(:event_option_id, :other, :params)
end
end
--
The above would work well if the user could choose the event_option_id in the form; if you're using a hidden_field, you'll be better using nested routes:
#config/routes.rb
resources :event_option do
resources :registrations #-> url.com/event_options/:event_option_id/registrations/new
end
This will set the event_option_id as part of the top-level params hash, which will be passed to the controller as params[:event_option_id] (as you have it already):
#app/controllers/registrations_controller.rb
class RegistrationsController < ApplicationController
def create
#event_option = EventOption.find params[:event_option_id]
end
end
Tip - you can declare multiple resources at once:
#config/routes.rb
resource :cart, only: [:show]
resources :orders, :order_items, :registrations, :event_options, :events, :charges

Resources