I'm trying to use Ryan Bates' excellent 'nested forms' to create the following->
When creating a new 'Job', a user will have the option to write in multiple bullet points about it, and multiple 'Roles'
Here's how the models are set up :
Job Model
# == Schema Information
#
# Table name: jobs
#
# id :integer not null, primary key
# job_title :string(255)
# job_summary :string(255)
# qualifications :string(255)
# created_at :datetime
# updated_at :datetime
#
class Job < ActiveRecord::Base
validates :job_title, presence: true
validates :job_summary, presence: true
validates :qualifications, presence: true
has_many :bullets, dependent: :destroy
has_many :roles, dependent: :destroy
accepts_nested_attributes_for :bullets, :reject_if => lambda { |a| a[:bullet].blank? }, :allow_destroy => true
accepts_nested_attributes_for :roles, :reject_if => lambda { |a| a[:role_title].blank? }, :allow_destroy => true
#lambda { |a| a[:bullet].blank? } makes sure that on edit, if a value is left blank, then it doesn't save it
end
Bullet Model
# == Schema Information
#
# Table name: bullets
#
# id :integer not null, primary key
# job_id :integer
# bullet :string(255)
# created_at :datetime
# updated_at :datetime
#
class Bullet < ActiveRecord::Base
belongs_to :job
validates :job_id, presence: true
validates :bullet, presence: true
end
Role Model
# == Schema Information
#
# Table name: roles
#
# id :integer not null, primary key
# job_id :integer
# role_title :string(255)
# role_desc :string(255)
# created_at :datetime
# updated_at :datetime
#
class Role < ActiveRecord::Base
belongs_to :job
validates :job_id, presence: true
validates :role_title, presence: true
validates :role_desc, presence: true
end
Jobs Controller :
class JobsController < ApplicationController
before_action :signed_in_user, only: [:new, :create, :update, :show, :edit, :destroy] # index, at least partially is available to all viewers
before_action :admin_user, only: [:new, :edit, :update, :create, :destroy] # only admins can make jobs
def new
#job = Job.new
3.times {
#job.bullets.build
#job.roles.build
}
end
def create
#job = Job.new(job_params)
if #job.save
redirect_to root_path, :flash => { :success => "Job created!" }
else
render 'new'
end
end
def job_params
params.require(:job).permit(:job_title, :job_summary, :qualifications,
bullets_attributes: [:id, :bullet, :_destroy],
roles_attributes: [:id, :role_title,:role_desc, :_destroy])
end
end
Jobs/New View
<% provide(:title, "Publish a new job") %>
<%= nested_form_for #job do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :job_title %>
<%= f.text_field :job_title %>
<div style="margin-left:30px">
<%= f.fields_for :bullets do |bullet_form| %>
<%= bullet_form.label :bullet %>
<%= bullet_form.text_field :bullet %>
<%= bullet_form.link_to_remove "Remove this bullet" %>
<% end %>
<p><%= f.link_to_add "Add a Bullet", :bullets %></p>
</div>
<%= f.label :job_summary %>
<%= f.text_area :job_summary %>
<%= f.label :qualifications %>
<%= f.text_area :qualifications %>
<div style="margin-left:30px">
<%= f.fields_for :roles do |role_form| %>
<%= role_form.label :role_title %>
<%= role_form.text_field :role_title %>
<%= role_form.label :role_desc %>
<%= role_form.text_field :role_desc %>
<%= role_form.link_to_remove "Remove this Role" %>
<% end %>
<p><%= f.link_to_add "Add a Role", :roles %></p>
</div>
<%= f.submit "Publish the Job", class: "button" %>
<% end %>
Alas, it looks like I'm doing it incorrectly. When I try to create a new job, I get the error :
* Bullets job can't be blank
* Roles job can't be blank
It seems to me like the info for job_id is not passing through the nested forms.
What you think is happening, and ideas about making this work would be greatly appreciated :)
Logs
Here are some logs to make it a bit clearer:
1) When I submit the form, choosing only 1 bullet I get the following parameters in development:
--- !ruby/hash:ActionController::Parameters
utf8: ✓
authenticity_token: ezgktBZjcrdI6nMTro8Aqd0Djs2k3M+HFAdACrxajS8=
job: !ruby/hash:ActionController::Parameters
job_title: Job Title example
bullets_attributes: !ruby/hash:ActionController::Parameters
'0': !ruby/hash:ActiveSupport::HashWithIndifferentAccess
bullet: Example bullet
_destroy: 'false'
job_summary: Job Summary Example
qualifications: Example Qualifications here
roles_attributes: !ruby/hash:ActionController::Parameters
'1387448871560': !ruby/hash:ActiveSupport::HashWithIndifferentAccess
role_title: Example Role Name
role_desc: Example Role Description
_destroy: 'false'
commit: Publish the Job
action: create
controller: jobs
On my Rails Server, this is what it's saying ->
Started POST "/jobs" for 127.0.0.1 at 2013-12-19 14:28:01 +0400
Processing by JobsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"ezgktBZjcrdI6nMTro8Aqd0Djs2k3M+HFAdACrxajS8=", "job"=>{"job_title"=>"Job Title example", "bullets_attributes"=>{"0"=>{"bullet"=>"Example bullet", "_destroy"=>"false"}}, "job_summary"=>"Job Summary Example", "qualifications"=>"Example Qualifications here", "roles_attributes"=>{"1387448871560"=>{"role_title"=>"Example Role Name", "role_desc"=>"Example Role Description", "_destroy"=>"false"}}}, "commit"=>"Publish the Job"}
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."remember_token" = '449addc616f3035c031b3220404a4d5eb5351c74' LIMIT 1
(0.2ms) begin transaction
(0.2ms) rollback transaction
Rendered shared/_error_messages.html.erb (2.4ms)
Rendered jobs/new.html.erb within layouts/application (15.7ms)
Rendered layouts/_shim.html.erb (0.1ms)
Rendered layouts/_header.html.erb (1.3ms)
Completed 200 OK in 101ms (Views: 32.0ms | ActiveRecord: 0.7ms | Solr: 0.0ms)
From what you've posted, there are several issues which could be causing the problem
Strong Params
def job_params
params.require(:job).permit(:job_title, :job_summary, :qualifications,
bullets_attributes: [:bullet],
roles_attributes: [:role_title,:role_desc])
end
I don't know why you've called your columns role_title and role_desc, but it's better to call the columns only by their actual name (title and desc)
It's basically because if you load #role, it's much more efficient to write #role.title rather than #role.role_title. The same for your other tables
You may wish to update your schemas to reflect that
ActiveRecord Build
Second problem is you're building your new ActiveRecord objects 3 times:
def new
#job = Job.new
3.times {
#job.bullets.build
#job.roles.build
}
end
This would be okay if you wanted to send 3 objects, but you only have 1. And I know you've done it so you can add extra fields (I'll explain that in a second)
You should change to this:
def new
#job = Job.new
#job.bullets.build
#job.roles.build
end
I believe this is your main issue actually - if you build the objects & they don't get populated, they are blank, no?
I would only declare the objects I wanted to show up front, and add the extras when required
Adding Fields
If you wish to add fields on the fly (with Ajax), I would highly recommend this tutorial. It uses the same principles as Ryan Bates' method, but is much cleaner & adds the objects when required. I can talk about this via chat if you wanted
Related
EDIT 3: Just to clarify, the goal and problem is to create 2 new records from the same form of which one is the parent and one is the child. The child needs the parent ID, but the parent is created from the same form that the child is.
EDIT 2: I think I'm getting closer. See log file at end, the deal is successfully saved and it looks like the client entry is starting to commit but then not saving. Code is updated below for changes.
I followed the Railscast #196 for nested forms and I am successfully able to edit, add and delete from nested forms as long as the record is already created. Now I am trying to use nested forms to create new records. I think I'm 99% of the way there, but I'm missing something I can't see anymore. I just need to figure out how to pass the id of the parent to the child.
In addition to the Railscast I used this answer to set inverse_of relationships and call the save order (using create instead of save though). I'm pretty sure the problem is in the form or the controller (but models are listed below too)
Nested Form (I tried to simplify to make it easier to read)
EDIT 2: remove hidden field
<%= form_for(#deal) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="deal-<%= #deal.id %>" >
<div class="form-group">
<%= f.label :headline %>
<%= f.text_field :headline, required: true, placeholder: "Headline" %>
</div>
<div class="form-group" id="clients">
<%= f.fields_for :clients do |client_form| %>
<div class="field">
<%= client_form.label :client %><br />
<%= client_form.text_field :name, placeholder: "Client name" %>
</div>
<% end %>
<%= link_to_add_fields "Add client", f, :clients %>
</div>
<div class="form-group">
<%= f.label :matter %>
<%= f.text_field :matter, placeholder: "Matter", rows: "4" %>
</div>
<div class="form-group">
<%= f.label :summary %>
<%= f.text_area :summary, placeholder: "Deal summary", rows: "4" %>
</div>
<div class="form-group">
<div class="action-area">
<%= f.submit "Add deal" %>
</div>
</div>
</div>
<% end %>
Controller
EDIT 2: include deal_id param & change save calls
class DealsController < ApplicationController
before_action :require_login
def new
#deal = Deal.new
#client = #deal.clients
end
def create
#deal = current_user.deals.create(deal_params)
if #deal.save
flash[:success] = "Your deal was created!"
redirect_to root_url
else
render 'deals/new'
end
end
private
def deal_params
params.require(:deal).permit(:headline, :matter, :summary, clients_attributes: [:id, :deal_id, :name, :_destroy])
end
end
EDIT 2: No longer yields errors in browser and success flash message is triggered
EDIT 2: Here is the console output on submit (the record is saved and can be viewed it just doesn't have a client)
Started POST "/deals" for ::1 at 2017-04-26 00:13:08 +0200
Processing by DealsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"hbvpS6KsZOorR3u4LgNoG5WHgerok6j3yYzO+dFUHs9thsxRi+rbUkm88nb7A5WvlmWZEcvaDvCKywufP3340w==", "deal"=>{"headline"=>"headline", "client"=>{"name"=>"aaa"}, "matter"=>"", "summary"=>""}, "commit"=>"Add deal"}
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT ? [["id", 1], ["LIMIT", 1]]
Unpermitted parameter: client
(0.1ms) begin transaction
SQL (0.5ms) INSERT INTO "deals" ("headline", "matter", "summary", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?) [["headline", "headline"], ["matter", ""], ["summary", ""], ["user_id", 1], ["created_at", 2017-04-25 22:13:08 UTC], ["updated_at", 2017-04-25 22:13:08 UTC]]
(3.8ms) commit transaction
(0.1ms) begin transaction
(0.1ms) commit transaction
Redirected to http://localhost:3000/
Completed 302 Found in 16ms (ActiveRecord: 4.6ms)
Models for reference
Users
class User < ApplicationRecord
has_many :deals
end
Deals
class Deal < ApplicationRecord
belongs_to :user
has_many :clients, inverse_of: :deal
validates :headline, presence: true
accepts_nested_attributes_for :clients, allow_destroy: true
end
Clients
class Client < ApplicationRecord
belongs_to :deal, inverse_of: :clients
validates :name, presence: true
validates :deal_id, presence: true
end
You are missing the deal_id on the params
def deal_params
params.require(:deal).permit(:headline, :matter, :summary, clients_attributes: [:id, :deal_id, :name, :_destroy])
end
and on the create of deal, you can make something like this
def create
#deal = Deal.new(deal_params)
if #deal.save
...
and just add a hidden field on the form for the user_id parameter.
The problem is the validations in the client model. Removing the validations will allow the records to be correctly saved from the nested form.
In the Client model remove the name and deal_id validators
class Client < ApplicationRecord
belongs_to :deal, inverse_of: :clients
end
In the controller add build to the new action so it's nicer for the users:
def new
#deal = Deal.new
#client = #deal.clients.build
end
.build is not strictly necessary since the helper method that was created from the Rails Cast will create new client entries on demand, but with it the user is presented with the placeholder first entry which is ignored if blank.
To keep empty client records from saving I added a reject_if: proc to the deal model
class Deal < ApplicationRecord
belongs_to :user
has_many :clients, inverse_of: :deal
validates :headline, presence: true
accepts_nested_attributes_for :clients, allow_destroy: true, reject_if: proc { |attributes| attributes['name'].blank? }
end
I'll leave this open/unanswered in case someone can explain better why my solution worked and if there's a better way to do it.
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!
I have a form that includes two models, one of which is nested in another. When on the development server I submit the form, the submission generates the flash error message:
The form contains 1 error:
* Members organization can't be blank
Each Member belongs to 1 Organization. Might the error have to do with that the organization isn't saved yet when it's trying to save the Member (for a new organization, it's supposed to simulateneously save the organization and member)? Should I add code that sets organization_id for the member (but I would expect #organization.members.build to take care of that)? What am I doing wrong here?
The server log:
Processing by OrganizationsController#create as HTML
Parameters: {"utf8"=>"✓", "organization"=>{"org_name"=>"sadfsdaf", "phone"=>"sdfds", "members_attributes"=>{"0"=>{"username"=>"fsfdsfsad", "email"=>"sfdsdf#sfsdf.com", "fullname"=>"sdfds", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}}}, "admin"=>"true", "commit"=>"Sign up"}
(0.5ms) begin transaction
Member Exists (0.4ms) SELECT 1 AS one FROM "members" WHERE LOWER("members"."email") = LOWER('sfdsdf#sfsdf.com') LIMIT 1
Member Exists (0.2ms) SELECT 1 AS one FROM "members" WHERE LOWER("members"."username") = LOWER('fsfdsfsad') LIMIT 1
Organization Exists (0.3ms) SELECT 1 AS one FROM "organizations" WHERE LOWER("organizations"."org_name") = LOWER('sadfsdaf') LIMIT 1
Organization Exists (0.2ms) SELECT 1 AS one FROM "organizations" WHERE LOWER("organizations"."phone") = LOWER('sdfds') LIMIT 1
(0.2ms) rollback transaction
Rendered shared/_error_messages.html.erb (1.9ms)
Organization model:
has_many :members, dependent: :destroy
accepts_nested_attributes_for :members, :reject_if => :all_blank, :allow_destroy => true
Member model:
belongs_to :organization
validates :organization_id, presence: true # There are other validations as well, but I don't think they matter for this question.
def send_activation_email
MemberMailer.account_activation(self).deliver_now
end
Organization controller:
def new
#organization = Organization.new
#member = #organization.members.build
end
def create
#organization = Organization.new(organizationnew_params)
if #organization.save
#member.send_activation_email # Method in Member model file.
flash[:success] = "Please check your email to activate your account."
redirect_to root_url
else
render 'new'
end
end
private
def organizationnew_params
params.require(:organization).permit(:org_name,
:phone,
member_attributes: [:email,
:username,
:admin,
:password,
:password_confirmation
])
end
New view:
<%= form_for(#organization) do |f| %>
<%= f.text_field :org_name, %>
<%= f.text_field :phone %>
<%= f.fields_for :members do |p| %>
<%= p.text_field :username %>
<%= p.email_field :email %>
<%= p.password_field :password %>
<%= p.password_field :password_confirmation %>
<%= hidden_field_tag :admin, true %>
<% end %>
<%= f.submit "Sign up", class: "formbutton btn btn-default" %>
<% end %>
On the permitted params, try changing member_attributes to members_attributes. Also on the create action You are referring to #member but its not defined yet.
To debug further more replace your create action with:
def create
return render text: organizationnew_params
end
This way, when you send the form you will get all the permitted params and check if it's what you expected.
To Send the emails replace
#member.send_activation_email
With
#organization.members.each do |single_member|
single_member.send_activation_email
end
3rd Edit
Just use this
def create
#organization = Organization.new(organizationnew_params)
if #organization.save
#organization.members.each do |single_member|
single_member.send_activation_email
end
flash[:success] = "Please check your email to activate your account."
redirect_to root_url
else
render 'new'
end
end
private
def organizationnew_params
params.require(:organization).permit(:org_name,
:phone,
members_attributes: [:id,:email,
:username,
:admin,
:password,
:password_confirmation
])
end
4th Edit
The validation:
When creating members with oraganization ( in the OraganizationsController ), you do not need to validate organization_id because it will be created at the same time of the organization. Unfortunately you will need the validation of the organization_id when adding a new member to the organization. So what we can do is add a condition to the validation:
#member.rb
attr_accessor :require_organization_id
belongs_to :organization
validates :organization_id, presence: true, if: :require_organization_id
How to use it:
member = Member.new(name: "Some Name")
member.valid? #true
member.require_organization_id = true
member.valid? #false
member.organization = Organization.first
member.valid? #true
If you set validation in member model i.e.
validates :organization_id, presence: true
then it looks in member params for organization_id which doesn't present in params of memeber
"members_attributes"=>{"0"=>{"email"=>"sfsa#sfsd.com", "username"=>"fsfdsfsad", "fullname"=>"sdfds", "password"=>"foobar", "password_confirmation"=>"foobar"}}
thats why its showing validation error so remove this line
validates :organization_id, presence: true
from your member model
Also for #member issue you need to do like this
#organization.memebers.each do |member|
member.send_activation_email
end
I'm having a time trying to get this nested model working. I've tried all manner of pluralization/singular, removing the attr_accessible altogether, and who knows what else.
restaurant.rb:
# == RESTAURANT MODEL
#
# Table name: restaurants
#
# id :integer not null, primary key
# name :string(255)
# created_at :datetime not null
# updated_at :datetime not null
#
class Restaurant < ActiveRecord::Base
attr_accessible :name, :job_attributes
has_many :jobs
has_many :users, :through => :jobs
has_many :positions
accepts_nested_attributes_for :jobs, :allow_destroy => true
validates :name, presence: true
end
job.rb:
# == JOB MODEL
#
# Table name: jobs
#
# id :integer not null, primary key
# restaurant_id :integer
# shortname :string(255)
# user_id :integer
# created_at :datetime not null
# updated_at :datetime not null
#
class Job < ActiveRecord::Base
attr_accessible :restaurant_id, :shortname, :user_id
belongs_to :user
belongs_to :restaurant
has_many :shifts
validates :name, presence: false
end
restaurants_controller.rb:
class RestaurantsController < ApplicationController
before_filter :logged_in, only: [:new_restaurant]
def new
#restaurant = Restaurant.new
#user = current_user
end
def create
#restaurant = Restaurant.new(params[:restaurant])
if #restaurant.save
flash[:success] = "Restaurant created."
redirect_to welcome_path
end
end
end
new.html.erb:
<% provide(:title, 'Restaurant') %>
<%= form_for #restaurant do |f| %>
<%= render 'shared/error_messages' %>
<%= f.label "Restaurant Name" %>
<%= f.text_field :name %>
<%= f.fields_for :job do |child_f| %>
<%= child_f.label "Nickname" %>
<%= child_f.text_field :shortname %>
<% end %>
<%= f.submit "Done", class: "btn btn-large btn-primary" %>
<% end %>
Output Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"DjYvwkJeUhO06ds7bqshHsctS1M/Dth08rLlP2yQ7O0=",
"restaurant"=>{"name"=>"The Pink Door",
"job"=>{"shortname"=>"PD"}},
"commit"=>"Done"}
The error i'm receiving is:
ActiveModel::MassAssignmentSecurity::Error in RestaurantsController#create
Cant mass-assign protected attributes: job
Rails.root: /home/johnnyfive/Dropbox/Projects/sa
Application Trace | Framework Trace | Full Trace
app/controllers/restaurants_controller.rb:11:in `new'
app/controllers/restaurants_controller.rb:11:in `create'
Anyone have ANY clue how to get this to work? Thanks!
in restaurant.rb:
it should be
attr_accessible :name, :jobs_attributes
instead of
attr_accessible :name, :job_attributes
regarding your last comment:
you can submit the user_id within your job form, that should submit the user_id to the model
I'm having difficulty saving two fields in a nested form. The parent field saves fine, but the nested field is throwing the "WARNING: Can't mass-assign protected attributes" error.
I've placed things in the Item model with attr_accessible, but it's not solving the issue.
List_controller
def create
#list = List.new(params[:list])
#list[:user_id] = current_user.id
if #list.save
flash[:notice] = "Successfully created list."
redirect_to #list
else
render :action => 'new'
end
end
List model
class List < ActiveRecord::Base
has_many :items, :dependent => :destroy
accepts_nested_attributes_for :items, :reject_if => lambda { |a| a[:name].blank? }, :allow_destroy => true
end
Item model
class Item < ActiveRecord::Base
belongs_to :list
end
Form
<%= form_for(#list) do |list_form| %>
<p>
<%= list_form.label :title %><br />
<%= list_form.text_field :title %>
</p>
<p>
<%= render :partial => 'item_fields',
:locals => {:form => list_form} %>
</p>
<%= list_form.submit %>
<% end %>
Form partial
<%= form.fields_for :item do |item_form| %>
<%= item_form.label :name, 'item' %>
<%= item_form.text_field :name %>
<% end %>
Server error log
Started POST "/lists" for 127.0.0.1 at Sun Mar 27 02:54:18 -0400 2011
Processing by ListsController#create as HTML
Parameters: {"commit"=>"Create List", "list"=>{"title"=>"figaro", "item"=>{"na
me"=>"foobar"}}, "authenticity_token"=>"afu5xPgvJenu6XKXcsyilR8RconLP/OZ3NxsICE3RVk=
", "utf8"=>"Γ£ô"}
←[1m←[35mUser Load (1.0ms)←[0m SELECT "users".* FROM "users" WHERE "users"."i
d" = 2 LIMIT 1
WARNING: Can't mass-assign protected attributes: item
←[1m←[36mAREL (2.0ms)←[0m ←[1mINSERT INTO "lists" ("user_id", "updated_at", "
title", "created_at") VALUES (2, '2011-03-27 06:54:18.893302', 'figaro', '2011-0
3-27 06:54:18.893302')←[0m
Redirected to http://localhost:3000/lists/77
Completed 302 Found in 117ms
You're using fields_for :item but your List model has_many :items.
Try this:
<%= form.fields_for :items do |item_form| %>
If this doesn't help try to add attr_accessible :items_attributes to you List model. From the docs:
[..] If you are using attr_protected or attr_accessible, then you will need to add the attribute writer to the allowed list.
Add attr_accessible :items to your List class.