looking for the name of a particular concept in Ruby - ruby-on-rails

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.

Related

Does "module ClassMethods" has any special meaning in rails?

I came cross this code in ruby on rails:
class Person
include ActiveModel::Validations
attr_accessor :name, :email
validates :name, presence: true, length: { maximum: 100 }
end
My question is what makes this "validates" method callable in this class Person, in order for us to call "validates" within class context, this "validates" method must be defined in any of the following three places:
"class Class"
"class Module"
Person's eigenclass.
So I try to find if any of those cases are true, the only thing here is this line:
include ActiveModel::Validations
When I look at the source code of ActiveModel::Validations, validates is not defined directly in ActiveModel::Validations, but it is defined in ActiveModel::Validations::ClassMethods, so from what I see here, this struct definitely can not add validates methods to any of the above 3 places, the only suspect is this module name: ClassMethods, from its name, it looks like all its method will be defined in the eigenclass of the class which mix-in it, but I didn't find any code that actually does that.
So my question is: is this module name: ClassMethods has any special meaning in rails?
When we mix-in ActiveModel::Validations, does rails automatically find all the methods in ActiveModel::Validations::ClassMethods, and add those into the eigenclass of the current class?
Thanks.

How to validate only certain instances of a rails model

What is wrong with my model that I can't see the #black? method?
Error:
undefined method `black?' for Car:Class
Car Modal
# frozen_string_literal: true
class Cars < Base
if black?
validates :style, presence: true
end
private
def black?
color_options.include?("black")
end
end
If I do...
validates :style, presence: true, if: :seller?
The code works, but I plan on adding many more validations in the if statement block, so it would be nice to get the if statement block (if end) working.
You're calling black? on the class, not on an instance of the class. At the point that your if black? code is called, self evaluates to Cars! It wouldn't make any sense to call Cars.black? - you want to know if a single instance of the car is black!
It is good practice to wrap all your various validation conditions into a single predicate which you then reference with the :if syntax.
For example, if you had a complex set of conditions, you might set up a validator:
class Cars
validates :style, presence: true, if: :validate_style?
private
def validate_style?
color_options.include?("black") ||
wheel_options.include?("premium") ||
Time.now.wday == 3
end
end

How to make the validation(only accepts alphanumeric) global for my model in ruby

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.

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.

Resources