Saturday, December 23, 2006

Contextual Decoration of a Ruby object

One of my current work problems is that I have an Account class that needs optional decoration if it has been initialized in the context of an Import. I was kicking around an Observer or AspectR but the latter library wreaks of Java, and the former just isn't as seamless as I'd like. Also, I need to be mindful of multiple threads. Below is the solution I came up with, without the problem specific code.

The situation is actually more complicated than what I am modeling below.


class Import
attr_reader :start_at, :stop_at
class << self
def run!(&block)
Thread.new do
Thread.current['import_log'] = import_log = self.new
import_log.start!
yield block if block
import_log.stop!
end
end
end
[:start, :stop].eacha do |m|
define_method("#{m}!") do
instance_variable_set("@#{m}_at", Time.now)
end
end

def log_change(message)
... magical stuff happens here
end
end

class Account < ActiveRecord::Base
attr_reader :import_log
def initialize
@import_log = Thread.current['import_log']
super
end

def after_save
import_log.log_change("Saved Account with ID=#{self.id}") if import_log
end
end


What is actually happening is that I have numerous objects that use the "is_a_import_model" interface. If a class "is_a_import_model" then its initialize method needs to be overwritten to account for the import_log. There is also a StreamParser that is reading an XML document to process the lots of imports. If the StreamParser is created in the context of an Import.run!, it needs to log and rescue all exceptions, otherwise, it re-raises those exceptions.

Tuesday, December 19, 2006

Sandman and Desire

This week, I received an Amazon gift certificate. I spent quite awhile deliberating and thinking about what I wanted. Should I get Zelda for the Wii? Or should I get a couple of game books that I want? What about my wishlist?

I looked on my wishlist and started investigating what Dungeons and Dragons books I wanted. I looked at reviews and was close to ordering when I decided to give my wishlist a final review. There were a few books that I think would be truly interesting (Prime Obsession by John Derbyshire, The Master of Go by Yasunari Kawabata, A History of the Ancient Near East by Marc Van de Mieroop, etc). There were a couple of D&D books that I had earmarked, as well as a couple of CDs. And then there are the Sandman volumes (by Neil Gaiman), the perpetual inhabitant of my wishlist.

Four years ago, I borrowed the 10 volume Sandman series from a friend. I started reading it, and was immediately drawn in by Neil Gaiman's prose. I was hungry, and devoured the volumes in rapid succession, feeling a bit of remorse that I wasn't savoring this feast. As the series drew to a close, I knew that I wanted to own a copy of this particular work of art. The story so seamlessly dovetails with the human mythos that I can't help but feel like it is a part of me.

What is even more odd, is that during college, the Sandman series was being released, and several of my comic-book geek friends were all eagerly awaiting the next issue. They would talk about it, discuss and dissect and absorb each issue. I sort of wonder how I missed out on this phenomenon (ah yes Magic the Gathering and D&D). In any case, having read the 10 volume series I knew that I wanted to own it so I could read and re-read it.

As the years have passed, I've always thought about getting the whole shebang, but could never quite rationalize spending $150 in one pop. I also never felt quite right about just getting one book at a time (there is a part of me that figures that I'll stop at 6 books or something silly).

So with the above in mind I decided yet again to forgo getting the Sandman series, but I noticed something strange. Volumes 1 & 2 were not on the list. Had I missed them? No one ever orders anything from my wishlist, so I went through the steps of adding them back on, but lo, they refused to be added to the wishlist. Someone had in fact gotten me two of them.

So I cleared my shopping cart of some D&D books, and added the remaining 8 books. The gift certificate won't cover it all, but thats a small price to pay for literary genius.

Thursday, December 14, 2006

Lightsky Rocks!!!

Today was our Lightsky Christmas Break. We had snacks, drinks, and were given our Christmas present. Each employee received a Wii (actually 6 Wii were given out and there were three IOUs). The gift was most certainly a surprise. But what really impressed me was the amount of time and effort that went into these gifts. There were several early mornings, frequent visits to stores over lunch or on the way home, conscription of spouses to help get them. In all, a real dedication and desire to make sure that everyone got one. So while my place of employment rocks, its really my co-workers and the owners that rock.

I am stunned by this gracious gift of time and money.

Tuesday, December 12, 2006

Protected Find

