Rails 4 - Custom validation method not being called - ruby-on-rails

I have a validation method that should validate whether an user is part of a team. If it is not part of the team it should add an error and thus failing to save the record.
This is the current method that is inside the model:
def assignee_must_be_part_of_team
unless assignee_id.blank?
team = Team.find(self.team_id)
errors.add(:team, 'Equipe não existe') unless team
user = team.users.find(self.assignee_id)
errors.add(:assignee_id, 'Responsável não faz parte da equipe') unless user
end
end
And I am registering it in my model with this:
validate :assignee_must_be_part_of_team, on: :save
However, this method is not being even called when I save a new record! I even tried to add some logs to it but nothing happens and the record is being saved anyway.
Am I missing anything here?

Use create or update as the value of :on option.
Change this:
validate :assignee_must_be_part_of_team, on: :save
To:
validate :assignee_must_be_part_of_team, on: :create
or:
validate :assignee_must_be_part_of_team, on: :update
If you want your validation to run for both create and update actions, then you don't even need to specify the :on option at all, because that's the default behaviour. So, just this should work:
validate :assignee_must_be_part_of_team
See the documentation here for more information.

You are adding two errors in one validation. maybe you can split this into separate validations for easy debugging:
validates :team_id, presence: :true
validate :belong_to_team, :assignee_part_of_team
private
def belong_to_team
errors[:team] << 'Equipe não existe' unless self.team
end
def assignee_part_of_team
errors[:assignee] << 'Responsável não faz parte da equipe' unless self.team and self.team.users.include?(self.assignee)
end
Then you can know which is causing the fault here.

Related

Rails 4 before_save attribute changed encrypting password

