Tuesday, November 28, 2006

Ruby: Protected class methods

Given the following class subjects_xml and subjects_html are public, not protected.
class Blog
def self.available_subjects(format)
case format
when :xml then subjects_xml
when :html then subjects_html
end
end

protected
def self.subjects_xml
[xml implementation]
end

def self.subjects_html
[html implementation]
end
end
The def self. code can be thought of as defining a class method of Blog, but it is actually defining a method on self, where self is the virtual class of Blog. The protected visibility modifier is being used to specify the visibility of the methods of Blog. Since the subjects_xml and subjects_html methods are being defined on self (the virtual class of Blog) the protected visibility is not applied.

It is possible to define protected class methods in various ways.
class Blog
class << self
def available_subjects(format)
case format
when :xml then subjects_xml
when :html then subjects_html
end
end

protected
def subjects_xml
[xml implementation]
end

def subjects_html
[html implementation]
end
end
end

class Blog
def self.available_subjects(format)
case format
when :xml then subjects_xml
when :html then subjects_html
end
end

def self.subjects_xml
[xml implementation]
end

def self.subjects_html
[html implementation]
end

class<<self;self;end.send :protected, :subjects_xml, :subjects_html
end

Tuesday, November 21, 2006

RubyGems: Absolute Paths

In a previous post about RubyGems I mentioned that I prefer to unpack gems locally to remove dependency issues. While this does provide a code sharing solution it also relies on the developers of the RubyGems to provide compatible solutions. Unfortunately, absolute paths seems to be something many RubyGem creators are overlooking.

I'm not picking on postgres-pr, but I will use it as an example. Below the current implementation of postgres.rb is shown.
begin
require 'postgres.so'
rescue LoadError
require 'postgres-pr/postgres-compat'
end
The above implementation works when installed; however, when unpacked and uninstalled it raises the following error.
MissingSourceFile: no such file to load — postgres-pr/postgres-compat
To fix this issue you can manually change all the require statements to use absolute paths or you can add the directory where the gem is unpacked to the load path. I'm not a big fan of making changes to an unpacked gem since I will lose my changes when a new gem version comes out; therefore, adding another directory to the load path seems to be the lesser of two evils. Adding a directory to the load path in Ruby is easy enough.
$:.unshift [path]
But, I really don't think this should be necessary. Instead RubyGem developers should make the effort to use absolute paths.
require File.dirname(__FILE__) + [relative path]

Ruby: Testing Rake Tasks

If you develop in Ruby and you practice Test Driven Development you may consider the question: How do I test Rake tasks?

Let's start with a task that drops each table from a database:
  desc "Wipe the database"
task :wipe => :environment do
connection = ActiveRecord::Base.connection
connection.tables.each do |table|
begin
connection.execute("drop table #{table}")
puts "* dropped table '#{table}'"
rescue
end
end
end
It may be possible to test this task in it's current form; however, rewriting the task can create a familiar and easily testable class. The key is encapsulating the behavior of the task in a class.
  desc "Wipe the database"
task :wipe => :environment do
DatabaseFacade.clean
end

class DatabaseFacade
def self.clean
connection = ActiveRecord::Base.connection
connection.tables.each do |table|
begin
connection.execute("drop table #{table}")
puts "* dropped table '#{table}'"
rescue
end
end
end
end
After moving the behavior into a class method it's possible to test the class the same as any other class.
class DatabaseFacadeTest < Test::Unit::TestCase
def test_clean_drops_all_tables
ActiveRecord::Base.expects(:connection).returns(connection=mock)
connection.expects(:tables).returns(['table1','table2'])
connection.expects(:execute).with("drop table table1")
connection.expects(:execute).with("drop table table2")
DatabaseFacade.clean
end
end

Monday, November 20, 2006

BNL: Common Pieces

Business Natural Languages: Introduction
Business Natural Languages: The Problem
Business Natural Languages: The Solution
Business Natural Languages: DRY code, DAMP BNL
Business Natural Languages: Moving to the web and multiple contexts
Business Natural Languages: Limitations
Business Natural Languages: Common Pieces

