accepts_nested_attributes causing Stack level too deep in Rails - ruby-on-rails

I have a very basic example
app/models/user.rb
#name string
class User < ActiveRecord::Base
has_one :project,dependent: :destroy
validates :name,presence: true
validates_associated :project
accepts_nested_attributes_for :project
end
app/models/project.rb
#name string
#user_id integer
class Project < ActiveRecord::Base
belongs_to :user
has_many :tasks,dependent: :destroy
validates :name,presence: true
validates_associated :tasks
accepts_nested_attributes_for :tasks
end
app/models/tasks.rb
#name string
#project_id integer
class Task < ActiveRecord::Base
belongs_to :project,dependent: :destroy
validates :name,presence: true
end
That's all I have in model
On the Controller end for testing purpose, I have the following code.
app/controllers/users_controller.rb
def update
#user.project_attributes = {:name => "P#rand(100)",:tasks_attributes => [{name: "Task#{rand(100)}"}]}
respond_to do |format|
if #user.save
format.html { redirect_to #user, notice: 'User was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
Every time, I try to update a given User, I run into stack level too deep
problem
Any Clue?
Note :
Rails version 4.0.4
Ruby 2.1.2p95

Write dependent: :destroy only in one of models that share an association. If both models have dependent: :destroy in 'em, it goes for an infinite number of calls causing Stack level too deep error to occur.

Related

Rails 5 API - has_many through create action is returning 2 records although only 1 is persisted in the database

I have the following M2M through associations for these 3 models
Customer -> Residences <- Properties
Also Property model is related to Address:
class Address < ApplicationRecord
has_one :property
end
A customer will always exist before creating a Property.
A property is created by submitting an Address.
Here is the controller action, which works except on success the render always returns 2 properties (ie. basically 2 residence records).
However, only one is in the database. I understand it is related to stale objects, but cannot figure out how to solve it.
I tried adding #customer.reload and #customer.reload.residences and #customer.reload.properties but still get 2 records.
# POST /customers/:id/properties
def create
#customer = set_customer
Customer.transaction do
address = Address.find_by_place_id(address_params[:place_id])
if address.nil?
#property = #customer.properties.create
#property.address = Address.new(address_params)
if #property.save
#customer.reload
render json: #customer, status: :created
else
render json: #property.errors, status: :unprocessable_entity
end
else
# irrelevant code to the problem
end
end
end
def set_customer
Customer.find(params[:customer_id])
end
A comment on this question (from #Swaps) indicates using << instead of create can sometimes result in duplicates, but whichever way I do it I always get 2.
EDIT
I managed to force it work like this but this feels like a hack:
if #property.save
#customer = set_customer
render json: #customer, status: :created
else
** UPDATE - the models **
class Customer < ApplicationRecord
has_many :residences
has_many :properties, through: :residences
end
class Residence < ApplicationRecord
belongs_to :customer
belongs_to :property
end
class Property < ApplicationRecord
belongs_to :address
has_many :residences
has_many :customers, through: :residences
end
class Address < ApplicationRecord
has_one :property
has_one :location # ignore this, not relevant
end
You're trying to do manually what ActiveRecord can do automatically with accepts_nested_attributes_for. It even works with has_many through operations.
class Customer < ApplicationRecord
has_many: :residences, inverse_of :customer
has_many: :properties, through: :residences
accepts_nested_attributes_for :residences
end
class Residence < ApplicationRecord
belongs_to :customer
belongs_to :property
validates_presence_of :customer
validates_presence_of :property
accepts_nested_attributes_for :property
end
class Property < ApplicationRecord
has_one :address
has_many :residences
has_many :customers, through: :residences
accepts_nested_attributes_for :address
end
class Address < ApplicationRecord
belongs_to :property
end
class CustomersController < ApplicationController
def create
customer = Customer.new(customer_params)
if customer.save
redirect_to customer, notice: 'Customer saved!'
else
render :new
end
end
def customer_params
params.require(:customer).permit(
name:, ...,
residences_attributes: [
property_attributes: [
name, ...,
address_attributes: [
street, city, state, postal_code, ...
]
]
]
)
end
end
References:
https://www.pluralsight.com/guides/ruby-on-rails-nested-attributes
https://robots.thoughtbot.com/accepts-nested-attributes-for-with-has-many-through
Could you please try this?
def create
#customer = set_customer
Customer.transaction do
address = Address.find_by_place_id(address_params[:place_id])
if address.nil?
#customer.properties.new(address_params)
if #customer.save
render json: #customer, status: :created
else
render json: #customer.errors, status: :unprocessable_entity
end
else
# irrelevant code to the problem
end
end
end
I was thinking do you really need #property instance variable. Is it for your view files?
Update 1
Could you please add your Customer and Residence model as like this:
Customer model
class Customer < ApplicationRecord
has_many :residences
has_many :properties, through: :residences
end
Residence model
class Residence < ApplicationRecord
belongs_to :customer
belongs_to :property
end
The problem was stale objects in ActiveRecord versus what is in the database after saving.
The ".reload" did not work, I have to actually force ActiveRecord using my hack to force ActiveRecord to find the customer in the database again, and that forces a reload (I presume it invalidates the AR cache):
def create
#customer = set_customer
Customer.transaction do
address = Address.find_by_place_id(address_params[:place_id])
if address.nil?
#property = #customer.properties.create
#property.address = Address.new(address_params)
if #property.save!
#customer = set_customer # force reload from db
render json: #customer, status: :created
end
else
address.update!(address_params)
if #customer.properties.find_by_id(address.property.id).nil?
# although we updated the address, that is just a side effect of this action
# the intention is to create an actual residence record for this customer
#customer.properties << address.property
#customer = set_customer # force reload from db
render json: #customer, status: :created
else
#customer.errors.add(:customer, 'already has that property address')
render json: ErrorSerializer.serialize(#customer.errors), status: :unprocessable_entity
end
end
end
end
def set_customer
Customer.find(params[:customer_id])
end

3rd level nested model not receiving grandparent ID in form

I have a 3 models. User, CV, and Language. A User has one CV. A CV has many Languages. The User has many Languages through its CV. When I try to save the form I get an error that the Language does not have a User ID. How can I get the User ID to pass through the CV and to the Language in my form?
The CV is receiving the User ID properly. Languages is not.
I am using the Simple-Form and Cocoon gems.
Simplified version of form
= simple_form_for(#cv, url: user_cvs_path) do |f|
= f.simple_fields_for :languages do |language|
From User Model
has_one :cv, dependent: :destroy
has_many :languages, through: :cv, inverse_of: :user
From Cv Model
belongs_to :user
has_many :languages, dependent: :destroy
accepts_nested_attributes_for :languages, allow_destroy: true
From Language Model
belongs_to :user
belongs_to :cv
From the CV Controller
before_action :set_user
def new
#cv = #user.build_cv
#cv.languages.build
end
def create
#cv = #user.create_cv(cv_params)
respond_to do |format|
if #cv.save
format.html { redirect_to user_cv_url(#user, #cv), notice: 'Cv was successfully created.' }
format.json { render :show, status: :created, location: #cv }
else
format.html { render :new }
format.json { render json: #cv.errors, status: :unprocessable_entity }
end
end
end
def cv_params
params.require(:cv).permit(
:user_id,
:first_name,
:middle_name,
:last_name,
... # lots of params left out for brevity
languages_attributes: [
:id,
:cv_id,
:user_id,
:name,
:read,
:write,
:speak,
:listen,
:_destroy])
end
def set_user
#user = current_user if user_signed_in?
end
Your Language model does not need the belongs_to :user. Language belongs to CV and CV belongs to User, so the relation between Language and User is already in place. If you need to access the user for a specific language you can write #language.cv.user
To solve your problem just remove the belongs_to :user from the Language model, remove the user_id from languages_attributes, and remove the user_id from languages table.

Has And Belongs to Many Through Save and Update

Here are my models:
class Examination < ActiveRecord::Base
has_many :categorizations
has_many :exam_statuses, :through => :categorizations
end
class Categorization < ActiveRecord::Base
belongs_to :examination
belongs_to :exam_status
end
class ExamStatus < ActiveRecord::Base
has_many :categorizations
has_many :examinations, :through => :categorizations
end
I can assign relations from the console without any problem by typing;
e = Examination.first
e.exam_status_ids = [1,2]
And also in the examinations/index.html.erb file I can list exam_statuses without any problem.
The problem is, I can't update or create any exam_status relations from examinations/_form.html.erb file!
I'm trying to make this with simple_form:
<%= f.association :exam_statuses, as: :check_boxes, label: 'Sınavın Durumu' %>
Its listing all the statuses with checkboxes but not updating them.
Logs saying:
"Unpermitted parameters: exam_status_ids"
And finally my controller, which is generated by "scaffold" by default, for update is:
def update
respond_to do |format|
if #examination.update(examination_params)
format.html { redirect_to #examination, notice: 'Examination was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #examination.errors, status: :unprocessable_entity }
end
end
end
From what your logs say, you should permit the parameter, in the controller:
def examination_params
params.require(:examination).permit(:exam_status_ids)
end
Don't forget to add other parameters in the permit call!
Then you can use it in your controller's action:
def update
...
#examination.update_attributes! examination_params
...
end
I think you need to use accepts_nested_attributes in this case to get it updated.
For more details you can refer this article

Rails behaving strange

I have rails version 3.2.13 and ruby version 1.9.3.
I have caught into very strange and interesting situation.
In my application have a model 'Product' with custom validator.
product.rb
class Product < ActiveRecord::Base
attr_accessible :description, :name, :price, :short_description, :user_id
validates :name, :short_description, presence: true
validates :price, :numericality => {:greater_than_or_equal_to => 0}
validate :uniq_name
belongs_to :user
belongs_to :original, foreign_key: :copied_from_id, class_name: 'Product'
has_many :clones, foreign_key: :copied_from_id, class_name: 'Product', dependent: :nullify
def clone?
self.original ? true : false
end
private
#Custom validator
def uniq_name
return if clone?
user_product = self.user.products.unlocked.where(:name => self.name).first
errors[:name] << "has already been taken" if user_product && !user_product.id.eql?(self.id)
end
end
In products controller's create action when I am trying to create new product
def create
#product = current_user.products.new(params[:product])
respond_to do |format|
if #product.save
format.html { redirect_to #product, notice: 'Product was successfully created.' }
format.json { render json: #product, status: :created, location: #product }
else
#product.errors[:image] = "Invalid file extension" if #product.errors[:image_content_type].present?
format.html { render action: "new" }
format.json { render json: #product.errors, status: :unprocessable_entity }
end
end
end
Custom validator is being called when this line executed #product = current_user.products.new(params[:product]) and line # 2 of custom validator giving me error
undefined method `products' for nil:NilClass
I have inspected product object in custom validator but user_id is nil.
Why user_id is not being autoassigned?
Your help will be appreciated :)
So ... bypassing your question. Why aren't you just validating the uniqueness of name?
validates_uniqueness_of :name, :unless => :clone?
try to change .new to .build
#product = current_user.products.build(params[:product])
and be sure that you have relation in your User model
Class User < ActiveRecord::Base
has_many :products

Rails 3.1: Validation fails when saving new objects with has_many :through association

# article.rb
class Article < ActiveRecord::Base
has_many :article_categories, dependent: :destroy
has_many :categories, through: :article_categories
validates :title, :description, :body, presence: true
validates :categories, presence: true
validates_associated :categories
end
#articles_controller.rb
def create
#article = Article.new(params[:article])
respond_to do |format|
if #article.save
format.html { redirect_to #article, notice: 'Article was successfully created.' }
format.json { render json: #article, status: :created, location: #article }
else
format.html { render action: "new" }
format.json { render json: #article.errors, status: :unprocessable_entity }
end
end
end
I have an Article model related to one or more Categories. I want to make sure that an article is assigned to a category each time the record is saved. On update, this works fine, but on create, I get an unprocessable entity because the ArticleCategory association can't be created because it needs the id of the article. But, the id isn't set until the Article is saved. But I can't save invalid models. You get the idea. How can I create objects without sacrificing my validations?
Edit: I fixed the validation line. This is cleaned up to remove some other stuff so I accidentally deleted the presence: true
Instead of validating that an article has many categories, can you try validate the presence of the join table, like:
# article.rb
validates :article_categories, presence: true
Have a look at the Rails Guides for the proper format here:
has_many :patients, :through => :appointments
From here! I think you'll find your declarations in the improper format.

Resources