One of the greatest professional compliments I've ever received was regarding a system that I designed and developed. The purpose was to provide an interface for numerous reporting systems to funnel reporting data into a unified location. I designed the system so that all data was retrieved via task specific finders (i.e. :find_agreement_number, :find_current_document_type). As a result, if the database changed, the methods could still be used (albeit with a tweak to the internal logic). Since I was working in a compiled environment where changing a table meant recompiling the functions that directly accessed the table, the need for public finders was required.

The compliment I received was that 6 months after the design was done, the vendor's system that we were integrating with required a major upgrade and changes. And in order to make my system work, they just pointed the finders to a different table, and presto things were working again.

Now what does this have to do with Ruby on Rails? Today I was having a conversation with a co-worker regarding find. After some discussion, I came out and said I really wish :find were a protected method. Find is a very powerful method that allows you to fully access all data from anywhere. This power exposes your your view, controller, and other models to the risk that your model will change. To reduce this risk, I feel it is important to create specific finders in your model and test them. Also, if you use :find_by_name (a free method on any table that has a :name column), you should also write a test for this (at a minimum making sure that your model responds to that particular find).

The entire goal of creating specific finders is to make the maintenance of a project easier. In addition, it is much easier, in Rails, to test models than controllers or views.

Saturday, December 09, 2006

Pimping a Controller

I've recently started working on a REST based rails project. I've been thinking about the Rails model associations, and in particular has_many :through, which is used to help expose the join between two models. Given the following three models:



class User < ActiveRecord::Base
has_many :permissions
has_many :roles, :through => :permissions
end
class Permission < ActiveRecord::Base
belongs_to :role
belongs_to :user
end
class Role < ActiveRecord::Base
has_many :permissions
has_many :users, :through => :permissions
end



I want to be able to look at permissions in a few different ways (and roles and users for that matter). Now, I can modify my PermissionsController to set the object that will be used for all find calls. By default, the PermissionsController will use Permission.find, but if I swap the Permission object for @finder_object, I can set that @finder_object before_filter. See below:



class PermissionsController < ActiveRecord::Base
before_filter :determine_finder_object

def index
@permissions = @finder_object.find(:all)
# respond to stuff, though you might want to use different templates
# based on the finder object
end

def determine_finder_object
if params[:user_id]
@finder_object = User.find(params[:user_id]).permissions
elsif params[:role_id]
@finder_object = Role.find(params[:role_id]).permissions
else
@finder_object = Permission
end
end
end



Now, is this the best practice? I don't know, however, it feels like its something to explore. One noticeable caveat is that if you are using a REST-based controller, you will want to look at all of your Object.new calls and think about replacing them with @finder_object.build.

As an aside, the above controller would probably end up with several chunks of code that were:


if params[:user_id]
#do something for users
elsif params[:role_id]
#do something for roles
else
#do something in the generic
end


What can I do to encapsulate this particular logic? I dug up the code from the :respond_to controller instance method, and created a construct for:



