ryansch-andand 1.3.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,25 @@
1
+ == 1.3.2
2
+
3
+ * 1 minor enhancement
4
+ * better docs
5
+ * 2 major enhancement:
6
+ * Fix 1.9 warning (patch by George MacKerron)
7
+ * 3 major enhancement:
8
+ * Remove deprecated gemspec information (patch by Anthony Panozzo)
9
+
10
+ == 1.3.1 2008-07-12
11
+
12
+ * 1 major enhancement:
13
+ * Initial release
14
+ * 2 tiny enhancement:
15
+ * fixed specifications
16
+ * 3 minor enhancement
17
+ * tap is now a synonym for me
18
+ * 4 minor enhancement
19
+ * Object#dont
20
+ * 5 bug fix
21
+ * Plays well with other patch-artists
22
+ * 6 minor enhancement
23
+ * uses existing BlankSlate class if already defined
24
+ * 7 tiny enhancement
25
+ * use BasicObject as well
@@ -0,0 +1,26 @@
1
+ Copyright (c) 2008 Reginald Braithwaite
2
+ http://weblog.raganwald.com/2008/01/objectandand-objectme-in-ruby.html
3
+
4
+ Permission is hereby granted, free of charge, to any person
5
+ obtaining a copy of this software and associated documentation
6
+ files (the "Software"), to deal in the Software without
7
+ restriction, including without limitation the rights to use,
8
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the
10
+ Software is furnished to do so, subject to the following
11
+ 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
18
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23
+ OTHER DEALINGS IN THE SOFTWARE.
24
+
25
+ Some code adapted from Jim Weirich's post:
26
+ http://onestepback.org/index.cgi/Tech/Ruby/BlankSlate.rdoc
@@ -0,0 +1,162 @@
1
+ h1. Object#andand
2
+
3
+ h2. Breaking News
4
+
5
+ @Object#andand@'s functionality is also available as part of the "RewriteRails":http://github.com/raganwald/rewrite_rails plug-in. If you are using Ruby on Rails, please consider using RewriteRails instead of the andand gem.
6
+
7
+ h2. What
8
+
9
+ @Object#andand@ lets us write:
10
+
11
+ <pre syntax="ruby">
12
+ @phone = Location.find(:first, ...elided... ).andand.phone
13
+ </pre>And get a _guarded method invocation_ or _safe navigation method_. This snippet performs a @.find@ on the Location class, then sends @.phone@ to the result _if the result is not nil_. If the result is nil, then the expression returns nil without throwing a NoMethodError.
14
+
15
+ As Dejan Simic "put it":http://rors.org/2008/3/18/andand:
16
+
17
+ Why would you want to write this:
18
+
19
+ <pre syntax="ruby">
20
+ entry.at('description') && entry.at('description').inner_text
21
+ </pre>when you can write this:
22
+
23
+ <pre syntax="ruby">
24
+ entry.at('description').andand.inner_text
25
+ </pre>Why indeed! As a bonus, install andand and you will also receive an enhanced Object#tap method, _at no extra charge_!
26
+
27
+ h2. Installing
28
+
29
+ Update to RubyGems 1.2.0 before proceeding!!
30
+
31
+ <pre syntax="ruby">sudo gem sources -a http://gems.github.com (you only have to do this once)
32
+ sudo gem install raganwald-andand</pre>
33
+
34
+ Or:
35
+
36
+ <pre syntax="ruby">git clone git://github.com/raganwald/andand.git
37
+ cd andand
38
+ rake gem
39
+ rake install_gem</pre>
40
+
41
+ h2. The basics
42
+
43
+ h3. Object#andand
44
+
45
+ Ruby programmers are familiar with the two _guarded assignment_ operators @&&=@ and @||=@. The typical use for them is when you have a variable that might be nil. For example:
46
+
47
+ <pre syntax="ruby">
48
+ first_name &&= @first_name.trim
49
+ @phone ||= '612-777-9311'
50
+ </pre>You are trimming the first name provided it isn’t nil, and you are assigning ‘612-777-9311’ to the phone if it _is_ nil (or false, but that isn’t important right now). The other day we were discussing the guards and we agreed that we wished there was a _guarded method invocation_ operator. Here’s an example of when you would use it:
51
+
52
+ <pre syntax="ruby">
53
+ @phone = Location.find(:first, ...elided... )&&.phone
54
+ </pre>Meaning, search the location table for the first record matching some criteria, and if you find a location, get its phone. If you don’t, get nil. (Groovy provides this exact functionality, although Groovy uses @?.@ instead of @&&.@) However, @&&.@ won’t work because @&&.@ is not a real Ruby operator.
55
+
56
+ Object#andand let&rsquo;s us write:
57
+
58
+ <pre syntax="ruby">
59
+ @phone = Location.find(:first, ...elided... ).andand.phone
60
+ </pre>And get the same effect as:
61
+
62
+ <pre syntax="ruby">
63
+ @phone = ->(loc){ loc && loc.phone }.call(Location.find(:first, ...elided... ))
64
+ </pre>Note that because you accept any method using Ruby’s method invocation syntax, you can accept methods with parameters and/or blocks:
65
+
66
+ <pre syntax="ruby">
67
+ list_of_lists.detect { ...elided... }.andand.inject(42) { ...elided ... }
68
+ </pre>Object#andand emphasizes syntactic regularity: the goal was to make an @&&.@ operation that worked like @&&=@. @&&=@ looks just like normal assignment, you can use any expression on the RHS, only the semantics are different. The andand method also works just like a normal method invocation, only the semantics are modified.
69
+
70
+ h3. Block-Structured andand
71
+
72
+ You can use @andand@ with a block instead of a method:
73
+
74
+ <pre syntax="ruby">
75
+ @phone = Location.find(:first, ...elided... ).andand { |location|
76
+ YellowPages.reverse_lookup(location).phone
77
+ }
78
+ </pre>If the receiver is nil, the block is not executed and @andand@ returns @nil@. If the receiver is not nil, it is passed as a parameter to the block and @andand@ returns the value of the block. This makes it possible to perform conditional evaluation and also to make the scope of the variable really obvious.
79
+
80
+ h3. Scope
81
+
82
+ @Object#andand@ only works for one method call. For example, @fu.andand.bar.blitz@ is going to call @nil.blitz@ if @fu@ is @nil@. That's because @fu.andand.bar@ is going to evaluate to @nil@, and then we will call the method @blitz@ on it. In most cases, you want to use @fu.andand.bar.andand.blitz@. If that seems awkward, you might want to reconsider violating the Law of Demeter in an environment where you can't be sure if your receiver is @nil@ or not.
83
+
84
+ Another example of this (pointed out by Jan Zimmek):
85
+
86
+ <pre syntax="ruby">
87
+ x = nil
88
+ x.andand.length > 3
89
+ </pre>This results in a @NoMethodError@. Again, @x@ is @nil@, therefore @x.andand.length@ is @nil@, therefore we end up with @nil > 3@ which results in a @NoMethodError@. This can be fixed with @x.andand.length.andand > 3@ as above, or perhaps:
90
+
91
+ <pre syntax="ruby">
92
+ x = nil
93
+ x.andand { |value| value.length > 3 }
94
+ </pre>
95
+
96
+ h3. Enhanced Object#tap
97
+
98
+ Ruby 1.9 introduces "Object#tap":http://moonbase.rydia.net/mental/blog/programming/eavesdropping-on-expressions. This library implements Object#tap for Ruby 1.8 *and* enhances it. As in Ruby 1.9, you can call @.tap@ with a block:
99
+
100
+ <pre syntax="ruby">
101
+ blah.sort.grep( /foo/ ).tap { |xs| p xs }.map { |x| x.blah }
102
+ </pre> But like its sibling @.andand@, you can now call @.tap@ with a method as well:
103
+
104
+ <pre syntax="ruby">
105
+ [1, 2, 3, 4, 5].tap.pop.map { |n| n * 2 }
106
+ => [2, 4, 6, 8]
107
+ </pre>
108
+
109
+ h3. Doctor, it hurts when I do that
110
+
111
+ _So don't do that!_
112
+
113
+ The popular use case for Object#tap is poor man's debugging:
114
+
115
+ <pre syntax="ruby">
116
+ blah.sort.grep( /foo/ ).tap { |xs| p xs }.map { |x| x.blah }
117
+ </pre>Perhaps you want to remove the tap, you can delete it:
118
+
119
+ <pre syntax="ruby">
120
+ blah.sort.grep( /foo/ ).map { |x| x.blah }
121
+ </pre>Or, you can change it to @.dont@:
122
+
123
+ <pre syntax="ruby">
124
+ blah.sort.grep( /foo/ ).dont { |xs| p xs }.map { |x| x.blah }
125
+ </pre>Like @.andand@ and @.tap@, @.dont@ works with arbitrary methods, not just blocks:
126
+
127
+ <pre syntax="ruby">
128
+ (1..10).to_a.reverse!
129
+ => [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
130
+ (1..10).to_a.dont.reverse!
131
+ => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
132
+ </pre>
133
+
134
+ h2. A little more background
135
+
136
+ "Object#andand & Object#me in Ruby":http://weblog.raganwald.com/2008/01/objectandand-objectme-in-ruby.html explains the original motivations, as well as providing links to similar implementations you may want to consider. A few people have pointed out that Object#andand is similar to Haskell's Maybe monad. "The Maybe Monad in Ruby":http://blog.pretheory.com/arch/2008/02/the_maybe_monad_in_ruby.php is a good introduction for Ruby programmers.
137
+
138
+ h2. That's cool, but&hellip;
139
+
140
+ No problem, I get that andand isn't exactly what you need. Have a look at the "Invocation Construction Kit":http://ick.rubyforge.org or "Ick." The Ick gem _generalizes_ #andand and #tap: Ick provides four useful ways to block-structure your code, the methods #let, #returning, #inside, and #my. Ick also includes four quasi-monadic invocations, #maybe, #please, #tee, and #fork.
141
+
142
+ "Ick":http://ick.rubyforge.org provides abstractions for building your own invocations, so you can branch out and build some of your own abstractions with Ick's building blocks.
143
+
144
+ h2. How to submit patches
145
+
146
+ Read the "8 steps for fixing other people's code":http://drnicwilliams.com/2007/06/01/8-steps-for-fixing-other-peoples-code/.
147
+
148
+ The public clone url is @git://github.com/raganwald/andand.git@. Fork you very much.
149
+
150
+ h2. License
151
+
152
+ This code is free to use under the terms of the "MIT license":http://en.wikipedia.org/wiki/MIT_License.
153
+
154
+ h2. Shout Out
155
+
156
+ "Mobile Commons":http://mcommons.com/. Huge.
157
+
158
+ Also interesting: "Wicked":http://github.com/wideopenspaces/wicked
159
+
160
+ h2. Contact
161
+
162
+ Comments are welcome. Send an email to "Reginald Braithwaite":mailto:raganwald@gmail.com.
@@ -0,0 +1,148 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ module AndAnd
4
+
5
+ # This module is included in Object, so each of these methods are added
6
+ # to Object when you require 'andand'. Each method is an *adverb*: they are
7
+ # intended to be enchained with another method, such as receiver.adverb.method
8
+ #
9
+ # The purpose of an adverb is to modify what the primary method returns.
10
+ #
11
+ # Adverbs also take blocks or procs, passing the receiver as an argument to the
12
+ # block or proc. They retain the same semantics with a block or proc as they
13
+ # do with a method. This behaviour weakly resembles a monad.
14
+ module ObjectGoodies
15
+
16
+ # Returns nil if its receiver is nil, regardless of whether nil actually handles the
17
+ # actual method ot what it might return.
18
+ #
19
+ # 'foo'.andand.size => 3
20
+ # nil.andand.size => nil
21
+ # 'foo'.andand { |s| s << 'bar' } => 'foobar'
22
+ # nil.andand { |s| s << 'bar' } => nil
23
+ def andand (p = nil)
24
+ if self
25
+ if block_given?
26
+ yield(self)
27
+ elsif p
28
+ p.to_proc.call(self)
29
+ else
30
+ self
31
+ end
32
+ else
33
+ if block_given? or p
34
+ self
35
+ else
36
+ MockReturningMe.new(self)
37
+ end
38
+ end
39
+ end
40
+
41
+ # Invokes the method and returns the receiver if nothing is raised. Therefore,
42
+ # the purpose of calling the method is strictly for side effects. In the block
43
+ # form, it resembles #tap from Ruby 1.9, and is useful for debugging. It also
44
+ # resembles #returning from Rails, with slightly different syntax.
45
+ #
46
+ # Object.new.me do |o|
47
+ # def o.foo
48
+ # 'foo'
49
+ # end
50
+ # end
51
+ # => your new object
52
+ #
53
+ # In the method form, it is handy for chaining methods that don't ordinarily
54
+ # return the receiver:
55
+ #
56
+ # [1, 2, 3, 4, 5].me.pop.reverse
57
+ # => [4, 3, 2, 1]
58
+ def me (p = nil)
59
+ if block_given?
60
+ yield(self)
61
+ self
62
+ elsif p
63
+ p.to_proc.call(self)
64
+ self
65
+ else
66
+ ProxyReturningMe.new(self)
67
+ end
68
+ end
69
+
70
+ unless Object.instance_methods.include?('tap')
71
+ alias :tap :me
72
+ end
73
+
74
+ # Does not invoke the method or block and returns the receiver.
75
+ # Useful for comemnting stuff out, especially if you are using #me for
76
+ # debugging purposes: change the .me to .dont and the semantics of your
77
+ # program are unchanged.
78
+ #
79
+ # [1, 2, 3, 4, 5].me { |x| p x }
80
+ # => prints and returns the array
81
+ # [1, 2, 3, 4, 5].dont { |x| p x }
82
+ # => returns the array without printing it
83
+ def dont (p = nil)
84
+ if block_given?
85
+ self
86
+ elsif p
87
+ self
88
+ else
89
+ MockReturningMe.new(self)
90
+ end
91
+ end
92
+
93
+ end
94
+
95
+ end
96
+
97
+ class Object
98
+ include AndAnd::ObjectGoodies
99
+ end
100
+
101
+ unless Module.constants.map { |c| c.to_s }.include?('BlankSlate')
102
+ if Module.constants.map { |c| c.to_s }.include?('BasicObject')
103
+ module AndAnd
104
+ class BlankSlate < BasicObject
105
+ end
106
+ end
107
+ else
108
+ module AndAnd
109
+ class BlankSlate
110
+ def self.wipe
111
+ instance_methods.reject { |m| m =~ /^__/ }.each { |m| undef_method m }
112
+ end
113
+ def initialize
114
+ BlankSlate.wipe
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ module AndAnd
122
+
123
+ # A proxy that returns its target without invoking the method you
124
+ # invoke. Useful for nil.andand and #dont
125
+ class MockReturningMe < BlankSlate
126
+ def initialize(me)
127
+ super()
128
+ @me = me
129
+ end
130
+ def method_missing(*args)
131
+ @me
132
+ end
133
+ end
134
+
135
+ # A proxy that returns its target after invoking the method you
136
+ # invoke. Useful for #me
137
+ class ProxyReturningMe < BlankSlate
138
+ def initialize(me)
139
+ super()
140
+ @me = me
141
+ end
142
+ def method_missing(sym, *args, &block)
143
+ @me.__send__(sym, *args, &block)
144
+ @me
145
+ end
146
+ end
147
+
148
+ end
@@ -0,0 +1,9 @@
1
+ module Andand #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 1
4
+ MINOR = 3
5
+ TINY = 2
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -0,0 +1,133 @@
1
+ =begin
2
+
3
+ Copyright (c) 2008 Reginald Braithwaite
4
+ http://weblog.raganwald.com/2008/01/objectandand-objectme-in-ruby.html
5
+
6
+ Permission is hereby granted, free of charge, to any person
7
+ obtaining a copy of this software and associated documentation
8
+ files (the "Software"), to deal in the Software without
9
+ restriction, including without limitation the rights to use,
10
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the
12
+ Software is furnished to do so, subject to the following
13
+ conditions:
14
+
15
+ The above copyright notice and this permission notice shall be
16
+ included in all copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25
+ OTHER DEALINGS IN THE SOFTWARE.
26
+
27
+ =end
28
+
29
+ require File.dirname(__FILE__) + '/test_helper.rb'
30
+
31
+ require 'andand'
32
+
33
+ class Symbol
34
+ def to_proc
35
+ Proc.new { |*args| args.shift.__send__(self, *args) }
36
+ end
37
+ end
38
+
39
+ describe AndAnd, "Basic Behaviour" do
40
+ it "should conditionally yield to a block" do
41
+ 5.andand { |n| n*n }.should == 25
42
+ 5.andand(&:succ).should == 6
43
+ nil.andand { |n| n*n }.should == nil
44
+ false.andand { |n| n*n }.should == false
45
+ end
46
+ it "should conditionally call a proc-able parameter" do
47
+ 5.andand(:succ).should == 6
48
+ nil.andand(:succ).should == nil
49
+ false.andand(:succ).should == false
50
+ end
51
+ it "should conditionally proxy methods" do
52
+ 5.andand.succ.should == 6
53
+ nil.andand.succ.should == nil
54
+ false.andand.succ.should == false
55
+ (1..10).andand.inject(&:+).should == 55
56
+ nil.andand.inject(&:+).should == nil
57
+ false.andand.inject(&:+).should == false
58
+ 'HelloWeblogReaders'.andand[7,4].should == 'blog'
59
+ nil.andand[7,4].should == nil
60
+ false.andand[7,4].should == false
61
+ 'HelloWeblogReaders'.andand.tr('Wb','Bd').should == 'HelloBedlogReaders'
62
+ nil.andand.tr('Wb','Bd').should == nil
63
+ false.andand.tr('Wb','Bd').should == false
64
+ end
65
+ it "should handle infix operators" do
66
+ (5.andand * 2).should == 10
67
+ (nil.andand * 2).should == nil
68
+ (false.andand * 2).should == false
69
+ end
70
+ end
71
+
72
+ describe AndAnd, "Me" do
73
+ it "should unconditionally yield to a block" do
74
+ 5.me { |n| n*n }.should == 5
75
+ 5.me(&:succ).should == 5
76
+ nil.me { |n| n || true }.should be_nil
77
+ false.me { |n| n || true }.should == false
78
+ end
79
+ it "should unconditionally call a proc-able parameter" do
80
+ 5.me(:to_s).should == 5
81
+ nil.me(:to_s).should == nil
82
+ false.me(:to_s).should == false
83
+ end
84
+ it "should proxy methods" do
85
+ 5.me.to_s.should == 5
86
+ nil.me.to_s.should == nil
87
+ false.me.to_s.should == false
88
+ (1..10).me.inject(&:+).should == (1..10)
89
+ 'HelloWeblogReaders'.me[7,4].should == 'HelloWeblogReaders'
90
+ 'HelloWeblogReaders'.me.tr('Wb','Bd').should == 'HelloWeblogReaders'
91
+ end
92
+ it "should handle infix operators" do
93
+ (5.me * 2).should == 5
94
+ end
95
+ end
96
+
97
+ describe AndAnd, "Mixing Me with AndAnd" do
98
+ it "should compose me.andand" do
99
+ frobbish = :blitz
100
+ :foo.me.andand do
101
+ frobbish = :bar
102
+ end.should == :foo
103
+ frobbish.should == :bar
104
+ nil.me.andand do
105
+ frobbish = :barbarella
106
+ end.should be_nil
107
+ frobbish.should == :bar
108
+ false.me.andand do
109
+ frobbish = :hope
110
+ end.should == false
111
+ frobbish.should == :bar
112
+ end
113
+ end
114
+
115
+ class Foo
116
+ def frobbish
117
+ 'fnord'
118
+ end
119
+ end
120
+
121
+ describe AndAnd, "exception handling" do
122
+ it "should not swallow NoMethodErrors" do
123
+ foo = Foo.new
124
+ lambda do
125
+ foo.andand.frobbish
126
+ nil.andand.frobbish
127
+ nil.andand.hsibborf
128
+ end.should_not raise_error
129
+ lambda do
130
+ foo.andand.hsibborf
131
+ end.should raise_error
132
+ end
133
+ end
@@ -0,0 +1,2 @@
1
+ require 'test/unit'
2
+ require File.dirname(__FILE__) + '/../lib/andand'
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ryansch-andand
3
+ version: !ruby/object:Gem::Version
4
+ hash: 31
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 3
9
+ - 2
10
+ version: 1.3.2
11
+ platform: ruby
12
+ authors:
13
+ - Reg Braithwaite
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2008-11-07 00:00:00 -08:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: " Maybe Monad in idiomatic Ruby."
23
+ email: reg@braythwayt.com
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files:
29
+ - History.txt
30
+ - README.textile
31
+ files:
32
+ - History.txt
33
+ - License.txt
34
+ - README.textile
35
+ - lib/andand.rb
36
+ - lib/andand/version.rb
37
+ - test/andand_spec.rb
38
+ - test/test_helper.rb
39
+ has_rdoc: true
40
+ homepage: http://github.com/ryansch/andand
41
+ licenses: []
42
+
43
+ post_install_message:
44
+ rdoc_options:
45
+ - --main
46
+ - README.textile
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ hash: 3
55
+ segments:
56
+ - 0
57
+ version: "0"
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ hash: 3
64
+ segments:
65
+ - 0
66
+ version: "0"
67
+ requirements: []
68
+
69
+ rubyforge_project:
70
+ rubygems_version: 1.6.2
71
+ signing_key:
72
+ specification_version: 3
73
+ summary: The Maybe Monad in idiomatic Ruby
74
+ test_files:
75
+ - test/andand_spec.rb
76
+ - test/test_helper.rb