ActiveRecord custom validation problem - ruby-on-rails

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.

Related

rails 5 update attribute only if it's currently nil

What is the preferred way in Rails 5 with activerecord to update the attribute only if it is currently nil.
car = Car.first
car.connected_at = Time.zone.now
car.save
OR
car = Car.first
car.update!(connected_at: Time.zone.now)
it should update only if car.connected_at is nil
You can simply check for #nil?
car = Car.first
car.update_attribute(:connected_at, Time.zone.now) if car.connected_at.nil?
That's not generic enough. I want something like before_validation etc. I am just not sure which way is the preferred one.
Well if you want to go for validation, it would look something like this..
before_save :validate_connected_at
private
def validate_connected_at
connected_at = connected_at_was if connected_at_changed? && connected_at_was.present?
end
OR
before_save :set_connected_at
private
def set_connected_at
connected_at = Time.zone.now if connected_at.nil?
end
As you can see, more checks, more methods. I would definitely go for the first one.
However, if you want to add error message, then this is the way
errors.add(:connected_at, 'Already present!')
So "#{attr}_was" is always available on all the defined attrs in before_save method?
They are available in general and not only in before_save, e.g. in the console..
car = Car.first
car.connected_at
=> 'some value'
car.connected_at = 'some other value'
car.connected_at
=> 'some other value'
car.connected_at_was
=> 'some value'
It sounds like you're saying you want to modify the behaviour of how a particular attribute works so it quietly ignores you. I think the instinct behind why you want to seal this off is reasonable one but if you think about it a bit more you might consider that if you do this kind of thing in a lot of places then using your objects will start to become confusing particularly for someone else who doesn't know the code well.
Perhaps you want to do this because there's other code using the Car model that wants to make connections but doesn't really have the full picture so it tries stuff which you only want to succeed the first time. It's much better to handle such operations solely inside a class which does have the full picture such as the Car model or a service object.
If you still really want to control this "connecting" behaviour outside the Car then you can override the attr_writer completely in the Car class. I'd definitely recommend doing this on before_save callback instead though.
def connected_at=(new_value)
if #connected_at
raise StandardError, 'connected_at has already been set'
end
#connected_at = new_value
end
That will work whichever way you try to assign the value. If you're wondering about what's going on above have a read about attr_accessor in ruby.
this is my understanding of your question.
Car can update only if connected_at is nil
class Car < ApplicationRecord
before_save :updatable?
def updatable?
connected_at.blank?
end
end
The point is return false when before_save.
You could:
car = Car.first
car.connected_at ||= Time.zone.now
car.save
That will only assign if connected_at is nil of false.
I would propose to use the before_update callback and rephrase the intention of the OP as "discard updates if my attribute already has a value".
I came up with this solution (which works well with mass assignments such as Car.update(car_params)):
before_update :ignore_updates_to_connected_at
def ignore_updates_to_connected_at
return unless connected_at.present? && connected_at_changed?
clear_attribute_change(:connected_at)
end
The <attribute_name>_changed? and clear_attribute_change methods come from ActiveModel::Dirty.

