Understanding ruby on rails documentation - ruby-on-rails

I am currently creating a rails application.
I am writing a model and wants to add some validation.
from the documentation I see that doing something like this works
class Person < ApplicationRecord
validates :terms_of_service, acceptance: { message: 'must be abided' }
end
I am trying to understand the validates method here.
At a more general level I would like to understand rails documentation better.
My understanding is that validates is a class method of ApplicationRecord::Base.
It is possible to reuse it with various parameters and options.
The best doc I found is this.
I do not understand where I can find a list of all validates options and parameters.
In this case,
what is acceptance?
where can I find a description of it in the doc?
where can I find a list of all other possible validates parameters?
Any tips on how to understand ruby on rails documentation better would be appreciated.

validates :terms_of_service, acceptance: true
acceptance maps to AcceptanceValidator which is a default rails validator:
https://github.com/rails/rails/blob/main/activemodel/lib/active_model/validations/acceptance.rb
All of the default validators are listed in the example:
https://api.rubyonrails.org/classes/ActiveModel/Validations/ClassMethods.html#method-i-validates
absence
acceptance
confirmation
exclusion
format
inclusion
length
numericality
presence
and additional validators that are added by ActiveRecord:
associated
uniqueness
https://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html
Available options for each validaror are documented in the helper methods here:
https://api.rubyonrails.org/classes/ActiveModel/Validations/HelperMethods.html
validates :terms_of_service, acceptance: true
# is the same as using a helper method
validates_acceptance_of :terms_of_service
# and both map to rails default `AcceptanceValidator`
You can have custom validators as well:
validates :terms_of_service, terms: true # maps to `TermsValidator`
# because there is no TermsValidator class in rails, you have to define it
# class TermsValidator
# # TODO: see docs for examples of custom validators
# end
https://guides.rubyonrails.org/active_record_validations.html#performing-custom-validations

Related

How to understand the colon operator usage in a Ruby class

I am learning both Ruby (2.3.x) & Rails (4.x). I was going through the Ruby On Rails Tutorial and I encountered this syntax and am having trouble reading it:
class User < ApplicationRecord
validates :name, presence: true
validates :email, presence: true
end
Does this class define validates as a method which takes in a :name symbol and a hash presence:true? The same thing applies to line 3.
Or it is something entirely else? All attempts to run it resulted in:
uninitialized constant ApplicationRecord.
I looked at the source(maybe?) but am still not clear.
This is a special DSL introduced by ApplicationRecord. What you are actually doing is calling those methods inside class during declaration. It adds those validations to your class, so whenever you try to save a record it will fail if you don't have email or name
Try this
user = User.new
user.save
user.valid? # false
And try to do the same without validates.
If it will make things more clear for you, you can try write this class like this
class User < ApplicationRecord
validates(:name, presence: true)
validates(:email, presence: true)
end
validates is implemented as a class method in ActiveModel::Validations.
The ActiveModel::Validations module is included in ApplicationRecord, therefore you are able to call that method when your User class is loaded.
validates accepted an array and treats the last element of that array as an options hash (if the last element is an hash).
validates is a pre-defined helper that Active Record offers to use in Rails to make the validation work easier, this way you can with some single lines of code manage several validations of several attributes.
As it's a helper within Rails it's also a method defined in the ActiveModel module, in the core of the framework, see: active_model/validations.rb
The most common is the presence attribute which you're facing the trouble with, that specifies that the attribute used isn't empty, doing it in the Ruby way through the blank? method to check if the value passed isn't blank nor nil.

Clarification on Ruby syntax in Rails

I'm learning Rails and in going through the official guides, I came across some code which I could not really understand the meaning of.
Case 1 -
class Person < ApplicationRecord
validates :name, presence: true
end
It looks to me that validates is a method that takes a symbol called :name as an argument. But then, what is presence? Is it also a method? But if it is, what is the significance of the : right after presence. I understand that the value true is being set for presence, which serves as kind of a validation, requiring the presence of (in other words). But I'm not quite clear on the syntax.
It might also be possible that presence: true is just a hash, where :presence (the symbol) is the key, and true is the value.
Case 2 -
class Person < ApplicationRecord
validates :terms_of_service, acceptance: true, message: 'must be abided'
end
Again, validates is the method that takes a symbol :terms_of_service as an argument. But what about the rest? Is it a hash with 2 key-value pairs, somewhat like {acceptance: true, message: 'must be abided'}?
And if it is indeed a hash, why is it tacked on to the validates method in each case? Why can't it be
validates :terms_of_service
acceptance: true, message: 'must be abided'
Thanks for the help!
That is the syntax for passing a hash to the method. What that is doing is the same thing as validates(:terms_of_service, {acceptance: true, message: 'must be abided'}). It's a common way of passing extra options to a method.
In Ruby there's a strong tradition for passing in options as a Hash as the last argument, strong enough that this tradition became new feature borrowed from Python: Keyword arguments.
In classic Ruby the method would be defined as this:
def validates(*args)
options = args.last.is_a?(Hash) ? args.pop : { }
# args is a list of fields
end
In Ruby 2.3 you can do this:
def validates(*args, **options)
# options is automatically any hash-style arguments if present
end
In Ruby 2.0+ you can also do this:
def validates(*args, acceptance: false, message: nil)
end
Where that defines options as first-class variables.
It's a common Ruby pattern, so it's good to understand what's going on here. Try writing your own methods that take options and you'll see how it plays out.

How to validate presence of an array using Enumerize?

