News: Order Avandia Purchase Lasix Order Leukeran Menosan Order Brite Buy Ansaid Order Sarafem Purchase Adalat Cheap Celexa Purchase Bontril Purchase Phentermine Purchase Ophthacare Cheap Methocarbam Buy Geriforte Cheap Cephalexin Buy Ashwagandha Order Retin-A Lexapro Buy Lukol Purchase Nexium Cheap Fioricet CLA Order Cymbalta Buy Myambutol Order Nimotop Buy Rocaltrol Cheap Himcospaz Buying Didrex Order Zimulti Cheap Plendil Purchase Koflet Cheap Noroxin Cheap Deltasone Inderal Order Motrin Nicotinell Cheap Nizoral Buy Mentax Vasodilan Buy Parlodel Cheap Loprox Norpace CR Shoot Paxil Purchase Avandamet Order Requip Buy Rogaine Female Viagra Cheap Imitrex Cheap Hyzaar Buying Phentermine Lotrisone Purchase Fosamax Purchase Xeloda Zithromax Cheap Requip Buy Norvasc Purchase Quibron-T Buy Topamax Eurax Cheap Nicotinell Purchase Sorbitrate Buy Coreg Cheap Leukeran Viagra Purchase Methocarbam Purchase Zithromax Cheap Flovent Cheap Calan Purchase Cordarone Buy Pamelor Purchase Endep Purchase Menosan Buy Vasodilan Order Zerit Cheap Lotrisone Buy Seroquel Order Serophene Augmentin Buy Effects Cheap Tricor Cheap Prandin Purchase Loprox Purchase Augmentin Cheap Dilantin Lopressor Purchase High Buy Motrin Buy Zithromax Flovent Purchase CLA Cheap Superman Purchase Lisinopril Cheap Viramune Buy Abana Cephalexin Buy Protonix Buy Fioricet Order Crestor Buy Hydrocodone Order Tenormin Order Carisoprodol Atrovent Cheap Prozac Purchase Reosto Purchase Atrovent Purchase Flovent Cheap Tenuates Purchase Cardizem Buy Flexeril Cheap Styplon Order Methocarbam Purchase Pamelor Cheap Coreg Purchase Buspar ActoPlus Met Deltasone Purchase Retin-A Mentax Purchase Cytotec Buy Sustiva Motrin Purchase Ashwagandha Lariam Cheap Zerit Order Mobic Cheap Bonnisan Cheap Isoptin Order Seroquel Geriforte Cheap Accutane Purchase Lexapro Cheap Lariam Purchase Prevacid Cheap Xenacore Order Lotensin Buy Requip Buy Mevacor Purchase Norvasc Purchase Tricor Prometrium Isoptin Adalat Purchase Himcocid Purchase Flexeril Order Eurax Cheap Norvasc Pamelor Cheap Effexor Hoodia Cheap Accupril Cheap Dostinex Meridia Purchase Shoot Purchase Zebeta Order Capoten Cystone Starlix Purchase Brite Order Nolvadex Cheap V-Gel Aricept Purchase Nolvadex Cheap Flonase Buy Diakof Buy Inderal Order Kytril Arava Buy Celebrex Order Aceon Buy Clonazepam Buy Leukeran Purchase Fioricet Buy Effexor Buy Speman Purchase Diflucan Rumalaya Purchase Kytril Cheap Isordil Buy Lopid Order Diflucan Cheap Lasix Cheap Phentermine Cheap Zyloprim Fastin Cheap Nimotop Buy Famvir Buy Lotrisone Watson Cheap Lincocin Oxytrol Adderall Cheap Maxaquin Zimulti Purchase Capoten Avapro Cheap Parlodel Order Atarax Order Tulasi Buy Ismo Order Avandamet Buy Didrex Order Danazol Purchase Penisole Female Sexual Buy Darvocet Buy Lasix Buy Purinethol Purchase Ativan Penis Growth Buy Proventil Vicodin Order Pletal Oxycontin Plendil Calan Zyrtec Purchase Didrex Leukeran Cheap Pravachol Cheap Myambutol Purchase Rumalaya Order AyurSlim Buy Exelon Herbolax Buy Quibron-T Buy CLA Buy Online Renalka Cheap Aldactone Buy Noroxin Order Lopid Order Zyprexa Cheap Purinethol Buy Clarina Buy Avodart Arimidex Orgasm Enhancer Purchase Lukol Purchase Inderal Atacand Order Overnight Codeine Cheap Clarinex Buy Monoket Depakote Buying Viagra Order Amoxil Purchase Zanaflex Purchase V-Gel Purchase Prinivil Order Ephedrine Order Plavix Purchase Mexitil Order Synthroid Order Antabuse Buy Lortab Purchase Herbolax Buy Lopressor Mexitil Cheap Antabuse Buy Zanaflex Buy Pletal Cheap Motrin Buy Plan Buy Rimonabant Cheap Prinivil Order Himcospaz Purchase Mentax Order Fioricet Order Nicotinell Buy Cozaar Buy Septilin Fosamax Butalbital Purchase Adipex Purchase Septilin Order Karela Cheap Ventolin Lincocin Purchase Aristocort Coumadin Purchase Desyrel Cheap Elavil Plan B Cheap AyurSlim Order Elimite Order Viramune Buy Purim Purchase Keftab Cheap Azulfidine Cheap Atacand Buy Lincocin Order Myambutol Order Cardura Cheap Brahmi Purchase Topamax Buy Cordarone Buy Trandate Buy Triphala Purchase Clomid Elimite Order Renalka AyurSlim Buy Superman Buy Lynoral Zebeta Cheap Rumalaya Cheap Ophthacare Order Mevacor Purchase Danazol Brahmi Liv.52 Cheap Diflucan Cheap Mobic Purchase Codeine Buy Nonoxinol Viagra Jelly Purchase Detrol Order Xenacore Purchase Sarafem Order Aciphex Purchase Bactroban Chitosan Order Imdur Cheap Prometrium Buy Actos Nizoral Buy Cialis Rimonabant Buy Nimotop Buy Zyvox Order V-Gel Touch-Up Kit Cardura Buy Tulasi Purchase Diazepam Feldene Purchase Requip Buy Emsam Purchase Actos Male Sexual Prandin Septilin Buy Plendil Order Proventil Cheap Lorazepam Purchase Avapro Cheapest Ultram Buy Norco Prevacid Coreg Cheap Stromectol Buy Naprosyn Clonazepam Purchase Famvir Order Menosan Buy Xanax Buy Omnicef Cheap Zanaflex Buy Geodon Purchase Soma Buy Dospan Order Hoodia Buy Prevacid Buy Retin-A Cheap Prilosec Purchase Cozaar Order StretchNil Order Hydrocodone Purchase Evista Aciphex Acne-n-Pimple Cream Order Accupril V-Gel Purchase Zyloprim Cyklokapron Cheap Tulasi Buy Desyrel Order Isordil Purchase Lariam Order Serevent Claritin Purchase Tenuate Ventolin Buy Sarafem Order Diabecon Order Koflet Purchase Zyban Superloss Multi Purchase Xanax Cheap Gasex Cordarone Buy Penisole Ashwagandha Cheap Proventil Order Atacand Buy Proscar Order Trimox Cheap Exelon Purchase Ambien Buy Lioresal Buy Oxycontin Buy Lamictal Order Sinequan Purchase Renalka Buy Differin Purchase Didronel Order Aldactone Mentat Trimox Buy Cipro Purchase Pletal Purchase Tenormin Buy Tricor Purchase Celebrex Cheap Sarafem Viagra Soft Cheap Shallaki Lisinopril Parlodel Order Lamisil Order Watson Himcocid Cheap Bactroban Order Imitrex Proventil Phentermine Order Sumycin Order Brahmi Buy Diethylpropion Cheap Zetia Purchase Diabecon Purchase Hytrin Buy Zocor Purchase Prednisone Order Combivent Buy Fastin Order Lotrisone Order Ventolin Professional Plasma Cheap Menosan Purchase Trandate Cheap Differin Purchase Oxytrol Buy Plavix Lopid Cheap Trazodone Brafix Buy Prometrium Cheap Lopid Order Didrex Cheap Himplasia Cheap Zelnorm Cheap Famvir Cheap Clarina Purchase Noroxin Order Evista Purchase Zyrtec Order Maxaquin Kytril Order Levlen Lukol Cheap Oxycontin Cheap Mysoline Purchase Prometrium Buy Carisoprodol Cheap Zithromax Purchase Ultram Order Keftab Reosto Order Parlodel Epivir-HBV Order Adalat Altace Order Mysoline Purchase Nicotinell Cheap Loxitane Order Biaxin Cheap Nonoxinol Cheap Coumadin Buy Atarax Order Nonoxinol Buy Hoodia Buy Vantin Hoodia Weight Cheap Flexeril Purchase Rocaltrol Buy Risperdal Buy Bonnisan Order Vasodilan Purchase Combivent Order Prozac Buy Revia Cheap Avandia Buy AyurSlim Buy Flonase Buy Kytril Cheap Diazepam Purchase Cymbalta Order Gasex Order Lopressor Purchase Cipro Cheap Lexapro Buy Vicodin Order Ionamin Cheap Sorbitrate Purchase Tramadol Buy Cephalexin Purchase Isordil Grifulvin V Order Clonazepam Cheap Biaxin Purchase Zimulti Order Geriforte Cheap Diakof Purchase Snoroff Cheap Diovan Order Diovan Order Trandate Cheap Diarex Purchase Antabuse Cheap Relafen Order Sustiva Purchase Mycelex-G Buy Zimulti Buy Buspar Cheap Rogaine Purchase Confido Order Zithromax Purchase Purinethol Cheap Phentrimine Buy Avapro Nonoxinol Order Claritin Purchase Lopressor Hair Loss Order Micardis Cheap Arimidex Order Sorbitrate Cheap Omnicef Cheap Levlen Buy Lamisil Avandia Order Bactroban Buy Kamagra Buy Ventolin Order Nirdosh Accupril Purchase Crestor Order Paxil Cheap Endep Cheap Serevent Order Cephalexin Buy Brite Purchase Zocor Order Aricept Order Lasix Buy Atacand Buy Xeloda Purchase Cardura Purchase Zestril Buy Azulfidine Buy Isordil Buy Zovirax Lotensin Buy Valium InnoPran XL Buy Levothroid Purchase Percocet Zantac Cheap Zyrtec Loprox Order Monoket Cheap StretchNil Buy Glucophage Buy Vasotec Mental Booster Buy Adipex Purchase Levaquin Order Levitra Pilex Cheap Snoroff Buy Accupril Lasix Hytrin Order Zestril Cheap Zovirax Aleve Diabecon Xanax Order Chitosan Cheap Proscar Tulasi Purchase Prilosec Purchase Feldene Cheap Celebrex Cheap Prograf Soma Buy Singulair Order Zantac Cheap Ephedrine Buy Starlix Order Levaquin Purchase Cyklokapron Buy Prandin Trazodone Ismo Purchase Darvocet Flonase Purchase Micardis Buy Menosan Purchase Himplasia Cheap Brite Purchase Allegra Order Ashwagandha Cheap Pilex Order Adipex Order Exelon Purchase Bonnisan Pravachol Order Premarin Clarinex Cheap CLA Aristocort Buy Lotensin Cheap Elimite Purchase Speman Cheap Watson Norco Zero Nicotine Cheap Atrovent Cheap Acomplia Order Reosto Purchase Pilex Purchase Acticin Adipex Order Fosamax Purchase Vasotec Cheap Feldene Capoten Order Lincocin Ephedrine Order Pravachol Exelon Cheap Sinequan Order Casodex Men Attracting Himcospaz Celexa Order Buspar Cheap Tenuate Order Lariam Purchase Lorazepam Cheap Copegus Order Trazodone Order Pamelor Flexeril Cheap Aristocort Diovan Cheap Monoket Purchase Premarin Cheap Glucophage Order Prevacid Order Septilin Buy Cardizem Didrex Order Lasuna Purchase Lamisil Buy Dilantin Purchase Miacalcin Buy Avandamet Plavix Buy Elavil Virility Gum Buy Calan Darvocet Buy Vytorin Purchase Aciphex Order Naprosyn Purchase Celexa Purchase Himcolin Cheap Zebeta Buy Augmentin Cheap Depakote Cheap Aceon Buy Snoroff Purchase Protonix Order Calan Buy Shallaki Order Copegus Buy Atrovent Cheap Femcare Diflucan Cheap Levitra Buy Dosages Nexium Purchase Mevacor Avandamet Cheap Cialis Triphala Combivent Himplasia Buy Amoxil Buy Evecare Cheap Avodart Purchase Singulair Green Tea Zerit Cheap Casodex Purchase Glucophage Purchase Relafen Pletal Buy Zestril Buy Femcare Purchase Stromectol Detrol Cheap Geriforte Buy Himplasia Buy Cyklokapron Buy Amaryl Women Attracting Buy Coumadin Buy Keftab Cheap Butalbital Order Vytorin Purchase Biaxin Purchase Nonoxinol Cheap Inderal Cheap Ativan Buy Karela Buy Loprox Order Amaryl Buy Levaquin Cheap Vicodin Lasuna Hydrocodone Buy Mentat Cheap Ansaid Buy Codeine Buy Biaxin Buy Oxytrol Cheap Evista Buy Cystone Vitamin A Buy Imdur Order Diazepam Cheap Cymbalta Cheap Serophene Order Protonix Purchase Prandin Cheap Lopressor Male Enhancement Cheap Nexium Purinethol Order Prometrium Purchase Aldactone Gyne-Lotrimin Protonix Order Bonnisan Order Flovent Cheap Adalat Buy Nolvadex Order Zyvox Cheap Mycelex-G Purchase Effexor Danazol Purchase Imdur Levlen Order Plan Order Tenuate Purchase StretchNil Viramune Purchase Triphala Order Zyrtec Cheap Hydrocodone Rogaine Buy Avandia Order Norco Cheap Urispas Buy Zebeta Methocarbam Buy Methocarbam Buy Zyrtec Antabuse Cheap Ambien Buy Aricept Buy Procardia Purchase Lipitor Order Codeine Order Cordarone Purchase Zyvox Myambutol Cheap Lioresal Cipro Lioresal Cheap Crestor Order Cipro Cheap Nirdosh Purchase Sumycin Order Acyclovir Cheap Brafix Avodart Order Cardizem Aldactone Desyrel Buy Tramadol Buy Urispas Purchase Lortab Diet Maxx Order Confido Purchase Zovirax Cheap Atarax Order Zanaflex Order Emsam Purchase Zelnorm Cheap Buspar Lozol Buying Tenuate Cheap Levaquin Zyprexa

