Rails accepts_nested_attributes_for - ruby-on-rails

I am working on hand-rolling a user authentication system for fun. I've created two different models, a UserAccount and UserCredential the idea being that credentials such as email, username, profile pic, etc are separate from the actual account.
In order to create a UserCredential when a UserAccount is created I am using accepts_nested_attributes_for. I am running into some issues saving the UserCredential when the UserAccount saves.
Migration:
create_table :user_accounts, id: :uuid do |t|
t.references :user_credentials
...
end
add_reference :user_credentials, :user_account, type: :uuid, null: false, foreign_key: true
user_account.rb
has_one :user_credential, inverse_of: :user_account, dependent: :destroy
...
accepts_nested_attributes_for :user_credential
validates_associated :user_credential
user_credential.rb
class UserCredential < ApplicationRecord
validates :email, uniqueness: true
belongs_to :user_account, inverse_of: :user_credential
validates_presence_of :user_account
accounts_controller.rb
def new
#user = UserAccount.new
end
def create
#user = UserAccount.create!(user_params)
end
private
def user_params
params.require(:user_account).permit(user_credentials: [:email])
# I'm not sure if `user_credential` should be plural here or not?
end
_form.html.erb
<%= form_with model: #user, local: true do |form| %>
<%= form.fields_for :user_credentials do |u| %>
<div class="form-control">
<%= u.label :email %>
<%= u.email_field :email %>
</div>
<% end %>
...
<% end %>
Two small things I've noticed:
if I change the params and the fields_for to user_credential (removing the plural) the email field disappears.
If I keep the params as is I get this exception: ActiveModel::UnknownAttributeError (unknown attribute 'user_credentials' for UserAccount.):
I've seen people recommending adding a #user.user_credential.build() to the new method but doing that just gives me a nil:NilClass error.

I guess you need to change user_credentials to user_credentials_attributes
def user_params
params.require(:user_account).permit(user_credentials_attributes: [:email])
end
When nested attributes are submitted through a form, the _attributes tag is appended for the nested attributes parent.

Related

Validate presence of fields from another model

With the following associations:
class Profile < ActiveRecord::Base
belongs_to :visitor
belongs_to :contact_point
validates :contact_point, presence: true
end
class Visitor < User
has_one :profile, dependent: :destroy
has_one :contact_point, through: :profile
end
class ContactPoint < User
has_many :profiles
has_many :visitors, through: :profiles
end
Each ContactPoint has a email. When the visitor creates her profile using the following form, she needs to determine the profiles contact point using the email address belonging to ContactPoint. The contact point users are already created and the visitors should not be able to update ContactPoint model.
<%= form_for #profile do |f| %>
<%= f.label 'First Name' %>
<%= f.text_field :first_name %>
<%= f.label 'Last Name' %>
<%= f.text_field :last_name %>
<%= fields_for :contact_point, #profile.contact_point do |ff| %>
<%= ff.label 'Contact point email' %>
<%= ff.text_field :email %>
<% end %>
<% end %>
In ProfilesController I am passing parameters to profile model this way:
def create
#profile = Profile.create(profile_params)
end
def profile_params
contact_point = ContactPoint.find_by_email(params[:contact_point][:email])
params.require(:profile).permit(:first_name, :last_name)
.merge(visitor_id: current_user.id, contact_point: contact_point)
end
With the above setup, when there is no ContactPoint with the provided email address, the contact_point variable will set to be nil and the validator can't distinguish whether the contact point email in the filled in from was empty or not.
Now, how can I add a validation to check the presence of this email address in contact_points table and show a custom error message?
You would have to do it yourself in your controller, something like:
def create
#profile = Profile.create(profile_params)
if !#profile.contact_point
if params[:contact_point][:email].present?
#profile.errors.add(:contact_point, 'No contact with found')
else
#profile.errors.add(:contact_point, 'Please provide an email')
end
render :new
end
end
The best would be to use a custom validation that checks if contact_pounts.email is blank? If yes, then return false.
EDIT:
My brain is functioning better now after some sleep. You can do this using rails. This is how I would do it.
class Profile < ActiveRecord::Base
belongs_to :visitor
belongs_to :contact_point
validates :contact_point, presence: true
accepts_nested_attributes_for :contact_point
end
class Visitor < User
has_one :profile, dependent: :destroy
has_one :contact_point, through: :profile
end
class ContactPoint < User
has_many :profiles
has_many :visitors, through: :profiles
validates :email, presence: true
end
What is going on here? We accept nested attributes for the association (ContactPoint) from Profile, so we can pass them through the #profile form you have to the controller. The models will handle the validation and set the error messages accordingly.
Does this makes sense?

Creating new resources with nested form with polymorphic relationships in Rails 4 presence

