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.
Related
In ruby, you often execute methods like has_many or validates when you instanciate a class. Is there a name to designate this kind of method specifically created to be called at class instanciation ?
Building on Stefan's excellent comment...
They are inherited class methods forming a domain specific language used to make declarations about the class.
Those are all class methods, so called because you call then directly on the class. self will be the class. As opposed to instance methods which are called on the instance of the class, the object.
They are also inherited methods from ActiveRecord, they are
not defined within your class but in an ancestor class or module.
More specifically, they form a domain specific language; a DSL. These are method calls written in a specific way to seem like a new language. For example,
class Player < ApplicationRecord
validates :terms_of_service, presence: true, acceptance: { message: 'must be abided' }
end
Is really...
Player.validates(
:terms_of_service,
presence: true,
acceptance: { message: 'must be abided' }
)
Which is a wrapper around...
Player.validates_presence_of(:terms_of_service)
Player.validates_acceptance_of(:terms_of_service, message: 'must be abided')
Which is a wrapper around...
Player.validates_with(PresenceValidator, :terms_of_service)
Player.validates_with(AcceptanceValidator, :terms_of_service, message: 'must be abided')
Which is a wrapper around pushing validation objects onto a Hash of validations for this class.
# Roughly...
Player._validators[:terms_of_service] <<
PresenceValidator.new(
attributes: [:terms_of_service]
)
Player._validators[:terms_of_service] <<
AcceptanceValidator.new(
attributes: [:terms_of_service],
message: 'must be abided'
)
Finally, Domain Specific Languages are form of declarative programming.
In other types of programming you tell the program how to do it. For example, let's say you wanted to make sure an attribute is a positive integer greater than zero. You can do it procedurally...
object.number.match?(/^\d+$/) && object.number > 0
Or you ask a specific method to do it...
NumericalityValidator.new(
attribute: [:number],
only_integer: true, greater_than: 0
).validate(object)
But in declarative programming you declare how it's going to be and the language figures out how to make it happen.
class Player < ApplicationRecord
validates :number, numericality: { only_integer: true, greater_than: 0 }
end
You probably already know two declarative languages: SQL and Regular Expressions.
I am currently new in ruby on rails and I have gem called rails admin. I want the validation not repeatable, i want it to save in one method and make it global so that in my model I can call the validation format.
I added a def in my application controller and inside of it i declare it as global but when I type special characters, it will be add.
Note: My validation is only allowed alphanumeric or underscore..
Expected Output: It wont add unless the input is alphanumeric or underscore
Question: Is my global variable wrong? How could I make a global variable so that I will just call it
Model
class ActivityType < ApplicationRecord
has_many :activities
validates :name, presence: true, length: { maximum: 20 },
:uniqueness => true,
format: { with: /\A[\w\_]+\z/ }
validates :description, presence: true,
format: { with: /\A[\w]+\z/ }
end
RegexValidations
module RegexValidations
def self.alphanumeric_underscore
{ with: /\A[\w\_]+\z/ }
end
end
Validation are done at model level for their respective attributes.
Defining methods in application_controller doesn't make it global, but accessible to all controllers inheriting from it.
Your model validation validates should be enough to validate format of the string in column.
Edit/Improvisation:
If you want to make the regex dry/not repeat for every model. Create a module in your lib folder say:
module RegexValidations
def self.alphanumeric_underscore
{ with: /\A[\w\_]+\z/ }
end
end
And in your model include it and say:
include RegexValidations
validates :description, presence: true, format: RegexValidations.alphanumeric_underscore
This way you can write multiple regex in one file and use it in every model keeping it DRY.
PS: unfortunately i couldn't test it..but it should work. Let me know if you encounter any error.
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.
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.
I have a form where i pass a field named :type and i want to check if it's value is inside an array of allowed types so that no one is allowed to post not-allowed types.
the array looks like
#allowed_types = [
'type1',
'type2',
'type3',
'type4',
'type5',
'type6',
'type7',
etc...
]
i have tried using validates_exclusion_of or validates_inclusion_of but it doesn't seem to work
first, change the attribute from type to something else, type is a reserved attrubute name use for Single Table Inheritance and such.
class Thing < ActiveRecord::Base
validates :mytype, :inclusion=> { :in => #allowed_types }
ActiveModel::Validations provides a helper method for this. An example call would be:
validates_inclusion_of :type, in: #allowed_types
ActiveRecord::Base is already a ActiveModel::Validations, so there is no need to include anything.
http://apidock.com/rails/ActiveModel/Validations/HelperMethods/validates_inclusion_of
Also, #RadBrad is correct that you should not use type as a column name as it is reserved for STI.
Just for those lazy people (like me) to copy the latest syntax:
validates :status, inclusion: %w[pending processing succeeded failed]
validates_inclusion_of is out of date since Rails 3.
:inclusion=> hash syntax is out of date since Ruby 2.0.
In favor of %w for word array as the default Rubocop option.
With variations:
Default types as a constant:
STATUSES = %w[pending processing succeeded failed]
validates :status, inclusion: STATUSES
OP's original:
validates :mytype, inclusion: #allowed_types