Skip to content

maiha/must

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

55 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Must
====
  add Object#must method to constrain its origin and conversions

  # can write like this
    num = params[:num].to_i.must.match(1..300) {10}

  # rather than
    num = params[:num].to_i
    num = 10 unless (1..300) === num

  and has duck-type features

    1.must.duck?(:to_s)  # => true
    io.must.duck(:write) { io.extend Writable }

  and has struct assetions

    pages = [{:name=>"...", :url=>"...",} ...]
    pages.must.struct([Hash])

  You want Boolean class? try this!

    flag = hash["flag"].must(true, false)


Asking Methods
==============
  be      : check whether object equals to the argument
  kind_of : check whether object is a kind of the arguments
  coerced : check whether object can be coerced to the argument
  blank   : check whether object is blank?
  exist   : check whether object is not nil (NOTE: false is ok)


Logical Methods
===============
  not : logical NOT


Nop Methods
===========
  a  : return self
  an : return self

These effect nothing but exist only for English grammar.


Duck Methods
============
  duck("foo")  : check whether object responds to "foo" method.
  duck(:foo)   : same above
  duck(".foo") : same above
  duck("#foo") : check whether object has "foo" instance method. (tested only in class/module)
  duck?(...)   : acts same as "duck", but this returns a just boolean
  duck!("foo") : if foo exists, call it. otherwise raises Invalid


Struct Methods
============
  struct(...)  : check whether object has a same struct with ...
  struct?(...) : acts same as "struct", but this returns a just boolean


Basic Examples
==============

# test its value exactly
  1.must.be 1              # => 1
  [1,2,3].must.be [1,2,3]  # => [1,2,3]

# exceptions
  1.must.be []             # Must::Invalid exception
  1.must.be([]) {:ng}      # => :ng
  1.must.be(1) {:ng}       # => 1

# as default value
  name = params[:name].must.not.be.blank{ "No name" }

# existing test
  1.must.exist             # => 1
  nil.must.exist           # Must::Invalid exception
  false.must.exist         # => false

# test class : ensures that a class of the object is one of given arguments
  1.must.be.kind_of(Integer)         # => 1
  1.must.be.kind_of(Integer, Array)  # => 1
  [].must.be.kind_of(Integer, Array) # => []
  1.must.be.kind_of(String, Array)   # Must::Invalid: expected String/Array but got Fixnum

# must(*args) is a syntax sugar for kind_of
  1.must(Integer)          # same as "1.must.be.kind_of(Integer)"

# coercing : looks like kind_of except converting its value if possible
  1.must.be.coerced(Integer, String => proc{|val| val.to_i})    # => 1
  "1".must.be.coerced(Integer, String => proc{|val| val.to_i})  # => 1
  "1".must.be.coerced(Integer, String => :to_i)                 # => 1     (NOTE: inline Symbol means sending the method)
  "1".must.be.coerced(Integer, Symbol, String => proc{:to_i})   # => :to_i (NOTE: use proc to return Symbol itself)

# struct assertions

  uris = build_uris		# ex) [{:host=>"...", :port=>"..."}, ...]
  uris.must.struct([Hash])


Actual Examples
===============

1)

  normal code:

    def set_reader(reader)
      if reader.is_a?(CSV::Reader)
        @reader = reader
      elsif file.is_a?(String)
        @reader = CSV::Reader.create(i)
      elsif file.is_a?(Pathname)
        @reader = CSV::Reader.create(reader.read)
      else
        raise 'invalid reader'
      end
    end

  refactor above code with must plugin
 
    def set_reader(reader)
      @reader = reader.must.be.coerced(CSV::Reader, Pathname=>:read, String=>{|i| CSV::Reader.create(i)}) {raise 'invalid reader'}
    end

2)

  class DateFolder
    def initialize(date)
      @date = date.must.be.coerced(Date, String=>proc{|i| Date.new(*i.scan(/\d+/).map{|i|i.to_i})})
    end
  end

  # this can accept both formats

  DateFolder.new Date.today
  DateFolder.new "2008-12-9"


NOTE
====
"must(*args)" is a shortcut for not "be(*args)" but "kind_of(*args)" and "struct(*args)".

  1.must(1)        # => 1
  1.must(Fixnum)   # => 1
  1.must(2)        # => 1    # NOTE
  1.must.be(2)     # Invalid
  Fixnum.must(1)   # => Fixnum


Bundled Class
=============

  struct = Must::StructInfo.new({"1.1" => {"jp"=>[{:a=>0},{:b=>2}]}})
  struct.types   # => [Hash, String, Array, Symbol, Fixnum]
  struct.compact # => {String=>{String=>[{Symbol=>Fixnum}]}}
  struct.inspect # => "{String=>{String=>[{Symbol=>Fixnum}]}}"


TODO
====
  * add proper error messages


Install
=======
  gem install must

  % irb -r rubygems -r must
  irb(main):001:0> 1.must.be 1
  => 1


Github
======
  http://github.com/maiha/must


Author
======
  maiha@wota.jp

About

a runtime specification tool

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages