Skip to content

justinwiley/higher-expectations

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

= higher_expectations

* http://higher-expect.rubyforge.org

== DESCRIPTION:

Provides an easy and quick way to make sure method arguments are what you expect them to be.

You want to make sure that methods explode if they are given inappropriate inputs, but you don't want to deal with a complete design-by-contract implementation like RDBC.  Please note that this is nothing like a formal design-by-contract in any number of important ways.  It provides something -like- the "obligations" component of DBC.

Writing explicit exception checking is tiring, redundant and error prone:

def calc_sunrise(day, month, year, latitude, longitude, planet)
  raise Exception.new("day should be numeric and in the range of 1-32) unless day.kind_of?(Numeric) && day > 0 && day < 32
  ...etc. etc. ...
end

Higher expectations provides an easy and human readable alternative:

def calc_sunrise(day, month, year)
  has_expectations(day, month)
  day.must_not_be(Numeric).and_must_be_in_range(0..5)
  month.must_be(Numeric)
  ...do other critical work below...
end

== FEATURES/PROBLEMS:

* Please note that this is alpha software
* Provides a set of usefull methods for determining what an object is at runtime, and raising an exception
* Avoids creating these methods in Object directly, and instead extends the objects passed in (although it does add them directly to Numeric due to constraints in Ruby's Numeric implementation)
* Allows for method chaining to provide a dose of syntactic sugar

== SYNOPSIS:

Imagine you have a method to calculate sunrise buried within a 1D planet simulator codebase.  Throughout the codebase, validations are used to check data input, and there is a well thoughtout and well written test suite.

  def calc_sunrise(day, month)
    sunrise = (day - 50000)/month   # arbitrary calculation that assumes day is a number and not negative
  end

However, Joey your coworker hacks away and calls the following function:

  PlanetEarth.sunrise = calc_sunrise(-5, 1)

Code executes, no exceptions are raised, but earths sunrise changes to a weird value.  No amount of unit testing, specing, validating outside the model would have stopped Joey from making this hambone maneuver.

Checking the arguments within the method would have prevented this:

def calc_sunrise(day, month)
  raise ArgumentError.new("day must be numeric") unless day.kind_of?(Numeric)
  raise ArgumentError.new("day must be in range of 1-31") unless day > 1 && day < 31
  raise ArgumentError.new("month must be numeric") unless month.kind_of?(Numeric)
  raise ArgumentError.new("month must be in range of 1-31") unless month > 1 && day < 31  # note subtle bug
  raise ArgumentError.new("month must not be nil") unless month > 1 && month < 31
  ...sunrise calc...
end

But writing this sort of code is slow and error prone.  Wouldn't you like to do this instead?

require 'higher_expectations'
class PlanetCalculations
  include HigherExpectations
  def calc_sunrise(day, month)
      has_expectations(day, month)    # attach expectation methods to each object
      day.must_be_an(Integer)          # day must kind_of? Integer or an exception will be raised
      day.must_be_in_range(1..31)     # day must be in the given range or exception
      # a neat combination of both
      month.must_be_an(Integer).and_must_be_in_range(1..31)
      # since it raises an exception, its trappable, allowing for more flexible handling
      month.must_be_nil rescue ArgumentError nil
    ...sunrise calc
  end
end

...without comments:

def calc_sunrise(day,month)
  has_expectations(day,month)
  day.must_be_a(Integer).and_must_be_in_range(1..31)
  month.must_be_a(Integer).and_must_be_in_range(1..31
  ...sunrise calc...
end

See spec below for details on methods possible.

Copyright (c) 2008 Justin Tyler Wiley (justintylerwiley.com), under GPL V3

== SPEC:

HigherExpectations
- should not raise an exception when included

HigherExpectations#has_expectations
- should loop through given objects, extending each with instance_expectations.rb methods

InstanceMethods

InstanceMethods#raise_ae
- should raise an ArgumentException with the given message

InstanceMethods#must_be_/must_not_be_ - true, false, and nil
- should raise exception if expected to be true false or nil, and they are not
- should not raise exception if expected to be true false or nil, and they are

InstanceMethods#must_be and must_not_be a particular value
- should raise exception if item must be something and isnt
- should not raise exception if item must be something and IS
- (#not) should raise exception if item must not be something and IS
- (#not) should not raise exception if item must not be something and is not

InstanceMethods#must_be_a and must_not_be_a particular class of object
- should raise exception if item must be a class and isnt
- should not raise exception if item must be a class and IS
- (#not) should raise exception if item must not be a class and is not
- (#not) should not raise exception if item must not be a class and IS

InstanceMethods#must_be_in_range and must_not_be_in_range of numbers
- should raise exception if item must be in a range and isnt
- should not raise exception if item must be in a range and is
- (#not) should raise exception if item in a range
- (#not) should not raise exception if item not in range
- should raise HigherExpectation exception if handed something besides and array or a range

InstanceMethods#must_match and must_not_match a given pattern
- should raise exception if item does not match
- should not raise exception if item matches
- (#not) should not raise exception if item does not match
- (#not) should raise exception if item matches

InstanceMethods#must_respond_to and must_not_respond_to a given method
- should raise exception if item does not respond
- should not raise exception if item respond
- (#not) should raise exception if item responds
- (#not) should not raise exception if does respond

== REQUIREMENTS:

* hoe

== INSTALL:

* To build and install the gem from withing higher_expectations directory:
  
  rake local_deploy
  
* To build and install the gem independantly
  
  rake package
  sudo gem install higher_expectations  (where x is version)

== LICENSE:

Copyright (C) 2008 Justin Tyler Wiley

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>

About

Provides a clean, easy way to check method arguments at runtime.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published