Archive for the 'Object Thinking' Category

RSpec, meet Flow.

Flow is the demon that appears after about an hour of using RSpec. He sits on my shoulder and watches me code.

After a few minutes he stands up. I know to turn down the music. He whispers into my ear. I nod in agreement and mouth back a response.

Flow understands my frustration with testing individual methods outside any greater context or usage scenario.

RSpec is a beautiful tool. Leaps and bounds ahead of the standard testing framework built into Rails. It does this, I believe, by allowing you to use more natural language. But it can be be misused. Which is actually closer to abuse, because unless you’re paying respect to flow, you’re fucking yourself.

Don’t test methods outside of the context in which they should be used. It doesn’t mean shit to the person reading your code — even if that person is you.

Your program is a play. Your objects are actors. You are the writer and director. You say action! You say cut! You know all the lines, but it’s not your job to voice them. You assign those roles to your actors, and they carry it out much better than your sorry ass ever could.

Context. Acknowledge it. If your tests look like this:

describe 'An InterestList' do
 
  before(:each) do
    @interest_list = InterestList.create
  end
 
  describe 'upon creation' do
 
    it 'should not be a new record'
    it 'should be empty'
    it 'should report having zero Interest objects'
 
  end
 
  describe 'when adding a ProductInventoryPrototype' do
 
    before(:each) do
      @cheese = inventory_prototypes(:cheese)
    end
 
    it 'should answer true'
 
  end
 
  describe 'with one ProductInventoryPrototype in it' do
 
    before(:each) do
      @cheese = inventory_prototypes(:cheese)
      @interest_list.add(@cheese)
    end
 
    it 'should not be empty'
    it 'should report having one interest'
 
  end
 
  describe 'removing a ProductInventoryPrototype' do
 
    before(:each) do
      @cheese = inventory_prototypes(:cheese)
      @interest_list.add(@cheese)
    end
 
    it 'should answer with the item when that item is removed'
 
  end
 
  describe 'after removing a ProductInventoryPrototype' do
 
    before(:each) do
      @cheese = inventory_prototypes(:cheese)
      @interest_list.add(@cheese)
      @interest_list.remove(@cheese)
    end
 
    it 'should be empty when that item is removed'
    it 'should report having zero items'
 
  end
 
