<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-37792061</id><updated>2011-07-28T18:38:26.225-04:00</updated><category term='ruby'/><category term='d20'/><category term='other'/><category term='git'/><category term='books'/><category term='rails'/><category term='programming'/><category term='session'/><category term='rdoc'/><category term='textmate'/><category term='testing'/><category term='review'/><category term='bash'/><category term='sandman'/><category term='osx'/><category term='gaming'/><category term='OGL'/><category term='svn'/><category term='OGC'/><title type='text'>Reclusive Geek</title><subtitle type='html'>A humble attempt at deluding myself into thinking I can write with any regularity.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>36</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-37792061.post-48700116805298208</id><published>2008-04-21T19:14:00.004-04:00</published><updated>2008-05-03T10:02:51.295-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Harvesting Fixtures from a Live Rails project</title><content type='html'>So I've put together a simple plugin for grabbing fixtures out of a Live Rails project.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Harvixture is a tool that can be used to extract data, in the form of fixtures,&lt;br /&gt;from a Rails project.  It is done by pointing the harvixture at a request_path&lt;br /&gt;and dumping fixtures for all "found" ActiveRecord objects.&lt;br /&gt;&lt;br /&gt;$ sudo gem install jeremyf-harvixture --source=http://gems.github.com/&lt;br /&gt;&lt;br /&gt;$ cd path/to/rails/project&lt;br /&gt;&lt;br /&gt;$ . harvixture run --request_path=/users/1/calendars./bug_100/users.yml&lt;br /&gt;./bug_100/calendars.yml&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Get the plugin here.&lt;br /&gt;http://github.com/jeremyf/harvixture/tree/master&lt;br /&gt;&lt;br /&gt;TODO:&lt;br /&gt;- Possibly convert to a gem.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-48700116805298208?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/48700116805298208/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=48700116805298208' title='45 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/48700116805298208'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/48700116805298208'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2008/04/harvesting-fixtures-from-live-rails.html' title='Harvesting Fixtures from a Live Rails project'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>45</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-5885615521342409044</id><published>2008-04-14T19:01:00.005-04:00</published><updated>2008-04-14T22:53:58.380-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='bash'/><category scheme='http://www.blogger.com/atom/ns#' term='git'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='svn'/><title type='text'>A little bash script hacking</title><content type='html'>Puttering around the blog-o-tubes, I discovered an awesome little &lt;a href="http://unboundimagination.com/Current-Git-Branch-in-Bash-Prompt"&gt;post&lt;/a&gt; concerning git and your command line.  The problem with the script is its too limiting.  I use both svn and git.  So enter my little hack.  &lt;br /&gt;&lt;br /&gt;In ~/.bashrc&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;function parse_git_branch() {&lt;br /&gt;  git branch 2&gt; /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/(git::\1)/'&lt;br /&gt;}&lt;br /&gt;function parse_svn_branch() {&lt;br /&gt;  parse_svn_url | sed -e 's#^'"$(parse_svn_repository_root)"'##g' | awk -F / '{print "(svn::"$1 "/" $2 ")"}'&lt;br /&gt;}&lt;br /&gt;function parse_svn_url() {&lt;br /&gt;  svn info 2&gt;/dev/null | grep -e '^URL*' | sed -e 's#^URL: *\(.*\)#\1#g '&lt;br /&gt;}&lt;br /&gt;parse_svn_repository_root() {&lt;br /&gt;  svn info 2&gt;/dev/null | grep -e '^Repository Root:*' | sed -e 's#^Repository Root: *\(.*\)#\1\/#g '&lt;br /&gt;}&lt;br /&gt;function parse_scm_branch() {&lt;br /&gt;  local value=$(parse_git_branch) &lt;br /&gt;  if [ -z "$value" ]; then&lt;br /&gt;    local value=$(parse_svn_branch)&lt;br /&gt;  fi&lt;br /&gt;  echo "$value"&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;export PS1="\[\033[00m\]\u@\h\[\033[01;34m\] \w \[\033[31m\]\$(parse_scm_branch) \[\033[00m\]$\[\033[00m\] "&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;For git directories&lt;br /&gt;&lt;code&gt;&lt;br /&gt;jeremyf@reclusive-geek &lt;span style="color:blue;"&gt;~/Documents/Repositories/git/ccs_portal&lt;/span&gt; &lt;span style="color:red;"&gt;(git::master)&lt;/span&gt; $&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;For svn directories&lt;br /&gt;&lt;code&gt;&lt;br /&gt;jeremyf@reclusive-geek &lt;span style="color:blue;"&gt;~/Documents/Repositories/svn/ccs_main/app/controllers&lt;/span&gt; &lt;span style="color:red;"&gt;(svn::ccs_main/trunk)&lt;/span&gt; $&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;If anyone knows how to tighten this script up, let me know.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-5885615521342409044?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/5885615521342409044/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=5885615521342409044' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/5885615521342409044'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/5885615521342409044'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2008/04/little-bash-script-hacking.html' title='A little bash script hacking'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-3751228252713492851</id><published>2008-04-08T15:25:00.002-04:00</published><updated>2008-04-08T15:34:11.821-04:00</updated><title type='text'>git add --patch file.name</title><content type='html'>I would highly recommend reading http://tomayko.com/writings/the-thing-about-git, it highlights yet another awesome feature of git.  Git lets you do your work how you do it, instead of how the SCM says you should.  Here is a great example.  I have a hopelessly muddled set of changes in file_the_other.txt.  The changes are for two (or more) different purposes, and I need to only push a portion of it.  Enter `git add --patch`.  I can go through each diff element and select to put it in the queue (the Index) for what will be committed.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;$ git add --patch file_the_other.txt &lt;br /&gt;diff --git a/file_the_other.txt b/file_the_other.txt&lt;br /&gt;index 2120d21..e3aa90d 100644&lt;br /&gt;--- a/file_the_other.txt&lt;br /&gt;+++ b/file_the_other.txt&lt;br /&gt;@@ -1,3 +1,6 @@&lt;br /&gt;+World is here&lt;br /&gt; A file like another file, but with different content.&lt;br /&gt; &lt;br /&gt;-There is even more content here.&lt;br /&gt;\ No newline at end of file&lt;br /&gt;+There is even more content here.&lt;br /&gt;+&lt;br /&gt;+Hello World&lt;br /&gt;\ No newline at end of file&lt;br /&gt;Stage this hunk [y/n/a/d/s/?]? &lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-3751228252713492851?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/3751228252713492851/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=3751228252713492851' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/3751228252713492851'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/3751228252713492851'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2008/04/git-add-patch-filename.html' title='git add --patch file.name'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-1871631524604627583</id><published>2008-03-14T12:43:00.003-04:00</published><updated>2008-03-14T12:50:59.697-04:00</updated><title type='text'>Some rspec love for acts_as_state_machine</title><content type='html'>So I've once again revisited the almighty "acts_as_state_machine" plugin (http://elitists.textdriven.com/svn/plugins/acts_as_state_machine).  Its really helpful, but one thing I've never liked is testing the valid transitions.  Because, really you should test a whole lot of stuff;  Enter :describe_valid_event_transitions.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;class MyStatedObject &lt; ActiveRecord::Base&lt;br /&gt;  acts_as_state_machine :initial =&gt; :pending, :column =&gt; 'status'&lt;br /&gt;  state :pending&lt;br /&gt;  state :active&lt;br /&gt;  state :completed&lt;br /&gt;  state :rejected&lt;br /&gt;&lt;br /&gt;  event :approve! do&lt;br /&gt;    transitions :to =&gt; :active, :from =&gt; :pending&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  event :reject! do&lt;br /&gt;    transitions :to =&gt; :rejected, :from =&gt; :pending&lt;br /&gt;  end&lt;br /&gt;  event :complete! do&lt;br /&gt;    transitions :to =&gt; :completed, :from =&gt; :active&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;describe MyStatedObject do&lt;br /&gt;  describe_valid_event_transitions do |transition|&lt;br /&gt;    transition.is_valid(:event =&gt; :approve!,  :from =&gt; :pending, :to =&gt; :active)&lt;br /&gt;    transition.is_valid(:event =&gt; :reject!,   :from =&gt; :pending, :to =&gt; :rejected)&lt;br /&gt;    transition.is_valid(:event =&gt; :complete!, :from =&gt; :active,  :to =&gt; :completed)&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Using the above code it will generate 3 (events) * 4 (statuses) * 4 (statuses) specs.  If I were to inadvertently add a validator for :event =&gt; :stupify!, then the number of specs would be 4 * 4 * 4, and there would be a whole lot of failures.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;module Spec&lt;br /&gt;  module Rails&lt;br /&gt;    module Matchers&lt;br /&gt;      # Specifies a valid state change&lt;br /&gt;      #&lt;br /&gt;      # options&lt;br /&gt;      # :from: The from state&lt;br /&gt;      # :to: The desired state&lt;br /&gt;      # :via: The event to fire the transition&lt;br /&gt;      def change_state(*args)&lt;br /&gt;        options = args.extract_options!&lt;br /&gt;        from_status = options[:from]&lt;br /&gt;        to_status = options[:to]&lt;br /&gt;        via_event = options[:via]&lt;br /&gt;        return simple_matcher("model should change status from :#{from_status} to :#{to_status} via :#{via_event} event") do |klass|&lt;br /&gt;          object = klass.is_a?(Class) ? klass.new : klass&lt;br /&gt;          object.stub!(:current_state).and_return(from_status.to_sym)&lt;br /&gt;          object.stub!(:status).and_return(from_status.to_s)&lt;br /&gt;          if event = object.next_states_for_event(via_event).detect{|event| event.to == to_status}&lt;br /&gt;            event.to.to_sym == to_status.to_sym&lt;br /&gt;          else&lt;br /&gt;            false&lt;br /&gt;          end&lt;br /&gt;        end&lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;class Spec::Rails::Example::RailsExampleGroup&lt;br /&gt;&lt;br /&gt;  class ValidTransitionCollector #:nodoc:&lt;br /&gt;&lt;br /&gt;    def events&lt;br /&gt;      transition_table.collect{|t| t[:event]}.uniq&lt;br /&gt;    end&lt;br /&gt;&lt;br /&gt;    def states&lt;br /&gt;      transition_table.collect{|t| [t[:to], t[:from]]}.flatten.uniq&lt;br /&gt;    end&lt;br /&gt;&lt;br /&gt;    def add(options = {})&lt;br /&gt;      options.symbolize_keys!&lt;br /&gt;      transition_table &lt;&lt; {:event =&gt; (options[:event] || options[:via]).to_sym, :from =&gt; options[:from].to_sym, :to =&gt; options[:to].to_sym}&lt;br /&gt;    end&lt;br /&gt;    alias_method :is_valid, :add&lt;br /&gt;&lt;br /&gt;    def has?(event, from, to)&lt;br /&gt;      transition_table.detect{|o| o[:event] == event &amp;&amp; o[:from] == from &amp;&amp; o[:to] == to}&lt;br /&gt;    end&lt;br /&gt;    protected&lt;br /&gt;    def transition_table&lt;br /&gt;      @transition_table ||= []&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  class &lt;&lt; self&lt;br /&gt;    # This method assumes the use of the awesome acts_as_state_machine, &lt;br /&gt;    #&lt;br /&gt;    # Given all events (both defined and proposed to the validator)&lt;br /&gt;    # and all statuses (both defined and proposed to the validator),&lt;br /&gt;    # this method will iterate over the events, and then over&lt;br /&gt;    # the statuses as the "from" status, and then over the&lt;br /&gt;    # statuses again as the "to" status.  &lt;br /&gt;    #&lt;br /&gt;    # With the event, from status and to status, this method &lt;br /&gt;    # will check against the validator to say it should or&lt;br /&gt;    # should_not state_change&lt;br /&gt;    #&lt;br /&gt;    #&lt;br /&gt;    # Usage:&lt;br /&gt;    #&lt;br /&gt;    # describe MyStatedObject do&lt;br /&gt;    #   describe_valid_event_transitions do |transition|&lt;br /&gt;    #     transition.is_valid(:event =&gt; :approve!,  :from =&gt; :pending, :to =&gt; :active)&lt;br /&gt;    #     transition.is_valid(:event =&gt; :reject!,   :from =&gt; :pending, :to =&gt; :rejected)&lt;br /&gt;    #     transition.is_valid(:event =&gt; :complete!, :from =&gt; :active,  :to =&gt; :completed)&lt;br /&gt;    #   end&lt;br /&gt;    # end&lt;br /&gt;    #&lt;br /&gt;    def describe_valid_event_transitions&lt;br /&gt;      collector = ValidTransitionCollector.new&lt;br /&gt;      yield(collector)&lt;br /&gt;      klass = description.constantize&lt;br /&gt;      describe 'status changes' do&lt;br /&gt;        (klass.event_table.keys + collector.events).uniq.each do |event|&lt;br /&gt;          (klass.states + collector.states).each do |possible_from_state|&lt;br /&gt;            (klass.states + collector.states).each do |possible_to_state|&lt;br /&gt;              should_transition = false&lt;br /&gt;              if result = collector.has?(event, possible_from_state, possible_to_state)&lt;br /&gt;                should_transition = true&lt;br /&gt;              end&lt;br /&gt;&lt;br /&gt;              it "should #{should_transition ? '' : 'NOT '}change from :#{possible_from_state} to :#{possible_to_state} via :#{event}" do&lt;br /&gt;                klass.send("#{should_transition ? 'should' : 'should_not'}", change_state(:via =&gt; event, :from =&gt; possible_from_state, :to =&gt; possible_to_state))&lt;br /&gt;              end&lt;br /&gt;            end&lt;br /&gt;          end&lt;br /&gt;        end&lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-1871631524604627583?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/1871631524604627583/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=1871631524604627583' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/1871631524604627583'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/1871631524604627583'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2008/03/some-rspec-love-for-actsasstatemachine.html' title='Some rspec love for acts_as_state_machine'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-4065447534634584798</id><published>2008-01-24T16:34:00.000-05:00</published><updated>2008-01-24T16:49:03.230-05:00</updated><title type='text'>RSpec Delegation Assertion</title><content type='html'>So I've made the jump to using RSpec, and I love it.  I really like the declarative nature of the matchers, and feel compelled to write useful ones.  Below is a matcher for testing delegations.  As of right now, I have not yet decided if I should have the&lt;br /&gt;matcher be at the class level or the object level.  We'll see.  Perhaps iteration 2.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;class Comment &lt; ActiveRecord::Base&lt;br /&gt;  extend Forwardable&lt;br /&gt;  belongs_to :user&lt;br /&gt;  def_delegator :user, :name, :author_name&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;describe Comment do&lt;br /&gt;  it 'should delegate author_name to user' do   &lt;br /&gt;    Comment.should delegate(:author_name, :to =&gt; :user, :via =&gt; :name)&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;Below is a helpful tester for delegations, inspired by &lt;a href="http://blog.jayfields.com/2007/05/ruby-unit-testing-delegations.html"&gt;Jay Fields&lt;/a&gt;, and more importantly made possible by Obie Fernandez's most excellent &lt;a href="http://www.rubyinside.com/the-rails-way-by-obie-fernandez-679.html"&gt;The Rails Way&lt;/a&gt;.&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;module Spec&lt;br /&gt;  module Rails&lt;br /&gt;    module Matchers&lt;br /&gt;      # describe Comment do&lt;br /&gt;      #   it 'should delegate author_name to user' do&lt;br /&gt;      #      Comment.should delegate(:author_name, :to =&gt; :user, :via =&gt; :name)&lt;br /&gt;      #   end&lt;br /&gt;      # end&lt;br /&gt;      class Delegation&lt;br /&gt;        private&lt;br /&gt;        attr_reader :expected, :actual, :method_name, :options, :to, :via&lt;br /&gt;        public&lt;br /&gt;        def initialize(method_name, options = {})&lt;br /&gt;          @method_name = method_name&lt;br /&gt;          options.symbolize_keys!&lt;br /&gt;          @to = options[:to]&lt;br /&gt;          @via = options[:via] || method_name&lt;br /&gt;        end&lt;br /&gt;&lt;br /&gt;        def matches?(klass)&lt;br /&gt;          @actual = klass&lt;br /&gt;          delegate_class = Class.new&lt;br /&gt;          delegate_class.send(:attr_accessor, @via)&lt;br /&gt;&lt;br /&gt;          delegate_object = delegate_class.new&lt;br /&gt;          delegate_object.send("#{@via}=", :spec_rails_matchers_delegation)&lt;br /&gt;&lt;br /&gt;          @base_object = @actual.new&lt;br /&gt;          &lt;br /&gt;          # Use the singleton class of the base_object&lt;br /&gt;          # I am rewriting the @to method on the base_object's meta_class&lt;br /&gt;          # but not altering the base_object's parent class&lt;br /&gt;          # So if I instantiate another instance of @actual&lt;br /&gt;          # it will not have the below behavior, only the @base_object will&lt;br /&gt;          #&lt;br /&gt;          # http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html&lt;br /&gt;          singleton = @base_object.instance_eval("class &lt;&lt; self; self; end")&lt;br /&gt;          singleton.class_eval("attr_accessor :#{@to}")&lt;br /&gt;&lt;br /&gt;          @base_object.send("#{@to}=", delegate_object)&lt;br /&gt;          @base_object.send(method_name) == :spec_rails_matchers_delegation&lt;br /&gt;        end&lt;br /&gt;&lt;br /&gt;        def failure_message&lt;br /&gt;          "expected #{actual.to_s} to delegate :#{method_name} to :#{to} via :#{via}"&lt;br /&gt;        end&lt;br /&gt;&lt;br /&gt;        def negative_failure_message&lt;br /&gt;          "expected #{actual.to_s} to NOT delegate :#{method_name} to :#{to} via :#{via}"&lt;br /&gt;        end&lt;br /&gt;&lt;br /&gt;        def to_s&lt;br /&gt;          "#{actual.to_s} delegates :#{method_name} to :#{to} via :#{via}"&lt;br /&gt;        end&lt;br /&gt;&lt;br /&gt;      end&lt;br /&gt;      def delegates(method_name, options = {})&lt;br /&gt;        Delegation.new(method_name, options)&lt;br /&gt;      end&lt;br /&gt;      alias_method :delegate, :delegates&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-4065447534634584798?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/4065447534634584798/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=4065447534634584798' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/4065447534634584798'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/4065447534634584798'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2008/01/rspec-delegation-assertion.html' title='RSpec Delegation Assertion'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-6694929835778395574</id><published>2007-08-27T19:38:00.000-04:00</published><updated>2007-08-27T19:39:47.421-04:00</updated><title type='text'>Ruby UML Diagrammer</title><content type='html'>Well, after a few bursts of creativity, I've finally posted the Ruby UML Diagrammer (http://rumld.rubyforge.org/)&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;$ sudo gem install rumld --included-dependencies&lt;br /&gt;$ rumld --help&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-6694929835778395574?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/6694929835778395574/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=6694929835778395574' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/6694929835778395574'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/6694929835778395574'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2007/08/ruby-uml-diagrammer.html' title='Ruby UML Diagrammer'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-2939637674928895848</id><published>2007-08-08T23:36:00.000-04:00</published><updated>2007-08-08T23:37:18.901-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rdoc'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Digging for method signatures</title><content type='html'>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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;Enter &lt;a href="http://pastie.textmate.org/86186" title="RdocInspector" target="_blank"&gt;RdocInspector&lt;/a&gt;.  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).&lt;br /&gt;&lt;br /&gt;My UML automation work is not yet done, but the issue of what methods are explicitly defined in a given file has been solved.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-2939637674928895848?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/2939637674928895848/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=2939637674928895848' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/2939637674928895848'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/2939637674928895848'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2007/08/digging-for-method-signatures.html' title='Digging for method signatures'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-2482020446542032615</id><published>2007-07-18T22:03:00.000-04:00</published><updated>2007-07-18T22:13:54.029-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><title type='text'>Mocha</title><content type='html'>So my previous &lt;a href="http://reclusive-geek.blogspot.com/2007/07/overriding-defdelegator.html"&gt;post &lt;/a&gt; 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.&lt;br /&gt;&lt;br /&gt;So instead of my convoluted example of conditionally_overriding_delegation, I can do the following (but hey you may have already known this):&lt;br /&gt;&lt;br /&gt;&lt;pre class='code'&gt;&lt;br /&gt;class AbstractUnit&lt;br /&gt;  extend Forwardable&lt;br /&gt;  def_delegate :foo, :name&lt;br /&gt;&lt;br /&gt;  def has_name?&lt;br /&gt;    !(name.nil? || name.blank?)&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;class AbstractUnitTest &lt; Test::Unit::TestCase&lt;br /&gt;  def setup&lt;br /&gt;     @object = AbstractUnit.new&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  # Perhaps a reasonable test for delegation?&lt;br /&gt;  def test_should_delegate_name_to_foo&lt;br /&gt;    assert_equal @object.name, @object.foo.name&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def test_has_name_should_be_false_if_name_is_empty&lt;br /&gt;    @object.stubs(:name).returns('')&lt;br /&gt;    assert ! @object.has_name?&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def test_has_name_should_be_false_if_name_is_nil&lt;br /&gt;    @object.stubs(:name).returns(nil)&lt;br /&gt;    assert ! @object.has_name?&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def test_has_name_should_be_false_if_name_is_blank&lt;br /&gt;    @object.stubs(:name).returns('hello world')&lt;br /&gt;    assert @object.has_name?&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;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.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-2482020446542032615?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/2482020446542032615/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=2482020446542032615' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/2482020446542032615'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/2482020446542032615'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2007/07/mocha.html' title='Mocha'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-2469245784178866758</id><published>2007-07-16T21:23:00.000-04:00</published><updated>2007-07-16T21:29:03.356-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>assert_interface</title><content type='html'>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.&lt;br /&gt;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.&lt;br /&gt;However, I want to make sure that my objects quack appropriately.  Enter assert_interface(object, module).  The object is assumed to be an&lt;br /&gt;instance of a class.  The module is assumed to be "well-formed" for interfaces &lt;br /&gt;&lt;pre class="code"&gt;&lt;br /&gt;module WellFormedInterface&lt;br /&gt;  def self.included(base)&lt;br /&gt;    base.send(:include, InstanceMethods)&lt;br /&gt;    base.extend(ClassMethods)&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  module ClassMethods&lt;br /&gt;  end&lt;br /&gt;  module InstanceMethods&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;class AbstractUnit&lt;br /&gt;  include WellFormedInterface&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="code"&gt;&lt;br /&gt;class Test::Unit::TestCase&lt;br /&gt;  # Given an object (i.e. Foo.new), and a module (i.e. SomethingCool)&lt;br /&gt;  # assert that all methods explicitly defined in the module&lt;br /&gt;  # are present in the object.&lt;br /&gt;  #&lt;br /&gt;  # This isn't the end all test, as you are still responsible for&lt;br /&gt;  # testing the implementation of the interface&lt;br /&gt;  def assert_interface(object, mod)&lt;br /&gt;    methods = {object =&gt; [], object.class =&gt; []}&lt;br /&gt;&lt;br /&gt;    mod::InstanceMethods.instance_methods(false).each do |method|&lt;br /&gt;      methods[object] &lt;&lt; method unless object.respond_to?(method)&lt;br /&gt;    end&lt;br /&gt;&lt;br /&gt;    mod::ClassMethods.instance_methods(false).each do |method|&lt;br /&gt;      methods[object.class] &lt;&lt; method unless object.class.respond_to?(method)&lt;br /&gt;    end&lt;br /&gt;  &lt;br /&gt;    msg = []&lt;br /&gt;    methods.each_pair do |key, value|&lt;br /&gt;      msg &lt;&lt; "Expected #{key} to respond to the following :#{value.join(' :')}" unless value.empty?&lt;br /&gt;    end&lt;br /&gt;&lt;br /&gt;    if msg.empty?&lt;br /&gt;      assert true, 'Hey, it all worked'&lt;br /&gt;    else&lt;br /&gt;      flunk msg.join('. ')&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-2469245784178866758?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/2469245784178866758/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=2469245784178866758' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/2469245784178866758'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/2469245784178866758'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2007/07/assertinterface.html' title='assert_interface'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-741983576197509385</id><published>2007-07-12T14:22:00.000-04:00</published><updated>2007-07-16T15:10:26.148-04:00</updated><title type='text'>Overriding def_delegator</title><content type='html'>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.&lt;br /&gt;&lt;br /&gt;Enter the ConditionallyOverrideDelegation module.  This is a rails specific solution as it makes use of alias_method_chain.&lt;br /&gt;&lt;br /&gt;&lt;pre class='code'&gt;&lt;br /&gt;module ConditionallyOverrideDelegation&lt;br /&gt;  def self.included(base)&lt;br /&gt;    base.extend(Macro)&lt;br /&gt;  end&lt;br /&gt;  module Macro&lt;br /&gt;&lt;br /&gt;    # class Foo&lt;br /&gt;    #   extend Forwardable&lt;br /&gt;    #   def_delegator :bar, :name&lt;br /&gt;    #   conditionally_override_delegation :name&lt;br /&gt;    # end&lt;br /&gt;    # class Bar&lt;br /&gt;    #   def name&lt;br /&gt;    #     'Bar'&lt;br /&gt;    #   end&lt;br /&gt;    # end&lt;br /&gt;    # &lt;br /&gt;    # @foo = Foo.new&lt;br /&gt;    # @foo.bar.name == @foo.name&lt;br /&gt;    # =&gt; true&lt;br /&gt;    #&lt;br /&gt;    # @foo.name_with_delegation_override = @foo.bar.name * 2&lt;br /&gt;    # @foo.bar.name == @foo.name&lt;br /&gt;    # =&gt; false&lt;br /&gt;    #&lt;br /&gt;    # Take a look at the tests to see how this truly behaves&lt;br /&gt;    #&lt;br /&gt;    # Really you probably only want to use this method in test&lt;br /&gt;    #&lt;br /&gt;    def conditionally_override_delegation(*targets)&lt;br /&gt;      targets.flatten.each do |target|&lt;br /&gt;        aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1&lt;br /&gt;        src = &lt;&lt;-EOV&lt;br /&gt;        def #{aliased_target}_with_delegation_override#{punctuation}(*args)&lt;br /&gt;          if @#{aliased_target}_with_delegation_override.nil?&lt;br /&gt;            #{aliased_target}_without_delegation_override#{punctuation}(*args)&lt;br /&gt;          else&lt;br /&gt;            @#{aliased_target}_with_delegation_override&lt;br /&gt;          end&lt;br /&gt;        end&lt;br /&gt;        EOV&lt;br /&gt;        module_eval src, __FILE__, __LINE__&lt;br /&gt;        alias_method_chain target, :delegation_override&lt;br /&gt;        attr_writer "#{aliased_target}_with_delegation_override"&lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt;&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;And the obligatory tests&lt;br /&gt;&lt;br /&gt;&lt;pre class='code'&gt;&lt;br /&gt;require File.dirname(__FILE__) + '/../test_helper'&lt;br /&gt;require 'conditionally_override_delegation'&lt;br /&gt;class ConditionallyOverrideDelegationTest &lt; Test::Unit::TestCase&lt;br /&gt;  class AbstractUnit&lt;br /&gt;    extend Forwardable&lt;br /&gt;    include ConditionallyOverrideDelegation&lt;br /&gt;    &lt;br /&gt;    def_delegators :related_object, :to_s, :string=, :string, :string?&lt;br /&gt;    &lt;br /&gt;    conditionally_override_delegation :to_s, :string&lt;br /&gt;    &lt;br /&gt;    def related_object&lt;br /&gt;      @related_object ||= RelatedObject.new&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  class RelatedObject&lt;br /&gt;    def to_s&lt;br /&gt;      'Hello World'&lt;br /&gt;    end&lt;br /&gt;    attr_accessor :string&lt;br /&gt;    def string?&lt;br /&gt;      !!string&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def setup&lt;br /&gt;    @unit = AbstractUnit.new&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def test_delegation_is_working&lt;br /&gt;    assert_equal @unit.related_object.to_s, @unit.to_s&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def test_overridding_delegation_is_working&lt;br /&gt;    @unit.to_s_with_delegation_override = 'Turkey Sandwich'&lt;br /&gt;    assert_equal 'Turkey Sandwich', @unit.to_s&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def test_overridding_delegation_is_working_when_override_is_false&lt;br /&gt;    @unit.to_s_with_delegation_override = false&lt;br /&gt;    assert_equal false, @unit.to_s&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def test_overridding_delegation_is_working_when_override_is_nil&lt;br /&gt;    @unit.to_s_with_delegation_override = nil&lt;br /&gt;    assert_equal @unit.related_object.to_s, @unit.to_s&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def test_overridding_delegation_is_working_with_punctuation&lt;br /&gt;    @unit.related_object.string = 'Troy Mclure'&lt;br /&gt;    assert_equal 'Troy Mclure', @unit.string&lt;br /&gt;    &lt;br /&gt;    @unit.string_with_delegation_override = 'Pancake'&lt;br /&gt;    assert_equal 'Pancake', @unit.string&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-741983576197509385?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/741983576197509385/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=741983576197509385' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/741983576197509385'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/741983576197509385'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2007/07/overriding-defdelegator.html' title='Overriding def_delegator'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-9115178834153640729</id><published>2007-07-07T20:53:00.000-04:00</published><updated>2007-07-07T21:16:44.683-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='gaming'/><title type='text'>Of Childhood Discoveries</title><content type='html'>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.&lt;br /&gt;&lt;br /&gt;Twenty one years ago, I played my first &lt;a href="http://en.wikipedia.org/wiki/Role-playing_game"&gt;role-playing game&lt;/a&gt;.  It was &lt;a href="http://www.starfrontiersman.com/"&gt;Star Frontiers&lt;/a&gt;, published by the now defunct TSR.  During the first session, I played a &lt;a href="http://en.wikipedia.org/wiki/Dralasite"&gt;Dralasite&lt;/a&gt; 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.  &lt;br /&gt;&lt;br /&gt;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&amp;D).&lt;br /&gt;&lt;br /&gt;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.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-9115178834153640729?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/9115178834153640729/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=9115178834153640729' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/9115178834153640729'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/9115178834153640729'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2007/07/of-childhood-discoveries.html' title='Of Childhood Discoveries'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-1356435414327915992</id><published>2007-05-21T08:42:00.000-04:00</published><updated>2007-05-21T08:51:07.608-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='session'/><title type='text'>Corrupt Session?</title><content type='html'>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.&lt;br /&gt;&lt;br /&gt;&lt;pre class="code"&gt;&lt;br /&gt;class ApplicationController &lt; ActionController::Base&lt;br /&gt;  session :session_expires =&gt; 12.hours.from_now&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;So if you are looking for corrupt session, session corruption, or unresponsive mongrel it may just be related to the code above.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-1356435414327915992?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/1356435414327915992/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=1356435414327915992' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/1356435414327915992'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/1356435414327915992'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2007/05/corrupt-session.html' title='Corrupt Session?'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-3473004237844496796</id><published>2007-05-09T11:21:00.000-04:00</published><updated>2007-05-09T11:24:56.349-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><title type='text'>A "Best Migration Practice"</title><content type='html'>So today, I discovered a "good idea" for migrations.&lt;br /&gt;&lt;br /&gt;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.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-3473004237844496796?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/3473004237844496796/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=3473004237844496796' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/3473004237844496796'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/3473004237844496796'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2007/05/best-migration-practice.html' title='A &quot;Best Migration Practice&quot;'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-1882637674687089669</id><published>2007-04-01T16:46:00.000-04:00</published><updated>2007-04-01T17:07:39.631-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Complex views and conventional trickery</title><content type='html'>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:&lt;br /&gt;&lt;br /&gt;&lt;pre class="ruby"&gt;&lt;br /&gt;module PagesHelper&lt;br /&gt;  # Generate a link to the show page of navigation node&lt;br /&gt;  def link_to_node(node)&lt;br /&gt;    link_to( h(node.display_title), send(*node.route_for_show), :class =&gt; node.published? ? 'published' : 'unpublished' ) &lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;In the above example, node the &lt;strong&gt; send(*node.route_for_show)&lt;/strong&gt;.  I ask the node (which is either a subweb or a page to tell me how to generate the path.  &lt;br /&gt;&lt;br /&gt;&lt;pre class="ruby"&gt;&lt;br /&gt;class Page &lt; ActiveRecord::Base&lt;br /&gt;  def route_for_show&lt;br /&gt;    return :page_path, subweb, self&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;class Subweb &lt; ActiveRecord::Base&lt;br /&gt;  def route_for_show&lt;br /&gt;    return :subweb_path, self&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-1882637674687089669?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/1882637674687089669/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=1882637674687089669' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/1882637674687089669'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/1882637674687089669'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2007/04/complex-views-and-conventional-trickery.html' title='Complex views and conventional trickery'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-5528804008807211164</id><published>2007-02-20T08:44:00.000-05:00</published><updated>2007-02-20T08:59:03.449-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>The ever evolving test suite</title><content type='html'>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 &lt;a href="http://jayfields.blogspot.com/index.html"&gt;Jay Fields&lt;/a&gt; has really got me excited.  Take a peek.&lt;br /&gt;&lt;br /&gt;&lt;pre class="code"&gt;&lt;br /&gt;class Test::Unit::TestCase&lt;br /&gt;  class &lt;&lt; self&lt;br /&gt;    def test(*names, &amp;block)&lt;br /&gt;      test_name = :"test_#{names.join('_').gsub(/[^\w ]+/, '').gsub(/ +/, '_')}"&lt;br /&gt;      raise ArgumentError, "#{test_name} is already defined" if self.instance_methods.include?( test_name.to_s )&lt;br /&gt;      if block_given?&lt;br /&gt;        define_method( test_name, &amp;block )&lt;br /&gt;      else&lt;br /&gt;        define_method( test_name) do&lt;br /&gt;          $stderr.puts "Incomplete test '#{names.join(', ')}' @ #{caller.last}"&lt;br /&gt;        end&lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;What the above method does is allows for very "fast and loose" test definitions.  For example, I can do the following:&lt;br /&gt;&lt;br /&gt;&lt;pre class="code"&gt;&lt;br /&gt;class Foo &lt; Test::Unit::TestCase&lt;br /&gt;  # This is a place holder test, and when ran will &lt;br /&gt;  # print a warning&lt;br /&gt;  test 'this will print a warning when the test runs'&lt;br /&gt;&lt;br /&gt;  # This is a genuine test&lt;br /&gt;  test 'this will assert true' do&lt;br /&gt;    assert true&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  # This is another genuine test, but I'm providing visually &lt;br /&gt;  # putting the test in another namespace&lt;br /&gt;  test :index =&gt; 'should render a list' do&lt;br /&gt;    assert_template 'list'&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Ultimately, I'm extremely satisfied with how this works.  My functional tests can have test definitions with a prefix of the action.&lt;br /&gt;&lt;br /&gt;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.)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-5528804008807211164?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/5528804008807211164/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=5528804008807211164' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/5528804008807211164'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/5528804008807211164'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2007/02/ever-evolving-test-suite.html' title='The ever evolving test suite'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-2423393033849706632</id><published>2007-02-07T13:22:00.000-05:00</published><updated>2007-02-07T13:32:13.776-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='gaming'/><category scheme='http://www.blogger.com/atom/ns#' term='d20'/><title type='text'>Hpricot, screen_scrape, and horrible pages</title><content type='html'>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.&lt;br /&gt;&lt;br /&gt;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:&lt;br /&gt;&lt;br /&gt;"/html/body/table[2]/tr[2]/td[3]/table/tr[3]/td/table[4]/tr/td/table/tr"&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;So I ask, why the heck does Wizards of the Coast use table based layouts.  They hurt my brain hurt. &lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;pre class="code"&gt;&lt;br /&gt;class Feat &lt; ActiveRecord::Base&lt;br /&gt;  validates_presence_of :name, :description&lt;br /&gt;  belongs_to :rule_source&lt;br /&gt;&lt;br /&gt;  class &lt;&lt; self&lt;br /&gt;    # Run the import, first getting the RuleSources&lt;br /&gt;    def import!&lt;br /&gt;      File.open(File.dirname(__FILE__) + '/feats.html', 'w') do |f|&lt;br /&gt;        f.puts (open("http://www.wizards.com/default.asp?x=dnd/lists/feats").read)&lt;br /&gt;      end&lt;br /&gt;      doc = File.open(File.dirname(__FILE__) + '/feats.html') {|f| Hpricot(f) }&lt;br /&gt;&lt;br /&gt;      RuleSource.parse_rule_source(doc)&lt;br /&gt;      &lt;br /&gt;      parse_feats(doc)&lt;br /&gt;    end&lt;br /&gt;    private&lt;br /&gt;    def parse_feats(doc)&lt;br /&gt;      feats = (doc/"/html/body/table[2]/tr[2]/td[3]/table/tr[3]/td/table[4]/tr/td/table/tr")      &lt;br /&gt;      feats.pop     # Get rid of the results count&lt;br /&gt;&lt;br /&gt;      feats.each_with_index do |feat, index|&lt;br /&gt;        parse_feat(feat) if index &gt; 1&lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt;    def parse_feat(feat)&lt;br /&gt;      ... Do the actual parsing of the row ...&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;class RuleSource &lt; ActiveRecord::Base&lt;br /&gt;  validates_presence_of   :code, :name&lt;br /&gt;&lt;br /&gt;  class &lt;&lt; self&lt;br /&gt;    def parse_rule_source(doc, publisher = nil)&lt;br /&gt;      (doc/"div.keyboxscroll/table/tr").each do |source|&lt;br /&gt;        if source_code = (source/"td[1]")&lt;br /&gt;          source_code  =  source_code.inner_html.strip&lt;br /&gt;        end&lt;br /&gt;        if source_name = (source/"td[2]/i")&lt;br /&gt;          source_name  = source_name.inner_html.strip&lt;br /&gt;        end&lt;br /&gt;        &lt;br /&gt;        find_or_create_by_code_and_name( source_code, source_name )&lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-2423393033849706632?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/2423393033849706632/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=2423393033849706632' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/2423393033849706632'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/2423393033849706632'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2007/02/hpricot-screenscrape-and-horrible-pages.html' title='Hpricot, screen_scrape, and horrible pages'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-3398415775671326539</id><published>2007-01-30T20:21:00.000-05:00</published><updated>2007-01-30T20:39:45.247-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Single Table Infuriation</title><content type='html'>Single Table Inheritance in Rails is an excellent pattern that works really well, except when it doesn't. &lt;br /&gt;&lt;br /&gt;&lt;pre class="ruby"&gt;&lt;br /&gt;class Parent &lt; ActiveRecord::Base&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;class Child &lt; Parent&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;class GrandChild &lt; Child&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;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:&lt;br /&gt;&lt;br /&gt;&lt;pre class="ruby"&gt;&lt;br /&gt;SELECT * FROM parents WHERE (type='Child')&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;In Rails 1.2 the fix for this is perhaps a little obfuscated.&lt;br /&gt;&lt;br /&gt;&lt;pre class="ruby"&gt;&lt;br /&gt;class Parent &lt; ActiveRecord::Base&lt;br /&gt;  require_dependency 'child'&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;class Child &lt; Parent&lt;br /&gt;  require_dependency 'grand_child'&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;class GrandChild &lt; Child&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;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.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-3398415775671326539?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/3398415775671326539/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=3398415775671326539' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/3398415775671326539'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/3398415775671326539'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2007/01/single-table-infuriation.html' title='Single Table Infuriation'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-1067057104948869037</id><published>2007-01-24T22:45:00.000-05:00</published><updated>2007-01-24T23:13:28.888-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>capture, binding and other diabolical machinations</title><content type='html'>For the &lt;a href="http://sbrb.org"&gt;South Bend Ruby Group&lt;/a&gt; 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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;pre class="code"&gt;&lt;br /&gt;&lt;% list_for(@list) do | l | %&gt;&lt;br /&gt;  &lt;% l.caption 'Hello World' %&gt;&lt;br /&gt;  &lt;% l.header_row(:id =&gt; 'list_head') do %&gt;&lt;br /&gt;    &lt;%= l.header_cell 'title' %&gt;&lt;br /&gt;    &lt;%= l.header_cell 'edit' %&gt;&lt;br /&gt;    &lt;%= l.header_cell 'delete' %&gt;&lt;br /&gt;  &lt;% end %&gt;&lt;br /&gt;  &lt;% l.row( :class =&gt; 'spam', :id =&gt; 'hot_dog') do | list_item | %&gt;&lt;br /&gt;    &lt;%= l.cell l.title %&gt;&lt;br /&gt;    &lt;%= l.cell 'edit_link' %&gt;&lt;br /&gt;    &lt;%= l.cell 'delete_link'  %&gt;&lt;br /&gt;  &lt;% end %&gt;&lt;br /&gt;&lt;% end %&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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).&lt;br /&gt;&lt;br /&gt;&lt;pre class="code"&gt;&lt;br /&gt;&lt;br /&gt;def list_for(object, *args, &amp;block)&lt;br /&gt;  raise ArgumentError, "Missing block" unless block_given?&lt;br /&gt;  options = args.last.is_a?(Hash) ? args.pop.symbolize_keys! : {}&lt;br /&gt;  builder = options.delete(:builder) || ListTableBuilder&lt;br /&gt;  builder.new(object, self, options, &amp;block)&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;# &amp;lt;table&amp;gt;&lt;br /&gt;#  &amp;lt;caption&amp;gt;&lt;br /&gt;#  &amp;lt;tr&amp;gt;&lt;br /&gt;#    &amp;lt;th /&amp;gt;&lt;br /&gt;#  &amp;lt;/tr&amp;gt;&lt;br /&gt;#  &amp;lt;tr&amp;gt;&lt;br /&gt;#    &amp;lt;td /&amp;gt;&lt;br /&gt;#  &amp;lt;/tr&amp;gt;&lt;br /&gt;# &amp;lt;/table&amp;gt;&lt;br /&gt;class ListTableBuilder&lt;br /&gt;&lt;br /&gt;  # Welcome to the insanely bizarre initialize function&lt;br /&gt;  def initialize(collection, template, options, &amp;proc)&lt;br /&gt;    @collection, @template, @proc = collection, template, proc        &lt;br /&gt;    @options = options.reverse_merge( :class =&gt; 'list', :id =&gt; 'list')&lt;br /&gt;    @template.concat( @template.content_tag('table', @template.capture(self, &amp;proc) , @options ), @proc.binding)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  # :options are applied to content_tag('td')&lt;br /&gt;  def cell(text, options = {})&lt;br /&gt;    @template.content_tag('td', text, options)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  # :options are applied to content_tag('th')&lt;br /&gt;  def header_cell(title, options = {}) &lt;br /&gt;    @template.content_tag('th', title, options)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  # :options are applied to content_tag('caption')&lt;br /&gt;  def caption(text, options={})&lt;br /&gt;    @template.concat( @template.content_tag('caption', text, options), @proc.binding )&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  # :options are applied to content_tag('tr')&lt;br /&gt;  def header_row(options={}, &amp;block)&lt;br /&gt;    raise ArgumentError, "Missing block" unless block_given?&lt;br /&gt;    @template.concat( @template.content_tag('tr', @template.capture(&amp;block), options) , block.binding)&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  # Options&lt;br /&gt;  # The options will be added to each row.  &lt;br /&gt;  # :class will append and not destroy the internal classes&lt;br /&gt;  # :id will be used as a prefix for the internal id generation&lt;br /&gt;  def row(options={}, &amp;block)&lt;br /&gt;    raise ArgumentError, "Missing block" unless block_given?&lt;br /&gt;    options.symbolize_keys!&lt;br /&gt;    @collection.each do | c |&lt;br /&gt;      row_options = options.reverse_merge( :class =&gt; '' )&lt;br /&gt;      row_options[:class] += ' ' + @template.cycle('odd_row', 'even_row')&lt;br /&gt;      row_options[:id] += '_' + @template.send(:dom_id, c)&lt;br /&gt;      @template.concat( @template.content_tag('tr', @template.capture(c,  &amp;block ), row_options), block.binding )&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-1067057104948869037?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/1067057104948869037/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=1067057104948869037' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/1067057104948869037'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/1067057104948869037'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2007/01/capture-binding-and-other-diabolical.html' title='capture, binding and other diabolical machinations'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-4706474677297137885</id><published>2007-01-22T20:00:00.000-05:00</published><updated>2007-01-22T20:01:11.212-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='review'/><title type='text'>Ruby Cookbook from O'Reilly</title><content type='html'>Ruby Cookbook: Recipes for Object-Oriented Scripting&lt;br /&gt;by Lucas Carlson &amp; Leonard Richardson&lt;br /&gt;&lt;br /&gt;ISBN 0-596-52369-6&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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).  &lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;All told, this book does an amazing job of going through a wide range of problems, from Strings &amp; Numbers to Meta-Programming &amp; Rails and Graphics.  This book is loaded with useful bits of information and I would highly recommend adding it to your collection.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-4706474677297137885?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/4706474677297137885/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=4706474677297137885' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/4706474677297137885'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/4706474677297137885'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2007/01/ruby-cookbook-from-oreilly.html' title='Ruby Cookbook from O&apos;Reilly'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-6897736167162233464</id><published>2007-01-17T22:13:00.000-05:00</published><updated>2007-01-17T22:33:16.281-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Some Weird Weird Ruby (or nest Classes and Inheritence)</title><content type='html'>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 &lt;br /&gt;&lt;br /&gt;&lt;pre class='code'&gt;&lt;br /&gt;class PageBlock &lt; ActiveRecord::Base&lt;br /&gt;  class &lt;&lt; self&lt;br /&gt;    # I want to be able to configure the underlying DataSerializer&lt;br /&gt;    # in the containing class&lt;br /&gt;    delegate :set_valid_serialized_keys, :to =&gt; :data_serializing_class&lt;br /&gt;    def data_serializing_class&lt;br /&gt;      self::DataSerializer&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;  class DataSerializer&lt;br /&gt;    class &lt;&lt; self&lt;br /&gt;      def set_valid_serialized_keys( *attrs )&lt;br /&gt;        write_inheritable_attribute :valid_serialized_keys, attrs&lt;br /&gt;        class_inheritable_reader    :valid_serialized_keys&lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt;    &lt;br /&gt;    def add_data(data = {})&lt;br /&gt;      data.assert_valid_keys(valid_serialized_keys)&lt;br /&gt;      ... do the magic ...&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Now &lt;br /&gt;&lt;pre&gt;&lt;strong&gt;&lt;br /&gt;$ PageBlock.valid_serialized_keys&lt;br /&gt;=&gt; NoMethodError: undefined method `valid_serialized_keys' for PageBlock::DataSerializer:Class&lt;br /&gt;&lt;/strong&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="code"&gt;&lt;br /&gt;class PageLinkBlock &lt; PageBlock&lt;br /&gt;  set_valid_serialized_keys :path, :name&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;strong&gt;&lt;br /&gt;$ PageBlock.valid_serialized_keys&lt;br /&gt;=&gt; [:path, :name]&lt;br /&gt;&lt;/strong&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;DOH!!!  Man that is definitely not the behavior I want.  I guess my nested Class has some behavior that I'm not aware of. &lt;em&gt;I'm certain the behavior is intentional, I just didn't know about it&lt;/em&gt;&lt;br /&gt;&lt;br /&gt;So the solution?&lt;br /&gt;&lt;br /&gt;&lt;pre class="code"&gt;&lt;br /&gt;class PageBlock &lt; ActiveRecord::Base&lt;br /&gt;  class &lt;&lt; self&lt;br /&gt;    # I want to be able to configure the underlying DataSerializer&lt;br /&gt;    # in the containing class&lt;br /&gt;    delegate :set_valid_serialized_keys, :to =&gt; :data_serializing_class&lt;br /&gt;&lt;br /&gt;    def data_serializing_class&lt;br /&gt;      self::DataSerializer&lt;br /&gt;    end&lt;br /&gt;&lt;strong&gt;&lt;br /&gt;    def inherited(page_block_subclass)&lt;br /&gt;      page_block_subclass.module_eval &lt;&lt;-EOV&lt;br /&gt;      class DataSerializer &lt; PageBlock::DataSerializer&lt;br /&gt;      end&lt;br /&gt;      EOV&lt;br /&gt;      super&lt;br /&gt;    end&lt;br /&gt;&lt;/strong&gt;&lt;br /&gt;  end&lt;br /&gt;  class DataSerializer&lt;br /&gt;    class &lt;&lt; self&lt;br /&gt;      def set_valid_serialized_keys( *attrs )&lt;br /&gt;        write_inheritable_attribute :valid_serialized_keys, attrs&lt;br /&gt;        class_inheritable_reader    :valid_serialized_keys&lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt;    &lt;br /&gt;    def add_data(data = {})&lt;br /&gt;      data.assert_valid_keys(valid_serialized_keys)&lt;br /&gt;      ... do the magic ...&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Then I define PageLinkBlock and&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;strong&gt;&lt;br /&gt;$ PageBlock.valid_serialized_keys&lt;br /&gt;=&gt; NoMethodError: undefined method `valid_serialized_keys' for PageBlock::DataSerializer:Class&lt;br /&gt;&lt;/strong&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Alas, the behavior that I want.  Time to dig out the Pickaxe Book to see if there is a reference to this.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-6897736167162233464?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/6897736167162233464/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=6897736167162233464' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/6897736167162233464'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/6897736167162233464'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2007/01/some-weird-weird-ruby-or-nest-classes.html' title='Some Weird Weird Ruby (or nest Classes and Inheritence)'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-5863935105719680081</id><published>2007-01-16T19:32:00.000-05:00</published><updated>2007-01-16T19:41:10.913-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Helper Module Testing</title><content type='html'>First and foremost&lt;br /&gt;&lt;br /&gt;&lt;pre class="code"&gt;&lt;br /&gt;sudo gem install ZenTest&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Then in your app/test/test_helper.rb add&lt;br /&gt;&lt;br /&gt;&lt;pre class="code"&gt;&lt;br /&gt;require 'test/rails'&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Then make a test class&lt;br /&gt;&lt;pre class="code"&gt;&lt;br /&gt;class FooHelper&lt;br /&gt;  def print_hello_world&lt;br /&gt;    content_tag('p', 'Hello World!')&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;class FooHelperTest &lt; Test::Rails::HelperTestCase&lt;br /&gt;  def test_method_goes_here&lt;br /&gt;    assert_equal '&amp;lt;p&amp;gt;Hello World&amp;lt;/p&amp;gt;', print_hello_world&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;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.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-5863935105719680081?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/5863935105719680081/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=5863935105719680081' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/5863935105719680081'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/5863935105719680081'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2007/01/helper-module-testing.html' title='Helper Module Testing'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-2339576455012634448</id><published>2007-01-03T23:39:00.000-05:00</published><updated>2007-01-04T00:00:51.131-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rdoc'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Dear RDoc,</title><content type='html'>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.&lt;br /&gt;&lt;br /&gt;Oh the humanity!  &lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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.  &lt;br /&gt;&lt;br /&gt;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.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-2339576455012634448?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/2339576455012634448/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=2339576455012634448' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/2339576455012634448'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/2339576455012634448'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2007/01/dear-rdoc.html' title='Dear RDoc,'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-8479432066836245875</id><published>2007-01-03T23:16:00.000-05:00</published><updated>2007-01-03T23:38:38.793-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Only write what you need.</title><content type='html'>I don't recall where I read this, but someone once wrote that each line of code should be viewed as a liability.  &lt;br /&gt;&lt;br /&gt;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.  &lt;br /&gt;&lt;br /&gt;So the less code needed to get the job done, the better.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-8479432066836245875?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/8479432066836245875/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=8479432066836245875' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/8479432066836245875'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/8479432066836245875'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2007/01/only-write-what-you-need.html' title='Only write what you need.'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-5536411561251056066</id><published>2006-12-23T13:11:00.000-05:00</published><updated>2006-12-23T13:12:49.436-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Contextual Decoration of a Ruby object</title><content type='html'>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.&lt;br /&gt;&lt;br /&gt;The situation is actually more complicated than what I am modeling below.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;div class='ruby'&gt;&lt;br /&gt;class Import&lt;br /&gt;  attr_reader :start_at, :stop_at&lt;br /&gt;  class &lt;&lt; self&lt;br /&gt;    def run!(&amp;block)&lt;br /&gt;      Thread.new do&lt;br /&gt;        Thread.current['import_log'] = import_log = self.new&lt;br /&gt;        import_log.start!&lt;br /&gt;        yield block if block&lt;br /&gt;        import_log.stop!&lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;  [:start, :stop].eacha do |m|&lt;br /&gt;    define_method("#{m}!") do&lt;br /&gt;      instance_variable_set("@#{m}_at", Time.now)&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def log_change(message)&lt;br /&gt;    ... magical stuff happens here&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;class Account &lt; ActiveRecord::Base&lt;br /&gt;  attr_reader :import_log&lt;br /&gt;  def initialize&lt;br /&gt;    @import_log = Thread.current['import_log']&lt;br /&gt;    super&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def after_save&lt;br /&gt;    import_log.log_change("Saved Account with ID=#{self.id}") if import_log&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/div&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;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.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-5536411561251056066?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/5536411561251056066/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=5536411561251056066' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/5536411561251056066'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/5536411561251056066'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2006/12/contextual-decoration-of-ruby-object.html' title='Contextual Decoration of a Ruby object'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-8552790394512934077</id><published>2006-12-19T21:27:00.000-05:00</published><updated>2006-12-19T21:56:56.916-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='sandman'/><category scheme='http://www.blogger.com/atom/ns#' term='books'/><category scheme='http://www.blogger.com/atom/ns#' term='other'/><title type='text'>Sandman and Desire</title><content type='html'>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?&lt;br /&gt;&lt;br /&gt;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&amp;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.  &lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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&amp;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.&lt;br /&gt;&lt;br /&gt;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).    &lt;br /&gt;&lt;br /&gt;So with the above in mind I decided yet again to forgo getting the Sandman series, but I noticed something strange.  Volumes 1 &amp; 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.  &lt;br /&gt;&lt;br /&gt;So I cleared my shopping cart of some D&amp;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.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-8552790394512934077?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/8552790394512934077/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=8552790394512934077' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/8552790394512934077'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/8552790394512934077'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2006/12/sandman-and-desire.html' title='Sandman and Desire'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-6931455032333004720</id><published>2006-12-14T17:22:00.000-05:00</published><updated>2006-12-14T17:29:58.691-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='gaming'/><category scheme='http://www.blogger.com/atom/ns#' term='other'/><title type='text'>Lightsky Rocks!!!</title><content type='html'>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.&lt;br /&gt;&lt;br /&gt;I am stunned by this gracious gift of time and money.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-6931455032333004720?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/6931455032333004720/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=6931455032333004720' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/6931455032333004720'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/6931455032333004720'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2006/12/lightsky-rocks.html' title='Lightsky Rocks!!!'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-2862189648613862573</id><published>2006-12-12T19:03:00.000-05:00</published><updated>2006-12-12T19:37:17.234-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Protected Find</title><content type='html'>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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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).&lt;br /&gt;&lt;br /&gt;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.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-2862189648613862573?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/2862189648613862573/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=2862189648613862573' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/2862189648613862573'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/2862189648613862573'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2006/12/protected-find.html' title='Protected Find'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-423979884270360533</id><published>2006-12-09T14:00:00.000-05:00</published><updated>2006-12-09T14:17:37.616-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Pimping a Controller</title><content type='html'>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:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;div class="ruby"&gt;&lt;br /&gt;class User &lt; ActiveRecord::Base&lt;br /&gt;  has_many :permissions&lt;br /&gt;  has_many :roles, :through =&gt; :permissions&lt;br /&gt;end&lt;br /&gt;class Permission &lt; ActiveRecord::Base&lt;br /&gt;  belongs_to :role&lt;br /&gt;  belongs_to :user&lt;br /&gt;end&lt;br /&gt;class Role &lt; ActiveRecord::Base&lt;br /&gt;  has_many :permissions&lt;br /&gt;  has_many :users, :through =&gt; :permissions&lt;br /&gt;end&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;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:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;div class="ruby"&gt;&lt;br /&gt;class PermissionsController &lt; ActiveRecord::Base&lt;br /&gt;  before_filter :determine_finder_object&lt;br /&gt;&lt;br /&gt;  def index&lt;br /&gt;    @permissions = @finder_object.find(:all)&lt;br /&gt;    # respond to stuff, though you might want to use different templates&lt;br /&gt;    # based on the finder object&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def determine_finder_object&lt;br /&gt;    if params[:user_id]&lt;br /&gt;      @finder_object = User.find(params[:user_id]).permissions&lt;br /&gt;    elsif params[:role_id]&lt;br /&gt;      @finder_object = Role.find(params[:role_id]).permissions&lt;br /&gt;    else&lt;br /&gt;      @finder_object = Permission&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;As an aside, the above controller would probably end up with several chunks of code that were:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;div class="ruby"&gt;&lt;br /&gt;  if params[:user_id]&lt;br /&gt;    #do something for users&lt;br /&gt;  elsif params[:role_id]&lt;br /&gt;    #do something for roles&lt;br /&gt;  else&lt;br /&gt;    #do something in the generic&lt;br /&gt;  end&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;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:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;div class="ruby"&gt;&lt;br /&gt;determine do |d| &lt;br /&gt; d.user_id { #do something for users}&lt;br /&gt; d.role_id { #do something for roles}&lt;br /&gt;end&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I'm not satisfied with it, but am wondering what I might be able to do.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-423979884270360533?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/423979884270360533/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=423979884270360533' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/423979884270360533'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/423979884270360533'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2006/12/pimping-controller.html' title='Pimping a Controller'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-1444569077248987419</id><published>2006-11-29T21:55:00.000-05:00</published><updated>2006-11-29T22:15:05.852-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Creatively Captivated</title><content type='html'>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).   &lt;br /&gt;&lt;br /&gt;I owe a lot to that trip.&lt;br /&gt;&lt;br /&gt;$ raving_ruby_fanboy += 1&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-1444569077248987419?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/1444569077248987419/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=1444569077248987419' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/1444569077248987419'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/1444569077248987419'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2006/11/creatively-captivated.html' title='Creatively Captivated'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-5439829939058691258</id><published>2006-11-28T22:36:00.000-05:00</published><updated>2006-11-28T22:44:50.144-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Peepcode's Restful Rails</title><content type='html'>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.&lt;br /&gt;&lt;br /&gt;http://peepcode.com/articles/2006/10/08/restful-rails&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-5439829939058691258?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/5439829939058691258/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=5439829939058691258' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/5439829939058691258'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/5439829939058691258'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2006/11/peepcodes-restful-rails.html' title='Peepcode&apos;s Restful Rails'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-2133020642216550713</id><published>2006-11-27T20:18:00.000-05:00</published><updated>2006-11-27T20:27:56.920-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Loving the test dots ..................F.....................</title><content type='html'>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).  &lt;br /&gt;&lt;br /&gt;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.  &lt;br /&gt;&lt;br /&gt;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.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-2133020642216550713?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/2133020642216550713/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=2133020642216550713' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/2133020642216550713'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/2133020642216550713'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2006/11/loving-test-dots-f.html' title='Loving the test dots ..................F.....................'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-9077141467988176024</id><published>2006-11-26T22:49:00.000-05:00</published><updated>2006-11-26T23:02:23.051-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Creating a unified UI</title><content type='html'>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.   &lt;br /&gt;&lt;br /&gt;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.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-9077141467988176024?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/9077141467988176024/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=9077141467988176024' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/9077141467988176024'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/9077141467988176024'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2006/11/creating-unified-ui.html' title='Creating a unified UI'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-1698851213121447055</id><published>2006-11-26T15:13:00.000-05:00</published><updated>2006-11-26T15:21:16.015-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='OGC'/><category scheme='http://www.blogger.com/atom/ns#' term='gaming'/><title type='text'>BAB ruby class for OGL system</title><content type='html'>Jamis Buck's &lt;a href="http://weblog.jamisbuck.org/assets/2006/9/27/dice.rb"&gt;Dice class&lt;/a&gt; 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 &lt;a href="http://reclusive-geek.blogspot.com/2006/11/open-game-license-for-items-posted-on.html"&gt;OGL&lt;/a&gt;.  This class also has associated tests, if you are truly curious.  &lt;br /&gt;&lt;pre&gt;&lt;code class="ruby"&gt;&lt;br /&gt;$ 5.bab.good &lt;br /&gt;=&gt; 5&lt;br /&gt;&lt;br /&gt;$ 4.bab.poor # Suck it wizard&lt;br /&gt;=&gt; 2&lt;br /&gt;&lt;br /&gt;$ 3.bab.medium + 5.bab.poor + 3.bab.poor # 3 Rogue / 5 Wizard / 3 Arcane Trickster&lt;br /&gt;=&gt; 5&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;div class="ogl"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;code class="ruby"&gt;&lt;br /&gt;class Bab&lt;br /&gt;  class InvalidCount &lt; Exception # :nodoc:&lt;br /&gt;  end&lt;br /&gt;  TYPES = [:good, :medium, :poor]&lt;br /&gt;  attr_reader :count, :epic_count&lt;br /&gt;&lt;br /&gt;  def initialize( count )&lt;br /&gt;    raise InvalidCount unless count.is_a?(Integer) &amp;&amp; count &gt; 0&lt;br /&gt;    @count = count &gt; 20 ? 20 : count&lt;br /&gt;    @epic_count = count &gt; 20 ? count - 20 : 0&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def good&lt;br /&gt;    self.count + epic_value    &lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def medium&lt;br /&gt;    ((self.count * 3) / 4)  + epic_value&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def poor&lt;br /&gt;    (self.count / 2)  + epic_value&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  private&lt;br /&gt;  def epic_value&lt;br /&gt;    (self.epic_count + 1) / 2 &lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;class Integer&lt;br /&gt;  def bab&lt;br /&gt;    Bab.new(self)&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-1698851213121447055?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/1698851213121447055/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=1698851213121447055' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/1698851213121447055'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/1698851213121447055'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2006/11/bab-ruby-class-for-d20-system.html' title='BAB ruby class for OGL system'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-3986909273093534135</id><published>2006-11-26T14:52:00.000-05:00</published><updated>2006-11-26T15:21:42.798-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='OGL'/><category scheme='http://www.blogger.com/atom/ns#' term='gaming'/><title type='text'>The Open Game License for Items posted on this blog.</title><content type='html'>OPEN GAME LICENSE Version 1.0a&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;3.Offer and Acceptance: By Using the Open Game Content You indicate Your acceptance of the terms of this License.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;10 Copy of this License: You MUST include a copy of this License with every copy of the Open Game Content You Distribute.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;15 COPYRIGHT NOTICE&lt;br /&gt;Open Game License v 1.0 Copyright 2000, Wizards of the Coast, Inc.&lt;br /&gt;&lt;br /&gt;Jeremy Friesen copyright&lt;br /&gt;&lt;br /&gt;OPEN GAME CONTENT: Open game content will in box, as indicated below.&lt;br /&gt;&lt;br /&gt;&lt;div class="ogl"&gt;This is open game content&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-3986909273093534135?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/3986909273093534135/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=3986909273093534135' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/3986909273093534135'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/3986909273093534135'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2006/11/open-game-license-for-items-posted-on.html' title='The Open Game License for Items posted on this blog.'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-1587242282116906797</id><published>2006-11-26T12:57:00.000-05:00</published><updated>2006-11-26T13:07:47.914-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='osx'/><category scheme='http://www.blogger.com/atom/ns#' term='textmate'/><title type='text'>OSX and Textmate and Ruby</title><content type='html'>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 &lt;strong&gt;Rails &gt; Model &gt; Show DB Schema for Current Class&lt;/strong&gt; command.  I tried to run it, and it squacked.  The process.rb file said that rubygems was missing.  Odd.  &lt;br /&gt;&lt;br /&gt;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).  &lt;br /&gt;&lt;br /&gt;With the changes in place the command worked splendidly.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-1587242282116906797?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/1587242282116906797/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=1587242282116906797' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/1587242282116906797'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/1587242282116906797'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2006/11/osx-and-textmate-and-ruby.html' title='OSX and Textmate and Ruby'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-37792061.post-116452005617859629</id><published>2006-11-26T00:44:00.000-05:00</published><updated>2006-11-26T00:56:44.850-05:00</updated><title type='text'>As if to pretend like I'll post to this thing</title><content type='html'>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.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/37792061-116452005617859629?l=reclusive-geek.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://reclusive-geek.blogspot.com/feeds/116452005617859629/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=37792061&amp;postID=116452005617859629' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/116452005617859629'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/37792061/posts/default/116452005617859629'/><link rel='alternate' type='text/html' href='http://reclusive-geek.blogspot.com/2006/11/as-if-to-pretend-like-ill-post-to-this.html' title='As if to pretend like I&apos;ll post to this thing'/><author><name>Jeremy</name><uri>http://www.blogger.com/profile/16086849299137461149</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>