What is common between applications using Business Natural Languages?
Applications using Business Natural Languages often have common functionality. The most often seen functionality includes workflow for approving proposed business logic, history containing previous versions of business logic, an environment that allows you to test proposed business logic, and an editing environment that includes syntax and contextual validation.

All of these features have been fairly simple to implement in the systems I've worked on. In general the business logic is stored in a database as a string. The same table also contains a status field where you can store the current state of the business logic. For example, logic could be marked as 'proposed' if it has not yet been approved. Keeping a history is also as easy as inserting a new row into the table for each newly proposed string of logic. When the new logic is approved, the old logic is kept with a status of expired. In general, once logic has expired, it is not brought back to an active status. If you wish to revert, you should clone an expired version and activate it within the workflow.

The most complicated common piece to implement is the test environment. This environment often allows you to write ad-hoc logic and immediately execute to view results. Implementation of the test environment varies by project; however, in general it means executing the logic in a slightly different context. For example, you can still use the same code to process the business logic; however, you may persist the results to a table where only test data is persisted. Another option would allow you to execute the logic and see the results rendered in the UI instead of persisting to the database. Each project will have it's own implementation that makes sense, but in general they will all require some type of test environment.

While creating an editing environment that includes syntax and contextual validation sounds like a large task, it's actually nothing more than evaluating the same BNL script in another context. As previously shown a BNL can be executed to create a parse tree that is used for execution. For syntax and contextual validation the same BNL can be executed to create a parse tree of syntax validation objects. Once the syntax validation parse tree is created it can be used to provide feedback to the user. The most common question to this approach is how to keep the syntax validation classes in sync with the primary context classes. The answer to this question will vary with project and language; however, using Ruby it's possible to build the syntax checking classes using metaprogramming and using the primary context classes as templates. For example the RootSyntaxValidation class could define it's methods by grabbing all of the methods of Root that begin with an underscore.
class RootSyntaxValidation
Root.instance_methods.grep(/^_[^_]/).each do |phrase|
define_method phrase.to_sym do
...
end
end

def method_missing(sym, *args)
SyntaxError.new(sym)
end
end
Now that we've touched on identifying the common pieces the upcoming chapters will provide examples of how to implement these ideas in our sample application.

Friday, November 17, 2006

BNL: Limitations

Business Natural Languages: Introduction
Business Natural Languages: The Problem
Business Natural Languages: The Solution
Business Natural Languages: DRY code, DAMP BNL
Business Natural Languages: Moving to the web and multiple contexts
Business Natural Languages: Limitations
Business Natural Languages: Common Pieces

Limitations of Business Natural Languages
Business logic can be highly complex and unrelated. A Business Natural Language becomes more complicated as it expands the scope of problems it addresses. As the complication increases the value of the language decreases. Therefore, when designing a Business Natural Language it should address only related business logic. One application can contain several Business Natural Languages. By limiting the scope of each Business Natural Language it will be easier to understand and use.

Every application should not include a Business Natural Language. In fact, businesses should not invest in creating a Business Natural Language unless the application contains logic specific to the business. An example of an application that, despite being a complex application, likely doesn't warrant the use of a Business Natural Language is a content management system (CMS). Creating a CMS is no easy task; however, the business simply wants an application that allows them to easily display static information. There is no reason to design a Business Natural Language when an application, such as a CMS, does not contain any business logic.

Business Natural Languages are designed to empower people who contain specific knowledge. By empowering these individuals the business can run more efficiently. However, Business Natural Languages should not be used as an excuse to delegate work. For example, even though you could empower a subject matter expert to specify the database connection string, you shouldn't do it. Maintaining a database connection string is the responsibility of the IT staff. Creating a Business Natural Language is unnecessary in this scenario since the IT staff should be perfectly comfortable with XML, YAML, or some other form of configuration file.

It should be clear that you could use a Business Natural Language within a configuration file. This makes sense because configuration files are simply files that contain logic expressed in a Domain Specific Language, and Business Natural Languages, XML, YAML, etc are all DSLs. However, before investing the effort to develop a Business Natural Language you should ensure that someone would benefit from the investment. While it may be technically interesting to use a Business Natural Language to configure an Inversion of Control container, it is likely a waste of time. When determining whether or not to make the investment it is important to consider the following questions:

Who will be responsible for writing the business logic?
What are the chances that this logic will change?

If the answer to the first question is "a subject matter expert whose role does not require technical depth" and the answer to the second question is "it's possible" then you likely have a good candidate for a Business Natural Language. When thinking about the first question it is more important to consider the subject matter expert's role than a specific person's skill level. If you only think in terms of the person currently in that role you risk creating an un-maintainable system if the technically savvy subject matter expert leaves their current role. However, if you design a Business Natural Language that can be understood by any subject matter expert in that role, then your systems maintainability is greatly increased. The second question and answer should be fairly straightforward. It makes sense to abstract the logic in a way that allows a subject matter expert to maintain it if the logic has the possibility to change.

Another situation where it could be unclear whether to use a Business Natural Language or not is when a subject matter expert creates a specification that is not specific to the business. For example, a subject matter expert may create a requirement for the system such as 'the message entry textbox on the invitation page should not allow more than 500 characters'. While this requirement does come from a subject matter expert it is not a requirement imposed by the business. These types of requirements do not often warrant the investment of creating a Business Natural Language. A major reason why a Business Natural Language loses value here is because the business probably does not have specific language around field validation. So, from their perspective it's almost the same level of effort to read either of these two DSLs:

'the message entry textbox on the invitation page should not allow more than 500 characters'
page: invitation; field: message entry; limit: 500

That being said, if some of your validations were tied to business logic then it does make sense to use a Business Natural Language. Examples of this could be to only accept 9-digit social security numbers, Florida driver's licenses must always start with the first letter of the driver's last name, or if you claim a dependent you must specify their social security number also. Clearly, if some of your validation logic belongs in a Business Natural Language then it makes sense to express it all in one or more Business Natural Languages. If you were to maintain logic in two places, such as an XML file and a Business Natural language logic script, usability clearly suffers. On a similar note, if your Business Natural Language begins to explode with all the types of validations you are supporting, you should consider grouping similar validations in separate Business Natural Languages. As previously stated, you should look to separate a Business Natural Language when it will increase readability and ease of use.

One major advantage of creating a Business Natural Language is empowering subject matter experts to specify the behavior of the system in the language of their business. Unfortunately, language rarely transcends multiple corporations. Therefore, it is unlikely that a generic set of Business Natural Languages could be created to address the needs of a business vertical. (I.e. insurance, leasing, etc)

Monday, November 13, 2006

Rails: Get the schema_info version number from Rake

Recently a coworker asked if there was a simple way to get the current version out of the schema_info table. Of course, one simple answer is to simply look in the database.

However, if you want to quickly pull the version from a command line, this rake task should do the trick*:
namespace :db do
desc "Returns the current schema version"
task :version => :environment do
puts "Current version: " + ActiveRecord::Migrator.current_version.to_s
end
end
* 2nd version submitted by Doug as a comment.

Rails: Textmate Footnotes

If you are developing Rails applications using TextMate you should not be without TextMate Footnotes.
This plugin saves you time when moving between your browser and Textmate. It adds clickable links to the Rails backtrace when an error occurs, and clickable "footnotes" to the bottom of each page (in development mode only). These links open the correct file in Textmate and move your cursor to the precise line of interest.

In addition, you will have session, params and cookies information available to you in development mode on all rhtml rendered pages.
It's simple to add to your project: go to the command line and navigate to vendor/plugins and issue the following svn command.
svn propset svn:externals "footnotes http://macromates.com/svn/Bundles/trunk/Bundles/Rails.tmbundle/Support/plugins/footnotes" .

Sunday, November 12, 2006

Rails: Generate SQL from Migrations

Unfortunately, my new client will not allow migrations to be run in production. This isn't a surprise since they have a DB group that maintains their databases. And, the DB group requires application developers to submit SQL scripts when database modifications are necessary.