end

You’re fucking with the laws of nature, and you will be smitten.

What’s wrong with it? The

#before

code is reinitializing all the steps you’ve just accomplished.

Make it look like this.

describe 'When working with an InterestList,' do
 
  before(:each) do
    @interest_list = InterestList.create
  end
 
  describe 'it must first be created.' do
 
    it 'It should not be a new record.'
    it 'It should be empty.'
    it 'It should report having zero Interest objects.'
 
    describe ' After which you can add a product.' do
 
      before(:each) do
        @cheese = inventory_prototypes(:cheese)
      end
 
      it 'To which it should answer true.'
 
      describe 'It now has one product in it.' do
 
        before(:each) do
          @interest_list.add(@cheese)
        end
 
        it 'It should not be empty.'
        it 'It should report having one interest.'
 
        describe 'Now we remove a ProductInventoryPrototype.' do
 
          it 'It should answer with the item when that item is removed.'
 
          describe 'After removing a ProductInventoryPrototype,' do
 
            before(:each) do
              @interest_list.remove(@cheese)
            end
 
            it 'it should be empty when that item is removed.'
            it 'it should report having zero items'
 
          end
 
        end
 
      end
 
    end
 
  end
 
end

Of primary importance is a subtle point. I didn’t illustrate it in the two examples above, but I will now:

