SpineJS handle server-side rails model validations - ruby-on-rails

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.

Related

Adding presence validation on existing records

We have user records that have an attribute called first_name. Many of these records do no have the first_name attribute filled out and thus it equals nil. We want to introduce a presence validation on this attribute. However we've come across a huge problem. If a user updates their record during any request, that request will fail. This leads to a rather abrasive error that we don't know how to handle.
One solution is to only call the validation when the user is creating a record. This works great but we want to enforce this validation when they are on the profile page and they are attempting to update their profile.
Is there a better way to handle this where we can enforce first name requirements on the update page yet still allow users to update their record without ?
Introducing validations on existing data that does not satisfy the new requirements can be problematic. This concept you're after is fundamentally migration-on-write: You've introduce a data migration that happens over time as records are written to, because the migration cannot occur without individual user input. This is one technique for migrating very large data set in zero-downtime environments, or for forcing password resets on users.
Fundamentally, you need to define the conditions in which validation must happen and find a way to test records (on create or update) for that condition. Your condition should select all new records, plus the records being updated in the context where migration is possible.
Once you've defined the condition, you can modify your validation thusly:
validates :first_name, presence: true, if: -> { condition_for_migration }
Ideally the condition should be some field or combination of fields already present in your table that correctly identifies records as ready to be migrated, but this isn't always possible.
Failing that, you could introduce a field specifically for this purpose. You might call it version_number, set all existing records to 1, and then make the default for all new records 2. Your migration might look like this:
# All existing records will have their `version_number` set to the default of 1
add_column :users, :version_number: :integer, null: false, default: 1
# Change the default to 2 for any records created after this point
change_column_default :users, :version_number, 2
You can then use version_number to tell whether validation should take place:
validates :first_name, presence: true, if: -> { version_number >= 2 }
The key is to make sure that, in the context of your profile form, you also update version_number to enable the validation of first_name:
# app/viws/users/edit.html.haml
= form_for #user do |f|
= f.hidden_field :version, value: 2
= f.input :first_name
In the absence of a real database field for this purpose, you can add a temporary one to your model, which maintains the context only for the lifetime of a particular model instance:
Add an accessor to your model, ie update_from_profile_page
Include that field in the contexts in which you want to require validation
Validate first_name during the creation of any new record
Validate first_name during any update where update_from_profile_page is true
For example:
app/models/user.rb
class User < ActiveRecord::Base
attr_accessor :update_from_profile_page
validates :first_name, presence: true, on: :create
validates :first_name, presence: true, on: :update, if: -> { update_from_profile_page }
end
app/views/user/edit.html.haml (your profile page)
= form_for #user do |f|
= f.input :first_name
app/controllers/users_controller.rb
def update
#user = User.find(params[:id])
#user = update_from_profile_page = true
#user.update(params.require(:user).permit(:first_name)
end
This is less desirable than finding a concrete business-logic-based reason for conditional validation as it involves introducing a virtual field to your model that has no functional value outside of a single specific case of a form submission.

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

How can I skip a specific validation when importing data?

How can I skip a specific model validation when importing data?
For example, suppose I have this model:
class Account
validates :street_address, presence: true
end
Normally, I don't want accounts to be saved without addresses, but I'm also going to convert a lot of data from an old system, and many accounts there don't have addresses.
My goal is that I can add the old accounts to the new database, but in the future, when these accounts are edited, a street address will have to be added.
Clarification
As I said, I want to skip a specific validation; others should still run. For example, an account without an account number shouldn't be loaded into the new system at all.
This should work:
class Account
attr_accessor :importing
validates :street_address, presence: true,
unless: Proc.new { |account| account.importing }
end
old_system_accounts.each do |account|
# In the conversion script...
new_account = Account.new
new_account.importing = true # So it knows to ignore that validation
# ... load data from old system
new_account.save!
end
If you're only going to do the conversion one time (i.e, after importing the old data you won't need to do this again), you could just skip validations when you save the imported records instead of modifying your app to support it.
new_account.save validate: false
note that
account.update_attribute(:street_address, new_address)
will skip validations as well. #update_attributes (notice the 's') run validations, where update_attribute (singular) does not.

Rails error messages from models, is there a way to order them? issues with accepts_nested_attributes_for

I have a form that displays inputs for 2 models, I am using accepts_nested_attributes_for.
In my main model that has the accepts_nested_attributes_for, it looks like:
class Account <
accepts_nested_attributes_for :primary_user ...
Now in my form, I have a form_for on the #account, and then have fields_for the primary_user model.
If I hit submit, for some reason all the errors for the primary_user are displayed first. I would rather have the errors display in the same order as the input fields on the web page.
Is this possible to re-order them according how they are ordered in my form_for?
Also, the error messages have 'primary username cannot be black', is it possible for me to change it to 'username cannot be blank'? I don't really need to confuse the end user with the word 'primary' as it really doesn't make sense to them, its more of an internal thing.
Not sure about the re-ordering but you can change the message for a model validations as follows:
validates :username, presence: { message: "Username cannot be blank" }

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