Why Rails update_attributes is calling validation before saving - ruby-on-rails

I am having a very strange problem in Rails. I am using update_attributes on a Parent Object to update all the children objects. The children objects have (custom) validation and indeed this works ok, meaning that if I give wrong values the validation trigger and I get an error back.
Now I am in a strange situation where one of the model is invalid in the database (let's not question why, let's just say I can go in the DB and run some SQL to make the model invalid). If I go in my app I can see the invalid values and this is fine. I fix the values and save again and I can see, stepping in the ruby code that the validation is called also BEFORE saving the new values, meaning that I will get an error and Rails will never execute the SQL to actually update the values to the correct ones.
I hope the above makes sense. Do you have any idea or do you think there is something I am overlooking?
SOLUTION:
What was happening was that a many-to-many relationship was validating the existing DB data before being replaced by the new data. Basically the structure was like this:
class User
has_many :user_permissions
has_many :permissions, :through => :model_permissions
class Permission
has_many :user_permissions
has_many :users, :through => :user_permissions
class UserPermission
belongs_to :user
belongs_to :permission
validates_associated :user # THIS was causing the problem
validates_associated :permission # and THIS as well
I simply removed the validates_associated directive, since I am validating the linked records independently anyway.

Well, Rails does run your validation prior to writing the data to the database (please refer to Active Record callback sequence), so having validation errors means that some piece of the model you are trying to save is not valid. It might an associated model containing errors with the validation turned on or just some missing part – in any case, just have a look at what are the errors you are getting.
In case (let's no question why either :) you want to skip validation - you are open to choose from #update_attribute (to update just one attribute), calling #save(false), using +udpate_all method of the model class or even go down to ActiveRecord::Base.connection.execute – none of these will ever bother you with validation errors :)

Related

Cycle through object has_many association and remove certain one

Im creating an object (recipe_change) that has many ingredient_changes. However I want to run my own validation of sort and prevent the creation of ingredient_changes if they dont pass. I started with a validation in the ingredient_changes but with that if I added an error the valid? would prevent the change from being submitted if one of the many ingredient_changes wasnt valid...which I dont want. So then I tried the following in my create method in the recipe_change controller and this almost did the job except once it deleted one it would break the loop and not do the rest:
#recipe_change.ingredient_changes.each do |change|
if ...long conditional statement...
#recipe_change.ingredient_changes.delete(change)
end
end
Is there a function like the array .reject! that works for associations like this. At this point in the code they are not saved to the database and Im trying to not save them to the database if they dont meet my condition.
You can use reject_if to filter out invalid ingredient_changes in recipe_change model like this:
class RecipeChange < ActiveRecord::Base
has_many :ingredient_changes, reject_if: :invalid_ingredient_change?
def invalid_ingredient_change?(attributes)
end
end
See: http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

What is the rationale for `reject_if`?

I've come across reject_if in cocoon's README, and also in the documentation for Nested Attributes. What is the rationale for using reject_if when the associated active record objects can determine whether they're valid or not?
For nested objects, you may not want to attempt to submit a record that is :all_blank or for any other reason you may want to check on. The point being, an empty or incomplete (in some way) object can, this way, simply not be built / added to the nested objects collection.
Validations serve a different purpose. If an empty object, say, fails validation then the whole form submit will fail. The reject_if approach allows submission to succeed in such a case by removing the object from consideration before validations fire.
The Rails guides has a description of the rationale for :reject_if, though it doesn't explicitly compare this option to just validating the sub-objects:
9.5 Preventing Empty Records
It is often useful to ignore sets of fields that the user has not
filled in. You can control this by passing a :reject_if proc to
accepts_nested_attributes_for. This proc will be called with each
hash of attributes submitted by the form. If the proc returns false
then Active Record will not build an associated object for that hash.
The example below only tries to build an address if the kind
attribute is set.
class Person < ActiveRecord::Base
has_many :addresses
accepts_nested_attributes_for :addresses, reject_if: lambda {|attributes| attributes['kind'].blank?}
end
As a convenience you can instead pass the symbol :all_blank which
will create a proc that will reject records where all the attributes
are blank excluding any value for _destroy.

How to save related Models in one transaction?

I have two models:
class Customer < ActiveRecord::Base
has_many :contacts
end
class Contact < ActiveRecord::Base
belongs_to :customer
validates :customer, presence: true
end
Then, in my controller, I would expect to be able to create both in
"one" sweep:
#customer = Customer.new
#customer.contacts.build
#customer.save
This, fails (unfortunately translations are on, It translates to
something like: Contact: customer cannot be blank.)
#customer.errors.messages #=> :contacts=>["translation missing: en.activerecord.errors.models.customer.attributes.contacts.invalid"]}
When inspecting the models, indeed, #customer.contacts.first.customer
is nil. Which, somehow, makes sense, since the #customer has not
been saved, and thus has no id.
How can I build such associated models, then save/create them, so that:
No models are persisted if one is invalid,
the errors can be read out in one list, rather then combining the
error-messages from all the models,
and keep my code concise?
From rails api doc
If you are going to modify the association (rather than just read from it), then it is a good idea to set the :inverse_of option on the source association on the join model. This allows associated records to be built which will automatically create the appropriate join model records when they are saved. (See the ‘Association Join Models’ section above.)
So simply add :inverse_of to relationship declaration (has_many, belongs_to etc) will make active_record save models in the right order.
The first thing that came to my mind - just get rid of that validation.
Second thing that came to mind - save the customer first and them build the contact.
Third thing: use :inverse_of when you declare the relationship. Might help as well.
You can save newly created related models in a single database transaction but not with a single call to save method. Some ORMs (e.g. LINQToSQL and Entity Framework) can do it but ActiveRecord can't. Just use ActiveRecord::Base.transaction method to make sure that either both models are saved or none of them. More about ActiveRecord and transactions here http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html

How to intercept accepts_nested_attributes_for?

I have a Rails application, with two models: SalesTransactions and PurchaseOrders.
In the PurchaseOrders model, new entries are registered using 'purchase_order_number' as the key field. I use the create method of the model to search if that 'purchase_order_number' has been previously registered, and if so, reuse that record and use its id in the SalesTransaction record. If that name wasn't already registered, I go ahead and perform the create, and then use the new PurchaseOrder record id in the SalesTransaction (the foreign_id linking to the associated PO).
Note that I don't have the existing PurchaseOrder record id until I've done a look-up in the create method (so this is not a question of 'how do I update a record using 'accepts_nested_attributes_for'?', I can do that once I have the id).
In some situations, my application records a new SalesTransaction, and creates a new PurchaseOrder at the same time. It uses accepts_nested_attributes_for to create the PurchaseOrder record.
The problem appears to be that when using 'accepts_nested_attributes_for', create is not called and so my model does not have the opportunity to intercept the create, and look-up if the 'purchase_order_number' has already been registered and handle that case.
I'd appreciate suggestions as to how to intercept 'accepts_nested_attributes_for' creations to allow some pre-processing (i.e. look up if the PurchaseOrder record with that number already exists, and if so, use it).
Not all Sales have a PurchaseOrder, so the PurchaseOrder record is optional within a SalesTransaction.
(I've seen a kludge involving :reject_if, but that does not allow me to add the existing record id as the foreign_id within the parent record.)
Thanks.
You could use validate and save callbacks to do what you need.
Assuming the setup:
class SalesTransaction < ActiveRecord::Base
belongs_to :purchase_order, :foreign_key => "po_purchase_order_no",
:primary_key => "purchase_order_no"
accepts_nested_attributes_for :purchase_order
end
class PurchaseOrder < ActiveRecord::Base
has_many :sales_transactions, :foreign_key => "po_purchase_order_no",
:primary_key => "purchase_order_no"
before_validation :check_for_exisitng_po # maybe only on create?
accepts_nested_attributes_for :sales_transactions
private
def check_for_exisitng_po
existing_po = PurchaseOrder.find_by_purchase_order_no(self.purchase_order_no)
if existing_po
self.id = existing_po.id
self.reload # don't like this, also will overwrite incoming attrs
#new_record = false # tell AR this is not a new record
end
true
end
end
This should give back full use of accepts_nested_attributes_for again.
gist w/tests
Two ideas: Have you taken a look at association callbacks? Perhaps you can "intercept" accepts_nested_attributes_for at this level, using :before_add to check if it is already in the DB before creating a new record.
The other idea is to post-process instead. In an after_save/update you could look up all of the records with the name (that ought to be unique), and if there's more than one then merge them.
I was going to write a before_save function, but you say this:
It uses accepts_nested_attributes_for to create the PurchaseOrder record.
So in the SalesTransaction process flow, why look it up at all? You should just get the next one available... there shouldn't be a reason to search for something that didn't exist until NOW.
OK, I've left this question out there for a while, and offered a bounty, but I've not got the answer I'm looking for (though I certainly appreciate folk trying to help).
I'm concluding that I wasn't missing some trick and, at the time of writing, there isn't a neat solution, only work-arounds.
As such, I'm going to rewrite my App to avoid using accept_nested_attributes_for, and post the SalesTransaction and the PurchaseOrder records separately, so the create code can be applied in both cases.
A shame, as accept_nested... is pretty cool otherwise, but it's not complete enough in this case.
I still love Rails ;-)

How to validate that a parent object has a valid child object (Rails)

Let's say I have an ActiveRecord model called Book that has a has_many association with a model Pages.
class Book < ActiveRecord::Base
has_many :pages
end
I'd like to know if there is an established method of ensuring that a Book object cannot be saved to the database without having at least one valid Page object associated with it. My goal is not to test the presence of an association, but to validate that a parent object indeed has a valid child object. Does this make sense? Is this actually a case of testing an association? I'm familiar with the "validates_associated" method, but this validation will not fail if the association hasn't been assigned, but how do I ensure that there is a valid object on the other side of the association?
From the Rails 2.3.2 documentation for validates_associated:
NOTE: This validation will not fail if
the association hasn’t been assigned.
If you want to ensure that the
association is both present and
guaranteed to be valid, you also need
to use validates_presence_of.

Resources