Tuesday, March 12, 2013

Accessing EventBase Attributes Directly

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 Mixin Validations.

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 will show how to make EventBase attributes directly accessible from any class that calls acts_as_event (in my case, Event and Trip).

The Test

So, I start out by writing the test I want to be able to pass:
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
    
    it "  - can access attributes correctly" do
      @t = Trip.create
      @t.description = "test description"
      @t.save.should be_true
      @dbt = Trip.find(@t.id)
      @dbt.description.should == "test description"
    end
  end
end
Here's what's new in the test:
  • I attempt to set the .description attribute directly on @t.
  • I attempt to save @t.
  • I retrieve the Trip record as @dbt
  • I attempt to read the .description attribute
When I run:
$ rspec trip_spec.rb --format documentation$ rspec trip_spec.rb --format documentation

I get:
Trip
  functions properly
    - saves EventBase correctly
    - can access attributes correctly (FAILED - 1)

Failures:

  1) Trip functions properly   - can access attributes correctly
     Failure/Error: @t.description = "test description"
     NoMethodError:
       undefined method `description=' for #
     # ./trip_spec.rb:39:in `block (3 levels) in '

Finished in 0.47995 seconds
2 examples, 1 failure

Oh no! The test fails. Well, yeah, of course. Because I haven't done anything yet to make the attributes directly accessible...

Adding in Method Missing

So, our clue on how to fix this lies in the error message. Specifically, it says NoMethodError: undefined method `description='

Naturally, Trip doesn't have a .description method. The .description method is defined in EventBase. So, basically, what we need to do is to send missing method calls along to EventBase.

I did this by adding the following to InstanceMethods within the ActsAsEvent module:
  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
    
    def method_missing(meth, *args, &blk)
      event_base.send(meth, *args, &blk)
    rescue NoMethodError
      super
    end

  end # InstanceMethods

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

I get:
Trip
  functions properly
    - saves EventBase correctly
    - can access attributes correctly

Finished in 0.49288 seconds
2 examples, 0 failures

Success!

No comments:

Post a Comment