Let's say that I have an input field with a value, and I want to validate it (on the server side) to make sure, for instance, that the field has at least 5 characters.
The problem is that it is not something that I want to save in the database, or build a model. I just want to check that the value validates.
In PHP with Laravel, validation is quite easy:
$validator = Validator::make($data, [
'email' => ['required', 'email'],
'message' => ['required']]);
if ($validator->fails()) { // Handle it... }
Is there anything similar in Rails, without need of ActiveRecord, or ActiveModel? Not every data sent from a form makes sense as a Model.
You can use ActiveModel::Validations like this
class MyClass
include ActiveModel::Validations
validates :email, presence: true
validates :message, presence: true
end
It will act as a normal model and you will be able to do my_object.valid? and my_object.errors.
Rails validations live in ActiveModel so doing it without ActiveModel seems kind of counter-productive. Now, if you can loosen that requirement a bit, it is definitely possible.
What I read you asking for, and as I read the PHP code doing, is a validator-object that can be configured on the fly.
We can for example build a validator class dynamically and use instance of that class to run our validations. I have opted for an API that looks similar to the PHP one here:
class DataValidator
def self.make(data, validations)
Class.new do
include ActiveModel::Validations
attr_reader(*validations.keys)
validations.each do |attribute, attribute_validations|
validates attribute, attribute_validations
end
def self.model_name
ActiveModel::Name.new(self, nil, "DataValidator::Validator")
end
def initialize(data)
data.each do |key, value|
self.instance_variable_set("##{key.to_sym}", value)
end
end
end.new(data)
end
end
Using DataValidator.make we can now build instances of classes with the specific validations that we need. For example in a controller:
validator = DataValidator.make(
params,
{
:email => {:presence => true},
:name => {:presence => true}
}
)
if validator.valid?
# Success
else
# Error
end
Related
You have a model, say, Car. Some validations apply to every Car instance, all the time:
class Car
include ActiveModel::Model
validates :engine, :presence => true
validates :vin, :presence => true
end
But some validations are only relevant in specific contexts, so only certain instances should have them. You'd like to do this somewhere:
c = Car.new
c.extend HasWinterTires
c.valid?
Those validations go elsewhere, into a different module:
module HasWinterTires
# Can't go fast with winter tires.
validates :speed, :inclusion => { :in => 0..30 }
end
If you do this, validates will fail since it's not defined in Module. If you add include ActiveModel::Validations, that won't work either since it needs to be included on a class.
What's the right way to put validations on model instances without stuffing more things into the original class?
There are several solutions to this problem. The best one probably depends on your particular needs. The examples below will use this simple model:
class Record
include ActiveModel::Validations
validates :value, presence: true
attr_accessor :value
end
Rails 4 only
Use singleton_class
ActiveSupport::Callbacks were completely overhauled in Rails 4 and putting validations on the singleton_class will now work. This was not possible in Rails 3 due to the implementation directly referring to self.class.
record = Record.new value: 1
record.singleton_class.validates :value, numericality: {greater_than: 1_000_000}
record.valid? || record.errors.full_messages
# => ["Value must be greater than 1000000"]
Rails 3 and 4
In Rails 3, validations are also implemented using ActiveSupport::Callbacks. Callbacks exist on the class, and while the callbacks themselves are accessed on a class attribute which can be overridden at the instance-level, taking advantage of that requires writing some very implementation-dependent glue code. Additionally, the "validates" and "validate" methods are class methods, so you basically you need a class.
Use subclasses
This is probably the best solution in Rails 3 unless you need composability. You will inherit the base validations from the superclass.
class BigRecord < Record
validates :value, numericality: {greater_than: 1_000_000}
end
record = BigRecord.new value: 1
record.valid? || record.errors.full_messages
# => ["Value must be greater than 1000000"]
For ActiveRecord objects, there are several ways to "cast" a superclass object to a subclass. subclass_record = record.becomes(subclass) is one way.
Note that this will also preserve the class methods validators and validators_on(attribute). The SimpleForm gem, for example, uses these to test for the existence of a PresenceValidator to add "required" CSS classes to the appropriate form fields.
Use validation contexts
Validation contexts are one of the "official" Rails ways to have different validations for objects of the same class. Unfortunately, validation can only occur in a single context.
class Record
include ActiveModel::Validations
validates :value, presence: true
attr_accessor :value
# This can also be put into a module (or Concern) and included
with_options :on => :big_record do |model|
model.validates :value, numericality: {greater_than: 1_000_000}
end
end
record = Record.new value: 1
record.valid?(:big_record) || record.errors.full_messages
# => ["Value must be greater than 1000000"]
# alternatively, e.g., if passing to other code that won't supply a context:
record.define_singleton_method(:valid?) { super(:big_record) }
record.valid? || record.errors.full_messages
# => ["Value must be greater than 1000000"]
Use #validates_with instance method
#validates_with is one of the only instance methods available for validation. It accepts one or more validator classes and any options, which will be passed to all classes. It will immediately instantiate the class(es) and pass the record to them, so it needs to be run from within a call to #valid?.
module Record::BigValidations
def valid?(context=nil)
super.tap do
# (must validate after super, which calls errors.clear)
validates_with ActiveModel::Validations::NumericalityValidator,
:greater_than => 1_000_000,
:attributes => [:value]
end && errors.empty?
end
end
record = Record.new value: 1
record.extend Record::BigValidations
record.valid? || record.errors.full_messages
# => ["Value must be greater than 1000000"]
For Rails 3, this is probably your best bet if you need composition and have so many combinations that subclasses are impractical. You can extend with multiple modules.
Use SimpleDelegator
big_record_delegator = Class.new(SimpleDelegator) do
include ActiveModel::Validations
validates :value, numericality: {greater_than: 1_000_000}
def valid?(context=nil)
return true if __getobj__.valid?(context) && super
# merge errors
__getobj__.errors.each do |key, error|
errors.add(key, error) unless errors.added?(key, error)
end
false
end
# required for anonymous classes
def self.model_name
Record.model_name
end
end
record = Record.new value: 1
big_record = big_record_delegator.new(record)
big_record.valid? || big_record.errors.full_messages
# => ["Value must be greater than 1000000"]
I used an anonymous class here to give an example of using a "disposable" class. If you had dynamic enough validations such that well-defined subclasses were impractical, but you still wanted to use the "validate/validates" class macros, you could create an anonymous class using Class.new.
One thing you probably don't want to do is create anonymous subclasses of the original class (in these examples, the Record class), as they will be added to the superclass's DescendantTracker, and for long-lived code, could present a problem for garbage collection.
You could perform the validation on the Car, if the Car extends the HasWinterTires module... For example:
class Car < ActiveRecord::Base
...
validates :speed, :inclusion => { :in => 0..30 }, :if => lambda { self.singleton_class.ancestors.includes?(HasWinterTires) }
end
I think you can just do self.is_a?(HasWinterTires) instead of the singleton_class.ancestors.include?(HasWinterTires), but I haven't tested it.
Have you thought about using a Concern? So something like this should work.
module HasWinterTires
extend ActiveSupport::Concern
module ClassMethods
validates :speed, :inclusion => { :in => 0..30 }
end
end
The concern won't care that it itself has no idea what validates does.
Then I believe that you can just do instance.extend(HasWinterTires), and it should all work.
I'm writing this out from memory, so let me know if you have any issues.
You likely want something like this, which is very similar to your original attempt:
module HasWinterTires
def self.included(base)
base.class_eval do
validates :speed, :inclusion => { :in => 0..30 }
end
end
end
Then the behavior should work as expected in your example, though you need to use include instead of extend on HasWinterTires.
I need help with my ActiveRecord model. I have context based validations (mis)using the build-in context options for validations:
validates :foo, :on => :bar, :presence => true
model = Model.new
model.foo = nil
model.valid? # => true
model.save # works as expected
model.valid?(:bar) # => false
model.save(:context => :bar) # fails and returns false
But using my model in a accepts_nested_attributes_for :model and calling parent.save fails (the validation gets called and returns false), any suggestions or solutions?
Still no answer? To explain more about my problem: I have a model called Form which has many Fields. Users should see validation errors on submit, but the form should be saved anyway (with and without errors). There are different types of Fields, each with global validations (to ensure database consistency) and its own specific user-defined validations (to validate user-entered data). So my Fields look someway like that:
# Global validations, to ensure database consistency
# If this validations fail, the record should not be saved!
validates_associated :form, :on => :global
...
# Specific user-defined validations
# If this validations fail, the record should be saved but marked as invalid. (Which is done by a before_save filter btw.)
def validate
validations.each do |validation| # Array of `ActiveModel::Validations`, defined by the user and stored in a hash in the database
validation.new(:on => :specific).validate(self)
end
end
In my controller:
# def create
# ...
form.attributes = params[:form]
form.save!(:global)
form.save(:specific)
Is something similar possible in Rails using the built-in functionality? Btw this not my actual code, which is quite complicated. But I hope, you guys will get the idea.
Try conditional validations
class Customer
attr_accessor :managing
validates_presence_of :first_name
validates_presence_of :last_name
with_options :unless => :managing do |o|
o.validates_inclusion_of :city, :in=> ["San Diego","Rochester"]
o.validates_length_of :biography, :minimum => 100
end
end
#customer.managing = true
#customer.attributes = params[:customer]
#customer.save
"Ability to specify multiple contexts when defining a validation" was introduced in Rails 4.1 - check validate method, :on options description
Only for Rails 5+:
You are looking for
with_options on: :custom_event do
validates :foo, presence: true
validates :baz, inclusion: { in: ['b', 'c'] }
end
To validate or save use
model = YourModel.new
# Either
model.valid?(:custom_event)
# Or
model.save(context: :custom_event)
Change has_nested_attributes_for :model to accepts_nested_attributes_for :models.
Hope this helps.
Good luck.
Apologies if this is a really common and/or ridiculous question; I swear I've read over the documentation multiple times and everything seems so focused on ActiveRecord to the point they've wandered off the path of forms that do something other than create or edit model data.
Take for example a form with inputs to control the extraction and display of some statistics. What does rails provide me with for validating the user input of this form, which won't be invoking save on any records? Things like:
:email must be an email address
:num_products must be a positive whole number
:gender must be one of "M" or "F"
:temperature must be between -10 and 120
Etc etc (the sort of stuff that comes standard in most web frameworks)...
Is there something in Rails for me to perform this arbitrary validation and some view helper to display a list of errors, or is everything coupled with ActiveRecord?
Apologies if I've overlooked this in the documentation, but this and this don't really cover it, at least as far as mt weary eyes can tell.
scratches head
Thanks to Rob's answer, here's what I've come up with. I created a utility class (aptly named Validator) which is just embedded into my controllers for anything that needs it.
module MyApp
class Validator
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
def initialize(attributes = {})
super
attributes.each { |n, v| send("#{n}=", v) if respond_to?("#{n}=") }
end
end
end
Now in the controller, for example, just define a little inline-class:
class LoginController < ApplicationController
class AuthenticationValidator < MyApp::Validator
attr_accessor :email
attr_accessor :password
validates_presence_of :email, :message => "E-mail is a required field"
validates_presence_of :password, :message => "Password cannot be blank"
end
def authenticate
if request.post?
#validator = AuthenticationValidator.new(params)
if #validator.valid?
# Do something meaningful
end
end
end
It feels a bit unnecessary to stick every single set of validation rules in their own .rb when the logic is more controller-oriented IMHO. There is probably a more succinct way to write this, but I'm pretty new to Ruby and Rails.
Yes, it can be done fairly easily.
You can use the validations API for it.
As an example, here is a contact us model that I use for an application that is not using ActiveRecord.
class ContactUs
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :name, :email, :subject, :message
validates_presence_of :name, :email, :message, :subject
validates_format_of :email, :with => /\A[A-Za-z0-9._%+-]+#[A-Za-z0-9.-]+\.[A-Za-z]{2,4}\z/
def initialize(attributes=nil)
attributes.each do |name, value|
send("#{name}=", value)
end unless attributes.nil?
end
def persisted?
false
end
end
There is a valid method on model, which triggers validation.
so instead model.save, try model.valid?
I have a form that allows the user to send a message to an email, and I want to add validation to it. I do not have a model for this, only a controller. How should I do this in Rails?
I was considering doing the validation in the controller, and displaying the errors to the user using the flash object. Is there a better way of doing this?
The best approach would be to wrap up your pseudo-model in a class, and add the validations there. The Rails way states you shouldn't put model behavior on the controllers, the only validations there should be the ones that go with the request itself (authentication, authorization, etc.)
In Rails 2.3+, you can include ActiveRecord::Validations, with the little drawback that you have to define some methods the ActiveRecord layer expects. See this post for a deeper explanation. Code below adapted from that post:
require 'active_record/validations'
class Email
attr_accessor :name, :email
attr_accessor :errors
def initialize(*args)
# Create an Errors object, which is required by validations and to use some view methods.
#errors = ActiveRecord::Errors.new(self)
end
# Required method stubs
def save
end
def save!
end
def new_record?
false
end
def update_attribute
end
# Mix in that validation goodness!
include ActiveRecord::Validations
# Validations! =)
validates_presence_of :name
validates_format_of :email, :with => SOME_EMAIL_REGEXP
end
In Rails3, you have those sexy validations at your disposal :)
For Rails 3+, you should use ActiveModel::Validations to add Rails-style validations to a regular Ruby object.
From the docs:
Active Model Validations
Provides a full validation framework to your objects.
A minimal implementation could be:
class Person
include ActiveModel::Validations
attr_accessor :first_name, :last_name
validates_each :first_name, :last_name do |record, attr, value|
record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
end
end
Which provides you with the full standard validation stack that you
know from Active Record:
person = Person.new
person.valid? # => true
person.invalid? # => false
person.first_name = 'zoolander'
person.valid? # => false
person.invalid? # => true
person.errors.messages # => {first_name:["starts with z."]}
Note that ActiveModel::Validations automatically adds an errors method
to your instances initialized with a new ActiveModel::Errors object,
so there is no need for you to do this manually.
I want to be able to replace a field error with a warning when saving/updating a model in rails. Basically I want to just write a wrapper around the validation methods that'll generate the error, save the model and perhaps be available in a warnings hash (which works just like the errors hash):
class Person < ActiveRecord::Base
# normal validation
validates_presence_of :name
# validation with warning
validates_numericality_of :age,
:only_integer => true,
:warning => true # <-- only warn
end
>>> p = Person.new(:name => 'john', :age => 2.2)
>>> p.save
=> true # <-- able to save to db
>>> p.warnings.map { |field, message| "#{field} - #{message}" }
["age - is not a number"] # <-- have access to warning content
Any idea how I could implement this? I was able to add :warning => false default value to ActiveRecord::Validations::ClassMethods::DEFAULT_VALIDATION_OPTIONS
By extending the module, but I'm looking for some insight on how to implement the rest. Thanks.
The validation_scopes gem uses some nice metaprogramming magic to give you all of the usual functionality of validations and ActiveRecord::Errors objects in contexts other than object.errors.
For example, you can say:
validation_scope :warnings do |s|
s.validates_presence_of :some_attr
end
The above validation will be triggered as usual on object.valid?, but won't block saves to the database on object.save if some_attr is not present. Any associated ActiveRecord::Errors objects will be found in object.warnings.
Validations specified in the usual manner without a scope will still behave as expected, blocking database saves and assigning error objects to object.errors.
The author has a brief description of the gem's development on his blog.
I don't know if it's ready for Rails 3, but this plugin does what you are looking for:
http://softvalidations.rubyforge.org/
Edited to add:
To update the basic functionality of this with ActiveModel I came up with the following:
#/config/initializer/soft_validate.rb:
module ActiveRecord
class Base
def warnings
#warnings ||= ActiveModel::Errors.new(self)
end
def complete?
warnings.clear
valid?
warnings.empty?
end
end
end
#/lib/soft_validate_validator.rb
class SoftValidateValidator < ActiveModel::EachValidator
def validate(record)
record.warnings.add_on_blank(attributes, options)
end
end
It adds a new Errors like object called warnings and a helper method complete?, and you can add it to a model like so:
class FollowupReport < ActiveRecord::Base
validates :suggestions, :soft_validate => true
end
I made my own gem to solve the problem for Rails 4.1+: https://github.com/s12chung/active_warnings
class BasicModel
include ActiveWarnings
attr_accessor :name
def initialize(name); #name = name; end
warnings do
validates :name, absence: true
end
end
model = BasicModel.new("some_name")
model.safe? # .invalid? equivalent, but for warnings
model.warnings # .errors equivalent