I'm trying to implement to validations on a given model array-like field, using the Enumerize Gem. I want to:
validate that all the elements of a list belong to a given subset, using Enumerize
validate that the list is not empty (using validates :field, presence: true)
It seems that when I provide a list containing an empty string, the presence validator fails. See this example.
class MyModel
include ActiveModel::Model
extend Enumerize
enumerize :cities, in: %w(Boston London Berlin Paris), multiple: true
validates :cities, presence: true
end
# Does not behave as expected
a = MyModel.new(cities: [""])
a.cities.present? # => false
a.valid? # => true, while it should be false.
It seems to work in some other cases (for instance when you provide a non empty string that is not in the Enum). For instance
# Behaves as expected
a = MyModel.new(cities: ["Dublin"])
a.cities.present? # => false
a.valid? # => false
Is there a workaround available to be able to use both Enumerize validation and ActiveModel presence validation?
Thanks!
The enumerize gem is saving your multiple values as an string array. Something like this: "[\"Boston\"]". So, with an empty array you have: "[]". The presencevalidator uses blank? method to check if the value is present or not. "[]".blank? returns false obviously.
So, you can try some alternatives:
Option 1:
validates :cities, inclusion: { in: %w(Boston London Berlin Paris) }
Option 2:
Add a custom validator
validate :ensure_valid_cities
def ensure_valid_cities
errors.add(:cities, "invalid") unless cities.values.empty?
end
This is actually a bug that should be fixed in a later version of Enumerize (See https://github.com/brainspec/enumerize/pull/226).
In the meantime, you can use a custom validator as a workaround (See Leantraxx's answer).

How do I validate certain fields with rails devise on registration only

I have a set of custom fields attached to a devise model called Entrant.
I have two forms, one for registration form (three fields) and one which sits in the account area (12 fields). Most of the custom fields area required but only within the form the sits in the account area.
How do I achieve this?
I am using rails 4.2 and ruby 2.1
You can simply specify validations on actions, that is:
validates :name, presence: true, on: :create # which won't validate presence of name on update action
If you ask where to put your custom fields, then generate devise's views and update corresponding ones with these fields.
There are several ways! You could do conditional validations, for instance
class Entrant < ActiveRecord::Base
validate :foo, if: :account_area?
def account_area?
!new_record? # Assumes that Entrant that has already been saved
# is in the account area
end
end
However, it sounds like your needs are advanced enough that you should consider making a Form Object
A form object is an object that accepts parameters, performs validations on that data, then saves a model instance.
class AccountForm
include ActiveModel::Model
include Virtus # Provides AR like attribute functionality and mass assignment
def initialize(entrant)
#entrant = entrant
end
attribute :foo, String
validates :foo, presence: true # This is only used on the account page, so no need to mess with conditional logic
def save
if valid?
persist!
true
else
false
end
end
def persist!
#entrant.update_attributes(foo: self.foo)
end
end
This is just a great example of how non-rails-specific object oriented programming can make your life easier and your app more maintainable. Make a class like above, stick it in app/forms and restart your server. Then in your controller, you'll just pass it the model
class EntrantController < ApplicationController
def update
#form = Form.new(Entrant.find(params[:id]))
#form.attributes = params[:entrant]
if #form.save
redirect_to some_path
else
render "edit"
end
end
end
By default devise only asks for a combination of email/password, you can add other fields by adding a sanitizer (see there -> Devise how to add a addtional field to the create User form?).
If you want to add other fileds to validate, you should create a secondary Entrant controller and add a specific callback to your model.
Typically:
after_update :validate_entrant_form, if: :property_changed?
I hope this will help you.
validates :name, presence: true, if: :condition_holds?
def condition_holds?
# some code here that evaluates to a boolean
end
Maybe this way help you.
Add attribute in devise model : say attr_accessor :validate_certain. In your controller action, devise model instance say #user have to update like this #user.validate_certain = true. and change your appropriate validation conditions in devise model
validates :name, presence: true, if: :validate_certain_changed?
def validate_certain_changed?
validate_certain.present?
end
When I have to do something like this I like to think of it as it validates if something in in the field but you can also take a nil value
Entrant.validates_presence_of(:foo, :allow_nil => true)
I also have this concern when using devise on customer with forms on separate pages updating different set of customer fields
I believe most of the solution works but I was looking for the simplest, easiest and foolproof way to implement the solution
Thus came this.
validates :phone, :country, :postal_code, :street_address, presence: true, allow_nil: true
The allow_nil: true instruct the model to validate the fields ONLY if it exists on the submitted form. If you want more protection, you can use extra para like :on => :update

Selecting all current attributes of a model for validations in Rails

When setting up my models I often find myself having to write out all of its attributes when setting up certain validations. A common example is when I use the presence parameter:
validates :first_name, :last_name, :username, :email, presence: true
Is there a clever way to select all of its attributes without explicitly writing them all out similar to how you can retrieve them in the rails console?
User.columns
And pass it as an argument in the validates method?
ALL_ATTRIBUTES = User.columns
validates ALL_ATTRIBUTES, presence: true
Trying something like this out I got this error undefined method 'to_sym'
I will NOT encourage you or anyone to do this. Reason being when you run into issues, when an object of your model doesn't get saved and throw errors because of a new column which was added to application after some time in future, and you or new developers will wonder WHY?!?!.
However, if you must do then here you go:
validates *self.column_names.map(&:to_sym), presence: true
Here, * in Ruby is known as splat operator and here's the explanation on &:.
This is an awful idea. But you can do it this way:
attrs = column_names.map { |column| column.to_sym }
validates *attrs, presence: true
Why is it a bad idea? Because it's not very clear what is being validated it. It makes debugging hard, and could cause you have strange bugs. If you add a column in the future that does not require presence validation, you will trip up. Also, some things my not require presence. For example, an email field will need a regex validation, which automatically knows that a blank string is invalid. So a presence validator is redundant.
Beware of being too clever, as it's sometimes not so clever after all.

Resources