Mixed Validation and Callbacks methods - ruby-on-rails

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.

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

SpineJS handle server-side rails model validations

I have a model, let's call it Book. In rails, when the Book is saved, I validate the uniqueness of it's ISBN number. For my front end, I have a simple SpineJS app which let's me add a new book.
In SpineJS:
class App.Book extends Spine.Model
#configure 'Book', 'name', 'isbn'
#extend Spine.Model.Ajax
validate: ->
"Name required" unless #name
"ISBN required" unless #isbn
And in Rails:
class Book < ActiveRecord::Base
attr_accessible :name, :isbn
validates :name, :presence => true
validates :isbn. :presence => true, :uniqueness => true
end
My issue is that in my SpineJS app, it happily saves a new book with a duplicate ISBN number, even though the Rails server returns validation errors.
Is there some way to handle this error client-side on save?
Spine's manual claims:
If validation does fail server-side, it's an error in your client-side validation logic rather than with user input.
I don't see how this could easily work with your uniqueness requirement. It may be workable if you can load all database data that could affect validation into the client side and you can somehow avoid all multi-user race conditions.
You can catch the "ajaxError" event and tell the user to retry, although catching "ajaxError" goes against the recommendations in the manual. IIRC you may also have to do some juggling with object IDs to convince Spine that a new record was in fact not created.
Additionally, you can fire pre-emptive validation requests as the user is editing the data, but this is just for the user's convenience. In theory you can still hit a race condition where someone else creates a conflicting record just before the user hits save.
Personally I switched to Backbone since I found Spine's careless attitude to error handling too scary.

Is ok to create a custom validator that should run a query to validate?

I need to validate the membership card number of a table to be unique only for current year, so I must build a custom validator for my model column.
My questions are:
Is heavy to make a query to check for uniqueness in current year every time I update/create a single row? How am I expected to deal
with this?
I used custom validators only on CakePHP, can you post a stupid example only to show me a syntax example?
Edit 1:
Notice that the current year is stored in a column called expire_date which is a year-month-day date format.
The check should be performed only for the year however.
How can I deal with this? That's why I suppose I should use a custom validator, I think scope shouldn't work in this case.
Edit 2:
I just noticed a :if option, I'll check if I can implement this through it.
Rails 3 provides syntax sugar, so you can do something like this:
validates :car_number, :uniqueness => {:scope => :expire_year, :message => 'has already been taken this year'}, :on => :update
def expire_year
expire_date.strftime("%Y")
end
How the query heavy is depends on DB schema and load. Don't forget to setup an index on year column and check the rails logs.
Note this will work for update only, because new object doesn't have the timestamps populated until save.
ActiveRecord already have that!
validates_uniqueness_of :car_number, :scope => :year
Docs for validates_uniqueness_of

rails validation of uniqueness on update

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.

Rails: Validate unique combination of 3 columns

Hi I wan't to validate the unique combination of 3 columns in my table.
Let's say I have a table called cars with the values :brand, :model_name and :fuel_type.
What I then want is to validate if a record is unique based on the combination of those 3. An example:
brand model_name fuel_type
Audi A4 Gas
Audi A4 Diesel
Audi A6 Gas
Should all be valid. But another record with 'Audi, A6, Gas' should NOT be valid.
I know of this validation, but I doubt that it actually does what I want.
validates_uniqueness_of :brand, :scope => {:model_name, :fuel_type}
There is a syntax error in your code snippet. The correct validation is :
validates_uniqueness_of :car_model_name, :scope => [:brand_id, :fuel_type_id]
or even shorter in ruby 1.9.x:
validates_uniqueness_of :car_model_name, scope: [:brand_id, :fuel_type_id]
with rails 4 you can use:
validates :car_model_name, uniqueness: { scope: [:brand_id, :fuel_type_id] }
with rails 5 you can use
validates_uniqueness_of :car_model_name, scope: %i[brand_id fuel_type_id]
Depends on your needs you could also to add a constraint (as a part of table creation migration or as a separate one) instead of model validation:
add_index :the_table_name, [:brand, :model_name, :fuel_type], :unique => true
Adding the unique constraint on the database level makes sense, in case multiple database connections are performing write operations at the same time.
To Rails 4 the correct code with new hash pattern
validates :column_name, uniqueness: {scope: [:brand_id, :fuel_type_id]}
I would make it this way:
validates_uniqueness_of :model_name, :scope => {:brand_id, :fuel_type_id}
because it makes more sense for me:
there should not be duplicated "model names" for combination of "brand" and "fuel type", vs
there should not be duplicated "brands" for combination of "model name" and "fuel type"
but it's subjective opinion.
Of course if brand and fuel_type are relationships to other models (if not, then just drop "_id" part). With uniqueness validation you can't check non-db columns, so you have to validate foreign keys in model.
You need to define which attribute is validated - you don't validate all at once, if you want, you need to create separate validation for every attribute, so when user make mistake and tries to create duplicated record, then you show him errors in form near invalid field.
Using this validation method in conjunction with ActiveRecord::Validations#save does not guarantee the absence of duplicate record insertions, because uniqueness checks on the application level are inherently prone to race conditions.
This could even happen if you use transactions with the 'serializable' isolation level. The best way to work around this problem is to add a unique index to the database table using ActiveRecord::ConnectionAdapters::SchemaStatements#add_index. In the rare case that a race condition occurs, the database will guarantee the field's uniqueness.
Piecing together the other answers and trying it myself, this is the syntax you're looking for:
validates :brand, uniqueness: { scope: [:model_name, :fuel_type] }
I'm not sure why the other answers are adding _id to the fields in the scope. That would only be needed if these fields are representing other models, but I didn't see an indication of that in the question. Additionally, these fields can be in any order. This will accomplish the same thing, only the error will be on the :model_name attribute instead of :brand:
validates :model_name, uniqueness: { scope: [:fuel_type, :brand] }

Resources