So I wrote an app before that allowed for the standard way of encrypting a password using this and it worked fine:
before_save :create_hashed_password
Then:
def create_hashed_password
# validation code not shown
self.password = Digest::SHA1.hexdigest(password)
end
The problem is now in this app is that I have other user attributes I want to edit and every time I edit and save, I am hashing the already hashed password, thus making login impossible after updating.
I tested this in irb and it works:
irb(main):008:0> t.password = 'password'
=> "password"
irb(main):009:0> t.password_changed?
=> true
But when I use this line in the before filter:
before_save :create_hashed_password if password_changed?
It fails with the following error:
NoMethodError: undefined method `password_changed?' for User(no database connection):Class
(And before you ask, yes I do have a db connection, it's just with the User model because the before filter is there)
BTW I'm on Rails 4.
Try with:
before_save :create_hashed_password, if: :password_changed?
Short explanation: in your current syntax, the if part is not a param to the before_save method, this is why you need to add the coma, to send it as a parameter. Now it tries to call a class method: User.password_changed?, this doesn't make sense since you need to perform an instance method against a user object.
Try this:
before_save :create_hashed_password, if: Proc.new { &:password_changed? }
Hope this helps, happy coding

Save Model with a partially failed validation

Lets say I have the following model:
class Car < ActiveRecord::Base
attr_accessible :wheels,
:engine_cylinders
validates :wheels, :engine_cylinders, presence: true, numericality: true
end
Lets say I then have the following controller action:
#car = Car.find(params[:id])
#car.wheels = "foo"
#car.engine_cylinders = 4
#car.save
This save will fail as wheels will fail the numericality condition.
Is there any way to persist the succesful attributes (in this case engine_cylinders), while adding the invalid attributes to the errors array? E.g. is there a "soft" validation in Rails?
You want to write a Custom Validator.
class Car < ActiveRecord::Base
validate :wheel_range,
:engine_cylinder_range
def engine_cylinder_range
flash[:notice] = "engine_cylinder was not saved because it wasn't a number" unless engine_cylinder.is_a? Fixnum
# set engine_cylinder back to old value
end
def wheel_range
flash[:notice] = "wheels was not saved because it wasn't a number" unless wheels.is_a? Fixnum
# set wheels back to old value
end
end
You don't have to use flash here, you could use any variable for internal processing or re-display.
You may also want to put this custom validation check on the :before_save hook. Use the _was magic method to get the old value.
If you're looking to bypass validations you can always do so with:
if #car.save
# ...
else
#car.save(validate: false)
end
You may want to have a different conditional on that or whatever... but this is how you bypass validations on a one-off basis.
This may, however, destroy the errors array so you could rebuild it after the save(validate: false) with:
#car.valid?
You can also bypass validations one-at-a-time using #car.update_attribute(:attribute, <value>).
If you just want to know if the model is valid or not without saving it #car.valid? does exactly that. It also adds invalid attributes to errors array. And #pdobb already pointed out how to bypass validation when saving.

Rails - Validate on Create Only

I'm trying to get my custom validation to work on create. But when I do a find then save, rails treats it as create and runs the custom validation. How do I get the validations to only work when creating a new record on not on the update of a found record?
Try this on your line of validation code:
validate :custom_validation, on: :create
this specifies to only run the validation on the create action.
Source:
ActiveModel/Validations/ClassMethods/validate # apidock.com
TL;DR; Full example:
class MyPerson < ActiveRecord::Base
validate :age_requirement
private
def age_requirement
unless self.age > 21
errors.add(:age, "must be at least 10 characters in length")
end
end
end
To have the validator run only if a new object is being created, you can change the 2nd line of the example to: validate :age_requirement, on: :create
Only on updates: validate :age_requirement, on: :update
Hope that helps the next person!

Rails - Allowing for calling in Model Validation in a Controller

I have the following in my user.rb model:
INVALID_EMAILS = %w(gmail.com hotmail.com)
validates_format_of :email, :without => /#{INVALID_EMAILS.map{|a| Regexp.quote(a)}.join('|')}/, :message => "That email domain won't do.", :on => :create
For various reasons, I want to be able to use this logic in my controller to check an email's input before it is user.created, which is when the above normall runs.
How can I turn the above into a method that I can call in controllers other than user? Possible?
And if is called and returned false I then want to do errors.add so I can let the user know why?
Thanks
Trying:
def validate_email_domain(emailAddy)
INVALID_EMAILS = %w(gmail.com googlemail.com yahoo.com ymail.com rocketmail.com hotmail.com facebook.com)
reg = Regexp.new '/#{INVALID_EMAILS.map{|a| Regexp.quote(a)}.join('|')}/'
self.errors.add('rox', 'Hey, Ruby rox. You have to say it !') unless reg.match attribute
end
Update:
..
Rails.logger.info validate_email_domain(email)
...
def valid_email_domain(emailAddy)
reg = Regexp.new '/#{User::INVALID_EMAILS.map{|a| Regexp.quote(a)}.join("|")}/'
return true if emailAddy.scan(reg).size == 0
end
You cannot assign a constant inside a method, because that would make it "dynamic constant assignment". Instead, define this constant in your model class and then reference it in your controller by using User::INVALID_EMAILS
Okay, if I understand you.
You want to do something like below:
u = User.new
u.email = "jsmith#gmail.com"
if !u.valid?
puts u.errors.to_xml
//do something
return
end
What you do with those errors is going to come down to how you want those reported back, usually I just shoot them back as xml into a flash[:error], which is the normal default behavior if you're doing scaffolds. The puts is there so you can see how to access the errors.
Additional
As a rule try to avoid duplicating validation logic. Rails provides everything you need for validating without creating different methods in different places to accomplish the same thing.

ActiveRecord custom validation problem

I'm having a problem with validation in my RoR Model:
def save
self.accessed = Time.now.to_s
self.modified = accessed
validate_username
super
end
def validate_username
if User.find(:first, :select => :id, :conditions => ["userid = '#{self.userid}'"])
self.errors.add(:userid, "already exists")
end
end
As you can see, I've replaced the Model's save method with my own, calling validate_username before I call the parent .save method. My Problem is, that, even though the error is being added, Rails still tries to insert the new row into the database, even if the user name is a duplicate. What am I doing wrong here?
PS: I'm not using validate_uniqueness_of because of the following issue with case sensitivity: https://rails.lighthouseapp.com/projects/8994/tickets/2503-validates_uniqueness_of-is-horribly-inefficient-in-mysql
Update: I tried weppos solution, and it works, but not quite as I'd like it to. Now, the field gets marked as incorrect, but only if all other fields are correct. What I mean is, if I enter a wrong E-Mail address for example, the email field is marked es faulty, the userid field is not. When I submit a correct email address then, the userid fields gets marked as incorrect. Hope you guys understand what I mean :D
Update2: The data should be validated in a way, that it should not be possible to insert duplicate user ids into the database, case insensitive. The user ids have the format "user-domain", eg. "test-something.net". Unfortunately, validates_uniqueness_of :userid does not work, it tries to insert "test-something.net" into the database even though there already is an "Test-something.net". validate_username was supposed to be my (quick) workaround for this problem, but it didn't work. weppos solution did work, but not quite as I want it to (as explained in my first update).
Haven't figured this out yet... anyone?
Best regards,
x3ro
Why don't you use a callback and leave the save method untouched?
Also, avoid direct SQL value interpolation.
class ... < ActiveRecord::Base
before_save :set_defaults
before_create :validate_username
protected
def set_defaults
self.accessed = Time.now.to_s
self.modified = accessed
end
def validate_username
errors.add(:userid, "already exists") if User.exists?(:userid => self.userid)
errors.empty?
end
end
How about calling super only if validate_username returns true or something similar?
def save
self.accessed = Time.now.to_s
self.modified = accessed
super if validate_username
end
def validate_username
if User.find(:first, :select => :id, :conditions => ["userid = '#{self.userid}'"])
self.errors.add(:userid, "already exists")
return false
end
end
... I think that you could also remove totally the super call. Not sure, but you could test it out.

Resources