I have validations within each of my models. I also have a module defined in my lib folder with methods that update my my models. How do I get the validations I have defined in my models to work for the methods in my module.
You can do this:
class MyValidator < ActiveModel::Validator
def validate(record)
unless MYLIB::isthisgood(record.name)
record.errors[:base] << "Name is No Good"
end
end
end
class MyModel < ActiveRecord::Base
validates_with MyValidator
end
Related
I found two alternatives to make use of the ActiveModel::Validator helper, but I am failing to find an easier or clean(er) approach than either checking for the presence of record.id or creating multiple custom validators.
I have a User class as follows:
class User < ApplicationRecord
include ActiveModel::Validations
validates_with UserValidator
...
end
and a custom validator
class UserValidator < ActiveModel::Validator
def validate(record)
# validate some stuff
end
end
The logic inside the validate method is executed no matter the action from the controller. So on create or on update (or save).
My goal is to split the logic and I have found multiple approaches that work but I don't really like either of them.
Approach 1: Check if a record.id exists
class UserValidator < ActiveModel::Validator
def validate(record)
if record.id.nil?
# `on: :create` validations
else
# `on: :update` validations
end
end
end
Approach 2: Create multiple validator classes
class User < ApplicationRecord
include ActiveModel::Validations
validates_with UserCreateValidator, on: :create
validates_with UserUpdateValidator, on: :update
...
end
Preferably I would only have a single class UserValidator and within that custom validator check for some attribute or pass an option that would allow me to distinguish between create or update. Is that somehow achievable?
class UserValidator < ActiveModel::Validator
def validate(record)
if record.new_record?
# `on: :create` validations
else
# `on: :update` validations
end
end
end
See ActiveRecord::Persistence.
In my ruby classes I noticed that there are several validators that are repeated such as the validator that checks for the presence of Id...
I would like to gather these validators in a external class/module/file and then do something like
class User
include Mongoid::Document
...
validates_with :MyCustomExternalValidator
Is this possible?
Details:
-Latest Ruby
-RSPEC for testing
Yes, it is. From guides.rubyonrails.org:
class MyValidator < ActiveModel::Validator
def validate(record)
unless record.name.starts_with? 'X'
record.errors[:name] << 'Need a name starting with X please!'
end
end
end
class Person
include ActiveModel::Validations
validates_with MyValidator
end
I want to create a module which provides some common methods to the classes which are inherited from active record base.
Following is the two-way we can achieve it.
1)
module Commentable
def self.extended(base)
base.class_eval do
include InstanceMethods
extend ClassMethods
end
end
module ClassMethods
def test_commentable_classmethod
puts 'test class method'
end
end
module InstanceMethods
def test_commentable_instance_method
puts 'test instance method'
end
end
end
ActiveRecord::Base.extend(Commentable)
2)
module Commentable
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def test_commentable_classmethod
puts 'test class method'
end
end
def test_commentable_instance_method
puts 'test instance methods'
end
end
ActiveRecord::Base.send(:include, Commentable)
Which one is the preferred way to handle this?
And
What to use when?
As of Rails 5, the recommended way is to make a module and include it in the models where it is needed, or everywhere using ApplicationRecord, which all models inherit from. (You can easily implement this pattern from scratch in older versions of Rails.)
# app/models/concerns/my_module.rb
module MyModule
extend ActiveSupport::Concern
module ClassMethods
def has_some_new_fancy_feature(options = {})
...
end
end
end
# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
include MyModule
end
Modules are a form of multiple-inheritance, and sometimes add unnecessary complexity. Check if a decorator, service, or other kind of object makes more sense first. Not everything needs be a fancy macro that adds 50 callbacks to your model. You will hate your life if you do this too much.
If you want to monkey-patch (DON'T DO THIS), here is my old answer:
# config/initializers/activerecord_extensions.rb
ActiveRecord::Base.send(:include, MyModule)
Or without monkey-patching (see Mori's response):
# app/models/base_model.rb
class BaseModel < ActiveRecord::Base
self.abstract_class = true
include MyModule
end
Edit: Several months down the road in a large project, I have realized its better to have every model inherit from a new base model class, as Mori explains. The problem with including modules directly into ActiveRecord::Base is this can interfere with third-party code that also relies on ActiveRecord. It is just better not to monkey-patch when you don't have to. In this case, creating a new base class can end up being simpler in the long run.
Another way is make your own base class by inheriting from ActiveRecord::Base and then letting your models inherit from that base class. This has the advantage of making it clear that your models aren't running on vanilla ActiveRecord:
class MyBase < ActiveRecord::Base
self.abstract_class = true
def self.a_class_method
end
def an_instance_method
end
end
class Foo < MyBase
end
Foo.a_class_method
Foo.new.an_instance_method
reffering with Mori's answer...you can do something like:-
Module ActiveRecordUtilities
class MyBase < ActiveRecord::Base
self.abstract_class = true
def self.a_class_method
end
def an_instance_method
end
end
end##class ends
end##module ends
and can use it ...suppose in user.rb
include ActiveRecordUtilities::MyBase
User.a_class_method
#user.instance_method
============================OR====================
module MyUtils
def do_something_funky
# Some exciting code
end
end
class Account < ActiveRecord::Base
belongs_to :person, :extend => MyUtils
end
And then call it like this:
#account = Account.first
#account.person.do_something_funky
As an example:
module ModelHelper
def self.special_function(some_parameter)
do_some_special_thing
end
end
class Student < ActiveRecord::Base
def to_special
ModelHelper.special_function(a_variable_of_here)
end
end
class Teacher < ActiveRecord::Base
def to_special
ModelHelper.special_function(another_variable_of_here)
end
end
Where do I put model_helper.rb?
I generally make a file in lib and include it. Something like lib/special_model.rb:
module SpecialModel
included do
def to_special
do_some_special_thing
end
end
end
Then in app/models/student.rb:
class Student
include SpecialModel
end
You may also want to look at ActiveSupport::Concern for some rails help when working with modules:
http://api.rubyonrails.org/classes/ActiveSupport/Concern.html
Right now my classes are look like this.
class BalanceName < ActiveRecord
def before_validation
set_blank_attributes_to_nil(#attributes)
end
end
class Balance < ActiveRecord
def before_validation
set_blank_attributes_to_nil(#attributes)
end
end
I want to inherite activer record into one class and than want to inherite that class into other classes like.
I want something like this.
class CommonActiveRecord < ActiveRecord::Base
def before_validation
set_blank_attributes_to_nil(#attributes)
end
end
class BalanceName < CommonActiveRecord
def before_validation
super
end
end
class Balance < CommonActiveRecord
def before_validation
super
end
end
You can do exactly as you have done except you do not need to redefine the before_validation methods in your subclasses (though I guess these may be here prior to being filled with more specific validation).
You will also need to indicate to rails that your CommonActiveRecord class is abstract and therefore is not persisted by adding:
class CommonActiveRecord < ActiveRecord::Base
self.abstract_class = true
end
You can create module (e.g. lib/common_active_record.rb):
module CommonActiveRecord
def before_validation
set_blank_attributes_to_nil(#attributes)
end
end
And then in your model simply include it:
class BalanceName < ActiveRecord::Base
include CommonActiveRecord
end
class Balance < ActiveRecord::Base
include CommonActiveRecord
end