Why doesn't my custom validation run in Rails? - ruby-on-rails

This is my model:
class Goal < ActiveRecord::Base
belongs_to :user
validate :progress_is_less_than_max
private
def progress_is_less_than_max
if progress > max
errors.add(:progress, "should be less than max")
end
end
end
If I go into the console and do
some_user.goals.create! :name => 'test', :max => 10, :progress => 15, :unit => 'stuff'
it saves just fine, without any errors. What am I not doing right?

Well, that's not how you write a custom validator: your custom validator should inherit from ActiveModel::EachValidator.
See the bottom of this rails cast for an example of a customer validator: http://railscasts.com/episodes/211-validations-in-rails-3?view=asciicast

#jaydel is correct in that .create will return an instance of the model (regardless of if it is saved in the database or not).
Creates an object (or multiple objects) and saves it to the database, if validations pass. The resulting object is returned whether the object was saved successfully to the database or not.
However, calling .save! on the .create'd model or calling .create! to begin with will raise an exception if validations fail.
Creates an object just like ActiveRecord::Base.create but calls save! instead of save so an exception is raised if the record is invalid.
.save will run validations but returns false if they fail.
By default, save always run validations. If any of them fail the action is cancelled and save returns false. However, if you supply :validate => false, validations are bypassed altogether. See ActiveRecord::Validations for more information.

Related

Validations for api build with rails

