Sometimes I need not to simply validate smth in my app, but also alter it before/after validating.
I.e.
class Channel < ActiveRecord::Base
validate :validate_url
...
private
def validate_url
url = "rtmp://#{url}" if server_url[0..6] != "rtmp://" #alter cause need this prefix
unless /rtmp:\/\/[a-z0-9]{1,3}\.pscp\.tv:80\/[a-z0-9]\/[a-z0-9]{1,3}\//.match url
errors.add(:url, "...")
end
end
end
or smth like this
class Channel < ActiveRecord::Base
validate :validate_restreams
...
private
def validate_restreams
self.left_restreams = self.left_restreams - self.restreams #to be sure there's no intersections
end
end
But I feel it's not a right place for such things, so I need to know what's the way to do it right?
You can create a custom validator for a rails model. You should make a class, inherit it from ActiveModel::Validator, and define a validate(record) method there, which will add errors to the record. For example:
This is your validator class:
class MyValidator < ActiveModel::Validator
def validate(record)
unless url_valid?(record[:url])
record.errors.add(:url, 'is invalid')
end
end
private
def url_valid?(url)
# validate url and return bool
end
end
And now simply add this to the model:
validates_with MyValidator
Related
I faced with curious fact (for me) about using each validators. For example we have a some custom each validator and some model:
class Thing < ApplicationRecord
validates :field, custom: true
end
class CustomValidator < ActiveModel::EachValidator
def validate_each(record, _attribute, _value)
#record = record
end
end
I've found out that an instance of CustomValidator class will be created once when we call Thing model for the first time. It means that we will have same validator object for every Thing instance. And my question is: How do you think, can we use instance variables inside validators like this or not... because looks like validator object will be created only once, and, for example, if we will call Thing.first.valid? and then Thing.last.valid? the #record will have the same value before we reassign it.
Or maybe a separate validator object will be created for each client?
I just worry is it possible that we can face races around #record variable when several widgets will be validated at the same time?
Thnx
I found another way, just define the class with a class method "validate" and an instance method "validate", and use this class method like a block. So, we can move huge validation in class, use it shortly in model Thing, and use instance variable between validator methods
class Thing < ApplicationRecord
validate MyCustomValidator
end
class MyCustomValidator
def self.validate(record)
new(record).validate
end
def initialize(record)
#record = record
end
def validate
# some payload with #record
validate_breakdowns
validate_indicators
end
private
attr_reader :record
def validate_breakdowns
# some payload with #record
end
def validate_indicators
# some payload with #record
end
end
I have a few classes deriving from A.
A does some validation
In the specific case of class B that inherits from A, I'd like to skip the validation.
I'm using active interaction btw
class A < ActiveInteraction::Base
string :s
validate :valid
private
def vaild
#raise something unless s equals "banana"
end
end
class B < A
#do something here to skip A's validation??
def execute
#super cool logic
end
end
Since this is a subclass, you can override the valid method to do something else, or even nothing:
class B < A
def execute
#super cool logic
end
private
def valid
# Do nothing
end
end
You could add a callback for selectively skipping the validation:
class A < ActiveInteraction::Base
string :s
validate :valid, unless: :skip_validation
private
def vaild
# raise something unless s equals "banana"
end
def skip_validation
false
end
end
class B < A
def execute
#super cool logic
end
private
def skip_validation
true # or more fancy logic
end
end
I've got a sidekiq job that needs to be run after the commit, but only in some situations and not all, in order to avoid a common race condition.
For example, the below after_commit will always fire but the code inside will only execute if the flag is true (previously set in the verify method).
class User < ActiveRecord::Base
...
after_commit do |user|
if #enqueue_some_job
SomeJob.new(user).enqueue
#enqueue_some_job = nil
end
end
def verify
#enqueue_some_job = ...
...
save!
end
end
The code is a bit ugly. I'd much rather be able to somehow wrap the callback inline like this:
class User < ActiveRecord::Base
def verify
if ...
run_after_commit do |user|
SomeJob.new(user).enqueue
end
end
...
save!
end
end
Does anything built into Rails exist to support a syntax like this (that doesn't rely on setting a temporary instance variable)? Or do any libraries exist that extend Rails to add a syntax like this?
Found a solution using a via a concern. The snippet gets reused enough that it is probably a better option to abstract the instance variable and form a reusable pattern. It doesn't handle returns (not sure which are supported via after_commit since no transaction is present to roll back.
app/models/concerns/callbackable.rb
module Callbackable
extend ActiveSupport::Concern
included do
after_commit do |resource|
if #_execute_after_commit
#_execute_after_commit.each do |callback|
callback.call(resource)
end
#_execute_after_commit = nil
end
end
end
def execute_after_commit(&callback)
if callback
#_execute_after_commit ||= []
#_execute_after_commit << callback
end
end
end
app/models/user.rb
class User < ActiveRecord::Base
include Callbackable
def verify
if ...
execute_after_commit do |user|
SomeJob.new(user).enqueue
end
end
...
save!
end
end
You can use a method name instead of a block when declaring callbacks:
class User < ActiveRecord::Base
after_commit :do_something!
def do_something!
end
end
To set a condition on the callback you can use the if and unless options. Note that these are just hash options - not keywords.
You can use a method name or a lambda:
class User < ActiveRecord::Base
after_commit :do_something!, if: -> { self.some_value > 2 }
after_commit :do_something!, unless: :something?
def do_something!
end
def something?
true || false
end
end
Assuming that you need to verify a user after create.
after_commit :run_sidekiq_job, on: :create
after_commit :run_sidekiq_job, on: [:create, :update] // if you want on update as well.
This will ensure that your job will run only after a commit to db.
Then define your job that has to be performed.
def run_sidekiq_job
---------------
---------------
end
Hope it helps you :)
I would like to use an after_save callback to set the updated_by column to the current_user. But the current_user isn't available in the model. How should I do this?
You need to handle it in the controller. First execute the save on the model, then if successful update the record field.
Example
class MyController < ActionController::Base
def index
if record.save
record.update_attribute :updated_by, current_user.id
end
end
end
Another alternative (I prefer this one) is to create a custom method in your model that wraps the logic. For example
class Record < ActiveRecord::Base
def save_by(user)
self.updated_by = user.id
self.save
end
end
class MyController < ActionController::Base
def index
...
record.save_by(current_user)
end
end
I have implemented this monkeypatch based on Simone Carletti's advice, as far as I could tell touch only does timestamps, not the users id. Is there anything wrong with this? This is designed to work with a devise current_user.
class ActiveRecord::Base
def save_with_user(user)
self.updated_by_user = user unless user.blank?
save
end
def update_attributes_with_user(attributes, user)
self.updated_by_user = user unless user.blank?
update_attributes(attributes)
end
end
And then the create and update methods call these like so:
#foo.save_with_user(current_user)
#foo.update_attributes_with_user(params[:foo], current_user)
I have this code in my every model.
Class people
def before_validation
#attributes.each do |key,value|
self[key] = nil if value.blank?
end
end
end
Now i want to put my loop in separate module. Like
Module test
def before_validation
#attributes.each do |key,value|
self[key] = nil if value.blank?
end
end
end
And i want to call this before_validation this way
Class people
include test
def before_validation
super
.....Here is my other logic part.....
end
end
Are there any way to do it like that in rails??
You can setup multiple methods to be called by the before_validation callback. So instead of straight up defining the before_validation, you can pass the methods you want to get called before validation.
module Test
def some_test_before_validaiton_method
# do something
end
end
class People < ActiveRecord::Base
include Test
def people_before_validation_foo
#do something else
end
before_validation :some_test_before_validation_method
before_validation :people_before_validaiton_foo
end
You can read more about callbacks here: http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html