how to add customer validator in rails's model - ruby-on-rails

I know ActiveRecord provide some macros like validates_uniqueness_of validates_size_of
to do some validation for the user input. but I'm wondering whether it is possible to provide
some call-back like validation method to be used as a cutomised validation method in the model level. for example,
I want to check the input string only consits of letters from 'a' to 'h', funny? but it happened from time to time.

You can create custom functions with:
validate :custom_function
def custom_function
...
end
You can also use regular expressions to validate strings. For your example I would use:
validates_format_of :attribute, :with => /^[a-h]+$/

The rails guides has a good example of how to create your own custom validators. If you are using Rails 3 you could do it like this:
class Foo < ActiveRecord::Base
validate :from_a_to_h
# Use the name of your attribute in place of :input and input.
def from_a_to_h
errors.add(:input, "must contain only letters from a to h") if input =~ /[i-Z]+/
end
end

Related

Rails 5 re-use before_validation code in multiple models with different fields

I have a Rails 5 application where users can enter currency values in different fields and different models.
Since I only serve one locale, I want users to be able to enter decimals using both . and , decimal separators and ignore any thousands separator.
For example, my users might enter: 1023.45 or 1023,45 but never 1.023,45.
Simple solution that fits my use case: On specific decimal fields representing currency, replace , with . using gsub(',', '.').
What is the best place to put this code? There are multiple models with differently named fields that need to use this code.
Preferably, I would use something like a custom validator that I create once and simply reference with 1 line from all models with the appropriate field. Very much like this example of a custom validator that checks whether an entered value is positive:
class PositiveValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
if value and BigDecimal.new(value).negative?
record.errors[attribute] << 'is negative'
end
end
end
And then in the model reference it with:
validates :total, positive: true, allow_blank: true
However, this is ofcourse for validations, which shouldn't modify data. I tried to do this with concerns, but from the examples I have seen I should already know which fields are being transformed.
Do I really need to write in each model something like this:
before_validation lambda {self.total.gsub!(',', '.') if self.total}
or is there a more elegant Rails-like solution?
edit: again, note that field name 'total' might be different in each model
You could use custom type-casting starting from Rails 4.2
class LocalFloat < ActiveRecord::Type::Float
def cast_value(value)
value = value.gsub(',', '.') if value.is_a? String
super(value)
end
end
class Order < ApplicationRecord
attribute :total, LocalFloat.new
end
o = Order.new total: '5,5'
o.total # => 5.5

How do I force a rails ActiveRecord string column to only accept strings?

I have a Post class with a title:string field
post = Post.create!
#=> Post created
post.title = "foo"
post.save!
# success
post.title = 1
post.save!
# succeeds, but I want it to throw a type mismatch exception
How do I get the last save! to throw a type mismatch?
This really runs against the grain of what Ruby is all about. If it's a string column, the database layer will take care of rendering that value as a string and saving it.
The concept of a type is different in Ruby than in other languages. Generally if an object supports to_s then it's considered to be string-enough to use, the basic tenet of Duck Typing.
If you pass in something that can't be rendered as a string you will get an exception. For example:
post.title = Post.find(5)
If you're looking to ensure your fields are not numerical, you should add a validation that enforces a particular format.
A new gem has been created to help validate types in rails and an explanatory blog post exists to answer more of the "why" it was created in the first place.
With this library your code would simple be:
class Post < ActiveRecord::Base
validates_type :title, :string
end
This will throw an exception when an integer is assigned to title instead of quietly casting the title to a string and saving it.
Ruby is a duck-typed language and doesn't have type mismatches. If an object responds to to_s, Rails will call to_s on it and put it into a string field.
It sounds like what you really want is to validate the format of the value you're putting into the field (is the string "2" a valid value?), which is what validates_format_of is for. For example, if you want to ensure that the value has at least one non-digit character, you could do this:
class Post < ActiveRecord::Base
validates_format_of :title, with: /\D/
end
Of course, you very well may want to customize that regex a bit more.
If you really can't stand duck-typing and want to make sure nothing but a true String goes into that column, you can override title=:
class Post < ActiveRecord::Base
def title=(value)
throw "`title' must be a String" unless value.is_a?(String)
super
end
end
Note that this won't be invoked when you do e.g. post[:title] = ... or post.write_attribute(:title, ...) but it should cover 95% of situations.
To answer the exact question, if you want to make sure that only objects of class String are saved to the field, you should add the following custom validation.
validate :must_be_string
def must_be_string
errors.add(:base, 'The field must be String') unless title.class == String
end
You can define a custom validator.
validates :title, type: String
and then:
class TypeValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.errors.add attribute, (options[:message] || "is not of class #{options[:with]}") unless
value.class == options[:with]
end
end

