We have custom model. It is working without database and includes some mixins from active record:
class Node
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :title, :content
validates_presence_of :title, :content
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
def persisted?
false
end
def save
# we want to run validations here
end
end
Through googling got that it is possible to use #object.validate, but it complains about not having such method.
Help, please.
You are correct, .validate seems to be undefined.
#object.valid? should do the job for what you want.
Keep in mind, this returns a boolean value which you can use to control conditional behaviour based on your requirements.
Related
How to validate specific attribute using ActiveModel::EachValidator.
I have written the below snippet of code. This validation will not call on saving or validating object.
class EmailValidator < ActiveModel::EachValidator
def validate_each(record,attribute,value)
# Logic to check email is valid or not
end
end
This will work with rails 3.
You can put your EmailValidator class inside the models/concerns directory. Then inside your model you can validate the email attribute using the example below.
class User < ApplicationRecord
validates :email, presence: true, email: true
end
Rails will look for the EmailValidatorclass within the scope when it encounters email: true then validate the attribute using the validate_each method.
A simple base class that can be used along with ActiveModel::Validations::ClassMethods#validates_with
class User
include ActiveModel::Validations
validates_with EmailValidator
end
class EmailValidator < ActiveModel::Validator
def validate(record)
# Logic to check email is valid or not
record.errors.add :email, "This is some complex validation"
end
end
Any class that inherits from ActiveModel::Validator must implement a method called validate which accepts a record.
To cause a validation error, you must add to the record's errors directly from within the validators message.
For more details you can check here.
If you are looking for only email validation then you can try this.
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attr_name, value)
unless value =~ /^([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
record.errors.add(attr_name, :email, options.merge(:value => value))
end
end
end
Need idea how to refactor the code:
attr_accessor :product
attr_reader :name, :mark
def name=(value)
super unless product.present?
end
def mark=(value)
super unless product.present?
end
... and a whole bunch of method of sorts.
The idea is simple - to deny assigning values if a product is already set. But the code above isn't DRY at all.
Any ideas?
attr_accessor :product
attr_reader :name, :mark
["name", "mark"].each do |method|
define_method("#{method}=") do |value|
super(value) unless product.present?
end
end
You can create a method such as attr_* to handle this. This is done by reopening the Class class and defining the following method.
def attr_validator(*args)
#We simply iterate through each passed in argument...
args.each do |arg|
# Create an attr_reader
self.class_eval("
def #{arg};
##{arg};
end
")
# Here we hardcode "product" since this every attribute
# checks itself against this object
self.class_eval("
def #{arg}=(val);
super unless product.present?;
end
")
end
end
This way, we get rid of the redundancy of appending attributes to the pre-processor method (in my case, attr_validator) and also a different array for metaprogramming purposes.
This can be used thus...
attr_accessor :product
attr_validator :name, :mark, :price, :stock # ...and so on
I've set up an ActiveModel class in my Rails app like this:
class MyThingy
extend ActiveModel::Naming
extend ActiveModel::Translation
include ActiveModel::Validations
include ActiveModel::Conversion
attr_accessor :username, :favorite_color, :stuff
def initialize(params)
#Set up stuff
end
end
I really want to be able to do this:
thingy = MyThingy.new(params)
thingy.update_attributes(:favorite_color => :red, :stuff => 'other stuff')
I could just write update_attributes on my own, but I have a feeling it exists somewhere. Does it?
No, but there's common pattern for this case:
class Customer
include ActiveModel::MassAssignmentSecurity
attr_accessor :name, :credit_rating
attr_accessible :name
attr_accessible :name, :credit_rating, :as => :admin
def assign_attributes(values, options = {})
sanitize_for_mass_assignment(values, options[:as]).each do |k, v|
send("#{k}=", v)
end
end
end
It's from here. See the link for examples.
If you find yourself repeating this approach often, you can extract this method into a separate module and include include it on demand.
It looks like they pulled it out of active record and moved it to active model in Rails 5.
http://api.rubyonrails.org/classes/ActiveModel/AttributeAssignment.html#method-i-assign_attributes
You should be able to include the module:
include ActiveModel::AttributeAssignment
I'm trying to move my validations to a module. I want to extend an existing object an aribtrary set of validators, but I'm struggling to figure out how to get them to execute. Any ideas?
Active Record Object
class Test < ActiveRecord::Base
has_many :resources
end
Validator
module TestValidator
extend ActiveSupport::Concern
included do
validates_associated :resources
end
end
Console
t = Test.new
t.extend TestValidator
t.valid?
# true ... should be false
I hope this can help
6.1 Custom Validators
Custom validators are classes that extend ActiveModel::Validator. These classes must implement a validate method which takes a record as an argument and performs the validation on it. The custom validator is called using the validates_with method.
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
The easiest way to add custom validators for validating individual attributes is with the convenient ActiveModel::EachValidator. In this case, the custom validator class must implement a validate_each method which takes three arguments: record, attribute and value which correspond to the instance, the attribute to be validated and the value of the attribute in the passed instance.
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value =~ /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
record.errors[attribute] << (options[:message] || "is not an email")
end
end
end
class Person < ActiveRecord::Base
validates :email, :presence => true, :email => true
end
As shown in the example, you can also combine standard validations with your own custom validators.
https://guides.rubyonrails.org/active_record_validations.html#custom-validators
Is there any way I can validate a single attribute in ActiveRecord?
Something like:
ac_object.valid?(attribute_name)
You can implement your own method in your model. Something like this
def valid_attribute?(attribute_name)
self.valid?
self.errors[attribute_name].blank?
end
Or add it to ActiveRecord::Base
Sometimes there are validations that are quite expensive (e.g. validations that need to perform database queries). In that case you need to avoid using valid? because it simply does a lot more than you need.
There is an alternative solution. You can use the validators_on method of ActiveModel::Validations.
validators_on(*attributes) public
List all validators that are being used to validate a specific
attribute.
according to which you can manually validate for the attributes you want
e.g. we only want to validate the title of Post:
class Post < ActiveRecord::Base
validates :body, caps_off: true
validates :body, no_swearing: true
validates :body, spell_check_ok: true
validates presence_of: :title
validates length_of: :title, minimum: 30
end
Where no_swearing and spell_check_ok are complex methods that are extremely expensive.
We can do the following:
def validate_title(a_title)
Post.validators_on(:title).each do |validator|
validator.validate_each(self, :title, a_title)
end
end
which will validate only the title attribute without invoking any other validations.
p = Post.new
p.validate_title("")
p.errors.messages
#=> {:title => ["title can not be empty"]
note
I am not completely confident that we are supposed to use validators_on safely so I would consider handling an exception in a sane way in validates_title.
I wound up building on #xlembouras's answer and added this method to my ApplicationRecord:
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
def valid_attributes?(*attributes)
attributes.each do |attribute|
self.class.validators_on(attribute).each do |validator|
validator.validate_each(self, attribute, send(attribute))
end
end
errors.none?
end
end
Then I can do stuff like this in a controller:
if #post.valid_attributes?(:title, :date)
render :post_preview
else
render :new
end
Building on #coreyward's answer, I also added a validate_attributes! method:
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
def valid_attributes?(*attributes)
attributes.each do |attribute|
self.class.validators_on(attribute).each do |validator|
validator.validate_each(self, attribute, send(attribute))
end
end
errors.none?
end
def validate_attributes!(*attributes)
valid_attributes?(*attributes) || raise(ActiveModel::ValidationError.new(self))
end
end