At first this may sound like a pain, but it's actually quite easy to capture the SQL that migrations generate. There may be easier ways; however, it seemed simple enough to temporarily change the behavior of the execute method.
require 'find'

class SqlWriter

def self.write(path, string)
File.open(path, 'w') { |file| file << string }
end

end

class MigrationSql
class << self
def execute
connection = ActiveRecord::Base.connection
old_method = connection.class.instance_method(:execute)

define_execute(connection) { |*args| SqlWriter.write(@sql_write_path, args.first) }

root = RAILS_ROOT + "/db/migrate"
output_dir = root + "/../migration_sql"
Dir.mkdir output_dir unless File.exists? output_dir
Find.find(root) do |path|
unless path == root
require path
file = File.basename(path, ".rb")
write_sql(connection, output_dir, file, :up)
write_sql(connection, output_dir, file, :down)
end
end

define_execute(connection) { |*args| old_method.bind(self).call(*args) }
end

def write_sql(connection, output_dir, file, direction)
connection.instance_variable_set :@sql_write_path, output_dir + "/" + file + "_#{direction}.sql"
file.gsub(/^\d\d\d_/,'').camelize.constantize.send direction
end

def define_execute(connection, &block)
connection.class.send :define_method, :execute, &block
end
end
end
Now that we have the behavior necessary we can wrap the call to the execute method of the MigrationSql class in a rake task.
namespace "db" do
namespace "generate" do
desc "generate sql for migrations"
task "migration_sql" => :environment do
MigrationSql.execute
end
end
end

Friday, November 03, 2006

BNL: Moving to the web and multiple contexts - Update

Business Natural Languages: Introduction
Business Natural Languages: The Problem
Business Natural Languages: The Solution
Business Natural Languages: DRY code, DAMP BNL
Business Natural Languages: Moving to the web and multiple contexts
Business Natural Languages: Limitations
Business Natural Languages: Common Pieces

In 'The Solution' we proved our concept by replacing the existing system with one that allows the business logic to be written by the subject matter experts. However, the solution we provided has a few limitations our client would like us to overcome:
  • Our application can only be run from the command line, they would like a web interface.
  • Our application only executes compensation calculations for the entire group; they would like to execute individual compensation calculations.
  • The sales data that was previously stored in yaml files should now be read from their database.
  • Our application does not scale well; they would like the burden of calculation to be transferred to their database server when running for every employee.
Now that we are required to connect to the database and move to the web, Ruby on Rails is the obvious solution. This book assumes familiarity with creating a new Ruby on Rails applications. If you need more info on Ruby on Rails applications I suggest 'Agile Web Development with Rails' by Dave Thomas and DHH.

For those coding along, the next steps are:
1. create a Rails application named payroll
2. Set up a dev database and alter database.yml to point to your new database.
3. Add the following code to environment.rb:
Inflector.inflections do |inflect|
inflect.uncountable %w( employee_info )
end
We also need to create the sales data that we would expect to be available when our application executes. Creating the sales tables and data is accomplished with the CreateSalesData Migration class.
class EmployeeInfo < ActiveRecord::Base; end

class CreateSalesData < ActiveRecord::Migration
def self.up
create_table :employee_info, :force=>true do |table|
table.column :employee, :string
table.column :deals_this_month, :integer
table.column :year_old_deals, :integer
table.column :gross_profit, :integer
end

EmployeeInfo.create(:employee=>'jjones',
:deals_this_month=>7,
:year_old_deals=>2,
:gross_profit=>1400000)
EmployeeInfo.create(:employee=>'jjohnson',
:deals_this_month=>12,
:year_old_deals=>1,
:gross_profit=>2200000)
end

def self.down
drop_table :employee_info
end
end
After creating the sales data you can create the following SalesInfo class that represents the facade to the sales data. Note: The SalesInfo class still returns EmployeeInfo instances, and EmployeeInfo has now become an ActiveRecord::Base derived class.
class SalesInfo

def self.method_missing(sym, *args)
result = EmployeeInfo.find_by_employee sym.to_s
super if result.nil?
result
end

end

