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.
Related
In Rails 5 I can't seem to set a field without having the validation fail and return an error.
My model has:
validates_presence_of :account_id, :guid, :name
before_save :set_guid
private
def set_buid
self.guid = SecureRandom.uuid
end
When I am creating the model, it fails with the validation error saying guid cannot be blank.
def create
#user = User.new(new_user_params)
if #user.save
..
..
private
def new_user_params
params.require(:user).permit(:name)
end
2
Another issue I found is that merging fields doesn't work now either. In rails 4 I do this:
if #user.update_attributes(new_user_params.merge(location_id: #location_id)
If I #user.inspect I can see that the location_id is not set. This worked in rails 4?
How can I work around these 2 issues? Is there a bug somewhere in my code?
You have at least two options.
Set the value in the create action of your controller
Snippet:
def create
#user = User.new(new_user_params)
#user.guid = SecureRandom.uuid
if #user.save
...
end
In your model, use before_validation and add a condition before assigning a value:
Snippet:
before_validation :set_guid
def set_guid
return if self.persisted?
self.guid = SecureRandom.uuid
end
1
Use before_validation instead:
before_validation :set_guid
Check the docs.
2
Hash#merge works fine with rails ; your problem seems to be that user is not updating at all, check that all attributes in new_user_params (including location_id) ara valid entries for User.
If update_attributes fails, it will do so silently, that is, no exception will be raised. Check here for more details.
Try using the bang method instead:
if #user.update_attributes!(new_user_params.merge(location_id: #location_id))
I'm using Rail's ActiveRecord Validation Confirmation to validate an id. I have two form fields, cbiz_id and cbiz_id_confirmation, so that the fields receive exactly the same content.
cbiz_id is an integer and ActiveRecord makes cbiz_id_confirmation a string.
Is there a way to validate cbiz_id_confirmation as an integer?
validates :cbiz_id, confirmation: true, presence: { if: :has_any_qapps_role?, notice: I18n.t('messages.updated') }
I've attempted a number of things such as:
before_validation :change_cbiz_id_confirmation_to_integer
def change_cbiz_id_confirmation_to_integer
:cbiz_id_confirmation.to_i
end
I'm new to Ruby and Rails, so foundational explanations are appreciated!
Rails converts cbiz_id because it looks at the DB can sees that the column is an integer. Since there's no column for cbiz_id_confirmation, Rails doesn't have any type data, and the default type for any form submission (since form encoding and query params have no types) is a string.
Your change_cbiz_id_confirmation_to_string should work, but you're calling to_i on a symbol. You want just cbiz_id_confirmation.to_i (no leading :). Also, perhaps that method should end in to_integer.
If you want to ensure cbiz_id_confirmation is an integer even outside of validations, you can write a setter method that does the conversion for you.
attr_reader :cbiz_id_confirmation
def cbiz_id_confirmation=(value)
#cbiz_id_confirmation = Integer(value)
rescue TypeError
#cbiz_id_confirmation = nil
end
Integer(value) will raise an exception if the value cannot be converted (eg. it is nil or the word "ham"), so the rescue block makes this act like Rails and treat invalid data as nil. You can also use value.to_i, which that will coerce nil and "ham" to 0, or just let the exception happen and handle the problem elsewhere.
I solved the issue with the following code:
attr_accessor :current_user_qapps_role, :cbiz_id_confirmation
validates :cbiz_id, confirmation: true, presence:true, if: :has_any_qapps_role?
def cbiz_id_confirmation=(val)
#cbiz_id_confirmation = val.to_i
end
You can easily create a custom validation rule like this, but there's probably some more elegant solution to your problem if I'd understand the context:
validate :cbiz_matches
def cbiz_matches
errors.add(:cbiz_id, "ID's don't match") unless self.cbiz_id.to_i == self.cbiz_id_confirmaton.to_i
end
I'm trying to make a pet rails app. My pet model includes two boolean values, hungry and feed_me. Right now, hungry and feed_me can both be set in the view, but I'm trying to set up the model so that if feed_me is true, hungry will automatically be changed to false. No matter what I do, however, feed_me never resets hungry. This is what I have in the model now:
attr_accessor :feed_me
before_save :feed
def feed
#feed_me = Creature.find(params[:feed_me])
#hungry=Creature.find(params[:hungry])
if #feed_me==true
#hungry=false
end
end
I'm new to Rails, but my understanding is that model should have access to the params hash, so I'm confused about why I can't use it to reset values.
You're on the right track using model callbacks, however models don't have access to the param hash - its available to controllers.
The model already knows the value of it's own attributes, so you don't need to get them from params. The controller I imagine is updating feed_me.
Also you shouldn't need to declare feed_me as an attr_accessor assuming it is backed by a database column.
You can change before_save to:
def feed
if self.feed_me
self.hungry = false
end
end
In your controller, I imagine you'd do something like:
def update
pet = Pet.find(params[:id])
pet.feed_me = params[:feed_me]
if pet.save
redirect_to pet_path(pet)
else
flash[:notice] = 'Error saving pet'
render :edit
end
end
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?
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.