The Artima Developer Community
Sponsored Link

Ruby Buzz Forum
ActiveRecord Sugar [alias_column]

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
Obie Fernandez

Posts: 608
Nickname: obie
Registered: Aug, 2005

Obie Fernandez is a Technologist for ThoughtWorks
ActiveRecord Sugar [alias_column] Posted: Nov 28, 2005 7:22 PM
Reply to this message Reply

This post originated from an RSS feed registered with Ruby Buzz by Obie Fernandez.
Original Post: ActiveRecord Sugar [alias_column]
Feed Title: Obie On Rails (Has It Been 9 Years Already?)
Feed URL: http://jroller.com/obie/feed/entries/rss
Feed Description: Obie Fernandez talks about life as a technologist, mostly as ramblings about software development and consulting. Nowadays it's pretty much all about Ruby and Ruby on Rails.
Latest Ruby Buzz Posts
Latest Ruby Buzz Posts by Obie Fernandez
Latest Posts From Obie On Rails (Has It Been 9 Years Already?)

Advertisement

I was stirring up some ActiveRecord magic today using acts_as_nested_set and single-table inheritance and I found myself wanting an easy way to alias an ActiveRecord property. Rails, in case you haven't heard, automatically adds columns from the database table backing your ActiveRecord model class as property accessors. But when you're using single-table inheritance, there are probably going to be cases where you would do well to put a more semantically meaningful property accessor in place.

Confused? Here's the code...

First up, this is the migration that produced an outline_nodes table in my database:

class AddOutlineNodes < ActiveRecord::Migration
  def self.up
    create_table "outline_nodes", :force => true do |table|
      table.column "type", :string
      table.column "raw_text", :text
      table.column "html", :text
      table.column "parent_id", :integer
      table.column "lft", :integer
      table.column "rgt", :integer
    end
  end

  def self.down
    drop_table "outline_nodes"
  end
end

Then our ActiveRecord models (these would normally be in separate files named accordingly):

class OutlineNode < ActiveRecord::Base
  acts_as_nested_set
end

class Book < OutlineNode; end

class Chapter< OutlineNode; end

As you can see in the migration code, my OutlineNode class has a raw_text column which will be exposed as a property of OutlineNode instances, including its subclasses (thank you ActiveRecord single-table-inheritance).

The following unit test passes just fine.

class OutlineNodeTest < Test::Unit::TestCase
  fixtures :outline_nodes

  def test_outline_nodes
    book = outline_nodes(:the_book)
    assert_kind_of OutlineNode, book
    assert_kind_of Book, book
    assert_equal "Title of the Book", book.raw_text

    chapter = outline_nodes(:first_chapter)
    assert_kind_of Chapter, chapter
    assert_equal "Chapter 1", chapter.raw_text
  end
end

...and here are the fixtures in outline_nodes.yml

the_book:
  id: 1
  type: Book
  raw_text: "Title of the Book"
first_chapter:
  id: 2
  type: Chapter
  raw_text: "Chapter 1"

What I really want is to be able to refer to this property as book.title instead of book.raw_text, because in this particular case title is much more semantically meaningful and correct. The straightforward way is pretty easy to accomplish.

class Chapter < OutlineNode
  def title
    raw_text
  end
end

You might stop there, but I'm not quite happy with this solution. I'd much rather do a macro-style declaration at the top of the class.

class Chapter < OutlineNode
  alias_column :title, :raw_text
end

class Book < OutlineNode
  alias_column :title, :raw_text
end

So, let's start by writing another test and we'll see exactly how easy it is to extend ActiveRecord to do your bidding. Notice that in the following test I'm requiring 'extensions', which refers to a new extensions.rb file that I created in the lib directory of my rails app.

require 'extensions'

class ExtensionsTest < Test::Unit::TestCase
  fixtures :outline_nodes

  def test_alias_column
    assert_equal "Title of the Book", outline_nodes(:the_book).title
  end
end

And of course it fails...

>testrb test\unit\extensions_test.rb
Loaded suite extensions_test.rb
Started
E
Finished in 0.141 seconds.

  1) Error:
test_alias_column(ExtensionsTest):
NoMethodError: undefined method `alias_column' for Book:Class
    ../vendor/rails/activerecord/lib/active_record/base.rb:1005:in `method_missing'
    ../app/models/book.rb:2

1 tests, 0 assertions, 0 failures, 1 errors

We add the new macro in the extensions.rb file.

module ActiveRecord
  class Base
    class << self
      def alias_column(logical_name, column_name)
        define_method logical_name do
          @attributes[column_name.to_s]
        end
      end
    end
  end
end

Ah, sweet success... our test passes.

>testrb test\unit\extensions_test.rb
Loaded suite extensions_test.rb
Started
.
Finished in 0.125 seconds.

1 tests, 1 assertions, 0 failures, 0 errors

Now I can refactor my object model a bit and eventually end up with the following class definitions...

class OutlineNode < ActiveRecord::Base
  acts_as_nested_set
end

class Book < OutlineNode
  alias_column :title, :raw_text
end

class Heading < OutlineNode
  alias_column :title, :raw_text
end

class Chapter < Heading
  # the alias_column setting is inherited
end

class Paragraph < OutlineNode
  alias_column :body, :raw_text
end

How it works

I won't go into specifics of this implementation code, just like I didn't give a bunch of details about how Rails unit test fixtures work, etc. However, I will point you in the right direction by telling you that Ruby lets you open up specific instances of previously defined classes and change their implementation. In fact, it is considered correct style in Ruby to ExtendRatherThanWrap. What I've done is open up the specific singleton instance of the ActiveRecord::Base class and add a method to it (that when invoked, adds a method to its instances). Bingo -- a brand-spanking-new macro-style method available on ActiveRecord.

Wicked, huh? By the way, once you get used to coding this way, I guarantee you will really begin to wonder how you ever enjoyed Java programming at all. Especially if you get hooked on learning advanced Ruby techniques like metaclass programming via the zany writing of why_ the lucky stiff? ;)

Read: ActiveRecord Sugar [alias_column]

Topic: Graceful degredation in Rails Previous Topic   Next Topic Topic: My mind and continuations, callcc seen graphically

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use