I can't seem to successfully create new resources from a nested form that has polymorphic associations.
It seems the problem lies in validations with polymorphic 'belongs_to' relationships. For example:
class DealerUser < User
belongs_to :dealer, polymorphic: true, foreign_key: :loginable_id, foreign_type: :loginable_type
validates :dealer, presence: true
end
I am attempting to create a new resource called Dealer.
class Dealer < ActiveRecord::Base
has_many :dealer_locations, dependent: :destroy
has_one :dealer_user, as: :loginable, dependent: :destroy
accepts_nested_attributes_for :dealer_locations, allow_destroy: true
accepts_nested_attributes_for :dealer_user, allow_destroy: true
validates :name, presence: true, length: { maximum: 255 }, uniqueness: { case_sensitive: false }
validates_associated :dealer_locations
end
My form code:
<%= form_for(#dealer) do |d| %>
<%= render 'shared/error_messages', object: d.object %>
<br>
<div class="form-group">
<%= d.label :name %>
<%= d.text_field :name, class:"form-input-field" %>
</div>
<h4>Login Credentials</h4><br>
<%= d.fields_for :dealer_user do |du| %>
<div class="form-group">
<%= du.label :email %>
<%= du.text_field :email, class:"form-input-field" %>
</div>
<div class="form-group">
<%= du.label :password %>
<%= du.password_field :password, class:"form-input-field" %>
</div>
<% end %>
<%= d.submit class: "btn btn-large btn-primary" %>
<% end %>
Controller:
class DealersController < ApplicationController
def new
#dealer = Dealer.new
#dealer.build_dealer_user
end
def create
#dealer = Dealer.new(dealer_params)
puts #dealer.inspect
puts #dealer.dealer_user.inspect
if #dealer.save
redirect_to(#dealer, :notice => 'Dealer was successfully created.')
end
def dealer_params
params.require(:dealer).permit(:name, dealer_user_attributes: [:email, :password, :id])
end
end
When I submit the form, I am getting DealerUser.dealer can't be blank error.
#<Dealer id: nil, name: "NAME", created_at: nil, updated_at: nil>
#<DealerUser id: nil, type: "DealerUser", email: "fake#email.com", loginable_id: nil, loginable_type: "Dealer",...
It looks like the associations are working, since you can see type fields are set.
Update 3/11/16: params
{"utf8"=>"✓", "authenticity_token"=>"...",
"dealer"=>{"name"=>"ZZ WATER",
"dealer_user_attributes"=>{"email"=>"...", "password"=>"[FILTERED]"},
"dealer_locations_attributes"=>{"0"=>{"phone"=>"...", "contact_email"=>"...",
"address_attributes"=>{"line_1"=>"line1", "line_2"=>"", "city"=>"city", "country_code"=>"US", "state_code"=>"CA", "zip_code"=>"00000"}}}}, "commit"=>"Create Dealer"}
Is this an order of operations issue? DealerUser needs an ID to pass validation, but the Dealer ID wont get set until it's saved to the DB, which isn't happening since it's not passing validation. Can someone offer me some guidance.
Also, this is a simplified version of what I'm working on, there are nested locations and addresses too. I'm also only a couple months into using ruby on rails. Thank you.
I created a fresh similar project. And I also got the same error. In my attempt to fix this problem, I stumbled upon inverse_of, and it now worked.
Now try
class DealerUser < User
# ..
belongs_to :dealer, polymorphic: true, foreign_key: :loginable_id, foreign_type: :loginable_type, inverse_of: :dealer_user
# ..
end
class Dealer < ActiveRecord::Base
# ..
has_one :dealer_user, as: :loginable, dependent: :destroy, inverse_of: :dealer
# ..
end

Rails 4 - problems with nested attributes

I'm having an inordinate amount of trouble using a nested model with fields_for in a form. Specifically, not all of the nested fields save. A User has many Experiences, but when I submit the form, an Experience with the correct user_id but NO CONTENT is inserted into the database.
Looking at the logs, I also get an error:
unpermitted parameters: experience.
Rails 4 nested attributes not saving doesn't help, unfortunately.
Here's the code:
SCHEMA
create_table "experiences", force: true do |t|
t.string "content", null: false, default: ""
t.integer "user_id"
end
MODEL
#user.rb
class User < ActiveRecord::Base
has_many :experiences
accepts_nested_attributes_for :experiences
#experience.rb
class Experience < ActiveRecord::Base
belongs_to :user
end
CONTROLLER
class UsersController < ApplicationController
def new
#user = User.new
#user.experiences.build
end
def update
#user = current_user
#user.experiences.build
#user.update!(user_params)
redirect_to root_path
end
def user_params
params.require(:user).permit(:username, :email, :password,
:password_confirmation, :title, :blurb, :city, :state,
:style_list, :experience_attributes => [:id, :content])
end
VIEW
<%= form_for #user do |f| %>
<!-- (Omitted) user fields -->
<%= f.fields_for :experience do |experience_fields| %>
<%= experience_fields.text_field :content, placeholder: 'Content' %>
<% end %>
<%= f.submit 'Edit profile' %>
<% end %>
Any help would be greatly appreciated!
Here's your problem:
#user.experiences.build # -> note "experience**s**"
This means when you use fields_for, you have to reference :experiences (you're currently referencing the singular):
<%= f.fields_for :experiences do |experience_fields| %>
<%= experience_fields.text_field :content, placeholder: 'Content' %>
<% end %>
This also goes for your strong_params:
params.require(:user).permit(experiences_attributes: [:id, :content])

Rails 3 fail validation for parent model is validation of nested attributes fail

I have two models, User and House. They have one-to-one association.
class User < ActiveRecord::Base
attr_accessible :name, :house_attributes
has_one :house, :dependent => :destroy
validates :name, :presence => true
accepts_nested_attributes_for :house, allow_destroy: true, :reject_if => lambda { |a| a['desc'].blank? }
end
class House < ActiveRecord::Base
attr_accessible :desc, :price
belongs_to :user
validates :desc, :presence => true
end
Now I created a nested form inside of User new view like this:
<%= simple_form_for(#user) do |f| %>
<%= f.input :name %>
<%= f_builder.simple_fields_for :house, #house do |h| %>
<%= h.input :price %>
<%= h.input :desc %>
<% end %>
<%= f.button :submit %>
<% end %>
And the new controller is like this
def new
#user = User.new
#house = #user.build_house
respond_to do |format|
format.html # new.html.erb
end
end
I want to always create a house at the same time that user is created. So if house fails validation, it should not create user. (Now the house model has only one validation, which is :desc field needs to be present.)
The code in models, only guarantee house is not created, if :desc is blank. But it will still create the user.
I tried to add custom validation inside of User model, but can not find a way to access :desc attribute (failed to call self.desc) or :house_attributes (self.house_attributes) inside of User model. I really don't know where does rails store these house attributes before a house is created.
I hope you guys can help me figure out a nice and clear way to
1) Be able to validate and show error message for :desc attribute. Right now errors message will only show up for :name field of User model.
2) Do not save user if validation for either user or house is failed.
3) If any of validation failed, render :new
Thanks
You should add validates_associated :house to your User model. This will run the House validations and ensure they succeed before considering the user to be valid.

Mass assignment error with polymorphic association

I'm getting a mass assignment error when submitting a nested form for a has_one polymorphic model. The form is trying to create Employee and Picture instances based on the polymorphic association Rails guide.
I would appreciate literally ANY functioning example of a nested creation form for a has_one polymorphic model! I know there are tons of questions on mass assignment errors but I've never seen a working example with polymorphic associations.
Models
class Picture < ActiveRecord::Base
belongs_to :illustrated, :polymorphic => true
attr_accessible :filename, :illustrated
end
class Employee < ActiveRecord::Base
has_one :picture, :as => :illustrated
accepts_nested_attributes_for :picture
attr_accessible :name, :illustrated_attribute
end
Migrations
create_table :pictures do |t|
t.string :filename
t.references :illustrated, polymorphic: true
end
create_table :employees do |t|
t.string :name
end
controllers/employees_controller.rb
...
def new
#employee = Employee.new
#employee.picture = Picture.new
end
def create
#employee = Employee.new(params[:employee])
#employee.save
end
...
Error
Can't mass-assign protected attributes: illustrated
app/controllers/employees_controller.rb:44:in `create'
{"utf8"=>"✓", "authenticity_token"=>"blah"
"employee"=>{"illustrated"=>{"filename"=>"johndoe.jpg"},
"name"=>"John Doe"},
"commit"=>"Create Employee"}
In the models, I've tried every permutation of :illustrated, :picture, :illustrated_attribute, :illustrated_attributes, :picture_attribute, :picture_attributes, etc. Any tips or examples?
EDIT:
_form.html.erb
<%= form_for(#employee) do |f| %>
<%= f.fields_for :illustrated do |form| %>
<%= form.text_field :filename %>
<% end %>
<%= f.text_field :name %>
<%= f.submit %>
<% end %>
You need to specify the nested_attributes appropriately. accepts_nested_attributes_for takes the name of the association as parameter and then you need to add the same assoc_attributes to your attr_accessible. So change your Employee model to
class Employee < ActiveRecord::Base
has_one :picture, :as => :illustrated
accepts_nested_attributes_for :picture
attr_accessible :name, :picture_attribute
end
And change the field_for line in the view code to
<%= f.fields_for :picture do |form| %>

Resources