determine do |d|
d.user_id { #do something for users}
d.role_id { #do something for roles}
end



I'm not satisfied with it, but am wondering what I might be able to do.

Wednesday, November 29, 2006

Creatively Captivated

About 1 year ago, the developers of Lightsky went to the Snakes and Rubies seminar in Chicago (Dec 3, 2005). The event really got us talking and thinking. Corporately we decided to shift from PHP to Ruby on Rails. The decision has proven to be exhilarating. We started using Subversion. We started work on a core infrastructure that has been unit tested (and is being re-factored to REST). We are able to easily re-use code that is freely available (thank heaven for well tested plugins).

I owe a lot to that trip.

$ raving_ruby_fanboy += 1

Tuesday, November 28, 2006

Peepcode's Restful Rails

I just finished watching Peepcode's Restful-Rails video. I highly recommend it. I've followed the RESTful push by the rails team, but I haven't seen the true impact. And now, having watched the video, I'm in the mood to re-factor the existing code for my project. I'm going to refrain from this refactoring because of the complications of the existing code-base. However, I'm curious to see what can come of this. The next project I work on will make use of REST.

http://peepcode.com/articles/2006/10/08/restful-rails

Monday, November 27, 2006

Loving the test dots ..................F.....................

For the past while, I've been working on the largest project my company (www.lightsky.com) has ever undertaken. The scope is rather large and requires lots of data import. To make sure that we got off on the right foot, we spent a lot of time (perhaps too much) developing our framework for what is to come. One of the issues was that we were dealing with a very abstract tool in the beginning (an authorization system).

It has taken a lot of back and forth work, and a lot of diagramming and what-if scenarios. But in the end, I really think the system that we have is rather impressive. I also thank heaven that it is being developed in Ruby (forget Rails for the moment). Prior to Ruby, I had programmed in RPG, VBA, Cool: Plex (a CA case tool), PHP (only 3 months!) and Lotus Notes. One thing that was missing from all of these was a tightly integrated unit test suite. Ruby does this, and does it rather well. I'm certain there are test suites out there, but to have it built right into the language's core functions is shear brilliance.

Now, if I write good tests (which is a challenge as well), I can rest assured that my code works how I say it should work. This allows me to more easily pass the tool off and let others poke around with it. And, if a test breaks, its something they should look into. I have never felt so liberated by applying the constraint of testing as much as I can.

Sunday, November 26, 2006

Creating a unified UI

I've been working on a site that makes use of a view mixin structure. If the view is defined in the for the controller, use it, otherwise use the default view for that action. The views are very atomized [list.rhtml, _list.rhtml, _list_search_panel.rhtml, _list_search_form.rhtml, _list_table.rhtml, _list_table_head.rhtml, _list_table_row.rhtml]. It works great, but there are limitations. Now, the system was not created by me, but had input from me, so I'm not going to bad-mouth it. But what it has gotten me thinking about is my need for an ActionHelper object.

The ActionHelper object would be responsible for knowing what the breadcrumb trail should be as well as the page title, and "tabs" that should be on the page, actions that can be taken, the current location in the menu list. As of right now, this idea is sort of floating around gathering mental momentum. The purpose of this ActionHelper object is to create the interface for building a unified UI.

BAB ruby class for OGL system

Jamis Buck's Dice class inspired me (a lot). A few weeks ago, I decided to start thinking about a role-playing session manager. I'm still thinking about it, but in the interim, I started work on a few classes, just to "feel better". Included below is a BAB class. It is released as OGC as described by the OGL. This class also has associated tests, if you are truly curious.

$ 5.bab.good
=> 5

$ 4.bab.poor # Suck it wizard
=> 2

$ 3.bab.medium + 5.bab.poor + 3.bab.poor # 3 Rogue / 5 Wizard / 3 Arcane Trickster
=> 5






class Bab
class InvalidCount < Exception # :nodoc:
end
TYPES = [:good, :medium, :poor]
attr_reader :count, :epic_count

def initialize( count )
raise InvalidCount unless count.is_a?(Integer) && count > 0
@count = count > 20 ? 20 : count
@epic_count = count > 20 ? count - 20 : 0
end

def good
self.count + epic_value
end

def medium
((self.count * 3) / 4) + epic_value
end

def poor
(self.count / 2) + epic_value
end

private
def epic_value
(self.epic_count + 1) / 2
end

end

class Integer
def bab
Bab.new(self)
end
end


The Open Game License for Items posted on this blog.

OPEN GAME LICENSE Version 1.0a

The following text is the property of Wizards of the Coast, Inc. and is Copyright 2000 Wizards of the Coast, Inc ("Wizards"). All Rights Reserved.

1. Definitions: (a)"Contributors" means the copyright and/or trademark owners who have contributed Open Game Content; (b)"Derivative Material" means copyrighted material including derivative works and translations (including into other computer languages), potation, modification, correction, addition, extension, upgrade, improvement, compilation, abridgment or other form in which an existing work may be recast, transformed or adapted; (c) "Distribute" means to reproduce, license, rent, lease, sell, broadcast, publicly display, transmit or otherwise distribute; (d)"Open Game Content" means the game mechanic and includes the methods, procedures, processes and routines to the extent such content does not embody the Product Identity and is an enhancement over the prior art and any additional content clearly identified as Open Game Content by the Contributor, and means any work covered by this License, including translations and derivative works under copyright law, but specifically excludes Product Identity. (e) "Product Identity" means product and product line names, logos and identifying marks including trade dress; artifacts; creatures characters; stories, storylines, plots, thematic elements, dialogue, incidents, language, artwork, symbols, designs, depictions, likenesses, formats, poses, concepts, themes and graphic, photographic and other visual or audio representations; names and descriptions of characters, spells, enchantments, personalities, teams, personas, likenesses and special abilities; places, locations, environments, creatures, equipment, magical or supernatural abilities or effects, logos, symbols, or graphic designs; and any other trademark or registered trademark clearly identified as Product identity by the owner of the Product Identity, and which specifically excludes the Open Game Content; (f) "Trademark" means the logos, names, mark, sign, motto, designs that are used by a Contributor to identify itself or its products or the associated products contributed to the Open Game License by the Contributor (g) "Use", "Used" or "Using" means to use, Distribute, copy, edit, format, modify, translate and otherwise create Derivative Material of Open Game Content. (h) "You" or "Your" means the licensee in terms of this agreement.

2. The License: This License applies to any Open Game Content that contains a notice indicating that the Open Game Content may only be Used under and in terms of this License. You must affix such a notice to any Open Game Content that you Use. No terms may be added to or subtracted from this License except as described by the License itself. No other terms or conditions may be applied to any Open Game Content distributed using this License.

3.Offer and Acceptance: By Using the Open Game Content You indicate Your acceptance of the terms of this License.

4. Grant and Consideration: In consideration for agreeing to use this License, the Contributors grant You a perpetual, worldwide, royalty-free, non-exclusive license with the exact terms of this License to Use, the Open Game Content.

5.Representation of Authority to Contribute: If You are contributing original material as Open Game Content, You represent that Your Contributions are Your original creation and/or You have sufficient rights to grant the rights conveyed by this License.

6.Notice of License Copyright: You must update the COPYRIGHT NOTICE portion of this License to include the exact text of the COPYRIGHT NOTICE of any Open Game Content You are copying, modifying or distributing, and You must add the title, the copyright date, and the copyright holder's name to the COPYRIGHT NOTICE of any original Open Game Content you Distribute.

7. Use of Product Identity: You agree not to Use any Product Identity, including as an indication as to compatibility, except as expressly licensed in another, independent Agreement with the owner of each element of that Product Identity. You agree not to indicate compatibility or co-adaptability with any Trademark or Registered Trademark in conjunction with a work containing Open Game Content except as expressly licensed in another, independent Agreement with the owner of such Trademark or Registered Trademark. The use of any Product Identity in Open Game Content does not constitute a challenge to the ownership of that Product Identity. The owner of any Product Identity used in Open Game Content shall retain all rights, title and interest in and to that Product Identity.

8. Identification: If you distribute Open Game Content You must clearly indicate which portions of the work that you are distributing are Open Game Content.

9. Updating the License: Wizards or its designated Agents may publish updated versions of this License. You may use any authorized version of this License to copy, modify and distribute any Open Game Content originally distributed under any version of this License.

10 Copy of this License: You MUST include a copy of this License with every copy of the Open Game Content You Distribute.

11. Use of Contributor Credits: You may not market or advertise the Open Game Content using the name of any Contributor unless You have written permission from the Contributor to do so.

12 Inability to Comply: If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Open Game Content due to statute, judicial order, or governmental regulation then You may not Use any Open Game Material so affected.

13 Termination: This License will terminate automatically if You fail to comply with all terms herein and fail to cure such breach within 30 days of becoming aware of the breach. All sublicenses shall survive the termination of this License.

14 Reformation: If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable.

15 COPYRIGHT NOTICE
Open Game License v 1.0 Copyright 2000, Wizards of the Coast, Inc.

Jeremy Friesen copyright

OPEN GAME CONTENT: Open game content will in box, as indicated below.

This is open game content

OSX and Textmate and Ruby

This morning I spent an hour reviewing Textmate's Rails and Ruby bundle. Trying to eek out any bits of efficiency that I can. I was particularly interested in the Rails > Model > Show DB Schema for Current Class command. I tried to run it, and it squacked. The process.rb file said that rubygems was missing. Odd.

So time for a little hacking. I added `echo $PATH` and it returned rather odd results (at least as I was envisioning). It had omitted /usr/local/bin, which is where my primary ruby installation can be found. I checked the Textmate documentation, and all should have been well. In my ~/.bash_profile I was prepending /usr/local/bin, but this was not registering. So the fix I settled on was modifying /etc/profile (and restarting my machine).

With the changes in place the command worked splendidly.

As if to pretend like I'll post to this thing

This afternoon and evening, I spent most of my time verifying that the internet had not in fact gone anywhere. I checked my feeds, eager to find something new to read. I ended up reading several IBM articles related to Ruby and helped a friend with his statistics. All told, a rather fruitless day.