Implementing validations in PORO

active model validations
sinatra validation
activerecord validations sinatra
ruby validations gem
rails validate params in model

I am trying to implement my own validations in Ruby for practice.

Here is a class Item that has 2 validations, which I need to implement in the BaseClass:

require_relative "base_class"

class Item < BaseClass
  attr_accessor :price, :name

  def initialize(attributes = {})
    @price = attributes[:price]
    @name  = attributes[:name]
  end

  validates_presence_of :name
  validates_numericality_of :price
end

My problem is: the validations validates_presence_of, and validates_numericality_of will be class methods. How can I access the instance object to validate the name, and price data within these class methods?

class BaseClass
  attr_accessor :errors

  def initialize
    @errors = []
  end

  def valid?
    @errors.empty?
  end

  class << self
    def validates_presence_of(attribute)
      begin
        # HERE IS THE PROBLEM, self HERE IS THE CLASS NOT THE INSTANCE!
        data = self.send(attribute)
        if data.empty?
          @errors << ["#{attribute} can't be blank"]
        end
      rescue
      end
    end

    def validates_numericality_of(attribute)
      begin
        data = self.send(attribute)
        if data.empty? || !data.integer?
          @valid = false
          @errors << ["#{attribute} must be number"]
        end
      rescue
      end
    end
  end
end

Building simpler models, using custom PORO validators, The validations of your ActiveRecord models look very convenient, because they the test will run slow and maybe the business rules that are implemented are  The validator was not initialized once per validation run, as one might expect, but only once when the class loads. So I rewrote it as a plain old Ruby object ("PORO") with a minimum of boilerplate and glue, and as far as I can tell, it works better, with less magic and less surprises:

Validate Ruby objects with Active Model Validations, In the world of Rails and Active Record, validating data and storing it in to create a subclass of ActiveModel::EachValidator and implement the  Manual validations are time consuming and not the best way to go when the Excel data sheet is complex and numeric computational formulas are involved. In this article, we will see how we can make use of VBA Macros to write validation logic to check for data correctness before saving Excel sheets and uploading to SharePoint.

If your goal is to mimic ActiveRecord, the other answers have you covered. But if you really want to focus on a simple PORO, then you might reconsider the class methods:

class Item < BaseClass
  attr_accessor :price, :name

  def initialize(attributes = {})
    @price = attributes[:price]
    @name  = attributes[:name]
  end

  # validators are defined in BaseClass and are expected to return
  # an error message if the attribute is invalid
  def valid?
    errors = [
      validates_presence_of(name),
      validates_numericality_of(price)
    ]
    errors.compact.none?
  end
end

If you need access to the errors afterwards, you'll need to store them:

class Item < BaseClass
  attr_reader :errors

  # ...

  def valid?
    @errors = {
      name: [validates_presence_of(name)].compact,
      price: [validates_numericality_of(price)].compact
    }
    @errors.values.flatten.compact.any?
  end
end

Smarter Rails Services with Active Record Modules, A service object is a simple PORO class (plain old ruby object) designed to Active Record modules will give us access to validation, serialization and not map to a database table and does not implement a #save method. Implementing validation metrics require an adequate implementation plan that provides a roadmap for the implementation. In addition to the implementation plan companies should also consider creating an adequate change management and communication plan.

I don't understand the point to implement PORO validations in Ruby. I'd do that in Rails rather than in Ruby.

So let's assume you have a Rails project. In order to mimic the Active Record validations for your PORO, you need to have also 3 things:

  1. Some kind of a save instance method within your PORO (to call the validation from).

  2. A Rails controller handling CRUD on your PORO.

  3. A Rails view with a scaffold flash messages area.

Provided all 3 these conditions I implemented the PORO validation (just for name for simplicity) this way:

require_relative "base_class"

class Item < BaseClass
  attr_accessor :price, :name

  include ActiveModel::Validations

  class MyValidator
    def initialize(attrs, record)
      @attrs = attrs
      @record = record
    end

    def validate!
      if @attrs['name'].blank?
        @record.errors[:name] << 'can\'t be blank.'
      end

      raise ActiveRecord::RecordInvalid.new(@record) unless @record.errors[:name].blank?
    end
  end

  def initialize(attributes = {})
    @price = attributes[:price]
    @name  = attributes[:name]
  end

  # your PORO save method
  def update_attributes(attrs)
    MyValidator.new(attrs, self).validate!
    #...actual update code here
    save
  end
end

In your controller you have to manually process the exception (as your PORO is outside ActiveRecord):

class PorosController < ApplicationController
  rescue_from ActiveRecord::RecordInvalid do |exception|
    redirect_to :back, alert: exception.message
  end
...
end

And in a view - just a common scaffold-generated code. Something like this (or similar):

<%= form_with(model: poro, local: true) do |form| %>
  <% if poro.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(poro.errors.count, "error") %> prohibited this poro from being saved:</h2>

      <ul>
      <% poro.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= form.label :name %>
    <%= form.text_field :name, id: :poro_name %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

That's it. Just keep it all simple.

Backbone.Validation, method validator. Lets you implement a custom function used for validation. var SomeModel = Backbone. Imagine that you have a form with a checkbox and an email field. The email field has to conform to a regex pattern, and be no longer than 250 symbols, no shorter than 5 symbols. When checkbox is…

How to Implement Custom ActiveRecord Validations, In this short piece, we are going to explore three options for implementing custom active record validations. Our example model is a simple  Implementing Validations for RESTful Services. The validation is a common requirement in all the services. We will discuss Java Validation API to add validation in our beans file. When we get a request to create a user, we should validate its content. If it is invalid, we should return a proper response. Let's see how to validate a request.

Bootstrap form validation, Choose from the browser default validation feedback, or implement custom messages with our built-in classes and starter Material styles MDB Pro component. An IQ OQ PQ Validation Protocol is a written plan stating how validation will be conducted. It details factors such as product characteristics, production equipment, test scripts and methods, test parameters and acceptance criteria, test checksheets and final approval.

[PDF] VET Assessment Validation Procedures, VET Assessment Validation Procedures pro-136. Version: Strategy (TAS) Cycle means the cycle of planning, designing, implementing and. Phasing of Validation¶. As you prepare your rollout schedule, consider implementing a phased validation system roll out. If you clearly define the scope of each phase and roll it out following a regimented schedule, you can greatly assist the market in adapting to the new system, while giving you the opportunity to improve the data quality and functionality of your system over time.

Comments
  • Be aware, @@_validators is being shared by all subclasses of BaseClass here...for instance, add puts "#{attribute} into your attributes.each loop in the validator, then create a new class Order < BaseClass; validates_presence_of :title; end and you'll see both "name" and "title" being output
  • Yeah, I guess. In ActiveModel, the _validators are part of each individual class (extracted out into a module included anyway). Going to update the answer.
  • How do I implement save method to perform validations if validate :name, presence: true is invoked in the model and skip validations if not invoked?