Before:

describe 'When working with an InterestList,' do
 
  before(:each) do
    @interest_list = InterestList.create
  end
 
  describe 'it must first be created.' do
 
    it 'It should not be a new record.'
    it 'It should be empty.'
    it 'It should report having zero Interest objects.'
 
    describe ' After which you can add a product.' do
 
      before(:each) do
        @cheese = inventory_prototypes(:cheese)
      end
 
      it 'To which it should answer true.'
      it 'It should not be empty.'
      it 'It should report having one interest.'
...

After:

describe 'When working with an InterestList,' do
 
  before(:each) do
    @interest_list = InterestList.create
  end
 
  describe 'it must first be created.' do
 
    it 'It should not be a new record.'
    it 'It should be empty.'
    it 'It should report having zero Interest objects.'
 
    describe ' After which you can add a product.' do
 
      before(:each) do
        @cheese = inventory_prototypes(:cheese)
      end
 
      it 'To which it should answer true.'
 
      describe ' At which point' do
 
        it 'it should not be empty.'
        it 'it should report having one interest.'
...

On the surface it’s just another level of nesting. After meditation, you realize you are first testing the object’s answer to your message. Second, you are testing the state of the universe after that action is made. The difference is subtle, but important. If you don’t understand the importance, recall the indifference you felt when you first saw RSpec: it’s Rails’ standard test framework with different words thrown in.

