Tuesday, March 12, 2013

Mixin Validations

This post is one in a series on how I achieved composite classes and multi-table inheritance. For background, see Composite classes and multi-table inheritance. The last post in the series was First Cut at ActsAsEvent Module.

For proper attribution, I'll note that a whole lot of the code in this post came directly from or was inspired by Multiple Table Inheritance with ActiveRecord.

In this post, I want to show how a validation defined in the ActsAsEvent module can be enforced in any class that includes ActsAsEvent.

Delegate Validation to EventBase

In my EventBase class definition, I have a validation for description:
class EventBase < ActiveRecord::Base
  
  # ==========================================================================================
  #  Validations
  # ==========================================================================================  
    validates_presence_of :description

  # ==========================================================================================
  #  Associations
  # ==========================================================================================  
    belongs_to  :recur_rule, 
                dependent: :destroy  
    belongs_to  :eventable, polymorphic: true, dependent: :destroy
                
end

Getting classes that act_as_event to respect this validation is actually pretty straight forward. I updated my ActsAsEvent module like this:
module ActsAsEvent

  def acts_as_event
    class_eval do
      include InstanceMethods
      extend ClassMethods
      has_one :event_base, as: :eventable, :autosave => true, :dependent => :destroy
      validate :validate_event_base_properties
      alias_method_chain :event_base, :build
    end
  end
  
  module InstanceMethods
    def event_base_with_build
      event_base_without_build || build_event_base
    end

    def validate_event_base_properties
      unless event_base.valid?
        event_base.errors.each do |attr, message|
          errors.add(attr, message)
        end
      end
    end
  end # InstanceMethods

  module ClassMethods
  end # ClassMethods
    
end

ActiveRecord::Base.extend ActsAsEvent

Within the class_eval block, I added validate :validate_event_base_properties. This causes validate_event_base_properties to be called during an attempt to save. Then, I added the validate_event_base_properties instance method. Now, when the composing class (Event or Trip) tries to save, the EventBase validations are called (and enforced).

Running the Test

So, I have a test from my previous post that failed:
require 'spec_helper'

describe Trip do
  describe "responds to" do
  end
  
  describe "functions properly" do
    it "  - saves EventBase correctly" do
      tcount_before = Trip.find(:all).count
      ebcount_before = EventBase.find(:all).count
      @t = Trip.create
      tid = @t.id
      @t.respond_to?("event_base").should be_true
      @t.save.should be_false
      @t.event_base.description = "test description"
      @t.save.should be_true
      @dbt = Trip.find(@t.id)
      @dbt.event_base.description.should == "test description"
      Trip.find(:all).count.should == tcount_before + 1
      EventBase.find(:all).count.should == ebcount_before + 1
      @dbt.destroy
      Trip.find(:all).count.should == tcount_before
      EventBase.find(:all).count.should == ebcount_before
    end
  end
end

Now, when I run:
$ rspec trip_spec.rb --format documentation

I get:
Trip
  functions properly
    - saves EventBase correctly

Finished in 0.48657 seconds
1 example, 0 failures

Success! @t.save.should be_false now passes because the save fails (because description is not set). Then, when description is set, @t.save.should be_true now passes.

Next:



No comments:

Post a Comment