Validation rails one method for two fields

I have on my form two fields:
phone and mobile phone
I would like to use one method to validate two fields with one the same method how to do it??
Any validation can be used for any number of attributes. For example:
validates_presence_of :foo, :bar
If you're using a custom validation method, just make sure it inspects both attributes - something like this:
validate :phone_format
def phone_format
[phone, mobile].each do |attr|
errors.add(attr, "some error message") unless attr =~ /some regex/
end
end
Check out http://guides.rubyonrails.org/active_record_validations_callbacks.html
Write a custom validator

How to remove validation using instance_eval clause in Rails?

I would like to enhance existing class using instance_eval. There original definition contains validation, which require presence of certain fields, ie:
class Dummy < ActiveRecord::Base
validates :field, :presence => true
end
Now I want to change that to optional using instance_eval (or any other method, really):
Dummy.instance_eval do
...
end
What would be the proper syntax to remove the validation, so the field is optional. I would rather do this directly on the model layer, instead doing weird hacks in controllers or views. The use of instance_eval is not really required, but as far as I know, this is generally the best way to enhance classes in Rails.
Edit #1
In general - the original class is part of the gem and I don't want to fork it, nor tie to specific release. The general cause is not really important. Simply editing the original model has far worse consequences than monkey patching.
I found a solution, not sure how solid it is, but it works well in my case. #aVenger was actually close with his answer. It's just that the _validators accessor contains only information used for reflection, but not the actual validator callbacks! They are contained in the _validate_callbacks accessor, not to be confused with _validations_callbacks.
Dummy.class_eval do
_validators.reject!{ |key, _| key == :field }
_validate_callbacks.reject! do |callback|
callback.raw_filter.attributes == [:field]
end
end
This will remove all validators for :field. If you want to be more precise, you can reject the specific validator for _validators which is the same as the raw_filter accessor of validate callbacks.
I think this is the most actual solution at this moment (I'm using rails 4.1.6):
# Common ninja
class Ninja < ActiveRecord::Base
validates :name, :martial_art, presence: true
end
# Wow! He has no martial skills
Ninja.class_eval do
_validators[:martial_art]
.find { |v| v.is_a? ActiveRecord::Validations::PresenceValidator }
.attributes
.delete(:martial_art)
end
Easest way to remove all validations:
clear_validators!
As I was trying to do this to remove the phone validation from the spree Address model, below is the code I got to work. I added the type check for callback.raw_filter because I only wanted to remove the presence validator on the phone field. I also had to add it because it would fail when trying to run against one of the other validators specified in the Spree::Address model that did not have an 'attributes' key for callback.raw_filter, thus an exception was thrown.
Spree::Address.class_eval do
# Remove the requirement on :phone being present.
_validators.reject!{ |key, _| key == :phone }
_validate_callbacks.each do |callback|
callback.raw_filter.attributes.delete :phone if callback.raw_filter.is_a?(ActiveModel::Validations::PresenceValidator)
end
end
I had a similar problem and was able to get past it using:
class MyModel << Dummy
# erase the validations defined in the plugin/gem because they interfere with our own
Dummy.reset_callbacks(:validate)
...
end
This is under Rails 3.0. The caveat: It does remove ALL validations, so if there are others you want to keep you could try Dummy.skip_callback(...), but I could not figure out the right incantation of arguments to make that work.
One solution is to extend validates :
#no need of instance_eval just open the class
class Dummy < ActiveRecord::Base
#validates :field, :presence => true
def self.validates(*attributes)
if attributes.first == :field #=> add condition on option if necessary
return # don't validate
else
super(*attributes) #let normal behavior take over
end
end
end
And no that's not monkey-patching but extending or decorating a behavior. Rails 3.1 is built on the idea of "multi- inheritance" with module inclusion, specifically to allow this kind agility.
update #2
One caveat is you must load the class with the redefined validates method before the gem containing the call to validates. To do so, require the file in config/application.rb after require "rails/all" as suggested in the railsguides. Something like that :
require File.expand_path('../boot', __FILE__)
require 'rails/all' # this where rails (including active_record) is loaded
require File.expand_path('../dummy' __FILE__) #or wherever you want it
#this is where the gems are loaded...
# the most important is that active_record is loaded before dummy but...
# not after the gem containing the call to validate :field
if defined?(Bundler)
Bundler.require *Rails.groups(:assets => %w(development test))
end
Hope it works now!
Answer by aVenger has problems when you declare validations of more than one attribute in a line:
validates :name, :message, :presence => true
That's because this line creates a raw_filter with more than one attribute in attributes filter:
Model.send(:_validate_callbacks)
=> [#<ActiveSupport::Callbacks::Callback:0xa350da4 #klass=Model(...), ... , #raw_filter=#<ActiveModel::Validations::PresenceValidator:0x9da7470 #attributes=[:name, :message], #options={}>, #filter="_callback_before_75", #compiled_options="true", #callback_id=76>]
We have to delete the desired attribute from that array and reject the callbacks without attributes
Dummy.class_eval do
_validators.reject!{ |key, _| key == :field }
_validate_callbacks.each do |callback|
callback.raw_filter.attributes.delete :field
end
_validate_callbacks.reject! do |callback|
callback.raw_filter.attributes.empty? ||
callback.raw_filter.attributes == [:field]
end
end
I have this working on a Rails 3.2.11 app.
For rails 4.2 (~ 5.0) it can be used the following module with a method:
module ValidationCancel
def cancel_validates *attributes
attributes.select {|v| Symbol === v }.each do |attr|
self._validators.delete( attr )
self._validate_callbacks.select do |callback|
callback.raw_filter.try( :attributes ) == [ attr ] ;end
.each do |vc|
self._validate_callbacks.delete( vc ) ;end ;end ;end ;end
Note: Since the filtern can be a symbol of an association, or a specific validator, so we have to use #try.
Then we can use rails-friendly form in a class declaration:
class Dummy
extend ValidationCancel
cancel_validates :field ;end
Note: since removal of the validator is affecting to the whole class and its descendants globally, it is not recommended to use it to remove validations in such way, instead add if clause for the specific rule as follows:
module ValidationCancel
def cancel_validates *attributes
this = self
attributes.select {|v| Symbol === v }.each do |attr|
self._validate_callbacks.select do |callback|
callback.raw_filter.try( :attributes ) == [ attr ] ;end
.each do |vc|
ifs = vc.instance_variable_get( :#if )
ifs << proc { ! self.is_a?( this ) } ;end ;end ;end ;end
This restricts execution of the validation callback for the specified class and its descendants.
If you doesn't want to make any changes in Parent class then first clear all validations in child class and copy all required validation from parent class to child class
class Dummy < ActiveRecord::Base
validates :property, presence: true
validates :value, length: { maximum: 255 }
end
And override it in child class
Dummy.class_eval do
clear_validators!
validates :property, presence: true
end
If you really want to do this then here would be a good place to start digging: https://github.com/rails/rails/blob/ed7614aa7de2eaeba16c9af11cf09b4fd7ed6819/activemodel/lib/active_model/validations/validates.rb#L82
However, to be honest, inside of ActiveModel is not where I'd be poking with a stick.
If you can edit the constraint on the original model to put an :if => :some_function on it, you can easily change the behavior of the function it calls to return false. I tested this and it works pretty easily:
class Foo < ActiveRecord::Base
validates :field, :presence => true, :if => :stuff
attr_accessor :field
def stuff
return true;
end
end
and then somewhere else:
Foo.class_eval {
def stuff
false
end
}
Why not use #dummy.save_without_validation method to skip validations altogether? I prefer do something like this:
if #dummy.valid?
#dummy.save # no problem saving a valid record
else
if #dummy.errors.size == 1 and #dummy.errors.on(:field)
# skip validations b/c we have exactly one error and it is the validation that we want to skip
#dummy.save_without_validation
end
end
You could put this code in your model or in the controller, depending on your needs.
In Rails 4.1,
I was able to do _validate_callbacks.clear. In my case, I wanted all the validations for a gem removed, so I could create my own. I did this in a module that was patched into the class.
Module #Name
extend ActiveSupport::Concern
included do
_validate_callbacks.clear
#add your own validations now
end
end
Wanted to add that, if you're trying to clear validations on a instance of your Model (not the entire model class), don't do my_dummy._validate_callbacks.clear, as that will clear validations on every instance (and future instance) of your Dummy model class.
For just the instance (and if you wanted to reinstate the validations later), try the following:
Create a copy of the validate callbacks (if you want to reinstate later):
my_dummy_validate_callbacks = my_dummy._validate_callbacks.clone
Set the validate callbacks on your instance to empty:
my_dummy._validate_callbacks = {}
Do what you want on my_dummy validation free!
Reinstate the callbacks: my_dummy._validate_callbacks = my_dummy_validate_callbacks
I'd have to look more into the code and help, but I'm thining it might be possible to inspect the list of validators of the class, and then modify the entry for the validation you want to change to add in an :if => :some_function conditional to it.
You'll need to do it only once for production (so it can be put inside an initializer, but for development you'll need to put it in the model, or somewhere else that will get loaded each time the corresponding model is (perhaps an observer?).
(I'll edit the answer with more information as I come to research it.)
Every Rails validator, pre-defined or custom, is an object, and is expected to respond to #validate(record) method. You can monkey patch or stub this method.
# MyModel.validators_on(:attr1, :attr2, ...) is also useful
validator = MyModel.validators.detect do |v|
validator_i_am_looking_for?(v)
end
def validator.validate(*_)
true
end
# In RSpec you can also consider:
allow(validator).to receive(:validate).and_return(true)
Tested in Rails 5.1.
Don't do this unless you understand what you're doing ;)
This does not directly answer the question but here's an option you should consider in such a situation: instead of disabling validation, you could set the required fields in a before_validation hook.
Since you don't need those required fields, set them with some dummy data that satisfies the validation and forget about them.
No ugly monkey patching.
Assuming the original implementation of Dummy is defined in an engine there is a nasty hack that will do what you want. Define Dummy in your application to keep the original implementation of Dummy from being auto-loaded. Then load the source to Dummy and remove the line that does the validation. Eval the modified source.
Put the following in your app/models/dummy.rb
class Dummy < ActiveRecord::Base
end
# Replace DummyPlugin with name of engine
engine = Rails::Application::Railties.engines.find { |e| e.class == DummyPlugin::Engine }
dummy_source = File.read File.join(engine.config.root, "app", "models", "dummy.rb")
dummy_source = dummy_source.gsub(/validates :field, :presence => true.*/, "")
eval dummy_source
If it is regular gem instead of an engine the same concept would apply, just would need to load the source for Dummy from the gem root instead of the engine root.

Rails - Regex inside a method used for validations

I have a validation that checks the format of a url using regex. I was wondering if it's possible to put the regex inside of a method:
validates_format_of :table_name :with => /^(http|https):\/\/[a-z0-9]+([_-.]{1}[a-z0-9]+).[a-z]{2,5}(:[0-9]{1,5})?(\/.)?$/ix
How do I put that regex in a method and use it with the validation?
If you wish to tidy up code without creating a custom validation, then use a constant rather than a method to store the regex.
class Product < ActiveRecord::Base
URL_REGEX = /^(http|https):\/\/[a-z0-9]+([\_\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/ix
validates_format_of :table_name :with => URL_REGEX
end
It'll work with any class method, or variable/constant that's already defined. But why don't you create a new validator?
# config/initializers/my_validators.rb
ActiveRecord::Base.class_eval do
def self.validates_url_of(attr_name, n, options={})
validates_format_of attr_name, :with => /^(http|https):\/\/[a-z0-9]+([_-.]{1}[a-z0-9]+).[a-z]{2,5}(:[0-9]{1,5})?(\/.)?$/ix
end
end
Then:
class Foo < ActiveRecord::Base
validates_url_of :attribute
end
See http://guides.rubyonrails.org/active_record_validations_callbacks.html#creating-custom-validation-methods
I recommend you take a look at Ruby - Validate and update URL for a much better validation method than regex.
If you're desperate to use Regex, then take a look at this writeup from John Gruber for something much more valuable than the above.

Resources