You were wrong then and you’re wrong now.

Words offer the means to meaning, and for those who will listen, the enunciation of truth. - V

If an object’s answer to the message passed violates expectations, flow stops. You know, not that a method is broken, but that flow is broken.

Of secondary importance is that your

#before

methods are no longer redundant.

The result of running your tests now looks like:

When working with an InterestList, it must first be created. It should not be a new record. (Not Yet Implemented)
When working with an InterestList, it must first be created. It should be empty. (Not Yet Implemented)
When working with an InterestList, it must first be created. It should report having zero Interest objects. (Not Yet Implemented)
When working with an InterestList, it must first be created. After which you can add a product. To which it should answer true. (Not Yet Implemented)
When working with an InterestList, it must first be created. After which you can add a product. It now has one product in it. It should not be empty. (Not Yet Implemented)
When working with an InterestList, it must first be created. After which you can add a product. It now has one product in it. It should report having one interest. (Not Yet Implemented)
When working with an InterestList, it must first be created. After which you can add a product. It now has one product in it. Now we remove a ProductInventoryPrototype. It should answer with the item when that item is removed. (Not Yet Implemented)
When working with an InterestList, it must first be created. After which you can add a product. It now has one product in it. Now we remove a ProductInventoryPrototype. After removing a ProductInventoryPrototype, it should be empty when that item is removed. (Not Yet Implemented)
When working with an InterestList, it must first be created. After which you can add a product. It now has one product in it. Now we remove a ProductInventoryPrototype. After removing a ProductInventoryPrototype, it should report having zero items (Not Yet Implemented)

This is easier to read and understand than a series of isolated tests. You’re made aware of the context in which each test is being run, which is priceless when you’re attempting to understand the system from a high level.

Congratulations. You are no longer a moron coding outside the realm of the context in which your code lives.

I, Object.

From the GNU Smalltalk documentation:

I am an abstract class. My objects represent things that are discrete and map to a number line. My instances can be compared with < and >.

The documentation is written from the perspective of the object. Brilliant.

The Ruby forum is hot, hot today!

An avid Rubyist proposes a very interesting #as method. It definitely looks rubyesque, but others disagree, namely James Gray and a bright fellow named Ara.

The original posting that generated a lot of good debate on the merits of such an approach.

The OP’s second attempt at gaining the hearts of others, only to be fucking smashed by Ara. Beautiful.

I don’t mean to be sensational about it, but the code presented by Ara was simply gorgeous. It reeked of solid programming and, more importantly, a fundamental understanding that if you’re resorting to “neat tricks,” you your code needs to be smothered into submission.

That said, I still like the idea of #as. It’s different, and there are some drawbacks (mentioned on the forum), but I think it’s worth feeling out. It’s easy as hell to implement:

class Object
 
  def as
    yield(self)
  end
 
end

So it’s worth trying.

Here are the two examples in question.

Ara’s suggestion (”traditional” method, in my mind):

guesses = stems.map{|stem| "#{ stem }.#{ guess_extension stem }"}
basenames = transform2 source_files + transform1(guesses)
expanded = basenames.map{|basename| File.join dir basename}

FC’s suggestion with #as:

expanded = stems.map { |stem|
  "#{ stem }.#{ guess_extension stem }"
}.as { |guesses|
  transform2(source_files + transform1(guesses))
}.as { |basenames|
  basenames.map { |basename|
    File.join dir, basename
  }
}

Now, I will not use curly braces for multi-line blocks. Forget about it. Here’s what it looks like with do…end.

expanded = stems.map do |stem|
  "#{ stem }.#{ guess_extension stem }"
end.as do |guesses|
  transform2(source_files + transform1(guesses))
