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
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.