Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

must_be

Package Overview
Dependencies
Maintainers
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

must_be

  • 1.1.0
  • Rubygems
  • Socket score

Version published
Maintainers
1
Created
Source

must_be Runtime Assertions

Ruby doesn't have a static type system. Tests and specs are well and good. But at the end of the day, Cthulhu reigns and sanity needs checking. must_be provides runtime assertions in all kinds of delicious Ruby flavors.

You can configure must_be to notify you of trouble in any way imaginable. Just set MustBe.notifier to any proc or other call worthy object. By default must_be raises an error, but logging and debugging notifiers come included. Furthermore, you can disable must_be at any time if its performance penalty proves unsatisfactory.

Examples

After installing:

> sudo gem install must_be

Begin with customary oblations:

require 'rubygems'
require 'must_be'

Now Object has a number of must* and must_not* methods. Here are several examples of each. When an example notifies, the message corresponding to its MustBe::Note error is listed on the following comment line (#=> or #~> for a regexp match).

Basic

For your everyday, average sanity:

87.must_be_a(Float, Integer)
87.must_be_a(Symbol, String)
#=> 87.must_be_a(Symbol, String), but is a Integer

87.must_not_be_a(Symbol, String, "message")
87.must_not_be_a(Float, Integer, "message")
#=> 87.must_not_be_a(Float, Integer, "message"), but is a Integer

must_be_in uses include? to check. Multiple arguments are grouped together as an array:

2.must_be_in(1, 2, 3)
2.must_be_in([1, 2, 3])
2.must_be_in(1 => 3, 2 => 4)
2.must_be_in(1..3)
2.must_be_in
#=> 2.must_be_in

2.must_not_be_in
2.must_not_be_in(4, 5)
2.must_not_be_in(1 => 3, 2 => 4)
#=> 2.must_not_be_in({1=>3, 2=>4})

must_be_boolean ensures the value is true or false:

true.must_be_boolean
false.must_be_boolean
nil.must_be_boolean
#=> nil.must_be_boolean

Sometimes close is good enough:

2.must_be_close(2.0)
2.must_be_close(2.01)
2.must_be_close(2.1)
#=> 2.must_be_close(2.1, 0.1), difference is 0.1

2.must_be_close(2.1, 6)
2.must_be_close(9.0, 6)
#=> 2.must_be_close(9.0, 6), difference is 7.0

must_be uses case (===) equality:

34.must_be(Integer, :all)
:all.must_be(Integer, :all)
:none.must_be(Integer, :all)
#=> :none.must_be(Integer, :all), but matches Symbol

5.must_be(1..5)
5.must_be(1...5)
#=> 5.must_be(1...5), but matches Integer

5.must_not_be(1...5)
3.must_not_be(1...5)
#=> 3.must_not_be(1...5), but matches Integer

true.must_be
nil.must_be
#=> nil.must_be, but matches NilClass
false.must_be
#=> false.must_be, but matches FalseClass

nil.must_not_be
:hello.must_not_be
#=> :hello.must_not_be, but matches Symbol

:yep.must_be(lambda {|v| v == :yep})
:nope.must_be(lambda {|v| v == :yep})
#~> :nope.must_be\(#<Proc:0x[^.]+?>\), but matches Symbol

:yep.must_not_be(lambda {|v| v == :nope})
:nope.must_not_be(lambda {|v| v == :nope})
#~> :nope.must_not_be\(#<Proc:0x[^.]+?>\), but matches Symbol

Proxy

must either takes a block, or it returns a proxy for checking predicates:

7.must {|x| x > 3}
7.must {|x| x < 3}
#=> 7.must {}

7.must_not {|x| x < 3}
7.must_not {|x| x > 3}
#=> 7.must_not {}

7.must == 7
7.must > 3
7.must.even?
#=> 7.must.even?

7.must_not == 8
7.must_not < 3
7.must_not.odd?
#=> 7.must_not.odd?

Module#attr_typed

attr_typed is like attr_accessor but with type checking:

class Typed
  attr_typed :v, Symbol
  attr_typed :n, String, Integer
  attr_typed :o, &:odd?
end

t = Typed.new
t.v = :hello
t.v = "world"
#=> attribute `v' must be a Symbol, but value "world" is a String

t.n = 411
t.n = 4.1
#=> attribute `n' must be a String or Integer, but value 4.1 is a Float

t.o = 7
t.o = 8
#=> attribute `o' cannot be 8

Containers

It's good to be sure that an array or hash contains the right stuff:

["okay", :ready, "go"].must_only_contain(Symbol, String)
["okay", :ready, 4].must_only_contain(Symbol, String)
#=> must_only_contain: 4.must_be(Symbol, String), but matches Integer in container ["okay", :ready, 4]

["okay", :ready, "go"].must_not_contain(Numeric)
["okay", :ready, 4].must_not_contain(Numeric)
#=> must_not_contain: 4.must_not_be(Numeric), but matches Integer in container ["okay", :ready, 4]

[].must_only_contain(:yes, :no)
[:yep].must_only_contain(:yes, :no)
#=> must_only_contain: :yep.must_be(:yes, :no), but matches Symbol in container [:yep]

[].must_not_contain(:yes, :no)
[:yes, :no].must_not_contain(:yes, :no)
#=> must_not_contain: :yes.must_not_be(:yes, :no), but matches Symbol in container [:yes, :no]

[0, [], ""].must_only_contain
[nil].must_only_contain
#=> must_only_contain: nil.must_be, but matches NilClass in container [nil]

[nil, false].must_not_contain
[0].must_not_contain
#=> must_not_contain: 0.must_not_be, but matches Integer in container [0]

{:welcome => :home}.must_only_contain(Symbol => Symbol)
{:symbol => :s, :Integer => 5}.must_only_contain(Symbol => [Symbol, Integer])
{5 => :s, 6 => 5, :t => 5, :s => :s}.must_only_contain([Symbol, Integer] => [Symbol, Integer])
{6 => 5}.must_only_contain(Symbol => Integer, Integer => Symbol)
#=> must_only_contain: pair {6=>5} does not match [{Symbol=>Integer, Integer=>Symbol}] in container {6=>5}

{:welcome => nil}.must_not_contain(nil => Object)
{nil => :welcome}.must_not_contain(nil => Object)
#=> must_not_contain: pair {nil=>:welcome} matches [{nil=>Object}] in container {nil=>:welcome}

{:welcome => :home}.must_only_contain
{:welcome => nil}.must_only_contain
#=> must_only_contain: pair {:welcome=>nil} does not match [] in container {:welcome=>nil}

{nil => false, false => nil}.must_not_contain
{nil => 0}.must_not_contain
#=> must_not_contain: pair {nil=>0} matches [] in container {nil=>0}

It's better to be sure that a array or hash always contains the right stuff:

numbers = [].must_only_ever_contain(Numeric)
numbers << 3
numbers << :float
#=> must_only_ever_contain: Array#<<(:float): :float.must_be(Numeric), but matches Symbol in container [3, :float]

financials = [1, 4, 9].must_never_ever_contain(Float)
financials.map!{|x| Math.sqrt(x)}
#=> must_never_ever_contain: Array#map! {}: 3.0.must_not_be(Float), but matches Float in container [1.0, 2.0, 3.0]

MustBe::MustOnlyEverContain.register

It's best to be sure that your custom container contains the right stuff:

class Box  
  attr_accessor :contents
  
  def self.[](contents = nil)
    new(contents)
  end
  
  def initialize(contents = nil)
    @contents = nil
  end
  
  def each
    yield(contents) unless contents.nil?
  end
  
  def empty!
    self.contents = nil
  end
  
  def inspect
    "Box[#{contents.inspect}]"
  end
end

MustOnlyEverContain.register(Box) do
  def contents=(contents)
    must_check_member(contents)
    super
  end
  
  must_check_contents_after :empty!
end

box = Box[:hello].must_only_ever_contain(Symbol)
box.contents = :world
box.contents = 987
#=> must_only_ever_contain: Box#contents=(987): 987.must_be(Symbol), but matches Integer in container Box[987]

box = Box[2].must_never_ever_contain(nil)
box.contents = 64
box.empty!
#=> must_never_ever_contain: Box#empty!: nil.must_not_be(nil), but matches NilClass in container Box[nil]

Core

Define new must-assertions by using must_notify:

must_notify("message")
#=> message

must_notify(:receiver, :method, [:args], nil, ", additional message")
#=> :receiver.method(:args), additional message

note = MustBe::Note.new("message")
must_notify(note)
#=> message

Use must_check to temporarily override MustBe.notify usually as part of a bigger, better must-assertion.

note = must_check {}
note.must == nil

note = must_check { :it.must_not_be }
must_notify(note)
#=> :it.must_not_be, but matches Symbol

def add_more_if_notifies
  must_check(lambda do
    yield
  end) do |note|
    MustBe::Note.new(note.message+" more")
  end
end

result = add_more_if_notifies { 5 }
result.must == 5

add_more_if_notifies { must_notify("learn") }
#=> learn more

Nonstandard Control Flow

Last, but not least, we have must-assertions for raising and throwing.

def rescuing
  yield
rescue
  raise if $!.is_a? MustBe::Note
end

rescuing { must_raise(/match/) { raise ArgumentError, "should match" } }
must_raise { "But it doesn't!" }
#=> main.must_raise {}, but nothing was raised

must_not_raise { "I'm fine."}
rescuing { must_not_raise { raise } }
#=> main.must_not_raise {}, but raised RuntimeError

catch(:ball) { must_throw { throw :ball } }
catch(:ball) { must_throw(:party) { throw :ball } }
#=> main.must_throw(:party) {}, but threw :ball

catch(:ball) { must_not_throw(:ball, :fiercely) { throw :ball, :gently } }
catch(:ball) { must_not_throw { throw :ball } }
#=> main.must_not_throw {}, but threw :ball

Configuration

Set MustBe.notifier as needed. It takes a MustBe::Note as an argument and raises an exception unless its return value is false or nil:

MustBe.notifier = lambda do |note|
  puts note
  false
end

You can freely MustBe.disable and MustBe.enable as needed:

MustBe.disable

3.must == 4
# no message

MustBe.enable

3.must == 4
#=> 3.must.==(4)

Before requiring must_be, you can use ENV['MUST_BE__NOTIFIER'] to set the notifier:

# Default: just raises the note.
ENV['MUST_BE__NOTIFIER'] = :raise

# Puts the note together with its backtrace.
ENV['MUST_BE__NOTIFIER'] = :log

# Invokes ruby-debug.
ENV['MUST_BE__NOTIFIER'] = :debug

# Calls MustBe.disable.
ENV['MUST_BE__NOTIFIER'] = :disable

By default MustBe is mixed into Object. If you want to mix MustBe selectively, set:

ENV['MUST_BE__DO_NOT_AUTOMATICALLY_INCLUDE_IN_OBJECT'] = "anything"

Enjoy must_be.

FAQs

Package last updated on 22 May 2018

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc