$ sudo gem install rumld --included-dependencies
$ rumld --help
Monday, August 27, 2007
Ruby UML Diagrammer
Well, after a few bursts of creativity, I've finally posted the Ruby UML Diagrammer (http://rumld.rubyforge.org/)
Wednesday, August 08, 2007
Digging for method signatures
This evening I decided to write some UML for the permission sub-system that I'd been working on. I decided that I really should have it in dot format because 1) I want to learn it better, 2) its plain text and thus more "Pragmatic", 3) there are rails gems (i.e. railroad) that can generate dot format. So I wrote the dot file (with inspiration from a railroad output). During the process, I decided to include the method signatures, which definitely helps to explain what's going on.
Then, I decided that I'd really like my classes to be able to generate this UML (in particular I wanted the method signature). So with a little thinking, I started to roll my own, but it gets a bit complicated parsing the source file and determining what's going on. Fortunately, after 30 minutes, I realized that RDoc does the hard-work for me, and all I need to do is parse the html.
Enter RdocInspector. It takes a string and finds and collects the Public Class methods, Public Instance methods, and Protected Instance methods (as well as their arguements). The string is the result of File.read(some_rdoc_html_file).
My UML automation work is not yet done, but the issue of what methods are explicitly defined in a given file has been solved.
Then, I decided that I'd really like my classes to be able to generate this UML (in particular I wanted the method signature). So with a little thinking, I started to roll my own, but it gets a bit complicated parsing the source file and determining what's going on. Fortunately, after 30 minutes, I realized that RDoc does the hard-work for me, and all I need to do is parse the html.
Enter RdocInspector. It takes a string and finds and collects the Public Class methods, Public Instance methods, and Protected Instance methods (as well as their arguements). The string is the result of File.read(some_rdoc_html_file).
My UML automation work is not yet done, but the issue of what methods are explicitly defined in a given file has been solved.
Wednesday, July 18, 2007
Mocha
So my previous post talked about overriding delegation. Then I remembered, Mocha. An honest to goodness stubbing/mocking library. Rather obviously, when testing units, I really don't want to fiddle around with the interaction of objects in each and every instance.
So instead of my convoluted example of conditionally_overriding_delegation, I can do the following (but hey you may have already known this):
Even in this case, Mocha is rather useful, I don't have to worry about testing the coupling that occurs due to delegation, and can test the objects responsibilities. Of course, now its time to look more into Mocha's documentation. It looks like there is some rather amazing stuff that can be done.
So instead of my convoluted example of conditionally_overriding_delegation, I can do the following (but hey you may have already known this):
class AbstractUnit
extend Forwardable
def_delegate :foo, :name
def has_name?
!(name.nil? || name.blank?)
end
end
class AbstractUnitTest < Test::Unit::TestCase
def setup
@object = AbstractUnit.new
end
# Perhaps a reasonable test for delegation?
def test_should_delegate_name_to_foo
assert_equal @object.name, @object.foo.name
end
def test_has_name_should_be_false_if_name_is_empty
@object.stubs(:name).returns('')
assert ! @object.has_name?
end
def test_has_name_should_be_false_if_name_is_nil
@object.stubs(:name).returns(nil)
assert ! @object.has_name?
end
def test_has_name_should_be_false_if_name_is_blank
@object.stubs(:name).returns('hello world')
assert @object.has_name?
end
end
Even in this case, Mocha is rather useful, I don't have to worry about testing the coupling that occurs due to delegation, and can test the objects responsibilities. Of course, now its time to look more into Mocha's documentation. It looks like there is some rather amazing stuff that can be done.
Monday, July 16, 2007
assert_interface
I've been reading quite a bit on programming these days. And one of the things I'm liking more and more is the concept of programming for the interface.
Now I use Ruby and not Java or C# or other languages, so the idea of the interface is a bit different. I can implement them, kind of, via a module.
However, I want to make sure that my objects quack appropriately. Enter assert_interface(object, module). The object is assumed to be an
instance of a class. The module is assumed to be "well-formed" for interfaces
Now I use Ruby and not Java or C# or other languages, so the idea of the interface is a bit different. I can implement them, kind of, via a module.
However, I want to make sure that my objects quack appropriately. Enter assert_interface(object, module). The object is assumed to be an
instance of a class. The module is assumed to be "well-formed" for interfaces
module WellFormedInterface
def self.included(base)
base.send(:include, InstanceMethods)
base.extend(ClassMethods)
end
module ClassMethods
end
module InstanceMethods
end
end
class AbstractUnit
include WellFormedInterface
end
class Test::Unit::TestCase
# Given an object (i.e. Foo.new), and a module (i.e. SomethingCool)
# assert that all methods explicitly defined in the module
# are present in the object.
#
# This isn't the end all test, as you are still responsible for
# testing the implementation of the interface
def assert_interface(object, mod)
methods = {object => [], object.class => []}
mod::InstanceMethods.instance_methods(false).each do |method|
methods[object] << method unless object.respond_to?(method)
end
mod::ClassMethods.instance_methods(false).each do |method|
methods[object.class] << method unless object.class.respond_to?(method)
end
msg = []
methods.each_pair do |key, value|
msg << "Expected #{key} to respond to the following :#{value.join(' :')}" unless value.empty?
end
if msg.empty?
assert true, 'Hey, it all worked'
else
flunk msg.join('. ')
end
end
end
Thursday, July 12, 2007
Overriding def_delegator
In the project I work on, I make heavy use of the def_delegator method (yeah composition). One of the intrinsic problems of the delegation, is that my tests can get rather convoluted (and tightly coupled to implementation). I'm sure I could look into mock methods, but I needed an immediate solution.
Enter the ConditionallyOverrideDelegation module. This is a rails specific solution as it makes use of alias_method_chain.
And the obligatory tests
Enter the ConditionallyOverrideDelegation module. This is a rails specific solution as it makes use of alias_method_chain.
module ConditionallyOverrideDelegation
def self.included(base)
base.extend(Macro)
end
module Macro
# class Foo
# extend Forwardable
# def_delegator :bar, :name
# conditionally_override_delegation :name
# end
# class Bar
# def name
# 'Bar'
# end
# end
#
# @foo = Foo.new
# @foo.bar.name == @foo.name
# => true
#
# @foo.name_with_delegation_override = @foo.bar.name * 2
# @foo.bar.name == @foo.name
# => false
#
# Take a look at the tests to see how this truly behaves
#
# Really you probably only want to use this method in test
#
def conditionally_override_delegation(*targets)
targets.flatten.each do |target|
aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
src = <<-EOV
def #{aliased_target}_with_delegation_override#{punctuation}(*args)
if @#{aliased_target}_with_delegation_override.nil?
#{aliased_target}_without_delegation_override#{punctuation}(*args)
else
@#{aliased_target}_with_delegation_override
end
end
EOV
module_eval src, __FILE__, __LINE__
alias_method_chain target, :delegation_override
attr_writer "#{aliased_target}_with_delegation_override"
end
end
end
end
And the obligatory tests
require File.dirname(__FILE__) + '/../test_helper'
require 'conditionally_override_delegation'
class ConditionallyOverrideDelegationTest < Test::Unit::TestCase
class AbstractUnit
extend Forwardable
include ConditionallyOverrideDelegation
def_delegators :related_object, :to_s, :string=, :string, :string?
conditionally_override_delegation :to_s, :string
def related_object
@related_object ||= RelatedObject.new
end
end
class RelatedObject
def to_s
'Hello World'
end
attr_accessor :string
def string?
!!string
end
end
def setup
@unit = AbstractUnit.new
end
def test_delegation_is_working
assert_equal @unit.related_object.to_s, @unit.to_s
end
def test_overridding_delegation_is_working
@unit.to_s_with_delegation_override = 'Turkey Sandwich'
assert_equal 'Turkey Sandwich', @unit.to_s
end
def test_overridding_delegation_is_working_when_override_is_false
@unit.to_s_with_delegation_override = false
assert_equal false, @unit.to_s
end
def test_overridding_delegation_is_working_when_override_is_nil
@unit.to_s_with_delegation_override = nil
assert_equal @unit.related_object.to_s, @unit.to_s
end
def test_overridding_delegation_is_working_with_punctuation
@unit.related_object.string = 'Troy Mclure'
assert_equal 'Troy Mclure', @unit.string
@unit.string_with_delegation_override = 'Pancake'
assert_equal 'Pancake', @unit.string
end
end
Saturday, July 07, 2007
Of Childhood Discoveries
Recently I've been running a Star Wars Saga edition campaign for my children. They really love it (who doesn't want to be a Jedi or a space-faring princess). The Star Wars Saga rules are very slick and take a lot of the obnoxious complexities out of the d20 system. This has sparked my interest in running a sci-fi campaign. And as such, I've been trolling (in the fishing manner not the forum-posting manner) the inter-tubes for inspiration. And that is when I stumbled onto Star Frontiers.
Twenty one years ago, I played my first role-playing game. It was Star Frontiers, published by the now defunct TSR. During the first session, I played a Dralasite that made use of tangler grenades. It was a blast, and to this day, Star Frontiers holds a special spot in my heart. Apparently, there are many others that also remember the good ole' days, and are keeping the torch burning.
Its not like I've forgotten about the system, its just that its old and not the cool new system with all the bells and whistles. Its just a well written game, with an excellent underlying system that doesn't have quite the same internal harmony of a more modern gaming system. But oddly enough, I find myself intrigued and interested in running a game of it. Why? Partly because of nostalgia, and partly because the rules seem to focus more on playing the game and not tweaking yourself out for the next level (an intrinsic problem in the current manifestation of D&D).
All told, I'll probably run a Star Wars Saga edition game (its got Jedi), but maybe I won't use the Star Wars universe.
Twenty one years ago, I played my first role-playing game. It was Star Frontiers, published by the now defunct TSR. During the first session, I played a Dralasite that made use of tangler grenades. It was a blast, and to this day, Star Frontiers holds a special spot in my heart. Apparently, there are many others that also remember the good ole' days, and are keeping the torch burning.
Its not like I've forgotten about the system, its just that its old and not the cool new system with all the bells and whistles. Its just a well written game, with an excellent underlying system that doesn't have quite the same internal harmony of a more modern gaming system. But oddly enough, I find myself intrigued and interested in running a game of it. Why? Partly because of nostalgia, and partly because the rules seem to focus more on playing the game and not tweaking yourself out for the next level (an intrinsic problem in the current manifestation of D&D).
All told, I'll probably run a Star Wars Saga edition game (its got Jedi), but maybe I won't use the Star Wars universe.
Monday, May 21, 2007
Corrupt Session?
The system that I have been working on for the past while had a particularly vexing problem. Every morning, I had to restart the mongrel cluster. The reason, each morning the login process just stopped working. It was especially annoying, because I had one shot each day to attempt to fix it (to apply the fix, I had to restart the server). After a two weeks of various attempts at correcting the problem, we finally stumbled upon the problem.
I was assuming that a session would gracefully expire 12 hours after first being created. In fact, what was happening is the session would expire 12 hours after the server restarted. So once twelve hours would pass, each request would generate a new session that would in essence be immediately expired. Not exactly the behavior I was looking for.
So if you are looking for corrupt session, session corruption, or unresponsive mongrel it may just be related to the code above.
class ApplicationController < ActionController::Base
session :session_expires => 12.hours.from_now
end
I was assuming that a session would gracefully expire 12 hours after first being created. In fact, what was happening is the session would expire 12 hours after the server restarted. So once twelve hours would pass, each request would generate a new session that would in essence be immediately expired. Not exactly the behavior I was looking for.
So if you are looking for corrupt session, session corruption, or unresponsive mongrel it may just be related to the code above.
Wednesday, May 09, 2007
A "Best Migration Practice"
So today, I discovered a "good idea" for migrations.
Put your database schema changes in one migration, then your data changes in the next. This becomes self-evident when you mix the two and watch as part way through your migration, the data change fails and suddenly your tables have been altered.
Put your database schema changes in one migration, then your data changes in the next. This becomes self-evident when you mix the two and watch as part way through your migration, the data change fails and suddenly your tables have been altered.
Sunday, April 01, 2007
Complex views and conventional trickery
This past month, I've been working on a Content Management System (CMS). The CMS is comprised of many sites, each of which have many subwebs (permissioned collections), each site has many pages, each page has many content blocks. The navigation tree of a subweb is a polymorphic relation. So when generating the navigation in the CMS Admin, I use the following method:
In the above example, node the send(*node.route_for_show). I ask the node (which is either a subweb or a page to tell me how to generate the path.
So the module knows the method to generate its path, but can't actually build the URL. So it passes the information back to the helper, which can generate the path.
I don't know if this is the best way to do it, but its working for me (and I've used this pattern for rendering links for another polymorphic relation).
module PagesHelper
# Generate a link to the show page of navigation node
def link_to_node(node)
link_to( h(node.display_title), send(*node.route_for_show), :class => node.published? ? 'published' : 'unpublished' )
end
end
In the above example, node the send(*node.route_for_show). I ask the node (which is either a subweb or a page to tell me how to generate the path.
class Page < ActiveRecord::Base
def route_for_show
return :page_path, subweb, self
end
end
class Subweb < ActiveRecord::Base
def route_for_show
return :subweb_path, self
end
end
So the module knows the method to generate its path, but can't actually build the URL. So it passes the information back to the helper, which can generate the path.
I don't know if this is the best way to do it, but its working for me (and I've used this pattern for rendering links for another polymorphic relation).
Tuesday, February 20, 2007
The ever evolving test suite
I've been working on a rather http://www2.blogger.com/img/gl.link.giflarge rails project and have watched my testing style evolve. And this next step, inspired by Jay Fields has really got me excited. Take a peek.
What the above method does is allows for very "fast and loose" test definitions. For example, I can do the following:
Ultimately, I'm extremely satisfied with how this works. My functional tests can have test definitions with a prefix of the action.
I have updated my Textmate so I can Run Focused Unit Tests, as well as having test highlighted like other keywords (alias, class, require, etc.)
class Test::Unit::TestCase
class << self
def test(*names, &block)
test_name = :"test_#{names.join('_').gsub(/[^\w ]+/, '').gsub(/ +/, '_')}"
raise ArgumentError, "#{test_name} is already defined" if self.instance_methods.include?( test_name.to_s )
if block_given?
define_method( test_name, &block )
else
define_method( test_name) do
$stderr.puts "Incomplete test '#{names.join(', ')}' @ #{caller.last}"
end
end
end
end
end
What the above method does is allows for very "fast and loose" test definitions. For example, I can do the following:
class Foo < Test::Unit::TestCase
# This is a place holder test, and when ran will
# print a warning
test 'this will print a warning when the test runs'
# This is a genuine test
test 'this will assert true' do
assert true
end
# This is another genuine test, but I'm providing visually
# putting the test in another namespace
test :index => 'should render a list' do
assert_template 'list'
end
end
Ultimately, I'm extremely satisfied with how this works. My functional tests can have test definitions with a prefix of the action.
I have updated my Textmate so I can Run Focused Unit Tests, as well as having test highlighted like other keywords (alias, class, require, etc.)
Wednesday, February 07, 2007
Hpricot, screen_scrape, and horrible pages
I'm an avid Dungeons and Dragons player and have decided I'm going to make a Rails application that will help consolidate the rules for personal use.
So I decided to do a screen scrape http://www.wizards.com/default.asp?x=dnd/lists/feats . I wanted the list of feats. To get to the table, and each row of feats, here was the xPath I used:
"/html/body/table[2]/tr[2]/td[3]/table/tr[3]/td/table[4]/tr/td/table/tr"
Of particular note, I was having trouble with the tbody in xPath, so I removed those references. Let me tell you, without Firebug's copy xPath, I would've went insane getting this out.
So I ask, why the heck does Wizards of the Coast use table based layouts. They hurt my brain hurt.
If you are interested here is the code that I wrote. It doesn't have any underlying tests, but I ran the import without fail, so for now, its good enough for me.
So I decided to do a screen scrape http://www.wizards.com/default.asp?x=dnd/lists/feats . I wanted the list of feats. To get to the table, and each row of feats, here was the xPath I used:
"/html/body/table[2]/tr[2]/td[3]/table/tr[3]/td/table[4]/tr/td/table/tr"
Of particular note, I was having trouble with the tbody in xPath, so I removed those references. Let me tell you, without Firebug's copy xPath, I would've went insane getting this out.
So I ask, why the heck does Wizards of the Coast use table based layouts. They hurt my brain hurt.
If you are interested here is the code that I wrote. It doesn't have any underlying tests, but I ran the import without fail, so for now, its good enough for me.
class Feat < ActiveRecord::Base
validates_presence_of :name, :description
belongs_to :rule_source
class << self
# Run the import, first getting the RuleSources
def import!
File.open(File.dirname(__FILE__) + '/feats.html', 'w') do |f|
f.puts (open("http://www.wizards.com/default.asp?x=dnd/lists/feats").read)
end
doc = File.open(File.dirname(__FILE__) + '/feats.html') {|f| Hpricot(f) }
RuleSource.parse_rule_source(doc)
parse_feats(doc)
end
private
def parse_feats(doc)
feats = (doc/"/html/body/table[2]/tr[2]/td[3]/table/tr[3]/td/table[4]/tr/td/table/tr")
feats.pop # Get rid of the results count
feats.each_with_index do |feat, index|
parse_feat(feat) if index > 1
end
end
def parse_feat(feat)
... Do the actual parsing of the row ...
end
end
end
class RuleSource < ActiveRecord::Base
validates_presence_of :code, :name
class << self
def parse_rule_source(doc, publisher = nil)
(doc/"div.keyboxscroll/table/tr").each do |source|
if source_code = (source/"td[1]")
source_code = source_code.inner_html.strip
end
if source_name = (source/"td[2]/i")
source_name = source_name.inner_html.strip
end
find_or_create_by_code_and_name( source_code, source_name )
end
end
end
end
Tuesday, January 30, 2007
Single Table Infuriation
Single Table Inheritance in Rails is an excellent pattern that works really well, except when it doesn't.
Each of the above classes are defined in their own files (app/models). There are times in development mode that I've noticed that Child.find(:all) will generate the following SQL:
In Rails 1.2 the fix for this is perhaps a little obfuscated.
Now, the other part of this solution is that you probably want to call require_dependency at the end of the class definition as there may be methods in the parent object that the child object needs. I suppose you could re-open the class at the end of the file.
class Parent < ActiveRecord::Base
end
class Child < Parent
end
class GrandChild < Child
end
Each of the above classes are defined in their own files (app/models). There are times in development mode that I've noticed that Child.find(:all) will generate the following SQL:
SELECT * FROM parents WHERE (type='Child')
In Rails 1.2 the fix for this is perhaps a little obfuscated.
class Parent < ActiveRecord::Base
require_dependency 'child'
end
class Child < Parent
require_dependency 'grand_child'
end
class GrandChild < Child
end
Now, the other part of this solution is that you probably want to call require_dependency at the end of the class definition as there may be methods in the parent object that the child object needs. I suppose you could re-open the class at the end of the file.
Wednesday, January 24, 2007
capture, binding and other diabolical machinations
For the South Bend Ruby Group I volunteered to talk about form builders for February. At the time of volunteering, I knew close to nothing about form builders. I decided to take the upcoming meeting as a reason for learning about the builders. And now I'm hooked.
But that is not the point of this particular post. What I'm talking about is the bizarre magic involved in the form builder. In a project that I've been working on we have a need for rendering lists both in tables and in lists. However we'd really like to re-use the view logic in generating the list. Its pretty much a matter of hot-swapping the tags appropriately.
We opted to create a builder class. You call :list_for, and can optionally pass a builder class (like the one below). Of particular note there is use of bindings and captures. I'm still a bit dazzled by what all is going on (and wrote the tests to make sure it all works). Below is my understanding of what is going on.
1) There is the :concat method (as defined in the ActionView::Helpers::TextHelper module). We are taking the results of first parameter and appending it to the proc's binding. The proc's binding is the result of the stuff in that proc. In the case of the above :list_for its all of the text generated :list_for's block.
2) There is the :capture method (as defined in ActionView::Helpers::CaptureHelper module). This little critter packages up the input block as text. I tried using block.call but this didn't work as intended (I got double the block text). If I didn't want the option of passing options to each of the elements, I could conceptually simplify the method by doing a manual opening of a tag, block.call, then manually closing the tag. This could be a performance piece that I will implement at a later point (though I'll need a Hash#to_markup_attributes method).
But that is not the point of this particular post. What I'm talking about is the bizarre magic involved in the form builder. In a project that I've been working on we have a need for rendering lists both in tables and in lists. However we'd really like to re-use the view logic in generating the list. Its pretty much a matter of hot-swapping the tags appropriately.
<% list_for(@list) do | l | %>
<% l.caption 'Hello World' %>
<% l.header_row(:id => 'list_head') do %>
<%= l.header_cell 'title' %>
<%= l.header_cell 'edit' %>
<%= l.header_cell 'delete' %>
<% end %>
<% l.row( :class => 'spam', :id => 'hot_dog') do | list_item | %>
<%= l.cell l.title %>
<%= l.cell 'edit_link' %>
<%= l.cell 'delete_link' %>
<% end %>
<% end %>
We opted to create a builder class. You call :list_for, and can optionally pass a builder class (like the one below). Of particular note there is use of bindings and captures. I'm still a bit dazzled by what all is going on (and wrote the tests to make sure it all works). Below is my understanding of what is going on.
1) There is the :concat method (as defined in the ActionView::Helpers::TextHelper module). We are taking the results of first parameter and appending it to the proc's binding. The proc's binding is the result of the stuff in that proc. In the case of the above :list_for its all of the text generated :list_for's block.
2) There is the :capture method (as defined in ActionView::Helpers::CaptureHelper module). This little critter packages up the input block as text. I tried using block.call but this didn't work as intended (I got double the block text). If I didn't want the option of passing options to each of the elements, I could conceptually simplify the method by doing a manual opening of a tag, block.call, then manually closing the tag. This could be a performance piece that I will implement at a later point (though I'll need a Hash#to_markup_attributes method).
def list_for(object, *args, &block)
raise ArgumentError, "Missing block" unless block_given?
options = args.last.is_a?(Hash) ? args.pop.symbolize_keys! : {}
builder = options.delete(:builder) || ListTableBuilder
builder.new(object, self, options, &block)
end
# <table>
# <caption>
# <tr>
# <th />
# </tr>
# <tr>
# <td />
# </tr>
# </table>
class ListTableBuilder
# Welcome to the insanely bizarre initialize function
def initialize(collection, template, options, &proc)
@collection, @template, @proc = collection, template, proc
@options = options.reverse_merge( :class => 'list', :id => 'list')
@template.concat( @template.content_tag('table', @template.capture(self, &proc) , @options ), @proc.binding)
end
# :options are applied to content_tag('td')
def cell(text, options = {})
@template.content_tag('td', text, options)
end
# :options are applied to content_tag('th')
def header_cell(title, options = {})
@template.content_tag('th', title, options)
end
# :options are applied to content_tag('caption')
def caption(text, options={})
@template.concat( @template.content_tag('caption', text, options), @proc.binding )
end
# :options are applied to content_tag('tr')
def header_row(options={}, &block)
raise ArgumentError, "Missing block" unless block_given?
@template.concat( @template.content_tag('tr', @template.capture(&block), options) , block.binding)
end
# Options
# The options will be added to each row.
# :class will append and not destroy the internal classes
# :id will be used as a prefix for the internal id generation
def row(options={}, &block)
raise ArgumentError, "Missing block" unless block_given?
options.symbolize_keys!
@collection.each do | c |
row_options = options.reverse_merge( :class => '' )
row_options[:class] += ' ' + @template.cycle('odd_row', 'even_row')
row_options[:id] += '_' + @template.send(:dom_id, c)
@template.concat( @template.content_tag('tr', @template.capture(c, &block ), row_options), block.binding )
end
end
end
Monday, January 22, 2007
Ruby Cookbook from O'Reilly
Ruby Cookbook: Recipes for Object-Oriented Scripting
by Lucas Carlson & Leonard Richardson
ISBN 0-596-52369-6
I haven't read this book cover-to-cover, but I have given it a very thorough inspection, skimming through sections to find points of interest and then stopping to read those recipes. So, actually, I've probably read a whole bunch of the book and didn't realize it.
The book is written in a very concise manner, with plenty of examples, lots of useful code, excellent cross-references and a very generous license that allows you to use the code from the book (albeit with some restrictions).
Each recipe is presented as a quartet of Problem, Solution, Discussion and See Also sections. Each problem is presented succinctly, and the solutions dive right into the required code (with code comments explaining some of the more complicated lines of code, but not explaining the self-documenting lines). The discussion section of each solution really digs into what is going on, code choices that were made, variations, and basically anything else that might be interesting. The see also section cross-references other recipes that may be pertinent to the given problem.
All told, this book does an amazing job of going through a wide range of problems, from Strings & Numbers to Meta-Programming & Rails and Graphics. This book is loaded with useful bits of information and I would highly recommend adding it to your collection.
by Lucas Carlson & Leonard Richardson
ISBN 0-596-52369-6
I haven't read this book cover-to-cover, but I have given it a very thorough inspection, skimming through sections to find points of interest and then stopping to read those recipes. So, actually, I've probably read a whole bunch of the book and didn't realize it.
The book is written in a very concise manner, with plenty of examples, lots of useful code, excellent cross-references and a very generous license that allows you to use the code from the book (albeit with some restrictions).
Each recipe is presented as a quartet of Problem, Solution, Discussion and See Also sections. Each problem is presented succinctly, and the solutions dive right into the required code (with code comments explaining some of the more complicated lines of code, but not explaining the self-documenting lines). The discussion section of each solution really digs into what is going on, code choices that were made, variations, and basically anything else that might be interesting. The see also section cross-references other recipes that may be pertinent to the given problem.
All told, this book does an amazing job of going through a wide range of problems, from Strings & Numbers to Meta-Programming & Rails and Graphics. This book is loaded with useful bits of information and I would highly recommend adding it to your collection.
Wednesday, January 17, 2007
Some Weird Weird Ruby (or nest Classes and Inheritence)
Thinking cap time. I am working on a CMS and the short of it is that a Page will have many PageBlock objects. The PageBlock is versioned and can have multiple things associated with it (i.e. Links or Files or Other Stuff, thankfully each page block is homogenous). I decided that the Links/Files will be serialized. So I wrote a serializing helper object. This serializing object
Now
DOH!!! Man that is definitely not the behavior I want. I guess my nested Class has some behavior that I'm not aware of. I'm certain the behavior is intentional, I just didn't know about it
So the solution?
Then I define PageLinkBlock and
Alas, the behavior that I want. Time to dig out the Pickaxe Book to see if there is a reference to this.
class PageBlock < ActiveRecord::Base
class << self
# I want to be able to configure the underlying DataSerializer
# in the containing class
delegate :set_valid_serialized_keys, :to => :data_serializing_class
def data_serializing_class
self::DataSerializer
end
end
class DataSerializer
class << self
def set_valid_serialized_keys( *attrs )
write_inheritable_attribute :valid_serialized_keys, attrs
class_inheritable_reader :valid_serialized_keys
end
end
def add_data(data = {})
data.assert_valid_keys(valid_serialized_keys)
... do the magic ...
end
end
end
Now
$ PageBlock.valid_serialized_keys
=> NoMethodError: undefined method `valid_serialized_keys' for PageBlock::DataSerializer:Class
class PageLinkBlock < PageBlock
set_valid_serialized_keys :path, :name
end
$ PageBlock.valid_serialized_keys
=> [:path, :name]
DOH!!! Man that is definitely not the behavior I want. I guess my nested Class has some behavior that I'm not aware of. I'm certain the behavior is intentional, I just didn't know about it
So the solution?
class PageBlock < ActiveRecord::Base
class << self
# I want to be able to configure the underlying DataSerializer
# in the containing class
delegate :set_valid_serialized_keys, :to => :data_serializing_class
def data_serializing_class
self::DataSerializer
end
def inherited(page_block_subclass)
page_block_subclass.module_eval <<-EOV
class DataSerializer < PageBlock::DataSerializer
end
EOV
super
end
end
class DataSerializer
class << self
def set_valid_serialized_keys( *attrs )
write_inheritable_attribute :valid_serialized_keys, attrs
class_inheritable_reader :valid_serialized_keys
end
end
def add_data(data = {})
data.assert_valid_keys(valid_serialized_keys)
... do the magic ...
end
end
end
Then I define PageLinkBlock and
$ PageBlock.valid_serialized_keys
=> NoMethodError: undefined method `valid_serialized_keys' for PageBlock::DataSerializer:Class
Alas, the behavior that I want. Time to dig out the Pickaxe Book to see if there is a reference to this.
Tuesday, January 16, 2007
Helper Module Testing
First and foremost
Then in your app/test/test_helper.rb add
Then make a test class
Of particular note, the name of the class that inherits from Test::Rails::HelperTestCase is very important. If the inheriting class name is MyHelperTest, make sure a module named MyHelper exists.
sudo gem install ZenTest
Then in your app/test/test_helper.rb add
require 'test/rails'
Then make a test class
class FooHelper
def print_hello_world
content_tag('p', 'Hello World!')
end
end
class FooHelperTest < Test::Rails::HelperTestCase
def test_method_goes_here
assert_equal '<p>Hello World</p>', print_hello_world
end
end
Of particular note, the name of the class that inherits from Test::Rails::HelperTestCase is very important. If the inheriting class name is MyHelperTest, make sure a module named MyHelper exists.
Wednesday, January 03, 2007
Dear RDoc,
One thing that I've quickly taken for granted is RDoc. Prior to RDoc, I lamented that the documentation that I wrote in code was impossible to discover, and thus the in-code documentation was useless for non-programmers. Thus, in order to "say" what the system did, additional documentation needed to be created. So now there were two sets of documents, both saying what the system did, but not communicating with each other.
Oh the humanity!
Enter RDoc. I can now rake that information and generate a useful document defining the system. It gets even better if I start using should-language, either in specify or test context (i.e. specify 'student should have a name' or def test_student_should_have_a_name). Then I can rake just the method names and get a document that says concisely what the system should do.
Of course this leads itself to writing test stubs earlier in the project process. Even better, it is possible for a project manager to write some of these specifications.
Suddenly, the tests, which everyone wants and needs, become useful not only for documentation but also for communication and um... testing. And better yet, the documentation is not discarded nor duplicated.
Oh the humanity!
Enter RDoc. I can now rake that information and generate a useful document defining the system. It gets even better if I start using should-language, either in specify or test context (i.e. specify 'student should have a name' or def test_student_should_have_a_name). Then I can rake just the method names and get a document that says concisely what the system should do.
Of course this leads itself to writing test stubs earlier in the project process. Even better, it is possible for a project manager to write some of these specifications.
Suddenly, the tests, which everyone wants and needs, become useful not only for documentation but also for communication and um... testing. And better yet, the documentation is not discarded nor duplicated.
Only write what you need.
I don't recall where I read this, but someone once wrote that each line of code should be viewed as a liability.
At work, a colleague and I have begun referring to the test code as a contract; it describes the behavior of the system. The more tests, the tighter the contract. By extension, the more code in the system, the more restrictive the implicit contract. The implicit contract is the written, and all to often unwritten, expectations of the system.
So the less code needed to get the job done, the better.
At work, a colleague and I have begun referring to the test code as a contract; it describes the behavior of the system. The more tests, the tighter the contract. By extension, the more code in the system, the more restrictive the implicit contract. The implicit contract is the written, and all to often unwritten, expectations of the system.
So the less code needed to get the job done, the better.
Subscribe to:
Posts (Atom)