Reputation: 102
In my pursuit to add some dynamic policy logic to my ActiveRecord models I've attempted to create a way to add instance-level validations. Has anyone had experience with this? The stuff I've searched for has been less than helpful. Here is the solution I came up with. Please critique.
# This extension can be used to create instance-level validations. For example
# if you have an instance of 'user' you can do something like the following:
# @example:
# user.custom_validation ->(scope) {
# if scope.bad_logins >= scope.account.max_bad_logins
# scope.errors.add :bad_logins, "too many bad logins for your account policy"
# end
# }
# user.account.max_bad_logins = 5
# user.bad_logins = 5
# user.valid? => false
#
module ActiveRecordExtension
module CustomValidation
def self.included(base)
base.class_eval do
attr_accessor :custom_validation
validate :run_custom_validation
send :include, InstanceMethods
end
end
module InstanceMethods
def run_custom_validation
if custom_validation
custom_validation.call(self)
else
true
end
end
end
end
end
ActiveRecord::Base.send :include, ActiveRecordExtension::CustomValidation
Upvotes: 2
Views: 512
Reputation: 62668
This is a violation of separation of concerns. You're moving model validation logic into controller code. You really don't want your controllers to know about what makes a model valid - they should simply pass data to the model and get a valid-or-not response back. If you have a validator that should only be run on some instances of a model, then you can scope those validations to be run when certain conditions are set.
class User
attr_accessor :enforce_login_limits
validate :if => :enforce_login_limits do |user|
if user.bad_logins >= user.account.max_bad_logins
user.errors.add :bad_logins, "too many bad logins for your account policy"
end
end
end
# Controller
user.enforce_login_limits = true
user.bad_logins = 10
user.valid? # => false
Or, just attach custom validators with ActiveModel's existing #validates_with
mechanism:
# Controller/service/whatever
@user.validates_with Validators::BadLoginValidator
# lib/validators/bad_login_validator.rb
class Validators::BadLoginValidator < ActiveModel::Validator
def validate(user)
if user.bad_logins && user.bad_logins >= user.account.max_bad_logins
user.errors.add :bad_logins, "too many bad logins for your account policy"
end
end
end
Upvotes: 2