end.as do |basenames|
  basenames.map do |basename|
    File.join dir, basename
  end
end

Not so pretty, is it? It’s definitely less sentence-like than with curly braces.

In the end I suppose it comes down to style and being flexible. If I were so dead-set against { and } for multi-line blocks I could definitely see using it. On the other hand I question my conviction in that area… is it worth missing out on a potentially useful feature just to follow dogma?

Programming. It’s definitely an art.

Am I a bad programmer because I don’t like to write complicated code?

If a problem becomes more difficult than I feel it should be I decompose it. I take a step back, look at my surroundings and ask, “what am I trying to solve? Who are the characters in my story and what guides them to do the things they do?”

It sounds epic, poetic and a little corny, but it’s how I think. And it’s how I program. I don’t like solving complicated tasks. I like breaking complicated tasks into many smaller, manageable tasks and solving those. Once each smaller task is solved, I script a dialog between them to achieve the desired effect.

Imagine a movie in which there was only one actor. That actor would have to play the good guy, the bad guy, the dame, all supporting characters and the director’s cameo. Such a movie would have to be classified as a psychological thriller, because it would take an insane person to be able to keep everything together. While the move may have been fun to make (for a sadist), few members of the audience would be able to follow the movie all the way through.

So as your audience leaves the theater, as your programmers leave your project, you realize that being clever and complicated may not have been the best choice.

No, I don’t think I’m a bad programmer. I think I’m a programmer with taste, my own style and a little class. Instead of banging the keys for eight hours, I spend an hour drawing with a pen and paper. Then I spent twenty minutes staring at the wall. I get up, walk around and stretch my legs. I take a piss. When I come back to the computer I have an intricate web of many, simple objects with obvious names and well-defined behaviors. At the end of the day I’m proud of the code I wrote.

And most importantly, I don’t dread coming back to it the next day.

Discovering self-evaluating objects. (pt. 2 of 2)

Notice: There’s quite a bit of code covered in this post. You can follow along with most of it using IRB. However, to run the final example, you’ll be better off downloading this complete Rails application. Follow the instructions in doc/readme.

Notice: This is part two of a two-part post. Read the first post if you haven’t already.

All right! We’re taking the experience we gained from our last endeavor, and we’re putting it on rails.

First off, let’s think about our goal — what are we trying to accomplish? How does it flow? What does it look like? In this case it’s pretty simple. It should probably look something like this:

1
product.evaluate('cost')

That’s concise, and it looks good. But it’s not the whole story. So let’s think some more.

We know we have products, as shown above. We have rules, presumably shown above as the argument to the evaluate method. What about variables? I don’t see those in the code above.

Before we go any further, let’s hash out a quick drawing to show our understanding of our objects and their relationships. … Done!

diagram

Pretty basic right now, but let’s talk about it.

Rules have many products have many variables. Interesting. When you say it like that, it almost doesn’t make sense. A rule has many products? What would that look like code-wise?

1
2
3
4
5
class Rule
 
  has_many :products
 
end

Hmm… looks perverted, (a rule has products?) but let’s keep going with some pseudo code.

1
2
product = rule.products.create(:name => 'Cheese')
product.variables.create(:name => 'cost', :value => 2)

I dig the second part:

1
product.variables.create(:name => 'cost', :value => 2)

So let’s keep that in mind as we progress.

But what about rule.products? Our first line of code tells us that products should be able to evaluate rules, so there’s gotta be some connection between the two. What do we know least about? I’d probably say the rule objects themselves right now, so let’s take a stab at designing one.

What are rules? Well, let’s look at a real-life example.

1
(x + y) * z

What do you see? … You could say you see a simple math formula. Perhaps, if you recall David West’s explanation of self-evaluating objects, you see an array of operators and variables. Before I tell you what I see, I’d like to say that what I see is based off my previous experience handling self-evaluating objects. That said, I see a string.

That’s it. A simple string.

1
"(x + y) * z"

It’s so simple it’s obvious. And obvious things are often the most difficult thing to see. Now that we know what a rule is, let’s try and define it using object parlance. What can a rule do? What’s it comprised of? What are its responsibilities? Who must it collaborate with to get its job done?

The first thing I see is a definition. Every rule has a definition. I suppose also, that a rule could have a name; it would certainly make distinguishing one rule from another easier.

So now we have named rules with definitions. Let’s see some potential code.

1
2
rule = Rule.new(:name => 'Product cost')
rule.definition = '(cost * tax) + cost'

Looks pretty clean. What next? Oh, that’s right, the obvious: a rule can evaluate itself. Ah, there’s the meat. Now we have a new, non AR-inherited responsibility: evaluation. Let’s take a look..

1
2
3
4
5
6
7
class Rule < ActiveRecord::Base
 
  def evaluate
    eval(definition)
  end
 