I have a model Person.
One controller Api::V1::PersonsController
In my controller:
def index
#persons = Person.new(user_id: #current_user.id, type_id: params[:type_id]).method
render json: #persons, status: :ok
end
In my model:
attr_accessor :user_id, :type_id
validates_presence_of :type_id
Also tried:
validates :type_id, :presence => true
When I create my Person with no type_id, I don't get any error, what else do I need to do, or is there a better way of doing this?
From the Rails guide validation section:
The following methods trigger validations, and will save the object to
the database only if the object is valid:
create
create!
save
save!
update
update!
The bang versions (e.g. save!) raise an exception if the record is
invalid. The non-bang versions don't, save and update return false,
create just returns the object.
When you create an object using the new method, the validation rules do not fire as the object is not persisted to the database.
You can call Person.save or Person.save! after Person.new or create a Person object using create or create!. Both of these methods persist the object to the database so a validation error will be raised.
Also, in your case, you can do something like this:
Person.new(user_id: #current_user.id, type_id: params[:type_id]).valid? # => false
This way, you can check if the object is a valid object and then proceed with the rest of your code.
.new is not going to persist your Person to the database.
Validation will not be carried out unless using .save after .new or in a .create or .create! method.
Check out point 1.2 here in Rails validation guides ›

Invalidating parent model save through child before_save callback

I have two models, a Parent and a Child (as outlined below). The child model has a before_save callback to handle some external logic, and if it encounters any errors, the callback invalidates that model being saved.
class Parent < ActiveRecord::Base
has_one :child
accepts_nested_attributes_for :child
validates :child, :presence => true
validates_associated :child
end
class Child < ActiveRecord::Base
belongs_to :parent
before_save :external_logic
validates :parent, :presence => true
def external_logic
begin
# Some logic
rescue
#Invalidate child model
errors.add(:base, "external logic failed")
return false
end
end
end
The problem that I'm running into is that the Child model instance is created as through the nested attributes of the Parent model. When the external logic fails, I want the child model AND the parent model to not be saved, but instead the parent model is being saved on its own. How can I achieve this?
Please note, I am aware of validation callbacks, but they are not suitable in this case. The child model callback has to be a before_save.
EDIT #1
I already know about transactions, and don't consider someone telling me "hey, wrap it around a transaction externally" to be a valid response. This question is explicitly about how to solve this issue through a before_save call.
Why I can't use validations on create - as mentioned in the comments, the external bit of logic needs to be guaranteed to run ONLY before a database save. Validation calls can happen multiple times with or without altering the database record, so that's an inappropriate place to put this logic.
EDIT #2
Ok, apparently having the before_save return false does prevent the parent being saved. I have verified that through the console and actually inspecting the database. However, my rspec tests are telling me otherwise, which is just odd. In particular, this is failing:
describe "parent attributes hash" do
it "creates new record" do
parent = Parent.create(:name => "name", :child_attributes => {:name => "childname"})
customer.persisted?.should be_false
end
end
Could that be an rspec/factory_girl bit of weirdness?
EDIT #3
The test error is because I'm using transactional fixtures in Rspec. That was leading to tests that incorrectly tell me that objects were being persisted in the database when they really weren't.
config.use_transactional_fixtures = true
Okay so your problem is with the ActiveRecord::Callbacks order.
As you can see on the linked page first validation is processed and if validation was successful then before_save callbacks are run. before_save is a place where you can assume every validation passed so you can manipulate a bit data or fill a custom attribute based on other attributes. Things like that.
So what you could do is just say for the Child model:
validate :external_logic and just remove the before_save :external_logic callback.
It's equivalent with what you want to do. When a Parent instance is created it will just error out if the Child object fails to validate, which will happen in your :external_logic validation method. This is a custom validation method technique.
After OP update:
Still you can use :validate method. You can set it to only run on create with:
validate :external_logic, :on => :create.
If you are running into issue that you need this to run on update as well, that is the default behavior. Validations are run on .create and .update only.
OR If you want to stick to before_save:
The whole callback chain is wrapped in a transaction. If any before callback method returns exactly false or raises an exception, the execution chain gets halted and a ROLLBACK is issued; after callbacks can only accomplish that by raising an exception.
I see you did return false so it should work as expected. How do you use Parent.create! method? What are the arguments there?
Make sure you are using it like (supposing .name is an attribute of Parent and Child):
Parent.create!{
:name => 'MyParent'
# other attributes for Parent
:child_attributes => {
:name => 'MyChild'
# other attributes for Child
}
}
This way it both the Parent and Child object will be created in the same transaction, so if your before_save method returns false Parent object will be rolled back.
OR
If you cannot use this format you could just try using pure transactions (doc, example in guides):
Parent.transaction do
p = Parent.create
raise Exception if true # any condition
end
Anything you do inside of this transaction will be rolled back if there is an exception raised inside the block.

Rails validation fails even when data is entered

Here is the command that I'm executing in Rails Console:
Person.create!(:firstName=>"matt", :lastName=>"master", :gender => 1)
My result is this error message:
ActiveRecord::RecordInvalid: Validation failed: Firstname can't be blank
My model validation code looks as such:
class Person < ActiveRecord::Base
belongs_to :user, :class_name => 'User', :foreign_key => 'fk_ssmUserId'
validates_presence_of :firstName, :lastName
When I comment out validates_presence_of everything works and my data is entered properly into the database, so I know that the values are actually being passed into the new object. I even inspected the new object created inside the Rails::ActiveRecord::Validations code to make sure it was being instantiated correctly before being saved. It was. Also, I have other models with validates_presence_of that work 100% fine every time. It's just this one model. I am using Rails 3.1.0.rc1.
Any ideas?
Thanks!
:firstName=>"matt", :lastName=>"master", :gender => 1
check that the key correspond to your model columns. Seems to be everything should be fine.
I have a suspicion that this error relates to the fact that you're creating and saving the object using the .create! method from the console. Your Person class appears to require a foreign key, a value which is probably not being instantiated when you create an object at the console. To test this, try typing:
test = Person.create(:firstName=>"matt", :lastName=>"master", :gender => 1)
with no bang after the .create method. This should not generate an error. Now type:
test
It's very likely that you have required key values set as "nil" and that the object can't be saved from console until you fill in the appropriate values.

Calling valid? on new records, but ignore validity of built associations

I have three tables: tasks, departments, and department_tasks. I need to call "valid?" on new task objects, but I want to ignore the validity of any "built" department_tasks. We are doing bulk uploads, and so we load everything or nothing.
As we loop through the Excel file we are reading in, we build the new "Task" according to the values in each row. With each row, there may be an associated department for the task; if there is, we "build" the associated department_task object like so:
new_task.department_tasks.build(:department_id => d.id)
At the end of the loop, we test the validity of the new "task" object by calling "valid?"
new_task.valid?
If the task is valid, it goes in the "good" pile; if it's bad, it goes on the "bad" pile.
The problem is, we haven't saved the task and therefore it has no :id. Without an id, the "built" department_task is invalid (:department_id and :task_id must both be present).
I need to know how I can call "valid?" or test validity of the "new_task" object without the validation cascading down to the "task_department" associated object which cannot be valid before task is saved.
You can skip individual validations using :if or :unless
validates_presence_of :department_id,
:unless => lambda { |record| record.new_record? }
If I understand correctly, you have something like this:
class Task < ActiveRecord::Base
has_many :department_tasks
has_many :departments, :through => :department_tasks
validates_associated :department_tasks
end
class DepartmentTask < ActiveRecord::Base
belongs_to :task
belongs_to :department
validates_presence_of :department_id, :task_id
end
When Task is new the associated validation in DepartmentTask fails because task_id is nil. Correct?
I don't see an easy way around this. The most obvious solution is to just remove the validates_presence_of for task_id. If the only way that you create DepartmentTasks is to build them through the Task model, the presence_of validation seems unnecessary, since Rails will always add the task_id when Task is saved.
Another option is to wrap it in a transaction, create the new task (so it has an ID), then build and validate DepartmentTask, and rollback if invalid.
You should assign an object to your AR queries to check it's validity, also use .new(:department_id => d.id)
myrecord = new_task.department_tasks.new(:department_id => d.id)
if myrecord.save!
"good pile"
else
"bad pile"
end

ActiveRecord association getting saved before the record in an update

I have an Entry model which has many Tags. Tags are added to an entry by typing them into a textbox on my form, via a tag_names virtual attribute. Before validation on the Entry model, the tag_names string is converted into actual Tag objects using find_or_create_by_name. The Tag model also has validation to make sure the tag name matches a regex, which is run via the association.
My Entry model looks like this:
class Entry < ActiveRecord::Base
has_many :entry_tags
has_many :tags, :through => :entry_tags
before_validation :update_tags
attr_writer :tag_names
private
def update_tags
if #tag_names
self.tags = #tag_names.split(",").uniq.map do |name|
Tag.find_or_create_by_name(name.strip)
end
end
end
end
When I create a new Entry object and assign it tags, everything works correctly -- the tags are not saved if there is a validation error on one of the Tags, and an error message is passed back. However, if I try to update an existing Entry object with an invalid tag, instead of passing back a message, my self.tags= call (in update_tags above) is throwing an exception with the validation error message. Even if I overwrite find_or_create_by_name to actually just return a new object instead of calling create, I get the same outcome.
It seems to me (and the docs seem to corroborate) that the tags= call is actually saving my Tag objects before the main record gets saved when the Entry object already exists. Is there anything I can do to make this save not happen, or to stop it from raising an exception and just causing my save to return false?
I would try something like this:
class Entry < ActiveRecord::Base
has_many :entry_tags
has_many :tags, :through => :entry_tags
before_validation :update_tags
attr_writer :tag_names
validates_associated :tags
private
def update_tags
return unless #tag_names
current_tag_names = tags.map(&:name)
user_tag_names = #tag_names.split(",").uniq
#add in new tags
user_tag_names.each do |name|
next if current_tag_names.include?(name)
tags.build :name => name
end
#remove dropped tags
( current_tag_names - user_tag_names ).each do |name|
removed_tag = tags.find_by_name(name)
tags.delete(removed_tag)
end
end
end
This way you're only initializing the related models in your update_tags action and so won't throw validation errors. I also added in the validates_associated :tags so that errors on these related models can be reported back via the standard input form using error_messages_for :entry.
Update included code for removing dropped tags.
You could catch the exception raised and return false, in that case, from update_tags which will halt the save on the Entry.
Alternatively, if you want to avoid handling that exception, you could build a new Tag instance where one doesn't already exist and check whether it is valid before proceeding (new_tag.valid?) and if it is not then return false from update_tags.

Resources