update_attributes & update do not call validations on parent association - ruby-on-rails

So i have the following problem in my codebase.
In database there is data that doesn't satisfy the validations. (its not right I know, and will be fixed but the question remains why rails works like it does)
Calling save in create action will run validations on the parent object and wont save anything, however, calling the update or update_attributes in update action will not and the object will be saved with invalid parent.
Model:
class RussianDoll < ActiveRecord::Base
validates :name, :description, presence: true
has_and_belongs_to_many :parents,
class_name: 'RussianDoll',
join_table: :russian_dolls_russian_dolls,
foreign_key: :child_id,
association_foreign_key: :parent_id
has_and_belongs_to_many :children,
class_name: 'RussianDoll',
join_table: :russian_dolls_russian_dolls,
foreign_key: :parent_id,
association_foreign_key: :child_id
end
Controller:
class RussianDollsController < ApplicationController
def create
rd_params = russian_doll_params
rd_params[:parents] = RussianDoll.where(id: params[:parent_ids])
#russian_doll = RussianDoll.new(rd_params)
respond_to do |format|
if #russian_doll.save
format.html { render 'foo' }
format.xml { render 'bar' }
else
format.html { render action: 'new' }
format.xml { render xml: #russian_doll.errors }
end
end
end
def update
#russian_doll = RussianDoll.find(params[:id])
rd_params = russian_doll_params
rd_params[:parents] = RussianDoll.where(id: params[:parent_ids])
respond_to do |format|
if #russian_doll.update(rd_params)
format.html { render 'foo' }
format.xml { render 'bar' }
else
format.html { render action: 'edit' }
format.xml { render xml: #russian_doll.errors }
end
end
end
private
def russian_doll_params
params.require(:russian_doll).permit(:name, :description)
end
end
When you try to create a new RussianDoll (with name AND description) it wont save if the RussianDoll parent you are trying to associate it with doesnt have description set in database.
However if you create it without any parents, and then try to add a parent through the update action, then you will be able to set association to a RussianDoll entry that doesnt have description set (invalid entry).
As far as I know update should run all validations, same as the save action does but it doesn't seem to do that in this case.
Why do save validations work, but update ones don't?
Any help is appreciated!
[EDIT]
So I finally managed to find the root cause...
In create action we first create the #russian_doll with already set invalid parent in its association, so if we run .valid? on the object it will fail.
However in the update action, our #russian_doll is being first found in database, and before the .update(rd_params) object itself is not being changed, so running .valid? will return true, and update will manage to connect edited russian_doll with invalid parent.
What still bugs me, is that second time the update is called, #russian_doll will have invalid parent from a first update, but for some reason .valid? is still returning true, instead of failing.
It is like validations work for all nested entries only on object creation...

Related

Trouble creating a new record in Rails with Select Boxes and Foreign Keys

Ruby on Rails newbie here.
TL;DR: When trying to create a new record, I get an error message saying my foreign keys must exist. I'm using Select Boxes on my view. Need help.
I am creating a requests management system for our own support staff here in our company. So far, I have generated a scaffold for Issues and 2 models, one for Category and another for Subcategory.
So far, the relationship I came up with is designed like this:
Issue belongs to Category and Subcategory
Subcategory belongs to Category.
This means that a user may have a problem with his keyboard, for which he will create an Issue. This issues would belong the the subcategory named "Peripherals", which would in turn, belongs to a broader "Hardware" category.
I still need to implement Ajax to fetch data for my Select Boxes, but while testing it out in a simpler scenario, I couldn't create my Issues from the "New Issue" view. I come across an error message saying that Category and Subcategory must exist. I've reviewed what I've written so far, but couldn't find my mistake.
Here is the code for my create method in the Issues controller
def create
#issue = Issue.new(issue_params)
respond_to do |format|
if #issue.save
format.html { redirect_to root_path, notice: 'Issue was successfully created.' }
else
#categories = Category.enabled
#subcategories = Subcategory.enabled
format.html { render :new }
end
end
end
Here go my Models
Issue Model:
class Issue < ApplicationRecord
belongs_to :category
belongs_to :subcategory
end
Category Model:
class Category < ApplicationRecord
has_many :subcategories
has_many :issues
enum status: { disabled: 0, enabled: 1 }
after_initialize :set_defaults
def self.enabled
where(status: 'enabled')
end
def set_defaults
self.status ||= 1
end
end
Subcategory Model
class Subcategory < ApplicationRecord
belongs_to :category
has_many :issues
enum status: { disabled: 0, enabled: 1 }
def self.enabled
where(status: 'enabled')
end
after_initialize :set_defaults
def set_defaults
self.status ||= 1
end
end
And finally, here are the parameters passed to the controller:
Processing by IssuesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"tzKDayYfEbEwTaOFup/N9kQ+8tr9c0P5otV2B0boKGrgyv+HkQaEvYJ6ZMQeR+8XgCnhJR6PosVcx0jPJpqBEA==", "category_id"=>"1", "subcategory_id"=>"1", "issue"=>{"description"=>"Replace broken keyboard.", "status"=>"1", "criticality_level"=>"1"}, "commit"=>"Create Issue"}
I was able to create an Issue via Rails Console, though.
Could anyone give me a hint on how to solve this? Thanks guys!
modify create action as follows
def create
#category = Category.find(params[:category_id])
#issue = #category.issues.build(issue_params)
respond_to do |format|
if #issue.save
format.html { redirect_to root_path, notice: 'Issue was successfully created.' }
else
#categories = Category.enabled
#subcategories = Subcategory.enabled
format.html { render :new }
end
end
end

Rails 4 - Polymorphic associations with update action on nested polymorphic attributes

I'm trying to figure out a problem I seem to keep having in setting up polymorphic associations in my Rails 4 app.
I have a project model and an address model. The associations are:
Profile
has_many :addresses, as: :addressable
accepts_nested_attributes_for :addresses, reject_if: :all_blank, allow_destroy: true
Address
belongs_to :addressable, :polymorphic => true
I previously asked this question on the same problem. I couldn't (and still can't) understand the answers in that post: Rails 4 - Polymorphic associations
This time around - I'm having a problem that is triggered when I try to update a profile by inserting an address. The error message identifies the problem as coming from the update action in the profiles controller. The update action has:
My profiles controller update action has:
def update
# successful = #profile.update(profile_params)
# Rails.logger.info "xxxxxxxxxxxxx"
# Rails.logger.info successful.inspect
# user=#profile.user
# user.update.avatar
# Rails.logger.info "prof xxxxxxxxxxxxx"
# Rails.logger.info #profile.update(profile_params)
respond_to do |format|
if #profile.update(profile_params)
format.html { redirect_to #profile }
format.json { render :show, status: :ok, location: #profile }
else
format.html { render :edit }
format.json { render json: #profile.errors, status: :unprocessable_entity }
end
end
end
The error message says:
ERROR: duplicate key value violates unique constraint "index_addresses_on_addressable_type_and_addressable_id"
DETAIL: Key (addressable_type, addressable_id)=(Profile, 1) already exists.
Does anyone know what this message means, and how to address it?
In your database, you have set a unique constraint: , you can go to the database to see what you have set by the name of "index_addresses_on_addressable_type_and_addressable_id". As the error message show, you try to update a record with value ( Profile , 1) which has already been used by another record.
To solve this issue, there are two solutions:
one is from the database side:
you need to know why there is a unique constraint about addresses. if it is not need , you can remove it from the database.
the other is ensure the (addressable_type, addressable_id) is unique before you update your data into database.
hope this can give a kind of help

STI with a Single Controller

I have several models (User, Goal) and my Goal model has several subtypes (Exercise, Yoga, etc.)
A user can have many goals, but only one of each type.
class User < ActiveRecord::Base
has_many :goals, dependent: :destroy
has_one :exercise
has_one :water
has_one :yoga
def set_default_role
self.role ||= :user
end
end
and
class Goal < ActiveRecord::Base
self.inheritance_column = :description
belongs_to :user
validates :user_id, presence: true
validates :description, presence: true
end
where a subclass of goal is just something like this
class Exercise < Goal
belongs_to :user
end
I want to create all types of goals in my goal controller and have it set up so that localhostL:3000/waters/new will be my "water goal-type" creation page. I'm having trouble correctly setting it up though so that description is automatically set (my STI column) because I also want to build it through my user (so user_id is also set).
my goals controller currently looks like this...
def create
#goal = current_user.goals.build
???? code to set goal.description = subtype that the request is coming from
respond_to do |format|
if #goal.save
format.html { redirect_to #goal, notice: 'Goal was successfully created.' }
format.json { render action: 'show', status: :created, location: #goal }
else
format.html { render action: 'new' }
format.json { render json: #goal.errors, status: :unprocessable_entity }
end
end
end
I'm pretty new to rails so a little bit confused. Also using Rails 4.0
In that case you'd need to communicate the type to create to the controller somehow.
This is most easily established by including a hidden field in the form which has a value of the type you want to create.
Assuming your block variable for the form is f
<%= f.hidden_field :type, :value => "Excercise" %>
Then you can build a goal like this:
current_user.goals.build(type: params[:type])
You can easily see how this can be very dangerous, you should always be doubly careful when using user submitted data.
To guard against a malicious user, you could set a constant in your controller
AcceptedModels = ["Excercise", "Yoga", "Water"]
def accepted_type
(AcceptedModels & params[:type]).first
end
If you also want to hide your internal structure, you could use a hash and send any identifier
AcceptedModels = {"0": "Excercise", "1": "Yoga", "2": "Water"}
def accepted_type
AcceptedModels.fetch(params[:type], nil)
end
in either case you can then build your goal like this:
current_user.goals.build(type: accepted_type)
The biggest downside with this is that you will have to keep the AcceptedModels updated whenever you add/remove more goals

Rails object does not return has_many subclass object

I've got below setup in my Ticket.rb model
has_many :note
And in note.rb model
belongs_to :ticket
But I cannot seem to get note id or description using below code:
def show
#ticket = Ticket.find(params[:id])
#note_id = #ticket.note.id
respond_to do |format|
format.html
format.json { render json: #ticket }
end
end
It doesn't seem to detect note class at all when I call #ticket.node
I also have note table populated with a row that has ticket_id aswell
What could be wrong?
Ticket.note is a has many. It behaves like an array.
You either need to change it to has_one :note, or use #ticket.note.first.id. If you're not sure if the note exists for a given ticket, you could try #ticket.note.first.try(:id).

Rails Changing Polymorphic Type Of An Object

We have a parent model Vehicle that is inherited to make classes Car, Truck, SUV. In our form, we allow the user to edit the data for a bunch of Vehicles, and one of the attributes for each vehicle is a select menu for "type". The HTML attribute is named vehicle_type and updates the actual Polymorphic type attribute in the Vehicle Model:
# Get/Set Type bc rails doesnt allow :type to be set in mass
def vehicle_type
self.type
end
def vehicle_type=(type)
self.type = type
end
The problem we're having is that when we call update_attributes on form data and the type of an existing vehicle has been changed, rails is calling the validation for the old class (not new type) which results in errors. What we need to do is when vehicle_type is changed, that the model is changed to that new type as well.
Is there a way to do this?
Here is the update action (fleet has_many vehicles):
# PUT /fleet/1
# PUT /fleet/1.xml
def update
#fleet = Fleet.find(params[:id])
respond_to do |format|
if #fleet.update_attributes(params[:fleet])
flash[:notice] = 'Fleet of vehicles was successfully updated.'
format.html { render :action => "edit" }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #fleet.errors, :status => :unprocessable_entity }
end
end
end
Here is Fleet:
class Fleet < ActiveRecord::Base
has_many :vehicles, :dependent => :destroy, :order => 'position ASC'
accepts_nested_attributes_for :vehicles,
:reject_if => proc { |attrs| attrs['name'].blank? },
:allow_destroy => true
You could use a factory method to create the right type of vehicle, then assign the attributes.
def Vehicle.factory(type)
type.constantize.new
end
When you call #vehicle.save, you could add false as the first parameter to skip validation (#vehicle.save(false)). Another option is to use ActiveRecord::Base#update_attribute -- which skips validation.
So you could save your model without the type column changing, then add a #vehicle.update_attribute(:type, "Truck") in the same action. Two SQL queries, but may be necessary for your situation.

Resources