Wednesday, December 27, 2006
Rails: Unit Testing ActiveRecord Validations
There are at least 2 easy ways to unit test the validations of an ActiveRecord::Base subclass.
The most straight-forward way is to create a model and use the
An alternative test implementation could mock the
The load call is a bit messy; however, we can clean that up by adding another method to ActiveRecord::Base.
The most straight-forward way is to create a model and use the
valid? method to determine if it is valid or not. For an example I'm going to reuse the PhoneNumber class I defined in a previous entry on ActiveRecord Unit Testing. The following code shows the three tests I've written to ensure that I've correctly defined a validates_presence_of for digits.class PhoneNumberTest < Test::Unit::TestCaseAfter adding the validates_presence_of call to the PhoneNumber class, all the above tests pass.
Column = ActiveRecord::ConnectionAdapters::Column
test "digits are required to be valid" do
PhoneNumber.stubs(:columns).returns([Column.new("digits", nil, "string", false)])
number = PhoneNumber.new(:digits => "1234567890")
assert_equal true, number.valid?
end
test "invalid with no digits " do
PhoneNumber.stubs(:columns).returns([Column.new("digits", nil, "string", false)])
number = PhoneNumber.new()
assert_equal false, number.valid?
end
test "errors include digits on invalid digits" do
PhoneNumber.stubs(:columns).returns([Column.new("digits", nil, "string", false)])
number = PhoneNumber.new()
number.valid?
assert_equal "can't be blank", number.errors.on('digits')
end
end
class PhoneNumber < ActiveRecord::BaseThis all works fine, but I'm not sure it's necessary to actually cause a validation to occur just to test that one has been defined.
validates_presence_of :digits
end
An alternative test implementation could mock the
validates_presence_of call and load the file. If the validates_presence_of is defined correctly the class methods will be invoked.class PhoneNumberTest < Test::Unit::TestCaseThis method of testing doesn't require actually creating a PhoneNumber instance, instead it tests that the PhoneNumber validations are defined correctly.
Column = ActiveRecord::ConnectionAdapters::Column
test "validates_presence_of is defined for digits" do
PhoneNumber.expects(:validates_presence_of).with(:digits)
load "#{RAILS_ROOT}/app/models/phone_number.rb"
end
end
The load call is a bit messy; however, we can clean that up by adding another method to ActiveRecord::Base.
class << ActiveRecord::BaseFollowing the above code change the test can be rewritten to the code shown below.
def standard_path
File.expand_path("#{RAILS_ROOT}/app/models/#{self.name.underscore}.rb")
end
end
class PhoneNumberTest < Test::Unit::TestCaseWhich implementation is better? Both implementations provide pros and cons. I suggest trying both out to determine which works best for you (or your team).
Column = ActiveRecord::ConnectionAdapters::Column
test "validates_presence_of is defined for digits" do
PhoneNumber.expects(:validates_presence_of).with(:digits)
load PhoneNumber.standard_path
end
end
Labels: RailsConf2007
Monday, December 25, 2006
Rails: ActiveRecord Unit Testing
There's been a fair amount of information posted recently on the topic of unit testing ActiveRecord::Base subclasses. Of all the information, I think the most valuable was James' observation that a better data model would result in better tests with less dependencies.
That said, sometimes it would be nice to truly unit test an ActiveRecord::Base subclass. When I say unit test, I mean no dependency on the database. For example, I may store a phone number in the database as a 10 digit string, but I may want to expose that phone number with formatting. I may also want to expose the area code, exchange, and station as methods of the PhoneNumber class. To test this behavior I wrote the following tests that do hit the database.
The PhoneNumber class has the following implementation.
Now that we have a few tests we'll add the code that disallows database access from unit tests and rerun the tests. As expected the tests fail with the following error.
If you want to unit test a model the columns method is a good one to stub. I tried a few different options and the best one I found was stubbing the columns method with a method that returns an array of ActiveRecord::ConnectionAdapters::Column instances. Since I only needed the
That said, sometimes it would be nice to truly unit test an ActiveRecord::Base subclass. When I say unit test, I mean no dependency on the database. For example, I may store a phone number in the database as a 10 digit string, but I may want to expose that phone number with formatting. I may also want to expose the area code, exchange, and station as methods of the PhoneNumber class. To test this behavior I wrote the following tests that do hit the database.
class PhoneNumberTest < Test::Unit::TestCaseIf this syntax looks bizarre at all, you might want to read about how these tests take advantage of the
test "to_formatted_s returns US format" do
number = PhoneNumber.new(:digits => "1234567890")
assert_equal "(123) 456-7890", number.to_formatted_s
end
test "area code returns first 3 numbers" do
number = PhoneNumber.new(:digits => "1234567890")
assert_equal "123", number.area_code
end
end
test class method.The PhoneNumber class has the following implementation.
class PhoneNumber < ActiveRecord::BaseAs the implementation shows, the
def to_formatted_s
'(' + area_code + ') ' + exchange + '-' + station
end
def area_code
digits[0..2]
end
def exchange
digits[3..5]
end
def station
digits[6..9]
end
end
digits are stored in the database. Splitting the digits up or formatting them is handled by methods on the model.Now that we have a few tests we'll add the code that disallows database access from unit tests and rerun the tests. As expected the tests fail with the following error.
1) Error:Looking at the stack trace provided by the error you can track the database access to the columns method of ActiveRecord::Base.
test_area_code_returns_first_3_numbers(PhoneNumberTest):
ArgumentError: You cannot access the database from a unit test
If you want to unit test a model the columns method is a good one to stub. I tried a few different options and the best one I found was stubbing the columns method with a method that returns an array of ActiveRecord::ConnectionAdapters::Column instances. Since I only needed the
digits attribute for these tests, it was the only column I needed to return in the array. The following tests test my PhoneNumber class without requiring a trip to the database.class PhoneNumberTest < Test::Unit::TestCase
Column = ActiveRecord::ConnectionAdapters::Column
test "to_formatted_s returns US format" do
PhoneNumber.stubs(:columns).returns([Column.new("digits", nil, "string", false)])
number = PhoneNumber.new(:digits => "1234567890")
assert_equal "(123) 456-7890", number.to_formatted_s
end
test "area code returns first 3 numbers" do
PhoneNumber.stubs(:columns).returns([Column.new("digits", nil, "string", false)])
number = PhoneNumber.new(:digits => "1234567890")
assert_equal "123", number.area_code
end
end
Labels: RailsConf2007
Rails: Migrations with a large team part I
I've been working on a big (in Rails terms) team for a few months now and we've come to some conclusions concerning Migrations. I'll start with the bad and in part II I'll talk about the decisions we made and why.
Migrations are great, but they do come at a cost. When working with a large team (my current team size is 14 and growing) migration conflicts happen. This can be mitigated with communication, but migrations can definitely be a bottleneck. Also, the process of creating a migration can be painful on a large team. Before creating a migration you should always update from source control to ensure you get all the migrations checked in by your teammates. Then, the best case scenario is when you can create a migration that doesn't change anything and immediately check it in. Checking in a new migration immediately helps ensure you don't block other teammates from creating migrations; however, it's not always as simple as adding a new table. Migrations that alter the database structure often break several tests. Obviously, you can't check those migrations in until you fix all the breaking tests, which can take time. During this time, database changes are blocked for the entire team.
It can also be troublesome to find specific changes to the database within multiple migration files. Finding which migration adds a specific column can take a fair amount of time when you are working with over 50 migration files. Naming conventions can mitigate this issue some; however, naming conventions generally require that only one action occur per file. For example, the 023_create_customers_table.rb file can only create the customer table and cannot alter the purchases table to add the customer_id column. This type of naming convention helps on searching for specific changes to the database; however, it also results in a large number of migration files.
Migrations are great, but they do come at a cost. When working with a large team (my current team size is 14 and growing) migration conflicts happen. This can be mitigated with communication, but migrations can definitely be a bottleneck. Also, the process of creating a migration can be painful on a large team. Before creating a migration you should always update from source control to ensure you get all the migrations checked in by your teammates. Then, the best case scenario is when you can create a migration that doesn't change anything and immediately check it in. Checking in a new migration immediately helps ensure you don't block other teammates from creating migrations; however, it's not always as simple as adding a new table. Migrations that alter the database structure often break several tests. Obviously, you can't check those migrations in until you fix all the breaking tests, which can take time. During this time, database changes are blocked for the entire team.
It can also be troublesome to find specific changes to the database within multiple migration files. Finding which migration adds a specific column can take a fair amount of time when you are working with over 50 migration files. Naming conventions can mitigate this issue some; however, naming conventions generally require that only one action occur per file. For example, the 023_create_customers_table.rb file can only create the customer table and cannot alter the purchases table to add the customer_id column. This type of naming convention helps on searching for specific changes to the database; however, it also results in a large number of migration files.
Sunday, December 24, 2006
Ruby: Alias method alternative
Originally blogged by Martin @ split-s. I'm reposting because I don't see this method used very often.
Using Ruby's alias it is possible to reopen a class, override a method and still call the original.
An alternative is to capture the
Below are the tests that prove the concept.
Using Ruby's alias it is possible to reopen a class, override a method and still call the original.
class ClockRadioWhile this works, it can cause unexpected results and leave around artifacts. In isolation this doesn't look risky; however, in a large codebase someone could easily define
def on!
@on = true
end
def on?
@on
end
end
class ClockRadio
alias :old_on! :on!
def on!
old_on!
@display_time = true
end
def display_time?
@display_time
end
end
old_on! or use it as their alias name also. The other, much smaller, issue is that old_on! will be left as a method on ClockRadio when you actually have no desire to expose this method outside of calling it from on!.An alternative is to capture the
on! method as an unbound method, bind it to the current instance, and call it explicitly.class ClockRadioThe above version ensures that the correct version of
on = self.instance_method(:on!)
define_method(:on!) do
on.bind(self).call
@display_time = true
end
def display_time?
@display_time
end
end
on! will be called from the on! implementation defined in the reopened version ClockRadio. This version also lets the reference to the old_on! fall out of scope after the class is defined; therefore, there are no additional methods left around as side effects.Below are the tests that prove the concept.
class AliasMethodAlternativeTest < Test::Unit::TestCase
def test_aliased_method_returns_true_for_on
radio = ClockRadio.new
radio.on!
assert_equal true, radio.on?
end
def test_aliased_method_returns_true_for_display_time
radio = ClockRadio.new
radio.on!
assert_equal true, radio.display_time?
end
end
Saturday, December 23, 2006
Ruby: Multiline strings - here doc or quotes
Ruby supports multiline strings by providing two types of here doc syntax. The first syntax uses and additional dash, but allows you to indent the "end of here doc" delimiter ('eos' in the example).
<<-eosAnother here doc syntax doesn't require you to use the dash, but it does require that the "end of here doc" delimiter is in column 1 (or there are no spaces that precede it).
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute
irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia
deserunt mollit anim id est laborum.
eos
<<eosBoth options support the following syntax when passed as a parameter.
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute
irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia
deserunt mollit anim id est laborum.
eos
Content.new(:value => <<eos)As an alternative, you could also pass the parameter using quotes.
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute
irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia
eos
Content.new(:value => <<eos
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute
irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia
eos
)
Content.new(:value => "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod temporThe solution with quotes requires less lines, so I tend to prefer it. Also, the only benefit I can think of for the here doc syntax is that it allows quotes without having to escape them.
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute
irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia")
Friday, December 22, 2006
Ruby: Constant values
I generally use constants in Ruby for the following two situations: Markers or Constant values.
Markers are used as a standard for comparison.
Constant values are global values that should never change during the life of your application.
Markers are used as a standard for comparison.
module CreditCardTypesMarkers are initialized to a value; however, that value is unimportant as long as it is unique. Markers are generally used in an application within conditional statements.
Visa = 0
Mastercard = 1
end
case card.typeNote: you can also use symbols as markers, but I prefer constants. This preference is based on the fact that if I mistype CreditCardTypes::Vissa it will fail fast; however, if I mistype :credit_card_type_vissa, I will get a possibly hard to find bug.
when CreditCardTypes::Visa then VisaLuhnValidator
when CreditCardTypes::Mastercard then MastercardLuhnValidator
end
Constant values are global values that should never change during the life of your application.
module MathValuesConstant values can be used throughout applications to ensure that the same value is consistently used.
PI = 3.14
end
circumference = circle.diameter * MathValues::PIBased on these usages, I'm a bit concerned about some behavior I recently found.
irb(main):019:0> module MathVariablesWarning? This means anyone can redefine my constants at any time? Did I do something wrong? Does anyone else think this is dangerous?
irb(main):020:1> PI = 3.14
irb(main):021:1> end
=> 3.14
irb(main):022:0> module MathVariables
irb(main):023:1> PI = 3.14159265
irb(main):024:1> end
(irb):23: warning: already initialized constant PI
=> 3.14159265
Thursday, December 21, 2006
Rake: db:migration:conflict
This task originally written by Stephen Chu.
If you are using migrations on a large team, migration conflicts can and do occur. To help avoid conflicts you can add the following task to your codebase and run it after updating and before checking in (hopefully in your commit task).
If you are using migrations on a large team, migration conflicts can and do occur. To help avoid conflicts you can add the following task to your codebase and run it after updating and before checking in (hopefully in your commit task).
namespace :db do
namespace :migration do
desc "After downloading migrations from server, check to see if we have conflicting migration numbers."
task :conflict do
existing_migration_numbers = []
Dir.entries("#{RAILS_ROOT}/db/migrate").each do |entry|
migration_number = entry.scan(/^\d+/).first
next if migration_number.nil?
if existing_migration_numbers.include? migration_number
raise ArgumentError, "Migration #{migration_number} is already checked in on the server. Verify your migration numbers."
end
existing_migration_numbers << migration_number
end
end
end
end
Ruby: rake commit
update: Paul Gross created a GitHub project where these tasks live and can be improved. Check it out at http://github.com/pgr0ss/rake_commit_tasks/tree/master
I wrote in the past about
The first implementation of this task used a task with parameters; however, the final implementation uses
The
I wrote in the past about
rake pc, the task that I run before checking in. The rake pc task is good, but it stops one step short: actually checking in.The first implementation of this task used a task with parameters; however, the final implementation uses
STDIN.gets and a data file.The
rake commit task also allows you specify a pair if you are working on multiple pairing stations.desc "Run to check in"
task :commit => :pc do
commit_pair = retrieve_saved_data "pair"
commit_message = retrieve_saved_data "message"
command = %[svn ci -m "#{commit_pair.chomp} - #{commit_message.chomp}"]
puts command
puts %x[#{command}]
end
def retrieve_saved_data attribute
data_path = File.expand_path(File.dirname(__FILE__) + "/#{attribute}.data")
`touch #{data_path}` unless File.exist? data_path
saved_data = File.read(data_path)
puts "last #{attribute}: " + saved_data unless saved_data.chomp.empty?
print "#{attribute}: "
input = STDIN.gets
while (saved_data.chomp.empty? && (input.chomp.empty?))
print "#{attribute}: "
input = STDIN.gets
end
if input.chomp.any?
File.open(data_path, "w") { |file| file << input }
else
puts "using: " + saved_data.chomp
end
input.chomp.any? ? input : saved_data
end
Sunday, December 17, 2006
BNL: New location
Instead of continuing to post BNL updates to my blog I've put all the current content on http://bnl.jayfields.com.
I'll post an occasional blog entry here when significant updates occur.
I'll post an occasional blog entry here when significant updates occur.
Rails: Plugins, Why?
I've been wondering for awhile if the Plugin system is something worth taking out of Rails. It's not that I have anything against Rails plugins, but I cant find any way in which they are superior to RubyGems.
Two benefits I have heard are: The code can be packaged with the application and Rails auto-loads plugins. While I agree that these two things are important, it's possible to achieve the first by using gem unpack and it's also possible to auto-load unpacked gems.
Update: I hadn't even seen this post about Rails autoloading gems, by Chad, until today. It seems that more than a few people would like their gems to play nicely with Rails.
Another benefit I've heard is that plugins are easier to create. I've never created a plugin, but Dr Nic has made it quite easy to create gems using newgem.
While I cant find any features that plugins provide and gems do not, RubyGems do provide features that are not available in plugins such as versioning and dependencies.
So, a question and a request:
What benefits do plugins provide?
If there are no benefits to plugins, create gems for your reusable Ruby code instead of plugins.
Two benefits I have heard are: The code can be packaged with the application and Rails auto-loads plugins. While I agree that these two things are important, it's possible to achieve the first by using gem unpack and it's also possible to auto-load unpacked gems.
Update: I hadn't even seen this post about Rails autoloading gems, by Chad, until today. It seems that more than a few people would like their gems to play nicely with Rails.
Another benefit I've heard is that plugins are easier to create. I've never created a plugin, but Dr Nic has made it quite easy to create gems using newgem.
While I cant find any features that plugins provide and gems do not, RubyGems do provide features that are not available in plugins such as versioning and dependencies.
So, a question and a request:
What benefits do plugins provide?
If there are no benefits to plugins, create gems for your reusable Ruby code instead of plugins.
Ruby: instance and class methods from a module
Context: You have a presenter class that needs to validate attributes. You like the way ActiveRecord allows you to define validations using class methods. You also want to use a
Step one: You define the validation methods in a module.
Step two: Another common approach is to define the class methods in a ClassMethods module inside the Presenters::Validations module.
I tend to prefer explicitness; however, if something is used often enough it can turn from an anti-pattern to an idiom. I'm not sure if that is the case or not here, but I think it might be.
valid? method on your presenter instances to determine if they are valid or not.Step one: You define the validation methods in a module.
module Presenters::ValidationsThen, you
def validates_presence_of(*args)
..
end
def validates_format_of(*args)
..
end
end
extend the module to add the class methods to your presenter.class AccountInformationPresenterThis implementation also requires that you define
extend Presenters::Validations
validates_presence_of :username, :password
..
valid? in each presenter class.def valid?When building the second presenter it should be clear that the
self.class.validations.collect do |validation|
unless validation.valid?(self)
self.errors.add(validation.on, validation.message)
end
end.all?
end
end
valid? should be abstracted. This abstraction could result in another module. The new module could be included thus providing valid? as an instance method.Step two: Another common approach is to define the class methods in a ClassMethods module inside the Presenters::Validations module.
module Presenters::ValidationsThis approach, while common in Rails, can generate some dislike. A counter argument is that
module ClassMethods
def validates_presence_of(*args)
..
end
def validates_format_of(*args)
..
end
end
def self.included(base)
base.extend(ClassMethods)
end
def valid?
self.class.validations.collect do |validation|
unless validation.valid?(self)
self.errors.add(validation.on, validation.message)
end
end.all?
end
end
include is designed to add instance methods and using self.included is clever, but provides unexpected behavior. I've found that people who dislike the self.included trick prefer to explicitly use both include and extend.class AccountInformationPresenterWhich approach is better?
include Presenter::Validations
extend Presenter::Validations::ClassMethods
..
end
I tend to prefer explicitness; however, if something is used often enough it can turn from an anti-pattern to an idiom. I'm not sure if that is the case or not here, but I think it might be.
Saturday, December 16, 2006
Ruby: Use class methods to reduce duplication
Context: I had two method definitions in my class that were similar to the code below.
Solution: To reduce the duplication you could introduce a mixin with a method to remove the duplication.
class TaxRemoteFacadeI've only shown two, but we actually needed the
def state_tax
...
@remote.close
end
def federal_tax
...
@remote.close
end
end
@remote.close code at the end of several methods.Solution: To reduce the duplication you could introduce a mixin with a method to remove the duplication.
module RemoteFacadeThe TaxRemoteFacade can now be defined as the code below.
def remote_call(method_name, &block)
class_eval do
define_method name do
instance_eval &block
@remote.close
end
end
end
end
class TaxRemoteFacade
extend RemoteFacade
remote_call do :state_tax do
...
end
remote_call do :federal_tax do
...
end
end
Thursday, December 14, 2006
Rails: Running migrations in another environment
From the command line it's possible to run migrations in any environment. For example, using the code below it is possible to run migrations in the test environment.
rake db:migrate RAILS_ENV=testBecause the above code works it would appear that you could create the following rake task to automate running the migrations in the test environment.
task :test_migrate doUnfortunately, that didn't work, but the following change does work.
ENV["RAILS_ENV"] = "test"
Rake::Task["db:migrate"].invoke
end
task :test_migrate do
ActiveRecord::Base.establish_connection "test"
Rake::Task["db:migrate"].invoke
end
Wednesday, December 13, 2006
Rake: --dry-run, --trace, and -T
When using rake it can often be useful to know the order in which tasks are being executed. To list the execution order you can issue the following command.
rake --dry-runThe --dry-run option works well; however, it does not report tasks that are explicitly invoked.
Rake::Task["test:units"].invokeThe above code can be used to explicitly invoke a task (test:units in the example). Rails uses this in various database and testing tasks. However, it's still possible to see which tasks are being executed. The following code will show each task as it's executed.
rake --traceThe --trace option provides a lot of information, but if you have grep available it can be trimmed down easily.
rake --trace | grep ExecuteAlso, when running rake with the -T option you can also pass another parameter to list only the commands that contain that parameter. For example, the following code returns all the tasks that contain 'db' in their description.
rake -T db
Tuesday, December 05, 2006
BNL: Extracting Sales Person
Versioning on an individual sales person
Before we add version control to our application we need something to version. Logically it seems you would like to have a document history showing compensation agreements for each sales person. To accommodate this requirement, we can pull the sales person from the scripts and save them off separately. The first step is creating migrations that create and alter the tables to match our new intentions.
File: 003_create_sales_people.rb
File: 004_add_sales_person_to_compensation_script.rb
Following the above migrations the existing codebase will need to be updated to reflect the new database structure. The "new" page has been updated to include a textbox for entering the sales person's name.
File: new.rhtml
Introducing the name textbox requires us to create the new SalesPersonCompensationScript class. This is because a CompensationScript still contains the logic, but the SalesPerson class maintains the name captured in the "name" textbox. The SalesPersonCompensationScript is a simple class that is used only to store the data that will be used by the controller to save a SalesPerson and CompensationScript.
File: sales_person_compensation_script.rb
The PayrollController has been updated to use the SalesPersonCompensationScript class on the "new" view and on the create action. The create action is where the SalesPersonCompensationScript is used to create a CompensationScript and a new SalesPerson if necessary.
File: payroll_controller.rb
Every action within the PayrollController has been changed to reflect the new domain model that includes a SalesPerson as a concept. The index action now relies on an array of SalesPerson instances instead of an array of CompensationScript instances. The index.rhtml has also been updated to reflect this change.
File: index.rhtml
Navigating to the view page now requires a sales person instance to be available.
File: view.rhtml
The execute.rhtml was updated also, but only changed slightly to accommodate the introduction of the sales person concept.
File: execute.rhtml
The execute action in PayrollController relies on the short name of a sales person, and the view.rhtml page uses the active_compensation_script method of the SalesPerson instance.
File: sales_person.rb
The execute_all.rhtml also contains minor updates that reflect the introduction of SalesPerson
File: execute_all.rhtml
All these changes also mean the CompensationScript and Root classes have been simplified by removing the name of the employee.
File: compensation_script.rb
File: root.rb
Lastly, the Employee class can be completely removed since Root no longer parses an employee's name.
Before we add version control to our application we need something to version. Logically it seems you would like to have a document history showing compensation agreements for each sales person. To accommodate this requirement, we can pull the sales person from the scripts and save them off separately. The first step is creating migrations that create and alter the tables to match our new intentions.
File: 003_create_sales_people.rb
class CreateSalesPeople < ActiveRecord::Migration
def self.up
create_table :sales_people do |t|
t.column :name, :string
end
end
def self.down
drop_table :sales_people
end
end
File: 004_add_sales_person_to_compensation_script.rb
class AddSalesPersonToCompensationScript < ActiveRecord::Migration
def self.up
add_column :compensation_scripts, :sales_person_id, :integer
add_column :compensation_scripts, :created_at, :datetime
end
def self.down
remove_column :compensation_scripts, :sales_person_id
remove_column :compensation_scripts, :created_at
end
end
Following the above migrations the existing codebase will need to be updated to reflect the new database structure. The "new" page has been updated to include a textbox for entering the sales person's name.
File: new.rhtml
<% form_for :script, @script, :url => { :action => "create" } do |form| %>
Name<br>
<%= form.text_field :name %><br>
<br>
Compensation Rules<br>
<%= form.text_area :logic %><br>
<%= submit_tag %>
<% end %>
Introducing the name textbox requires us to create the new SalesPersonCompensationScript class. This is because a CompensationScript still contains the logic, but the SalesPerson class maintains the name captured in the "name" textbox. The SalesPersonCompensationScript is a simple class that is used only to store the data that will be used by the controller to save a SalesPerson and CompensationScript.
File: sales_person_compensation_script.rb
class SalesPersonCompensationScript
attr_accessor :name, :logic
def initialize(hash=nil)
unless hash.nil?
self.name = hash[:name]
self.logic = hash[:logic]
end
end
end
The PayrollController has been updated to use the SalesPersonCompensationScript class on the "new" view and on the create action. The create action is where the SalesPersonCompensationScript is used to create a CompensationScript and a new SalesPerson if necessary.
File: payroll_controller.rb
class PayrollController < ApplicationController
def index
@sales_people = SalesPerson.find :all
end
def new
@script = SalesPersonCompensationScript.new
end
def create
person_script = SalesPersonCompensationScript.new(params[:script])
script = CompensationScript.create(:logic => person_script.logic)
sales_person = SalesPerson.find_by_name(person_script.name) ||
SalesPerson.create(:name => person_script.name)
sales_person.compensation_scripts << script
redirect_to :action => :index
end
def view
@person = SalesPerson.find(params[:person_id])
end
def execute
@person = SalesPerson.find(params[:person_id])
vocabulary = InlineVocabulary.new(@person.short_name)
logic = @person.active_compensation_script.logic
@compensation = CompensationParser.parse(logic, vocabulary, InlineContext.new)
end
def execute_all
@compensations = {}
SalesPerson.find(:all).each do |person|
vocabulary = SqlVocabulary.new(person.short_name)
logic = person.active_compensation_script.logic
compensation = CompensationParser.parse(logic, vocabulary, SqlContext.new)
@compensations[person.name.to_sym] = compensation.amount
end
end
end
Every action within the PayrollController has been changed to reflect the new domain model that includes a SalesPerson as a concept. The index action now relies on an array of SalesPerson instances instead of an array of CompensationScript instances. The index.rhtml has also been updated to reflect this change.
File: index.rhtml
Employees:
<ul>
<% @sales_people.each do |person| %>
<li>
<%= person.name %> -
<%= link_to 'view', :action => :view, :person_id => person.id %> |
<%= link_to 'execute', :action => :execute, :person_id => person.id %>
</li>
<% end %>
</ul>
<%= link_to 'Execute All', :action => :execute_all %> |
<%= link_to 'Create new compensation script', :action => :new %>
Navigating to the view page now requires a sales person instance to be available.
File: view.rhtml
Compensation rules for <%= @person.name %>
<pre><%= @person.active_compensation_script.logic %></pre>
<%= link_to 'back', :action => :index %>
The execute.rhtml was updated also, but only changed slightly to accommodate the introduction of the sales person concept.
File: execute.rhtml
<%= @person.name %> compensation: <%= number_to_currency @compensation.amount %><br>
<%= link_to 'back', :action => :index %>
The execute action in PayrollController relies on the short name of a sales person, and the view.rhtml page uses the active_compensation_script method of the SalesPerson instance.
File: sales_person.rb
class SalesPerson < ActiveRecord::Base
has_many :compensation_scripts
def active_compensation_script
compensation_scripts.sort.first
end
def short_name
self.name.gsub(/^([A-Z])[a-z]+\s/, '\1').downcase
end
end
The execute_all.rhtml also contains minor updates that reflect the introduction of SalesPerson
File: execute_all.rhtml
<% @compensations.each_pair do |name, amount| %>
<%= name %> compensation: <%= number_to_currency amount %><br>
<% end %>
<%= link_to 'back', :action => :index %>
All these changes also mean the CompensationScript and Root classes have been simplified by removing the name of the employee.
File: compensation_script.rb
class CompensationScript < ActiveRecord::Base
def <=>(other)
other.created_at <=> self.created_at
end
end
File: root.rb
class Root
extend Vocabulary
def initialize(vocabulary, eval_context)
@compensations = []
@vocabulary, @eval_context = vocabulary, eval_context
end
def amount
@compensations.collect do |compensation|
compensation.amount
end.inject { |x, y| x + y }
end
def process(line)
instance_eval(line)
end
phrase :compensate do
@compensations << Compensation.new(@vocabulary, @eval_context)
@compensations.last
end
end
Lastly, the Employee class can be completely removed since Root no longer parses an employee's name.
Saturday, December 02, 2006
xUnit: Absence of Mocks and Stubs
In September of 2004 I met James Newkirk at the Microsoft Patterns and Practice Summit in Reston Virginia. If you don't know James, among other accomplishments he was one of the creators of NUnit. At the summit I stopped him to express my appreciation for NUnit. A conversation ensued in which he caught me off guard with the question: What is missing from NUnit?
Well, it's been over two years, but I finally have an answer: Mocks and Stubs
As long as I've needed them, Mocks have been available as additional libraries. When I was using NUnit I used NMock and when using Test::Unit I use Mocha. But, why are these libraries not simply included in xUnit frameworks?
In 2004 I was also introduced to Behavioral Testing as a formal concept. I had been using mocks for a bit; however, it was wile reading Mocks Aren't Stubs by Martin Fowler when the difference between state and behavioral testing became clear. Martin concludes the write up with the following question and answer:
I can't imagine having to choose either behavioral or state based testing exclusively. Both have their pros and cons and, I believe, should be used accordingly. So why then do xUnit implementations continue to ignore support for behavioral testing?
Given that the tests we write as developers have matured, I believe it's fair to expect our xUnit frameworks to evolve with our needs.
Well, it's been over two years, but I finally have an answer: Mocks and Stubs
As long as I've needed them, Mocks have been available as additional libraries. When I was using NUnit I used NMock and when using Test::Unit I use Mocha. But, why are these libraries not simply included in xUnit frameworks?
In 2004 I was also introduced to Behavioral Testing as a formal concept. I had been using mocks for a bit; however, it was wile reading Mocks Aren't Stubs by Martin Fowler when the difference between state and behavioral testing became clear. Martin concludes the write up with the following question and answer:
So which style is the best?It's been more than two years since Mocks Aren't Stubs was originally published and both behavioral and state based testing still exist. I'm skeptical that either will ever be declared better for all cases.
I find this a difficult question to answer with confidence.
I can't imagine having to choose either behavioral or state based testing exclusively. Both have their pros and cons and, I believe, should be used accordingly. So why then do xUnit implementations continue to ignore support for behavioral testing?
Given that the tests we write as developers have matured, I believe it's fair to expect our xUnit frameworks to evolve with our needs.