class EmployeeInfo < ActiveRecord::Base
end
With the sample data taken care of, the first thing necessary to accommodate the new requirements is create a table to store the compensation logic.
class CreateCompensationScripts < ActiveRecord::Migration
def self.up
create_table :compensation_scripts do |table|
table.column :logic, :string, :limit=>1000
end
end

def self.down
drop_table :compensation_scripts
end
end
We are also going to need a few views, a model, and a controller to allow the business users to input new compensation logic.

File: new.rhtml
<% form_for :script, @script, :url => { :action => "create" } do |form| %>
Compensation Script<br>
<%= form.text_area :logic %><br>
<%= submit_tag %>
<% end %>
File: index.rhtml
Employees:
<ul>
<% @scripts.each do |script| %>
<li>
<%= script.employee_name %> -
<%= link_to 'view', :action => :view, :id => script.id %> |
<%= link_to 'execute', :action => :execute, :id => script.id %>
</li>
<% end %>
</ul>
<%= link_to 'Execute All', :action=>:execute_all %> |
<%= link_to 'Create new compensation script', :action=>:new %>
File: view.rhtml
<pre><%= @script.logic %><pre>
<%= link_to 'back', :action => :index %>
File: compensation_script.rb
class CompensationScript < ActiveRecord::Base

def employee_name
logic.split(/\n/).first.gsub(/^employee\s/, '').chomp
end

def employee_short_name
name_parts = employee_name.split
(name_parts.first[0,1] + name_parts.last).downcase
end
end
File: payroll_controller.rb
class PayrollController < ApplicationController

def index
@scripts = CompensationScript.find :all
end

def new
@script = CompensationScript.new
end

def create
CompensationScript.create(params[:script])
redirect_to :action => :index
end

def view
@script = CompensationScript.find(params[:id])
end

def execute
script = CompensationScript.find(params[:id])
vocabulary = InlineVocabulary.new(script.employee_short_name)
@compensation = CompensationParser.parse(script.logic, vocabulary, InlineContext.new)
end

def execute_all
@compensations = []
CompensationScript.find(:all).each do |script|
vocabulary = SqlVocabulary.new(script.employee_short_name)
@compensations << CompensationParser.parse(script.logic, vocabulary, SqlContext.new)
end
end

end
As you can see there is logic for executing also included; however, for the time being let's focus on adding our compensation logic to the application. To store the compensation logic navigate to http://localhost:3000/payroll and click the 'Create new compensation script' link, then enter the following logic for John Johnson.

employee John Jones
compensate $2500 for each deal closed in the past 30 days
compensate $500 for each active deal that closed more than 365 days ago
compensate 5% of gross profits if gross profits are greater than $1,000,000
compensate 3% of gross profits if gross profits are greater than $2,000,000
compensate 1% of gross profits if gross profits are greater than $3,000,000

Repeat the previous steps and enter in the logic for Jackie Johnson.

employee Jackie Johnson
compensate $3000 for each deal closed in the past 30 days
compensate $800 for each active deal that closed more than 365 days ago
compensate 5% of gross profits if gross profits are greater than $1,000,000
compensate 5% of gross profits if gross profits are greater than $2,000,000

After the logic is saved to the database you should be able to click the 'view' link from the index page to verify that the logic has been entered correctly.

Now, back to the previously mentioned execute logic. The index page has an 'execute' link that links to a page that doesn't yet exist. The execute.rhtml file is a simple page that shows the employee's name and their calculated bonus:

File: execute.rhtml
<%= @compensation.name %> compensation: <%= number_to_currency @compensation.amount %><br>
<%= link_to 'back', :action => :index %>
A brief look in the PayrollController class reveals that execute method relies on the InlineVocabulary, CompensationParser, and InlineContext classes to create a parse tree from the logic stored in a CompensationScript. The InlineVocabulary has been previously shown as the CompensationVocabulary class. It has been renamed to clearly express intent and allow the SqlVocabulary class to be defined.

File: inline_vocabulary.rb
class InlineVocabulary
extend Vocabulary

def initialize(data_for)
@data_for = data_for
end