Validating Virtual Attributes In Forms : undefined method `valid?' for :name:Symbol

I have two fields in a form I would like to validate the presence of, before sending. The problem though is that the controller's model doesn't have these fields in the database, so I tried making virtual attributes. I'm not quite sure how to get it to work though.
I tried doing this in the Model, called "Find_number"
class FindNumber < ActiveRecord::Base
attr_accessor :name
attr_accessor :original_number
validates :name, presence: true
validates :original_number, presence: true
end
and the following in the create action of the Find_numbers controller
def create
#user = current_user
client = Twilio::REST::Client.new(#user.twilio_account_sid, #user.twilio_auth_token)
search_params = {}
%w[in_postal_code near_number contains].each do |p|
search_params[p] = params[p] unless params[p].nil? || params[p].empty?
end
local_numbers = client.account.available_phone_numbers.get('US').local
#numbers = local_numbers.list(search_params)
if :name.valid? && :original_number.errors.any?
unless #numbers.empty?
render 'find_numbers/show'
else
flash.now[:error] = "Sorry, We Couldn't Find Any Numbers That Matched Your Search! Maybe Something Simpler?"
render 'find_numbers/new'
end
else
flash.now[:error] = "Sorry, We Couldn't Find Any Numbers That Matched Your Search! Maybe Something Simpler?"
render 'find_numbers/new'
end
end
When I enter info though, I get the error
undefined method `valid?' for :name:Symbol
I'm probably calling the :name and :original_number attributes incorrectly, and the double if then statements look very newbish :P.
What would I need to replace if :name.valid? && :original_number.errors.any? , to make sure that it validates? Or is there a lot more I'm missing?
I think you are confusing the boundary between 'controller' and 'model'. The controller doesn't know anything about the validations inside of the model that you've written. The fact that the controller is called FindNumbersController and the model is called FindNumber doesn't really mean anything in terms of shared functionality.
You would need to explicitly create an instance of the model with the passed in params, and let the model perform the validation on the instance
find_number = FindNumber.new(params.slice(:name, :original_number))
Then you can ask whether the instance as a whole is valid
find_number.valid?
or whether a specific field has any error messages
find_number.errors[:field].any?
So, :name.valid? becomes find_number.errors[:name].empty? and :original_number.errors.any? becomes find_number.errors[:original_number].any?

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.

rails dynamic find_by method not working within Class method

I have a user model that has an authentication method in it.
If I test out using the Model in the rails console can create a user just fine and then I can do a find on the email and return the user perfectly like this.
user = User.find_by_email("someaddress#email.com")
Now if I try to call the authentication method like this and return the user the puts user statement in my authentication method returns nil
user = User.authenticate("someaddress#email.com", "foobar")
The model looks something like this
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :first_name, :last_name,
:email, :birth_date, :sex,
:password, :password_confirmation
def self.authenticate(email, submitted_password)
user = find_by_email(email)
puts user #this returns nil so my class is never able to authenticate the user
return nil if user.nil?
return user if user.has_password?(submitted_password)
end
end
I am at a loss for what the issue is. Some in sight into this issue would be very much appreciated.
The way you are using the find_by method inside the Class method is fine; that should work.
Are you sure that the nil output is from puts? The nil maybe the output of your method. It's possible that user.has_password? has an error in it.
Instead of puts, try:
p user
... just to be sure.
Did you check the email's value before calling find_by_email? Maybe it has an invalid space in it, so check the sql log and copy it to dbconsole.
This method would return nil if either:
There was no user object (unlikely, since find_by_email works in the console)
If has_password? returns false, which is likely.
Check your has_password? method is doing the right thing.
I found the answer guys ... thanks so much for jumping in and suggesting some solution.
I had another method that I didn't include in the code above that was the "real" issue.
As for my original question it turns out that the method was working. I had typo in the string that I was passing to the method. Basically I left the ".com" off the end of the email.
As usually a simple typo makes me feel really dumb for posting the question but over all thinking though the problem and looking at your suggestions helped me find the solution so thanks so much.

Ruby if Model.exists?

Have SubscriberList
When an order is placed I want to check if New User's email is all ready in our subscribers list.
If not, then add them. Problem is it adds them no matter what. Guess it's not performing check correctly.
Currently in my orders_controller I have
unless logged_in?
#order.subscribe_after_purchase(#order.user.email)
end
And in my Order.rb I have
def subscribe_after_purchase(email)
unless SubscriberList.exists?(email)
SubscriberList.create(:email => email)
end
end
Try using:
unless SubscriberList.exists?(:email => email)
SubscriberList.create(:email => email)
end
When you just pass the email address to the exists? method then ActiveRecord will interpret it as a primary key. Alternatively, you can use:
SubscriberList.find_or_create_by_email(email)
—which will have the same effect as your unless block; creating the record unless it already exists.
exists? API documentation

Resources