I have an account model like so
class Account < ActiveRecord::Base
end
I had some duplicates records on it in production so I've added this line to it
validates_uniqueness_of :owner_id, :on => :create
so it will only verify this on new records and NOT on save!
it works on localhost and even on console on production.
but I have a job that runs every 10 minutes that uses rails runner and it fails for
Validation failed: Owner has already been taken (ActiveRecord::RecordInvalid)
The runner does some actions on accounts and then saves them.
am I doing something wrong on this syntax ?
Only time I've seen this happen was when a controller "create" action was being sent as a :get request (instead of :post as it should be in REST). The result was the validation didn't realize it was doing a :create. Doesn't sound like what you've got here... but maybe similar?
FYI my fix at the time was to use if: :new_record? instead of on: :create until the request type could be fixed. You could try this.
Also, you can put a debug statement into the validation to interrogate the state of the object during that validation with e.g. if: -> { binding.pry } or if: -> { debugger } (depending on what you use to debug with, of course). Hopefully that will help figure out what the runner is doing different. You can ask the object if it's a new_record? or what changes it has to save, etc.
I have found that this method has no
:on => :create
only :if
so I've did this:
validates_uniqueness_of :owner_id, :if => Proc.new { |account| account.created_at > Time.now - 10.seconds }
Related
I have the following in my user model
attr_accessible :avatar, :email
validates_presence_of :email
has_attached_file :avatar # paperclip
validates_attachment_size :avatar,
:less_than => 1.megabyte,
:message => 'Image cannot be larger than 1MB in size',
:if => Proc.new { |imports| !imports.avatar_file_name.blank? }
in one of my controllers, I ONLY want to update and validate the avatar field without updating and validating email.
How can I do this?
for example (this won't work)
if #user.update_attributes(params[:user])
# do something...
end
I also tried with update_attribute('avatar', params[:user][:avatar]), but that would skip the validations for avatar field as well.
You could validate the attribute by hand and use update_attribute, that skips validation. If you add this to your User:
def self.valid_attribute?(attr, value)
mock = self.new(attr => value)
if mock.valid?
true
else
!mock.errors.has_key?(attr)
end
end
And then update the attribute thusly:
if(!User.valid_attribute?('avatar', params[:user][:avatar])
# Complain or whatever.
end
#user.update_attribute('avatar', params[:user][:avatar])
You should get your single attribute updated while only (manually) validating that attribute.
If you look at how Milan Novota's valid_attribute? works, you'll see that it performs the validations and then checks to see if the specific attr had issues; it doesn't matter if any of the other validations failed as valid_attribute? only looks at the validation failures for the attribute that you're interested in.
If you're going to be doing a lot of this stuff then you could add a method to User:
def update_just_this_one(attr, value)
raise "Bad #{attr}" if(!User.valid_attribute?(attr, value))
self.update_attribute(attr, value)
end
and use that to update your single attribute.
A condition?
validates_presence_of :email, :if => :email_changed?
Have you tried putting a condition on the validates_presence_of :email ?
http://ar.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#M000083
Configuration options:
if - Specifies a method, proc or string to call to determine if the validation should occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The method, proc or string should return or evaluate to a true or false value.
unless - Specifies a method, proc or string to call to determine if the validation should not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }). The method, proc or string should return or evaluate to a true or false value.
I am assuming you need this, because you have a multi-step wizard, where you first upload the avatar and the e-mail is filled in later.
To my knowledge, with your validations as they are, I see no good working solution. Either you validate all, or you update the avatar without validations. If it would be a simple attribute, you could check if the new value passes the validation seperately, and then update the model without validations (e.g. using update_attribute).
I can suggest two possible alternative approaches:
either you make sure that the e-mail is always entered first, which I believe is not a bad solution. And then, with each save, the validation is met.
otherwise, change the validation. Why would you declare a validation on a model, if there are records in the database that do not meet the validation? That is very counter-intuitive.
So I would propose something like this:
validate :presence_of_email_after_upload_avatar
def presence_of_email_after_upload_avatar
# write some test, when the email should be present
if avatar.present?
errors.add(:email, "Email is required") unless email.present?
end
end
Hope this helps.
Here is my solution.
It keeps the same behaviour than .valid? method, witch returns true or false, and add errors on the model on witch it was called.
class MyModel < ActiveRecord::Base
def valid_attributes?(attributes)
mock = self.class.new(self.attributes)
mock.valid?
mock.errors.to_hash.select { |attribute| attributes.include? attribute }.each do |error_key, error_messages|
error_messages.each do |error_message|
self.errors.add(error_key, error_message)
end
end
self.errors.to_hash.empty?
end
end
> my_model.valid_attributes? [:first_name, :email] # => returns true if first_name and email is valid, returns false if at least one is not valid
> my_modal.errors.messages # => now contain errors of the previous validation
{'first_name' => ["can't be blank"]}
I need to validate a value's presence, but only AFTER the value is populated. When a User is created, it is not required to set a shortcut_url. However, once the user decides to pick a shorcut_url, they cannot remove it, it must be unique, it must exist.
If I use validates_presence_of, since the shortcut_url is not defined, the User isn't created. If I use :allowblank => true, Users can then have "" as a shortcut_url, which doesn't follow the logic of the site.
Any help would be greatly appreciated.
Here we are always making sure the shortcut_url is unique, but we only make sure it is present if the attribute shortcut_selected is set (or if it was set and now was changed)
class Account
validates_uniqueness_of :shortcut_url
with_options :if => lambda { |o| !o.new_record? or o.shortcut_changed? } do |on_required|
on_required.validates_presence_of :shortcut_url
end
end
You'll need to test to make sure this works well with new records.
Try the :allow_nil option instead of :allow_blank. That'll prevent empty strings from validating.
Edit: Is an empty string being assigned to the shortcut_url when the user is being created, then? Maybe try:
class User < ActiveRecord::Base
validates_presence_of :shortcut_url, :allow_nil => true
def shortcut_url=(value)
super(value.presence)
end
end
try conditional validations, something like:
validates_presence_of :shortcut_url, :if => :shortcut_url_already_exists?
validates_uniqueness_of :shortcut_url, :if => :shortcut_url_already_exists?
def shortcut_url_already_exists?
#shortcut_url_already_exists ||= User.find(self.id).shortcut_url.present?
end
I would like to know if there's a way to use rails validations on a custom action.
For example I would like do something like this:
validates_presence_of :description, :on => :publish, :message => "can't be blank"
I do basic validations create and save, but there are a great many things I don't want to require up front. Ie, they should be able to save a barebones record without validating all the fields, however I have a custom "publish" action and state in my controller and model that when used should validate to make sure the record is 100%
The above example didn't work, any ideas?
UPDATE:
My state machine looks like this:
include ActiveRecord::Transitions
state_machine do
state :draft
state :active
state :offline
event :publish do
transitions :to => :active, :from => :draft, :on_transition => :do_submit_to_user, :guard => :validates_a_lot?
end
end
I found that I can add guards, but still I'd like to be able to use rails validations instead of doing it all on a custom method.
That looks more like business logic rather than model validation to me. I was in a project a few years ago in which we had to publish articles, and lots of the business rules were enforced just at that moment.
I would suggest you to do something like Model.publish() and that method should enforce all the business rules in order for the item to be published.
One option is to run a custom validation method, but you might need to add some fields to your model. Here's an example - I'll assume that you Model is called article
Class Article < ActiveRecord::Base
validate :ready_to_publish
def publish
self.published = true
//and anything else you need to do in order to mark an article as published
end
private
def ready_to_publish
if( published? )
//checks that all fields are set
errors.add(:description, "enter a description") if self.description.blank?
end
end
end
In this example, the client code should call an_article.publish and when article.save is invoked it will do the rest automatically. The other big benefit of this approach is that your model will always be consistent, rather than depending on which action was invoked.
If your 'publish' action sets some kind of status field to 'published' then you could do:
validates_presence_of :description, :if => Proc.new { |a| a.state == 'published' }
or, if each state has its own method
validates_presence_of :description, :if => Proc.new { |a| a.published? }
I need to validate a model only for a certain action (:create). I know this is not a good tactic, but i just need to do this in my case.
I tried using something like :
validate :check_gold, :if => :create
or
validate :check_gold, :on => :create
But i get errors. The problem is that i cannot have my custom check_gold validation execute on edit, but only on create (since checking gold has to be done, only when alliance is created, not edited).
Thanx for reading :)
I'm appending some actual code :
attr_accessor :required_gold, :has_alliance
validate :check_gold
validate :check_has_alliance
This is the Alliance model. :required_gold and :has_alliance are both set in the controller(they are virtual attributes, because i need info from the controller). Now, the actual validators are:
def check_gold
self.errors.add(:you_need, "100 gold to create your alliance!") if required_gold < GOLD_NEEDED_TO_CREATE_ALLIANCE
end
def check_has_alliance
self.errors.add(:you_already, "have an alliance and you cannot create another one !") if has_alliance == true
end
This works great for create, but i want to restrict it to create alone and not edit or the other actions of the scaffold.
All ActiveRecord validators have a :on option.
validates_numericality_of :value, :on => :create
Use the validate_on_create callback instead of validate:
validate_on_create :check_gold
validate_on_create :check_has_alliance
Edit:
If you use validates_each you can use the standard options available for a validator declaration.
validates_each :required_gold, :has_alliance, :on => :create do |r, attr, value|
r.check_gold if attr == :required_gold
r.check_has_alliance if attr == :has_alliance
end
Like Sam said, you need a before_create callback. Callbacks basically mean 'execute this method whenever this action is triggered'. (More about callbacks here : http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html).
This is what you want in your model:
before_create :check_gold
# other methods go here
private # validations don't need to be called outside the model
def check_gold
# do your validation magic here
end
The above method is the simplest to do what you want, but FYI there's also a way to use a before_save callback to execute additional actions on creation:
before_save :check_gold_levels
# other methods
private
def check_gold_levels
initialize_gold_level if new? # this will be done only on creation (i.e. if this model's instance hasn't been persisted in the database yet)
verify_gold_level # this happens on every save
end
For more info on 'new?' see http://api.rubyonrails.org/classes/ActiveResource/Base.html#method-i-new%3F
You need to look into callbacks. Someone once told me this and I didn't understand what they meant. Just do a search for rails callbacks and you will get the picture.
In your model you need to do a callback. The callback you need is before_create and then before a object is created you will be able to do some logic for check for errors.
model.rb
before_create :check_gold_validation
def check_gold_validation
validate :check_gold
end
def check_gold
errors.add_to_base "Some Error" if self.some_condition?
end
I want to execute a custom validation before the record is created?
It looks as if this is the right method: before_validation_on_create. For example:
before_validation_on_create :custom_validation
But am not sure. Any help would be appreciated.
In rails 3
before_validation_on_create :do_something
has been replaced with:
before_validation :do_something, :on => :create
before_validation_on_create hooks happen before validation on create… but they aren't validations themselves.
What you probably want to do is use validate and a private method which adds to the error array. like this:
class IceCreamCone
validate :ensure_ice_cream_is_not_melted, :before => :create
private
def ensure_ice_cream_is_not_melted
if ice_cream.melted?
errors.add(:ice_cream, 'is melted.')
end
end
end
The following worked for me in Rails 5:
validate :custom_validation_method, :on => :create
Running
validate :custom_validation_method, :before => :create
gave me following error:
Unknown key: :before. Valid keys are: :on, :if, :unless, :prepend.
There's a great resource here for information on callbacks and the order they happen in:
http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html