def logic_initializer
''
end

phrase "active deal that closed more than 365 days ago!" do
SalesInfo.send(@data_for).year_old_deals.to_s
end

phrase "are greater than" do
" > "
end

phrase "deal closed in the past 30 days!" do
SalesInfo.send(@data_for).deals_this_month.to_s
end

phrase "for each" do
"*"
end

phrase "gross profits" do
SalesInfo.send(@data_for).gross_profit.to_s
end

phrase "if" do
" if "
end

phrase "of" do
"*"
end

end
The Vocabulary module, which InlineVocabulary extends, remains unchanged; however, I will show it again for completeness.

File: vocabulary.rb
module Vocabulary

def phrase(name, &block)
define_method :"_#{name.to_s.gsub(" ","_")}", block
end

end
The InlineContext class introduces another layer of abstraction. This is necessary to allow the compensation to be evaluated in various contexts. When the execute method is invoked the InlineVocabulary will create valid Ruby code, thus allowing the InlineContext class to simply call instance_eval to evaluate and return.

File: inline_context.rb
class InlineContext

def evaluate(logic)
instance_eval(logic)
end

end
Processing the Business Natural Language is basically the same as the previous command line application; therefore, the CompensationParser class remains largely the same as the previously shown example. The only change to the class is that the parse method now accepts a context and passes this context to the instance of the Root object that is created.

File: compensation_parser.rb
class CompensationParser

class << self

def parse(script, vocabulary, context)
root = Root.new(vocabulary, context)
script.split(/\n/).each { |line| root.process(preprocess(line)) }
root
end

def preprocess(line)
line.chomp!
line.delete!('$,')
line.gsub!(/(\d+)%/, '\1percent')
line.gsub!(/\s/, '._')
"_#{line.downcase}!"
end

end

end
Similar to the CompensationParser class, the Root class also remains largely the same notwithstanding taking a context instance as a constructor argument and passing it to each newly created instance of the Compensation class.

File: root.rb
class Root
extend Vocabulary

def initialize(vocabulary, eval_context)
@compensations = []
@vocabulary, @eval_context = vocabulary, eval_context
end

def name
@employee.name
end

def amount
@compensations.collect do |compensation|
compensation.amount
end.inject { |x, y| x + y }
end

def process(line)
instance_eval(line)
end

phrase :employee do
@employee = Employee.new
end

phrase :compensate do
@compensations << Compensation.new(@vocabulary, @eval_context)
@compensations.last
end

end
The Root class also uses the unchanged Employee class.

File: employee.rb
class Employee

def initialize
@name_parts = []
end

def method_missing(sym,*args)
@name_parts << sym.to_s.delete('_!')
self
end

def name
@name_parts.collect { |part| part.to_s.capitalize }.join(' ')
end

end
The Compensation class also basically mirrors it's previously show version. A minimal change now initializes the compensation_logic instance variable to the value returned from the logic_initializer method of the vocabulary construtor argument. The logic_initializer method of the InlineVocabulary is an empty string; however, we will see later how the SqlVocabulary class uses this to create valid SQL statements. Another notable change to the Compensation class allows for the logic to be executed in various ways. This subtle change can be found in the amount method of the Compensation class. By delegating the evaluation to another class we can simply instance_eval, which InlineContext does, or pass the burden of evaluation on to another process, which will be seen within the SqlContext class.

Take a minute to contemplate this and the previous paragraph if you don't yet appreciate the value of various contexts. Various contexts allow you to evaluate the same code and produce different results based on your current needs. This can be used to delegate responsibility to an external application (e.g. Database server), external process (e.g. a process written in another language), or simply evaluate the code within your application in another way (e.g. Syntax checking). When using Domain Specific Languages, context is king or at least royalty.

Getting back on track, if you've been typing along you should now be able to execute the logic you entered for both John Jones and Jackie Johnson. At this point we have fulfilled the first three of the new requirements. A quick check reveals that both totals report the same numbers previously generated.

John Jones compensation: $88,500.00
Jackie Johnson compensation: $256,800.00

