rails validation of uniqueness on update - ruby-on-rails

I have the following validation rule on one of the models,
validates :reciept_num, :presence => true,
:numericality => { :only_integer => true },
:uniqueness => true,
:on => :update,
:if => "!status_id.nil?"`
Now, when I update the object using update_attributes method it gives me following error
reciept_num: has already been taken.
While updating the the object I'm not changing the reciept_num attribute? So, why does this validation fails on update?
If I'm not updating the value, it must be the old one and hence should pass validation. Am I missing something.

First off, validations don't run based on whether the attribute has changed or not (unless of course you ask for that explicitly). Everytime a record with a uniqueness validation saves and the validation can run (as defined by :on, :if, :unless options) it will check whether there are any instances other than itself with the value that is supposed to be unique.
Since you've got conditions on your validation, I imagine you could end up creating two instances with the same receipt num, but where both have a null status_id. Set the status_id column and the validation kicks into action and finds the other instances.
Another thing is that since your validation is on update only you could create multiple instances with the same receipt num, again trying to update the record would trigger the validation.
I'm only guessing at the precise scenarios though.

Related

Is there a better way of validating a non model field in rails

I have a form field in ROR 4 app called as 'measure'. It is not a database column, but its values will help model create child entries of its own via acts_as_tree : https://github.com/rails/acts_as_tree
I have to throw a validation when 'measure' is invalid. So I have created a virtual attribute known as measure and check for its validations only on a certain condition.
model someModel
attr_accessor :measure
validates_presence_of :measure, :if => condition?
Problem is when I am saving the code, I am thrown a validation which is fine. I am also thrown the same validation when I am trying to update the record in some other method of the model. The only way to surpass that is by writing this code:
# I do not want to do this, is there a better way?
self.measure = "someRandomvalue"
self.save
I am making this as virtual attribute only for throwing validations. Is there a better way of throwing validations? The form has other validations, I do not want the error for this validations to be shown differently just because it is not an attribute.
I want it to validated only when active record is saved via create and update action of the controller and not when it is being updated by some random method of model.
I have seen other developers in my team doing similar thing and was always curious about one thing - "What are you trying to achieve doing things the way you are doing?". You see, I am not sure if validators should be used for values that will not be serialized.
Anyways, you may try using format validator instead of presence, which worked in my team's case:
# Rails 3/4
validates :measure, format: { with: /^.+$/, allow_nil: true }
# Rails 2
validates_format_of :measure, :with => /^.+$/, :allow_nil => true
You may also try using allow_blank instead of allow_nil.
I would rather create a custom validator along the lines of validates_accessor_of for values that I know will never be serialized.
HTH

Rails validate :if with checkboxes

I am working with a form having 2 checkboxes: option_one and option_two.
I don't want to allow submission of the form if option_two is checked and option_one is not.
In other words if somebody checks option_two, they must check option_one as well.
So in my MyModel I wrote :
validates :option_one, :presence => true, :if => option_two_active?, :message => "Dummy message."
Then in the MyController, I added :
def option_two_active?
params[:option_two] == "1"
end
But it keeps giving me the following error :
NoMethodError in MyController#index
Is my approach correct ? How can I achieve this ? Thanks in advance.
You have to specify the conditional method with a symbol:
validates :option_one, :presence => true, :if => :option_two_active?, :message => "Dummy message."
Also, you since you can't use params from a model, you should assign that value to the model from the controller, either with create, update_attributes, or manually. If you want to persist the option_two field, then it should be a database column, else you can just create an attribute accessor:
attribute_accessor :option_two
The way you reference the method, it will be called directly as the class is first loaded. However, the :if parameter is expected to be used with either a proc which is then called during validation or with a symbol representing a method name. In your case, you should thus setup your validation like this:
validates :option_one, :presence => true, :if => :option_two?, :message => "Dummy message."
Notice the colon before the method name. Furthermor, the validation method needs to be defined on the model, not the controller. Fortunately, ActiveRecord already defines the proper methods for Boolean fields, as used here.

update_attribute/s() is calling callback for save password

I'm trying to update single attribute of a user model from a admin controller (not users controller).
While doing this I tried update_attribute() but it was changing the users password also.
I think the password is changing because I have before_save method on user model which hashes the password.
update_attributes() is not working because it is checking the validations for password which is presence=>true
Is there any way to achieve this?
You can set a condition on your validations by using the :if option. In my code, it looks something like this:
validates :password,
:length => { :minimum => 8 },
:confirmation => true,
:presence => true,
:if => :password_required?
def password_required?
crypted_password.blank? || password.present?
end
So basically, it's only if the crypted_password in the database is not set (meaning a new record is being created) or if a new password is being provided that the validations are run.
Try update_column(name, value), it might work.
You can update single attribute of user like this
#user is that user whose attribute you want to update
e.g user_name
#user.update_attributes(:user_name => "federe")
Try it and it will only update one attribute..
ActiveRecord has an 'update-column' method that skips both validations and callbacks:
http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-update_column
However, I'd suggest that could be dangerous - you have that :before_save filter for a reason. If you place an :except method on the filter to circumvent in specific cases, it not only becomes reusable but you keep behaviour consistent and avoid having a method buried in a controller that's bypassing your Model's validation/callback stack.
I'm personally not overly keen on seeing methods like update_column anywhere except as protected methods inside Models.
Try :
To bypass callback and validations use :
User.update_all({:field_name => value},{:id => 1})
Just wanted to let you know :
In Rails, update_attribute method bypasses model validations, while update_attributes and update_attributes! will fail (return false or raise an exception, respectively) if a record you are trying to save is not valid.
The difference between two is update_attribute use save(false) where as update_attributes uses save or you can say save(true) .

Rails validation problem

I've got a User model with three fields, :email, :display_name and :handle. Handle is created behind the scenes from the :display_name.
I'm using the following validations:
validates :display_name, :presence => :true, :uniqueness => { :message => "Sorry, another user has already chosen that name."}, :on => :update
validates :email, :presence => :true, :uniqueness => { :message => "An account with that email already exists." }
I use the handle as the to_param in the model. If the user fails the validation by submitting a :display_name that already exists, then tries to change it and resubmit the form, Rails seems to use the new handle as the validation for the email -- in other words, it assumes that the email doesn't belong to the current user and validation on the email then fails. At this point, Rails assumes that the changed display name/handle is the one to use for the look up and the update action can't complete at all, because it can't find the user based on the new handle.
Here's the update method:
def update
#user = User.find_by_handle(params[:id])
#handle = params[:user][:display_name]
#user.handle = #handle.parameterize
...
end
This problem doesn't happen when the validation first fails on a duplicate email, so I'm assuming it's something about the way I've written the update method -- maybe I should try setting the handle in the model?
maybe I should try setting the handle in the model?
^ This.
The controller isn't the place to do something like this. If it's model logic that's happening behind the scenes, beyond the user's control, why put it in controller code?
Do it instead in a before_save filter, which is guaranteed to run only after the chosen display name is determined to be available and the record is deemed valid. In this way the handle won't be changed on the cached record until it is actually committed to the db, eliminating the problem of the incorrectly generated URL.
before_save :generate_handle
...
def generate_handle
self.handle = display_name.parameterize
end

Mixed Validation and Callbacks methods

When my model instance is created, it generates an "invoice_no". This invoice_no depends on the financial_year of the invoice which is derived from the invoice_date given in the form.
Now i validate the presence of invoice_date. Only after the invoice_date is valid, can I generate the financial_year and invoice_no.
Now what would be the best way to validate for the uniqueness of invoice_no?
1. validates :invoice_date, :presence => true
2. before_create :assign_financial_year # Based on a valid invoice_date
3. before_create :generate invoice_no # Based on a valid financial_year
4. validates :invoice_no, :uniqueness => {:scope => [:financial_year, :another_field], :case_sensitive => false}
I've already put a unique index in the database on this table based on all the relevant fields.
What would be the best way to mix step 2 and 3 above with validations on step 1 and 4?
or Should I not bother on the uniqueness validation in rails since it's a generated number and already handled in the database? If I don't put this validation, what would be a graceful way to handle exception if raised due to uniqueness violation if ever it is generated?
I'm fairly experienced with Rails and have thought of a few ugly ways already. Just want more opinions on strategy from other experienced Rails programmers.
I don't think you need to necessarily calculate the year after the validation has run. You could do it beforehand and fail gracefully if there's no invoice_date. That way, your validation will still run and you can try again later once it's present.

Resources