end

That’d be nice if that’s all we had to do. Unfortunately it’s not. Executing that as is will raise an error. If we think back to our earlier work, we did simple string replacement to get the proper values into our definitions.

1
2
3
def evaluate
  definition.gsub('uh oh!', "we're missing something")
end

Things just got interesting. Our rule doesn’t have access to the variables needed for substitution in its definition. Hmm… let’s stop and think for a moment.

Rules, products and variables. Products have variables for use in rules. A rule’s definition must have its variables substituted out for actual values. What does that look like?

1
2
rule = Rule.new(:name => 'Product cost', :definition => '(cost * tax) + cost')
rule.evaluate(product)

Hmm… that could work. A rule requires a product be passed to it to gather the contents of its variables.

Let’s try that.

1
2
3
4
5
6
7
8
9
10
11
class Rule < ActiveRecord::Base
 
  def evaluate(product)
    product.variables.each do |variable|
      definition.gsub!(variable.name, variable.value)
    end
 
    eval(definition)
  end
 
end

Not bad. A rule can evaluate itself in the context of a given product’s variables. This will work provide the product passed has all the variables required of the rule, and vice versa.

Of course, this model doesn’t look like our original diagram, and that’s okay. Things rarely go according to plan. Let’s review what we’ve done so far.

Rule objects stand alone. They are named mathematical definitions that evaluate in the context of a given product. (In reality, ‘product’ could be any object that responds to a variables method.)

So our end result is now:

1
rule.evaluate(product)

That’s the opposite of what I originally proposed (product.evaluate(rule)), and I’ll be honest; it feels weird. On the bright side, this solution is much simpler than my original Rails version. However, I still like the feel of:

1
product.evaluate(rule)

But hey, whatever works, right? There’s just one more thing… rules within rules. Ah, interesting. What would that look like?

Well, actually, it shouldn’t look any different than what we have now:

1
rule.evaluate(product)

The above should still give us a single number. So it’s not the end-result (API) code that will be changing, it’s our internal code that will change. But how? Let’s take a look at our definition.

1
(cost * tax) + cost

The part in parentheses represents the tax of our product. So we’re really saying:

1
tax + cost

But now we’ve run into a problem. Is tax a rule or a variable? Currently there’s no way to know the difference. How can we solve that? We need to be able to differentiate between rules and variables. Or at least, I think we should.

We could not, and simply perform blind string replacement, replacing everything in our definition with every match in our variables method. The result is a definition where the only non-numeric or operator characters must be rules. But that sounds pretty flimsy. I can easily see there being a tax variable and a tax rule. So let’s make clear the difference between rules and variables.

1
r:tax + v:cost

Heh, that works. It changes our replacement around a bit, but not too much.

1
2
3
4
5
6
7
def evaluate(product)
  product.variables.each do |variable|
    definition.gsub!('v:' + variable.name, variable.value)
  end
 
  eval(definition)
end

Okay, so we can still evaluate our variables. But what about other rules? What do we do when we encounter another rule? Let’s try scanning the string for instances of r:xxx, do a find for that rule name, and then call evaluate on it…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  def evaluate(product)
    # Operate on a copy of the definition so the original is open to variable
    # changes.
    evaluated_definition = definition.dup
 
    # Recursively evaluate any rules in the definition.
    evaluated_definition.scan(/r:\w+/) do |match|
      evaluated_definition.gsub!(match, Rule.find_by_name(match[2, match.length]).evaluate(product).to_s)
    end
 
    # Replace variables in the definition with values provided by product.
    product.variables.each do |variable|
      evaluated_definition.gsub!('v:' + variable.name, variable.value.to_s)
    end
 
    # Evaluate the fully-substituted string.
    eval(evaluated_definition)
  end

Let’s run this! (You’ll need to download the complete Rails app and follow the directions in doc/readme.)

1
2
3
4
5
6
7
8
9
10
11
12
rule = Rule.new(:name => 'Product cost')
rule.definition = 'r:tax + v:cost'
rule2 = Rule.new(:name => 'tax')
rule2.definition = '(v:cost * v:tax)'
rule.save
rule2.save
 
product = Product.new(:name => 'Cheese')
product.variables.create(:name => 'cost', :value => 10)
product.variables.create(:name => 'tax', :value => 0.05)
 
rule.evaluate(product) # => 10.5

Scha-weet! But wait! Do I see a recursive function in there? I sure do! Let’s deconstruct it.

We have a rule. We scan its definition for strings that look like r:xxx. Once found, we perform a global string replacement on it, using r:xxx as the pattern match, with the substitute string being the return value of calling evaluate on the rule referenced by r:xxx.

