myrrha 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/CHANGELOG.md +5 -0
  2. data/Gemfile +2 -0
  3. data/Gemfile.lock +41 -0
  4. data/LICENCE.md +22 -0
  5. data/Manifest.txt +13 -0
  6. data/README.md +337 -0
  7. data/Rakefile +23 -0
  8. data/examples/String#toXXX.rb +40 -0
  9. data/examples/coerce.rb +26 -0
  10. data/examples/coerce_foo.rb +12 -0
  11. data/examples/coerce_foo2.rb +17 -0
  12. data/examples/coerce_foo3.rb +23 -0
  13. data/examples/coerce_intro.rb +10 -0
  14. data/examples/coerce_noext.rb +6 -0
  15. data/examples/examples_helper.rb +17 -0
  16. data/examples/friendly_but_safe_api.rb +73 -0
  17. data/examples/to_ruby_literal.rb +16 -0
  18. data/examples/to_ruby_literal_foo.rb +14 -0
  19. data/examples/to_ruby_literal_foo2.rb +17 -0
  20. data/examples/to_ruby_literal_foo3.rb +21 -0
  21. data/examples/to_ruby_literal_noext.rb +5 -0
  22. data/lib/myrrha/coerce.rb +93 -0
  23. data/lib/myrrha/loader.rb +0 -0
  24. data/lib/myrrha/to_ruby_literal.rb +76 -0
  25. data/lib/myrrha/version.rb +14 -0
  26. data/lib/myrrha/with_core_ext.rb +2 -0
  27. data/lib/myrrha.rb +295 -0
  28. data/myrrha.gemspec +191 -0
  29. data/myrrha.noespec +37 -0
  30. data/spec/coercions/test_append.rb +11 -0
  31. data/spec/coercions/test_belongs_to.rb +29 -0
  32. data/spec/coercions/test_convert.rb +23 -0
  33. data/spec/coercions/test_dup.rb +21 -0
  34. data/spec/coercions/test_subdomain.rb +11 -0
  35. data/spec/shared/a_value.rb +33 -0
  36. data/spec/spec_helper.rb +34 -0
  37. data/spec/test_assumptions.rb +18 -0
  38. data/spec/test_coerce.rb +119 -0
  39. data/spec/test_myrrha.rb +87 -0
  40. data/spec/test_to_ruby_literal.rb +19 -0
  41. data/spec/test_value.rb +6 -0
  42. data/tasks/debug_mail.rake +78 -0
  43. data/tasks/debug_mail.txt +13 -0
  44. data/tasks/examples.rake +13 -0
  45. data/tasks/gem.rake +68 -0
  46. data/tasks/spec_test.rake +79 -0
  47. data/tasks/unit_test.rake +77 -0
  48. data/tasks/yard.rake +51 -0
  49. metadata +195 -0
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # 1.0.0 / 2011-07-22
2
+
3
+ * Enhancements
4
+
5
+ * Birthday!
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'http://rubygems.org'
2
+ gemspec :name => "myrrha"
data/Gemfile.lock ADDED
@@ -0,0 +1,41 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ myrrha (1.0.0)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ bluecloth (2.0.11)
10
+ diff-lcs (1.1.2)
11
+ highline (1.6.2)
12
+ noe (1.3.0)
13
+ highline (~> 1.6.0)
14
+ quickl (~> 0.2.0)
15
+ wlang (~> 0.10.1)
16
+ quickl (0.2.2)
17
+ rake (0.9.2)
18
+ rspec (2.6.0)
19
+ rspec-core (~> 2.6.0)
20
+ rspec-expectations (~> 2.6.0)
21
+ rspec-mocks (~> 2.6.0)
22
+ rspec-core (2.6.4)
23
+ rspec-expectations (2.6.0)
24
+ diff-lcs (~> 1.1.2)
25
+ rspec-mocks (2.6.0)
26
+ wlang (0.10.2)
27
+ yard (0.7.2)
28
+
29
+ PLATFORMS
30
+ java
31
+ ruby
32
+
33
+ DEPENDENCIES
34
+ bluecloth (~> 2.0.9)
35
+ bundler (~> 1.0)
36
+ myrrha!
37
+ noe (~> 1.3.0)
38
+ rake (~> 0.9.2)
39
+ rspec (~> 2.6.0)
40
+ wlang (~> 0.10.1)
41
+ yard (~> 0.7.2)
data/LICENCE.md ADDED
@@ -0,0 +1,22 @@
1
+ # The MIT Licence
2
+
3
+ Copyright (c) 2011 - Bernard Lambeau
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Manifest.txt ADDED
@@ -0,0 +1,13 @@
1
+ examples/**/*
2
+ lib/**/*
3
+ spec/**/*
4
+ tasks/**/*
5
+ Rakefile
6
+ CHANGELOG.md
7
+ Gemfile
8
+ Gemfile.lock
9
+ LICENCE.md
10
+ Manifest.txt
11
+ myrrha.gemspec
12
+ myrrha.noespec
13
+ README.md
data/README.md ADDED
@@ -0,0 +1,337 @@
1
+ # Myrrha
2
+
3
+ ## Description
4
+
5
+ Myrrha provides the coercion framework which is missing to Ruby, IMHO. Coercions
6
+ are simply defined as a set of rules for converting values from source to target
7
+ domains (in an abstract sense). As a typical and useful example, it comes bundled
8
+ with a coerce() method providing a unique entry point for converting a string to
9
+ a numeric, a boolean, a date, a time, an URI, and so on.
10
+
11
+ ### Install
12
+
13
+ % [sudo] gem install myrrha
14
+
15
+ ### Bundler & Require
16
+
17
+ # Bug fixes (tiny) do not even add new default rules to coerce and
18
+ # to\_ruby\_literal. Minor version can, which could break your code.
19
+ # Therefore, please always use:
20
+ gem "alf", "~> 1.0.0"
21
+
22
+ ## Links
23
+
24
+ * http://rubydoc.info/github/blambeau/myrrha/master/frames (read this file there!)
25
+ * http://github.com/blambeau/myrrha (source code)
26
+ * http://rubygems.org/gems/myrrha (download)
27
+
28
+ ## The missing <code>coerce()</code>
29
+
30
+ Myrrha.coerce(:anything, Domain)
31
+ coerce(:anything, Domain) # with core extensions
32
+
33
+ ### What for?
34
+
35
+ Having a single entry point for coercing values from one data-type (typically
36
+ a String) to another one is very useful. Unfortunately, Ruby does not provide
37
+ such a unique entry point... Thanks to Myrrah, the following scenario is
38
+ possible and even straightforward:
39
+
40
+ require 'myrrha/with_core_ext'
41
+ require 'myrrha/coerce'
42
+ require 'date'
43
+
44
+ values = ["12", "true", "2011-07-20"]
45
+ types = [Integer, Boolean, Date]
46
+ values.zip(types).collect do |value,domain|
47
+ coerce(value, domain)
48
+ end
49
+ # => [12, true, #<Date: 2011-07-20 (...)>]
50
+
51
+ ### Example
52
+
53
+ require 'myrrha/with_core_ext'
54
+ require 'myrrha/coerce'
55
+
56
+ # it works on numerics
57
+ coerce("12", Integer) # => 12
58
+ coerce("12.0", Float) # => 12.0
59
+
60
+ # but also on regexp (through Regexp.compile)
61
+ coerce("[a-z]+", Regexp) # => /[a-z]+/
62
+
63
+ # and, yes, on Boolean (sorry Matz!)
64
+ coerce("true", Boolean) # => true
65
+ coerce("false", Boolean) # => false
66
+
67
+ # and on date and time (through Date/Time.parse)
68
+ require 'date'
69
+ require 'time'
70
+ coerce("2011-07-20", Date) # => #<Date: 2011-07-20 (4911525/2,0,2299161)>
71
+ coerce("2011-07-20 10:57", Time) # => 2011-07-20 10:57:00 +0200
72
+
73
+ # why not on URI?
74
+ require 'uri'
75
+ coerce('http://google.com', URI) # => #<URI::HTTP:0x8281ce0 URL:http://google.com>
76
+
77
+ # on nil, it always returns nil
78
+ coerce(nil, Integer) # => nil
79
+
80
+ ### No core extension? no problem!
81
+
82
+ require 'myrrha/coerce'
83
+
84
+ Myrrha.coerce("12", Integer) # => 12
85
+ Myrrha.coerce("12.0", Float) # => 12.0
86
+
87
+ Myrrha.coerce("true", Myrrha::Boolean) # => true
88
+ # [... and so on ...]
89
+
90
+ ### Adding your own coercions
91
+
92
+ The easiest way to add additional coercions is to implement a <code>coerce</code>
93
+ method on you class; it will be used in priority.
94
+
95
+ class Foo
96
+ def initialize(arg)
97
+ @arg = arg
98
+ end
99
+ def self.coerce(arg)
100
+ Foo.new(arg)
101
+ end
102
+ end
103
+
104
+ Myrrha.coerce(:hello, Foo)
105
+ # => #<Foo:0x869eee0 @arg=:hello>
106
+
107
+ If <code>Foo</code> is not your code and you don't want to make core extensions
108
+ by adding a <code>coerce</code> class method, you can simply add new rules to
109
+ Myrrha itself:
110
+
111
+ Myrrha::Coerce.append do |r|
112
+ r.coercion(Symbol, Foo) do |value, _|
113
+ Foo.new(value)
114
+ end
115
+ end
116
+
117
+ Myrrha.coerce(:hello, Foo)
118
+ # => #<Foo:0x8866f84 @arg=:hello>
119
+
120
+ Now, doing so, the new coercion rule will be shared with all Myrrha users, which
121
+ might be intrusive. Why not using your own set of coercion rules?
122
+
123
+ MyRules = Myrrha::Coerce.dup.append do |r|
124
+ r.coercion(Symbol, Foo) do |value, _|
125
+ Foo.new(value)
126
+ end
127
+ end
128
+
129
+ # Myrrha.coerce is actually a shortcut for:
130
+ Myrrha::Coerce.apply(:hello, Foo)
131
+ # => Myrrha::Error: Unable to coerce `hello` to Foo
132
+
133
+ MyRules.apply(:hello, Foo)
134
+ # => #<Foo:0x8b7d254 @arg=:hello>
135
+
136
+ ## The missing <code>to\_ruby\_literal()</code>
137
+
138
+ Myrrha.to_ruby_literal([:anything])
139
+ [:anything].to_ruby_literal # with core extensions
140
+
141
+ ### What for?
142
+
143
+ <code>Object#to\_ruby\_literal</code> has a very simple specification. Given an
144
+ object o that can be considered as a true _value_, the result of
145
+ <code>o.to\_ruby\_literal</code> must be such that the following invariant
146
+ holds:
147
+
148
+ Kernel.eval(o.to_ruby_literal) == o
149
+
150
+ That is, parsing & evaluating the literal yields the same value. When generating
151
+ (human-readable) ruby code, having a unique entry point that respects the
152
+ specification is very useful.
153
+
154
+ For almost all ruby classes, but not all, using o.inspect respects the
155
+ invariant. For example, the following is true:
156
+
157
+ Kernel.eval("hello".inspect) == "hello" # => true
158
+ Kernel.eval([1, 2, 3].inspect) == [1, 2, 3] # => true
159
+ Kernel.eval({:key => :value}.inspect) == {:key => :value} # => true
160
+ # => true
161
+
162
+ Unfortunately, this is not always the case:
163
+
164
+ Kernel.eval(Date.today.inspect) == Date.today
165
+ # => false
166
+ # => because Date.today.inspect yields "#<Date: 2011-07-20 ...", which is a comment
167
+
168
+ ### Example
169
+
170
+ Myrrha implements a very simple set of rules for implementing
171
+ <code>Object#to\_ruby\_literal</code> that works:
172
+
173
+ require 'date'
174
+ require 'myrrha/with_core_ext'
175
+ require 'myrrha/to_ruby_literal'
176
+
177
+ 1.to_ruby_literal # => "1"
178
+ Date.today.to_ruby_literal # => "Marshal.load('...')"
179
+ ["hello", Date.today].to_ruby_literal # => "['hello', Marshal.load('...')]"
180
+
181
+ Myrrha implements a best-effort strategy to return a human readable string. It
182
+ simply fallbacks to <code>Marshal.load(...)</code> when the strategy fails:
183
+
184
+ (1..10).to_ruby_literal # => "1..10"
185
+
186
+ today = Date.today
187
+ (today..today+1).to_ruby_literal # => "Marshal.load('...')"
188
+
189
+ ### No core extension? no problem!
190
+
191
+ require 'date'
192
+ require 'myrrha/to_ruby_literal'
193
+
194
+ Myrrha.to_ruby_literal(1) # => 1
195
+ Myrrha.to_ruby_literal(Date.today) # => Marshal.load("...")
196
+ # [... and so on ...]
197
+
198
+ ### Adding your own rules
199
+
200
+ The easiest way is simply to override <code>to\_ruby\_literal</code> in your
201
+ class
202
+
203
+ class Foo
204
+ attr_reader :arg
205
+ def initialize(arg)
206
+ @arg = arg
207
+ end
208
+ def to_ruby_literal
209
+ "Foo.new(#{arg.inspect})"
210
+ end
211
+ end
212
+
213
+ Myrrha.to_ruby_literal(Foo.new(:hello))
214
+ # => "Foo.new(:hello)"
215
+
216
+ As with coerce, contributing your own rule to Myrrha is possible:
217
+
218
+ Myrrha::ToRubyLiteral.append do |r|
219
+ r.coercion(Foo) do |foo, _|
220
+ "Foo.new(#{foo.arg.inspect})"
221
+ end
222
+ end
223
+
224
+ Myrrha.to_ruby_literal(Foo.new(:hello))
225
+ # => "Foo.new(:hello)"
226
+
227
+ And building your own set of rules is possible as well:
228
+
229
+ MyRules = Myrrha::ToRubyLiteral.dup.append do |r|
230
+ r.coercion(Foo) do |foo, _|
231
+ "Foo.new(#{foo.arg.inspect})"
232
+ end
233
+ end
234
+
235
+ # Myrrha.to_ruby_literal is actually a shortcut for:
236
+ Myrrha::ToRubyLiteral.apply(Foo.new(:hello))
237
+ # => "Marshal.load('...')"
238
+
239
+ MyRules.apply(Foo.new(:hello))
240
+ # => "Foo.new(:hello)"
241
+
242
+ ### Limitation
243
+
244
+ As the feature fallbacks to marshaling, everything which is marshalable will
245
+ work. As usual, <code>to\_ruby\_literal(Proc)</code> won't work.
246
+
247
+ ## The general coercion framework
248
+
249
+ A set of coercion rules can simply be created from scratch as follows:
250
+
251
+ Rules = Myrrha.coercions do |r|
252
+
253
+ # `upon` rules are tried in priority if PRE holds
254
+ r.upon(SourceDomain) do |value, requested_domain|
255
+
256
+ # PRE: - user wants to coerce `value` to a requested_domain
257
+ # - belongs_to?(value, SourceDomain)
258
+
259
+ # implement the coercion or throw(:newrule)
260
+ returned_value = something(value)
261
+
262
+ # POST: belongs_to?(returned_value, requested_domain)
263
+
264
+ end
265
+
266
+ # `coercion` rules are then tried in order if PRE holds
267
+ r.coercion(SourceDomain, TargetDomain) do |value, requested_domain|
268
+
269
+ # PRE: - user wants to coerce `value` to a requested_domain
270
+ # - belongs_to?(value, SourceDomain)
271
+ # - subdomain?(TargetDomain, requested_domain)
272
+
273
+ # implement the coercion or throw(:newrule)
274
+ returned_value = something(value)
275
+
276
+ # POST: returned_value belongs to requested_domain
277
+
278
+ end
279
+
280
+ # fallback rules are tried if everything else has failed
281
+ r.fallback(SourceDomain) do |value, requested_domain|
282
+
283
+ # exactly the same as upon rules
284
+
285
+ end
286
+
287
+ end
288
+
289
+ When the user invokes <code>Rules.apply(value, domain)</code> all rules for
290
+ which PRE holds are executed in order, until one succeed (chain of
291
+ responsibility design pattern). This means that coercions always execute in
292
+ <code>O(number of rules)</code>.
293
+
294
+ ### <code>belongs\_to?</code> and <code>subdomain?</code>
295
+
296
+ The pseudo-code given above relies on two main abstractions. Suppose the user
297
+ makes a call to <code>coerce(value, requested_domain)</code>:
298
+
299
+ * <code>belongs\_to?(value, SourceDomain)</code> is true iif
300
+ * <code>SourceDomain</code> is a <code>Proc</code> of arity 2, and
301
+ <code>SourceDomain.call(value, requested_domain)</code> yields true
302
+ * <code>SourceDomain</code> is a <code>Proc</code> of arity 1, and
303
+ <code>SourceDomain.call(value)</code> yields true
304
+ * <code>SourceDomain === value</code> yields true
305
+
306
+ * <code>subdomain?(SourceDomain,TargetDomain)</code> is true iif
307
+ * <code>SourceDomain == TargetDomain</code> yields true
308
+ * SourceDomain and TargetDomain are both classes and the latter is a super
309
+ class of the former
310
+
311
+ ### Advanced rule examples
312
+
313
+ Rules = Myrrha.coercions do |r|
314
+
315
+ # A 'catch-all' upon rule, always fired
316
+ catch_all = lambda{|v,rd| true}
317
+ r.upon(catch_all) do |value, requested_domain|
318
+ if you_can_coerce?(value)
319
+ # then do it!
320
+ else
321
+ throw(:next_rule)
322
+ end
323
+ end
324
+
325
+ # Delegate every call to the requested domain if it responds to compile
326
+ compilable = lambda{|v,rd| rd.respond_to?(:compile)}
327
+ r.upon(compilable) do |value, requested_domain|
328
+ requested_domain.compile(value)
329
+ end
330
+
331
+ # A fallback strategy if everything else fails
332
+ r.fallback(Object) do |value, requested_domain|
333
+ # always fired after everything else
334
+ # this is your last change, an Myrrha::Error will be raised if you fail
335
+ end
336
+
337
+ end
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ begin
2
+ gem "bundler", "~> 1.0"
3
+ require "bundler/setup"
4
+ rescue LoadError => ex
5
+ puts ex.message
6
+ abort "Bundler failed to load, (did you run 'gem install bundler' ?)"
7
+ end
8
+
9
+ # Dynamically load the gem spec
10
+ $gemspec_file = File.expand_path('../myrrha.gemspec', __FILE__)
11
+ $gemspec = Kernel.eval(File.read($gemspec_file))
12
+
13
+ # We run tests by default
14
+ task :default => :test
15
+
16
+ #
17
+ # Install all tasks found in tasks folder
18
+ #
19
+ # See .rake files there for complete documentation.
20
+ #
21
+ Dir["tasks/*.rake"].each do |taskfile|
22
+ instance_eval File.read(taskfile), taskfile
23
+ end
@@ -0,0 +1,40 @@
1
+ require File.expand_path('../examples_helper', __FILE__)
2
+ require 'date'
3
+
4
+ # In many cases, arguments coming from the user (in console mode, in web,
5
+ # and so on) are Strings, that must be converted to other types: numerics,
6
+ # booleans, and so on.
7
+ #
8
+ # The String class could be extended with to_Integer, to_Float, to_Date,
9
+ # to_Time and so on, but this is not a good idea... for obvious reasons.
10
+ #
11
+ # With Myrrha, building a set of rules for coercing strings is easy:
12
+
13
+ rules = Myrrha.coercions do |r|
14
+ r.coercion String, Integer, lambda{|s,t| Integer(s) }
15
+ r.coercion String, Float, lambda{|s,t| Float(s) }
16
+ r.coercion String, Date, lambda{|s,t| Date.parse(s) }
17
+ # ... add your own rules here ...
18
+ end
19
+
20
+ # Integers are recognized correctly
21
+ rules.coerce("12", Integer).should be_a(Integer)
22
+ rules.coerce("12", Integer).should eq(12)
23
+
24
+ # And so are Floats
25
+ rules.coerce("12.0", Float).should be_a(Float)
26
+ rules.coerce("12.0", Float).should eq(12.0)
27
+
28
+ #
29
+ # Interestingly, coercion to Numeric works as well. Because
30
+ # of the order in which the rules are defined, Integer are
31
+ # return in priority!
32
+ #
33
+ rules.coerce("12", Numeric).should be_a(Integer)
34
+ rules.coerce("12.0", Numeric).should be_a(Float)
35
+
36
+ # And dates will work as well
37
+ rules.coerce("2010-07-20", Date).should be_a(Date)
38
+ rules.coerce("2010-07-20", Date).should eq(Date.parse("2010-07-20"))
39
+
40
+ # Going further? See the set of rules in Myrrha::Ruby
@@ -0,0 +1,26 @@
1
+ require 'myrrha/with_core_ext'
2
+ require 'myrrha/coerce'
3
+
4
+ # it works on numerics
5
+ coerce("12", Integer) # => 12
6
+ coerce("12.0", Float) # => 12.0
7
+
8
+ # but also on regexp (through Regexp.compile)
9
+ coerce("[a-z]+", Regexp) # => /[a-z]+/
10
+
11
+ # and, yes, on Boolean (sorry Matz!)
12
+ coerce("true", Boolean) # => true
13
+ coerce("false", Boolean) # => false
14
+
15
+ # and on date and time (through Date/Time.parse)
16
+ require 'date'
17
+ require 'time'
18
+ coerce("2011-07-20", Date) # => #<Date: 2011-07-20 (4911525/2,0,2299161)>
19
+ coerce("2011-07-20 10:57", Time) # => 2011-07-20 10:57:00 +0200
20
+
21
+ # why not on URI?
22
+ require 'uri'
23
+ coerce('http://google.com', URI) # => #<URI::HTTP:0x8281ce0 URL:http://google.com>
24
+
25
+ # on nil, it always returns nil
26
+ coerce(nil, Integer) # => nil
@@ -0,0 +1,12 @@
1
+ require 'myrrha/coerce'
2
+
3
+ class Foo
4
+ def initialize(arg)
5
+ @arg = arg
6
+ end
7
+ def self.coerce(arg)
8
+ Foo.new(arg)
9
+ end
10
+ end
11
+
12
+ Myrrha.coerce(:hello, Foo)
@@ -0,0 +1,17 @@
1
+ require 'myrrha/coerce'
2
+
3
+ class Foo
4
+ def initialize(arg)
5
+ @arg = arg
6
+ end
7
+ end
8
+
9
+ Myrrha::Coerce.append do |r|
10
+ r.coercion(Symbol, Foo) do |value, _|
11
+ Foo.new(value)
12
+ end
13
+ end
14
+
15
+ Myrrha.coerce(:hello, Foo)
16
+ # => #<Foo:0x8866f84 @arg=:hello>
17
+
@@ -0,0 +1,23 @@
1
+ require 'myrrha/coerce'
2
+
3
+ class Foo
4
+ def initialize(arg)
5
+ @arg = arg
6
+ end
7
+ end
8
+
9
+ MyRules = Myrrha::Coerce.dup.append do |r|
10
+ r.coercion(Symbol, Foo) do |value, _|
11
+ Foo.new(value)
12
+ end
13
+ end
14
+
15
+ begin
16
+ Myrrha::Coerce.apply(:hello, Foo)
17
+ raise "Unexpected"
18
+ rescue Myrrha::Error
19
+ # => Myrrha::Error: Unable to coerce `hello` to Foo
20
+ end
21
+
22
+ MyRules.apply(:hello, Foo)
23
+ # => #<Foo:0x8b7d254 @arg=:hello>
@@ -0,0 +1,10 @@
1
+ require 'myrrha/with_core_ext'
2
+ require 'myrrha/coerce'
3
+ require 'date'
4
+
5
+ values = ["12", "true", "2011-07-20"]
6
+ types = [Integer, Boolean, Date]
7
+ values.zip(types).collect do |value,domain|
8
+ coerce(value, domain)
9
+ end
10
+ # => [12, true, #<Date: 2011-07-20 (...)>]
@@ -0,0 +1,6 @@
1
+ require 'myrrha/coerce'
2
+
3
+ Myrrha.coerce("12", Integer) # => 12
4
+ Myrrha.coerce("12.0", Float) # => 12.0
5
+
6
+ Myrrha.coerce("true", Myrrha::Boolean) # => true
@@ -0,0 +1,17 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require "myrrha"
3
+ class Object
4
+
5
+ def eq(x)
6
+ lambda{|y| raise("Expected #{x} but was #{y} ") unless x == y}
7
+ end
8
+
9
+ def be_a(clazz)
10
+ lambda{|y| raise("Expected #{y.inspect} to be a #{clazz}") unless y.is_a?(clazz)}
11
+ end
12
+
13
+ def should(matcher)
14
+ matcher.call(self)
15
+ end
16
+
17
+ end
@@ -0,0 +1,73 @@
1
+ require File.expand_path('../examples_helper', __FILE__)
2
+
3
+ #
4
+ # Sometimes building friendly public APIs requires some type flexibility
5
+ # at the public interface, that is coercion.
6
+ #
7
+ # In the example below, the API class defines a public interface. The Foo
8
+ # class is a pseudo-internal class: it is not intended to be instantiated
9
+ # by users, but can be manipulated once obtained (typically reused as an
10
+ # argument for a later call to the API). An array of symbol is a good kind
11
+ # of Foo literals. Therefore, the API should allow both Foo instance and
12
+ # arrays of symbols in a 'flexible but safe' way.
13
+ #
14
+ class API
15
+
16
+ # Foo is an internal class, that users should not instantiate directly.
17
+ # Instances of Foo can are sometimes returned by internals, are reused
18
+ # later as API arguments (see below).
19
+ class Foo
20
+
21
+ attr_reader :elements
22
+
23
+ def initialize(elements)
24
+ @elements = elements
25
+ end
26
+
27
+ def reverse
28
+ Foo.new(elements.reverse)
29
+ end
30
+
31
+ def inspect
32
+ "Foo(#{elements.inspect})"
33
+ end
34
+ alias :to_s :inspect
35
+
36
+ end
37
+
38
+ # We define a simple rule for coercing arrays of symvols
39
+ # as Foo instances
40
+ Coercions = Myrrha.coercions do |r|
41
+ FriendlyFoo = lambda{|v| v.is_a?(Array) and v.all?{|s| s.is_a?(Symbol)}}
42
+ r.coercion FriendlyFoo, Foo, lambda{|v,t| Foo.new(v)}
43
+ end
44
+
45
+ #
46
+ # Reverses a Foo.
47
+ #
48
+ # This method may be used with a Foo instance. It also accepts an array of
49
+ # symbols as a friendly Foo literal.
50
+ #
51
+ def self.reverse(foo)
52
+ Coercions.coerce(foo, Foo).reverse
53
+ end
54
+
55
+ end # class API
56
+
57
+ # An initial call with an array of symbols work
58
+ puts(x = API.reverse([:a, :b]))
59
+
60
+ # And so is a call with a Foo instance
61
+ puts(y = API.reverse(x))
62
+
63
+ # An invalid call will simply fail
64
+ begin
65
+ API.reverse("hello")
66
+ true.should eq(false)
67
+ rescue Myrrha::Error => ex
68
+ puts ex.message
69
+ end
70
+
71
+ # this is for testing purposes
72
+ API.reverse([:a, :b]).should be_a(API::Foo)
73
+ API.reverse(API.reverse([:a, :b])).should be_a(API::Foo)