To accommodate the last requirement we are going to create a new page that displays the return values from the database, use the previously shown execute_all method of the PayrollContorller class, create the SqlVocabulary class, and create the SqlContext class which will be used to evaluate the SQL statements generated by the SqlVocabulary class.

The execute_all.rhtml file is responsible for displaying the results of executing the compensation logic for all employees.

File: execute_all.rhtml
<% @compensations.each do |compensation| %>
<%= compensation.name %> compensation: <%= number_to_currency compensation.amount %><br>
<% end %>
<%= link_to 'back', :action => :index %>
The execute_all method of the PayrollController is similar to the execute method except it iterates through each CompensationScript and appends the results in an instance variable array. Another difference is the use of the SqlVocabulary and SqlContext classes. The SqlVocabulary class behaves exactly the same as the InlineVocabulary class, but returns fragments of SQL statements instead of Ruby code. The SqlVocabulary class also defines the logic_initializer method to return 'select ', the necessary prefix for the SQL statements we will generate.

File: sql_vocabulary.rb
class SqlVocabulary
extend Vocabulary

def initialize(data_for)
@data_for = data_for
end

def logic_initializer
'select '
end

def select_column(column)
" (select #{column} from employee_info where employee = '#{@data_for}') "
end

phrase "active deal that closed more than 365 days ago!" do
select_column("year_old_deals")
end

phrase "are greater than" do
" > "
end

phrase "deal closed in the past 30 days!" do
select_column("deals_this_month")
end

phrase "for each" do
"*"
end

phrase "gross profits" do
select_column("gross_profit")
end

phrase "if" do
" where "
end

phrase "of" do
"*"
end

end
The implementation of the SqlContext class uses the execute method of ActiveRecord::Base.connection. This anticlimactic implementation allows us to easily delegate evaluation to the database. I've used this pattern very successfully on two different projects that required calculation and modification of hundreds of millions of records within a few hours.

File: sql_context.rb
class SqlContext

def evaluate(logic)
result = ActiveRecord::Base.connection.select_value logic
result.to_i unless result.nil?
end

end
If you are still typing along you should now be able to navigate from the index page using the 'Execute All' link to a page that shows the following results.

John Jones compensation: $88,500.00
Jackie Johnson compensation: $256,800.00

This doesn't appear very impressive since we already knew how to calculate the results. However, the previously mentioned point of interest is that instead of calculating the results using ruby, the results are calculated from generated sql statements. For example, below is the SQL generated to calculate Jackie Johnson's bonus.
select 3000* (select deals_this_month from employee_info where employee = 'jjohnson') 
select 800* (select year_old_deals from employee_info where employee = 'jjohnson')
select 0.05* (select gross_profit from employee_info where employee = 'jjohnson') where (select gross_profit from employee_info where employee = 'jjohnson') > 1000000
select 0.05* (select gross_profit from employee_info where employee = 'jjohnson') where (select gross_profit from employee_info where employee = 'jjohnson') > 2000000
An important point to note is the that when Jackie Johnson's bonus terms change both the results for execute and execute_all will be updated by the single change that the subject matter expert will make. Obviously, system maintainability has been increased.

At this point we've satisfied the final requirement for applicaiton. The next chapter will focus on some of the limitations of our current implementation and Business Natural Languages in general.

Story Card Format

A friend of mine recently wrote:
Realizing that TW has NDA's to honor, is there anyway you could paraphrase a few User Stories for me so I can see good examples of what they should have? I realize they need only be 2-3 sentences, but since I've no experience with them at all (other than use cases) I don't know what the ideal story should be like. As in, what level of detail are they, verbage wise. Does the customer actually write them on the cards themselves while the developer is nearby to answer any questions?
Below is my response, that I thought might be useful to those adopting Agile.
At ThoughtWorks we generally use a form such as:
As a script author
I would like to save the script language to the database
so that it can be executed and altered in the future.
The underlined words are the 'template'. I've briefly written in the past about why I'm not a huge fan of the template; however, we have had success with it.

At the end of the day a story is a placeholder for a conversation; therefore, use whatever template you can create that will encourage the conversation.