Was that confusing? Don’t worry if it was. Recursion can be pretty difficult to wrap your head around, especially if you didn’t write it. If you’re determined to understand, I suggest reading it several times, line by line, very slowly. It may also help to add some puts messages in there and run it a few times in IRB. That way you can “watch” the recursion magic.

Anywho, just know that it works, and will go as deep as you need it to.

So there we have it! A fully Rails-based self-evaluating monster that will slay accountants everywhere! Okay not really, but it’s pretty cool nonetheless, right?

As excited as you may be, don’t lose yourself. It still only works in console. If you choose to use a solution like this you’ll still need to build an interface for people to use it. There are some other small issues, too. Rule names, for instance. The way the evaluate method is written, you can’t have multiple rules with the same name. Well, you could, but you’d only ever be able to access the first one. There’s also the rigidity of relationships. Product specifically defines its relationship to variables, and there’s still the (I consider) perversion of calling rule.evaluate(product). I’d love to see product.evaluate(rule).

That, however, is an exercise I leave to you. ;)

Oh, before I go, here’s the complete code listing:

rule.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Rule < ActiveRecord::Base
 
  # Answers with a BigDecimal after recursively solving rule definitions,
  # performing variable replacement and finally evaluating a flat formula.
  #
  def evaluate(product)
    # Operate on a copy of the definition so the original is open to variable
    # changes.
    evaluated_definition = definition.dup
 
    # Recursively evaluate any rules in the definition.
    evaluated_definition.scan(/r:\w+/) do |match|
      evaluated_definition.gsub!(match, Rule.find_by_name(match[2, match.length]).evaluate(product).to_s)
    end
 
    # Replace variables in the definition with values provided by product.
    product.variables.each do |variable|
      evaluated_definition.gsub!('v:' + variable.name, variable.value.to_s)
    end
 
    # Evaluate the fully-substituted string.
    eval(evaluated_definition)
  end
 
end

product.rb

1
2
3
4
5
class Product < ActiveRecord::Base
 
  has_many :variables, :dependent => :destroy
 
end

variable.rb

1
2
3
4
5
class Variable < ActiveRecord::Base
 
  belongs_to :product
 
end

schema.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ActiveRecord::Schema.define(:version => 3) do
 
  create_table "products", :force => true do |t|
    t.column "name", :string
  end
 
  create_table "rules", :force => true do |t|
    t.column "name",       :string
    t.column "definition", :string
  end
 
  create_table "variables", :force => true do |t|
    t.column "product_id", :integer
    t.column "name",       :string
    t.column "value",      :decimal, :precision => 8, :scale => 2
  end
 
end

Discovering self-evaluating objects. (pt. 1 of 2)

Notice: This is part one of a two-part post. Read the second post when you’re done with this one.

Have you ever written a program that sells products? Did you have to write the pricing rules for those products? Was it difficult? Time-consuming? Constantly changing? Screwing up your code and frustrating you to no end? Did people dip their grimy hands into your beautiful algorithm by asking you to make (gasp!) exceptions for this or that product? And don’t forget that special affiliate that everyone loves! He gets an extra percentage on top of everything else.

Stop. Breathe. Calm down. Everything will be okay. Object thinking to the rescue. Introducing the self-evaluating rule.

David West, author of Object Thinking, describes a self-evaluating rule thusly:

…think of a self-evaluating rule as an expression in the form of an ordered sequence of operators, variables, and constants. When the rule is asked to evaluate itself, each of its variables is asked to instantiate itself (become a known value), after which the rule resolves itself (applies the operators in the expression) to a final value — a Boolean true or false, for example.

Let’s take a stab at that, shall we? (You can paste the whole thing into IRB if you want to follow along.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class SelfEvaluatingRule
 
  attr_accessor :price, :tax
 
  def evaluate
    (@price * @tax) + @price
  end
 
end
 
rule = SelfEvaluatingRule.new
rule.price = 100
rule.tax = 0.05
rule.evaluate # 105.0

Interesting. Okay, not really. It might resemble something you’ve written before, but instead of calling the evaluate method, you called price (or something similar).

Let’s think for a moment… What we have is a collection of variables, and a hard-coded formula that puts those variables to use. Right now it’s pretty crappy code. How can we make it better?

  1. Enter variable names and values dynamically
  2. Enter arbitrary rules
  3. Evaluate changes to our variables and rules without rewriting code

Those sound like solid goals. We’re going to take another stab at it, but before we do, I’d like to show you something that might be new to you…

First, fire up IRB.

Now, type the following, then hit enter:

1
eval "1 + 1"

What did you get? 2? Pretty cool, eh? This simple trick forms the basis of everything else we’re going to do.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class SelfEvaluatingRule
 
  attr_accessor :price, :tax
 
  def evaluate(rule)
   eval(rule.gsub('price',