Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

#unwrap and #unwrap_or take a block

  • Loading branch information...
commit 2410bd461a6681561bfc85dcaf61c3a69f36bdbd 1 parent 3e37462
@mike-burns mike-burns authored
Showing with 73 additions and 31 deletions.
  1. +47 −25 README.md
  2. +10 −6 lib/wrapped/types.rb
  3. +16 −0 spec/wrapped_spec.rb
View
72 README.md
@@ -1,14 +1,18 @@
Wrapped
-------
-This gem is a tool you can use while developing your API to help consumers of your code to find bugs earlier and faster. It works like this: any time you write a method that could produce nil, you instead write a method that produces a wrapped value.
+This gem is a tool you can use while developing your API to help consumers of
+your code to find bugs earlier and faster. It works like this: any time you
+write a method that could produce nil, you instead write a method that produces
+a wrapped value.
Example
-------
Here's an example along with how it can help with errors:
-Say you have a collection of users along with a method for accessing the first user:
+Say you have a collection of users along with a method for accessing the first
+user:
class UserCollection
def initialize(users)
@@ -46,11 +50,14 @@ And then she tries it:
from (irb):51:in `first_names'
from (irb):57
-But that's odd; UserCollection definitely has a `#first_names` method, and we definitely passed a UserCollection, and ... oooh, we passed no users, and so we got `nil`.
+But that's odd; UserCollection definitely has a `#first_names` method, and we
+definitely passed a UserCollection, and ... oooh, we passed no users, and so we
+got `nil`.
Right.
-Instead what you want to do is wrap it. Wrap that nil. Make the user know that they have to consider the result.
+Instead what you want to do is wrap it. Wrap that nil. Make the user know that
+they have to consider the result.
class UserCollection
def initialize(users)
@@ -62,9 +69,12 @@ Instead what you want to do is wrap it. Wrap that nil. Make the user know that t
end
end
-Now in your documentation you explain that it produces a wrapped value. And people who skip documentation and instead read source code will see that it is wrapped.
+Now in your documentation you explain that it produces a wrapped value. And
+people who skip documentation and instead read source code will see that it is
+wrapped.
-So they unwrap it, because they must. They can't even get happy path without unwrapping it.
+So they unwrap it, because they must. They can't even get a happy path without
+unwrapping it.
class FriendGroups
def initialize(user_collections)
@@ -73,9 +83,7 @@ So they unwrap it, because they must. They can't even get happy path without unw
def first_names
@user_collections.map do |user_collection|
- user_collection.first_user.inject('') do |default,user|
- user.first_name
- end
+ user_collection.first_user.unwrap_or('') {|user| user.first_name }
end
end
end
@@ -83,49 +91,63 @@ So they unwrap it, because they must. They can't even get happy path without unw
Cool Stuff
----------
-In the example above I made use of the fact that a wrapped value mixes in Enumerable. The functional world would say "that's a functor!". That's right it is.
+A wrapped value mixes in Enumerable. The functional world would say "that's a
+functor!". They're right.
-This means that you can `map`, `inject`, `to_a`, `any?`, and so on over your wrapped value. By wrapping it you've just made it more powerful!
+This means that you can `map`, `inject`, `to_a`, `any?`, and so on over your
+wrapped value. By wrapping it you've just made it more powerful!
+
+For example:
+
+ irb(main):054:0> 1.wrapped.inject(0) {|_, n| n+1}
+ => 2
+ irb(main):055:0> nil.wrapped.inject(0) {|_, n| n+1}
+ => 0
Other Methods
-------------
Then we added some convenience methods to all of this. Here's a tour:
- irb(main):001:0> require 'wrapped'
+ irb> require 'wrapped'
=> true
- irb(main):002:0> 1.wrapped.unwrap_or(-1)
+ irb> 1.wrapped.unwrap_or(-1)
=> 1
- irb(main):003:0> nil.wrapped.unwrap_or(-1)
+ irb> nil.wrapped.unwrap_or(-1)
=> -1
- irb(main):004:0> 1.wrapped.present {|n| p n }.blank { puts "nothing!" }
+ irb> 1.wrapped.present {|n| p n }.blank { puts "nothing!" }
1
=> #<Present:0x7fc570aed0e8 @value=1>
- irb(main):005:0> nil.wrapped.present {|n| p n }.blank { puts "nothing!" }
+ irb> nil.wrapped.present {|n| p n }.blank { puts "nothing!" }
nothing!
=> #<Blank:0x7fc570ae21c0>
- irb(main):006:0> 1.wrapped.unwrap
+ irb> 1.wrapped.unwrap
=> 1
- irb(main):007:0> nil.wrapped.unwrap
+ irb> nil.wrapped.unwrap
IndexError: Blank has no value
from /home/mike/wrapped/lib/wrapped/types.rb:43:in `unwrap'
from (irb):7
- irb(main):008:0> 1.wrapped.present?
+ irb> 1.wrapped.present?
=> true
- irb(main):009:0> nil.wrapped.present?
+ irb> nil.wrapped.present?
=> false
- irb(main):010:0> nil.wrapped.blank?
+ irb> nil.wrapped.blank?
=> true
- irb(main):011:0> 1.wrapped.blank?
+ irb> 1.wrapped.blank?
=> false
+ irb> 1.wrapped.unwrap_or(0) {|n| n * 100}
+ => 100
+ irb> nil.wrapped.unwrap_or(0) {|n| n * 100}
+ => 0
Inspiration
-----------
-Inspired by a conversation about a post on the thoughtbot blog titled "If you gaze into nil, nil gazes also into you":
- http://robots.thoughtbot.com/post/8181879506/if-you-gaze-into-nil-nil-gazes-also-into-you
+Inspired by a conversation about a post on the thoughtbot blog titled [If you
+gaze into nil, nil gazes also into you](http://robots.thoughtbot.com/post/8181879506/if-you-gaze-into-nil-nil-gazes-also-into-you).
-Most ideas are from Haskell and Scala. This is not new: look into the maybe monad or the option class for more.
+Most ideas are from Haskell and Scala. This is not new: look into the maybe
+functor or the option class for more.
Copyright
---------
View
16 lib/wrapped/types.rb
@@ -5,8 +5,8 @@ def initialize(value)
@value = value
end
- def unwrap_or(_)
- unwrap
+ def unwrap_or(_, &block)
+ unwrap(&block)
end
def present(&block)
@@ -19,12 +19,16 @@ def blank(&ignored)
end
def each(&block)
- block.call(unwrap) unless block.nil?
+ unwrap(&block)
[unwrap]
end
def unwrap
- @value
+ if block_given?
+ yield @value
+ else
+ @value
+ end
end
def present?
@@ -47,7 +51,7 @@ def unwrap_or(default)
default
end
- def present(&ignored)
+ def present
self
end
@@ -56,7 +60,7 @@ def blank(&block)
self
end
- def each(&ignored)
+ def each
[]
end
View
16 spec/wrapped_spec.rb
@@ -26,6 +26,14 @@
it 'raises an exception when called on the wrapped nil' do
expect { nothing.unwrap }.to raise_error(IndexError)
end
+
+ it 'produces the value of the block for a wrapped object' do
+ just.unwrap {|n| n+1}.should == value+1
+ end
+
+ it 'raises an exception when called on the wrapped nil, even with a block' do
+ expect { nothing.unwrap { 2 } }.to raise_error(IndexError)
+ end
end
describe Wrapped, 'callbacks' do
@@ -135,4 +143,12 @@
it 'produces the default for a wrapped nil' do
nothing.unwrap_or(-1).should == -1
end
+
+ it 'produces the value of the block for a wrapped object' do
+ just.unwrap_or(-1) {|n| n+1}.should == value + 1
+ end
+
+ it 'produces the default for a wrapped nil even with a block' do
+ nothing.unwrap_or(-1) {2}.should == -1
+ end
end
Please sign in to comment.
Something went wrong with that request. Please try again.