shoulda 2.0.6 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. data/README.rdoc +35 -7
  2. data/Rakefile +5 -3
  3. data/lib/shoulda.rb +7 -15
  4. data/lib/shoulda/action_mailer.rb +1 -1
  5. data/lib/shoulda/action_mailer/assertions.rb +32 -33
  6. data/lib/shoulda/active_record.rb +6 -2
  7. data/lib/shoulda/active_record/assertions.rb +62 -81
  8. data/lib/shoulda/active_record/helpers.rb +40 -0
  9. data/lib/shoulda/active_record/macros.rb +518 -639
  10. data/lib/shoulda/active_record/matchers.rb +42 -0
  11. data/lib/shoulda/active_record/matchers/allow_mass_assignment_of_matcher.rb +83 -0
  12. data/lib/shoulda/active_record/matchers/allow_value_matcher.rb +102 -0
  13. data/lib/shoulda/active_record/matchers/association_matcher.rb +226 -0
  14. data/lib/shoulda/active_record/matchers/ensure_inclusion_of_matcher.rb +87 -0
  15. data/lib/shoulda/active_record/matchers/ensure_length_of_matcher.rb +141 -0
  16. data/lib/shoulda/active_record/matchers/have_db_column_matcher.rb +169 -0
  17. data/lib/shoulda/active_record/matchers/have_index_matcher.rb +105 -0
  18. data/lib/shoulda/active_record/matchers/have_named_scope_matcher.rb +125 -0
  19. data/lib/shoulda/active_record/matchers/have_readonly_attribute_matcher.rb +59 -0
  20. data/lib/shoulda/active_record/matchers/validate_acceptance_of_matcher.rb +41 -0
  21. data/lib/shoulda/active_record/matchers/validate_numericality_of_matcher.rb +39 -0
  22. data/lib/shoulda/active_record/matchers/validate_presence_of_matcher.rb +60 -0
  23. data/lib/shoulda/active_record/matchers/validate_uniqueness_of_matcher.rb +148 -0
  24. data/lib/shoulda/active_record/matchers/validation_matcher.rb +56 -0
  25. data/lib/shoulda/assertions.rb +50 -40
  26. data/lib/shoulda/autoload_macros.rb +46 -0
  27. data/lib/shoulda/context.rb +124 -126
  28. data/lib/shoulda/controller.rb +8 -8
  29. data/lib/shoulda/controller/formats/html.rb +158 -160
  30. data/lib/shoulda/controller/formats/xml.rb +132 -134
  31. data/lib/shoulda/controller/helpers.rb +51 -53
  32. data/lib/shoulda/controller/macros.rb +278 -258
  33. data/lib/shoulda/controller/resource_options.rb +211 -214
  34. data/lib/shoulda/helpers.rb +5 -7
  35. data/lib/shoulda/macros.rb +63 -64
  36. data/lib/shoulda/private_helpers.rb +16 -18
  37. data/lib/shoulda/rails.rb +1 -8
  38. data/lib/shoulda/rspec.rb +5 -0
  39. data/lib/shoulda/tasks/list_tests.rake +6 -1
  40. data/lib/shoulda/test_unit.rb +19 -0
  41. data/rails/init.rb +1 -1
  42. data/test/README +2 -2
  43. data/test/fail_macros.rb +16 -16
  44. data/test/functional/posts_controller_test.rb +5 -2
  45. data/test/matchers/allow_mass_assignment_of_matcher_test.rb +68 -0
  46. data/test/matchers/allow_value_matcher_test.rb +41 -0
  47. data/test/matchers/association_matcher_test.rb +258 -0
  48. data/test/matchers/ensure_inclusion_of_matcher_test.rb +80 -0
  49. data/test/matchers/ensure_length_of_matcher_test.rb +158 -0
  50. data/test/matchers/have_db_column_matcher_test.rb +169 -0
  51. data/test/matchers/have_index_matcher_test.rb +74 -0
  52. data/test/matchers/have_named_scope_matcher_test.rb +65 -0
  53. data/test/matchers/have_readonly_attributes_matcher_test.rb +29 -0
  54. data/test/matchers/validate_acceptance_of_matcher_test.rb +44 -0
  55. data/test/matchers/validate_numericality_of_matcher_test.rb +52 -0
  56. data/test/matchers/validate_presence_of_matcher_test.rb +86 -0
  57. data/test/matchers/validate_uniqueness_of_matcher_test.rb +141 -0
  58. data/test/model_builder.rb +61 -0
  59. data/test/other/autoload_macro_test.rb +18 -0
  60. data/test/other/helpers_test.rb +58 -0
  61. data/test/other/private_helpers_test.rb +1 -1
  62. data/test/other/should_test.rb +16 -16
  63. data/test/rails_root/app/controllers/posts_controller.rb +6 -5
  64. data/test/rails_root/app/models/pets/dog.rb +10 -0
  65. data/test/rails_root/app/models/treat.rb +3 -0
  66. data/test/rails_root/app/models/user.rb +2 -2
  67. data/test/rails_root/app/views/layouts/posts.rhtml +2 -0
  68. data/test/rails_root/config/database.yml +1 -1
  69. data/test/rails_root/config/environments/{sqlite3.rb → test.rb} +0 -0
  70. data/test/rails_root/db/migrate/001_create_users.rb +3 -2
  71. data/test/rails_root/db/migrate/011_create_treats.rb +12 -0
  72. data/test/rails_root/log/test.log +0 -0
  73. data/test/rails_root/test/shoulda_macros/custom_macro.rb +6 -0
  74. data/test/rails_root/vendor/gems/gem_with_macro-0.0.1/shoulda_macros/gem_macro.rb +6 -0
  75. data/test/rails_root/vendor/plugins/plugin_with_macro/shoulda_macros/plugin_macro.rb +6 -0
  76. data/test/test_helper.rb +3 -1
  77. data/test/unit/address_test.rb +1 -1
  78. data/test/unit/dog_test.rb +5 -2
  79. data/test/unit/post_test.rb +7 -3
  80. data/test/unit/product_test.rb +2 -2
  81. data/test/unit/tag_test.rb +2 -1
  82. data/test/unit/user_test.rb +17 -8
  83. metadata +44 -4
  84. data/test/rails_root/app/models/dog.rb +0 -5
@@ -1,236 +1,233 @@
1
- module ThoughtBot # :nodoc:
2
- module Shoulda # :nodoc:
3
- module Controller
4
- # Formats tested by #should_be_restful. Defaults to [:html, :xml]
5
- VALID_FORMATS = Dir.glob(File.join(File.dirname(__FILE__), 'formats', '*.rb')).map { |f| File.basename(f, '.rb') }.map(&:to_sym) # :doc:
6
- VALID_FORMATS.each {|f| require "shoulda/controller/formats/#{f}"}
1
+ module Shoulda # :nodoc:
2
+ module Controller
3
+ # Formats tested by #should_be_restful. Defaults to [:html, :xml]
4
+ VALID_FORMATS = Dir.glob(File.join(File.dirname(__FILE__), 'formats', '*.rb')).map { |f| File.basename(f, '.rb') }.map(&:to_sym) # :doc:
5
+ VALID_FORMATS.each {|f| require "shoulda/controller/formats/#{f}"}
6
+
7
+ # Actions tested by #should_be_restful
8
+ VALID_ACTIONS = [:index, :show, :new, :edit, :create, :update, :destroy] # :doc:
9
+
10
+ # A ResourceOptions object is passed into should_be_restful in order to configure the tests for your controller.
11
+ #
12
+ # Example:
13
+ # class UsersControllerTest < Test::Unit::TestCase
14
+ # fixtures :all
15
+ #
16
+ # def setup
17
+ # ...normal setup code...
18
+ # @user = User.find(:first)
19
+ # end
20
+ #
21
+ # should_be_restful do |resource|
22
+ # resource.identifier = :id
23
+ # resource.klass = User
24
+ # resource.object = :user
25
+ # resource.parent = []
26
+ # resource.actions = [:index, :show, :new, :edit, :update, :create, :destroy]
27
+ # resource.formats = [:html, :xml]
28
+ #
29
+ # resource.create.params = { :name => "bob", :email => 'bob@bob.com', :age => 13}
30
+ # resource.update.params = { :name => "sue" }
31
+ #
32
+ # resource.create.redirect = "user_url(@user)"
33
+ # resource.update.redirect = "user_url(@user)"
34
+ # resource.destroy.redirect = "users_url"
35
+ #
36
+ # resource.create.flash = /created/i
37
+ # resource.update.flash = /updated/i
38
+ # resource.destroy.flash = /removed/i
39
+ # end
40
+ # end
41
+ #
42
+ # Whenever possible, the resource attributes will be set to sensible defaults.
43
+ #
44
+ class ResourceOptions
45
+ # Configuration options for the create, update, destroy actions under should_be_restful
46
+ class ActionOptions
47
+ # String evaled to get the target of the redirection.
48
+ # All of the instance variables set by the controller will be available to the
49
+ # evaled code.
50
+ #
51
+ # Example:
52
+ # resource.create.redirect = "user_url(@user.company, @user)"
53
+ #
54
+ # Defaults to a generated url based on the name of the controller, the action, and the resource.parents list.
55
+ attr_accessor :redirect
7
56
 
8
- # Actions tested by #should_be_restful
9
- VALID_ACTIONS = [:index, :show, :new, :edit, :create, :update, :destroy] # :doc:
57
+ # String or Regexp describing a value expected in the flash. Will match against any flash key.
58
+ #
59
+ # Defaults:
60
+ # destroy:: /removed/
61
+ # create:: /created/
62
+ # update:: /updated/
63
+ attr_accessor :flash
64
+
65
+ # Hash describing the params that should be sent in with this action.
66
+ attr_accessor :params
67
+ end
10
68
 
11
- # A ResourceOptions object is passed into should_be_restful in order to configure the tests for your controller.
69
+ # Configuration options for the denied actions under should_be_restful
12
70
  #
13
71
  # Example:
14
- # class UsersControllerTest < Test::Unit::TestCase
15
- # fixtures :all
16
- #
17
- # def setup
18
- # ...normal setup code...
19
- # @user = User.find(:first)
72
+ # context "The public" do
73
+ # setup do
74
+ # @request.session[:logged_in] = false
20
75
  # end
21
76
  #
22
77
  # should_be_restful do |resource|
23
- # resource.identifier = :id
24
- # resource.klass = User
25
- # resource.object = :user
26
- # resource.parent = []
27
- # resource.actions = [:index, :show, :new, :edit, :update, :create, :destroy]
28
- # resource.formats = [:html, :xml]
29
- #
30
- # resource.create.params = { :name => "bob", :email => 'bob@bob.com', :age => 13}
31
- # resource.update.params = { :name => "sue" }
78
+ # resource.parent = :user
32
79
  #
33
- # resource.create.redirect = "user_url(@user)"
34
- # resource.update.redirect = "user_url(@user)"
35
- # resource.destroy.redirect = "users_url"
36
- #
37
- # resource.create.flash = /created/i
38
- # resource.update.flash = /updated/i
39
- # resource.destroy.flash = /removed/i
80
+ # resource.denied.actions = [:index, :show, :edit, :new, :create, :update, :destroy]
81
+ # resource.denied.flash = /get outta here/i
82
+ # resource.denied.redirect = 'new_session_url'
40
83
  # end
41
84
  # end
42
85
  #
43
- # Whenever possible, the resource attributes will be set to sensible defaults.
44
- #
45
- class ResourceOptions
46
- # Configuration options for the create, update, destroy actions under should_be_restful
47
- class ActionOptions
48
- # String evaled to get the target of the redirection.
49
- # All of the instance variables set by the controller will be available to the
50
- # evaled code.
51
- #
52
- # Example:
53
- # resource.create.redirect = "user_url(@user.company, @user)"
54
- #
55
- # Defaults to a generated url based on the name of the controller, the action, and the resource.parents list.
56
- attr_accessor :redirect
57
-
58
- # String or Regexp describing a value expected in the flash. Will match against any flash key.
59
- #
60
- # Defaults:
61
- # destroy:: /removed/
62
- # create:: /created/
63
- # update:: /updated/
64
- attr_accessor :flash
65
-
66
- # Hash describing the params that should be sent in with this action.
67
- attr_accessor :params
68
- end
69
-
70
- # Configuration options for the denied actions under should_be_restful
86
+ class DeniedOptions
87
+ # String evaled to get the target of the redirection.
88
+ # All of the instance variables set by the controller will be available to the
89
+ # evaled code.
71
90
  #
72
91
  # Example:
73
- # context "The public" do
74
- # setup do
75
- # @request.session[:logged_in] = false
76
- # end
77
- #
78
- # should_be_restful do |resource|
79
- # resource.parent = :user
80
- #
81
- # resource.denied.actions = [:index, :show, :edit, :new, :create, :update, :destroy]
82
- # resource.denied.flash = /get outta here/i
83
- # resource.denied.redirect = 'new_session_url'
84
- # end
85
- # end
86
- #
87
- class DeniedOptions
88
- # String evaled to get the target of the redirection.
89
- # All of the instance variables set by the controller will be available to the
90
- # evaled code.
91
- #
92
- # Example:
93
- # resource.create.redirect = "user_url(@user.company, @user)"
94
- attr_accessor :redirect
95
-
96
- # String or Regexp describing a value expected in the flash. Will match against any flash key.
97
- #
98
- # Example:
99
- # resource.create.flash = /created/
100
- attr_accessor :flash
101
-
102
- # Actions that should be denied (only used by resource.denied). <i>Note that these actions will
103
- # only be tested if they are also listed in +resource.actions+</i>
104
- # The special value of :all will deny all of the REST actions.
105
- attr_accessor :actions
106
- end
107
-
108
- # Name of key in params that references the primary key.
109
- # Will almost always be :id (default), unless you are using a plugin or have patched rails.
110
- attr_accessor :identifier
111
-
112
- # Name of the ActiveRecord class this resource is responsible for. Automatically determined from
113
- # test class if not explicitly set. UserTest => "User"
114
- attr_accessor :klass
115
-
116
- # Name of the instantiated ActiveRecord object that should be used by some of the tests.
117
- # Defaults to the underscored name of the AR class. CompanyManager => :company_manager
118
- attr_accessor :object
119
-
120
- # Name of the parent AR objects. Can be set as parent= or parents=, and can take either
121
- # the name of the parent resource (if there's only one), or an array of names (if there's
122
- # more than one).
92
+ # resource.create.redirect = "user_url(@user.company, @user)"
93
+ attr_accessor :redirect
94
+
95
+ # String or Regexp describing a value expected in the flash. Will match against any flash key.
123
96
  #
124
97
  # Example:
125
- # # in the routes...
126
- # map.resources :companies do
127
- # map.resources :people do
128
- # map.resources :limbs
129
- # end
130
- # end
131
- #
132
- # # in the tests...
133
- # class PeopleControllerTest < Test::Unit::TestCase
134
- # should_be_restful do |resource|
135
- # resource.parent = :companies
136
- # end
137
- # end
138
- #
139
- # class LimbsControllerTest < Test::Unit::TestCase
140
- # should_be_restful do |resource|
141
- # resource.parents = [:companies, :people]
142
- # end
143
- # end
144
- attr_accessor :parent
145
- alias parents parent
146
- alias parents= parent=
147
-
148
- # Actions that should be tested. Must be a subset of VALID_ACTIONS (default).
149
- # Tests for each actionw will only be generated if the action is listed here.
150
- # The special value of :all will test all of the REST actions.
151
- #
152
- # Example (for a read-only controller):
153
- # resource.actions = [:show, :index]
98
+ # resource.create.flash = /created/
99
+ attr_accessor :flash
100
+
101
+ # Actions that should be denied (only used by resource.denied). <i>Note that these actions will
102
+ # only be tested if they are also listed in +resource.actions+</i>
103
+ # The special value of :all will deny all of the REST actions.
154
104
  attr_accessor :actions
105
+ end
155
106
 
156
- # Formats that should be tested. Must be a subset of VALID_FORMATS (default).
157
- # Each action will be tested against the formats listed here. The special value
158
- # of :all will test all of the supported formats.
159
- #
160
- # Example:
161
- # resource.actions = [:html, :xml]
162
- attr_accessor :formats
163
-
164
- # ActionOptions object specifying options for the create action.
165
- attr_accessor :create
166
-
167
- # ActionOptions object specifying options for the update action.
168
- attr_accessor :update
169
-
170
- # ActionOptions object specifying options for the desrtoy action.
171
- attr_accessor :destroy
172
-
173
- # DeniedOptions object specifying which actions should return deny a request, and what should happen in that case.
174
- attr_accessor :denied
175
-
176
- def initialize # :nodoc:
177
- @create = ActionOptions.new
178
- @update = ActionOptions.new
179
- @destroy = ActionOptions.new
180
- @denied = DeniedOptions.new
181
-
182
- @create.flash ||= /created/i
183
- @update.flash ||= /updated/i
184
- @destroy.flash ||= /removed/i
185
- @denied.flash ||= /denied/i
186
-
187
- @create.params ||= {}
188
- @update.params ||= {}
189
-
190
- @actions = VALID_ACTIONS
191
- @formats = VALID_FORMATS
192
- @denied.actions = []
193
- end
194
-
195
- def normalize!(target) # :nodoc:
196
- @denied.actions = VALID_ACTIONS if @denied.actions == :all
197
- @actions = VALID_ACTIONS if @actions == :all
198
- @formats = VALID_FORMATS if @formats == :all
199
-
200
- @denied.actions = @denied.actions.map(&:to_sym)
201
- @actions = @actions.map(&:to_sym)
202
- @formats = @formats.map(&:to_sym)
203
-
204
- ensure_valid_members(@actions, VALID_ACTIONS, 'actions')
205
- ensure_valid_members(@denied.actions, VALID_ACTIONS, 'denied.actions')
206
- ensure_valid_members(@formats, VALID_FORMATS, 'formats')
207
-
208
- @identifier ||= :id
209
- @klass ||= target.name.gsub(/ControllerTest$/, '').singularize.constantize
210
- @object ||= @klass.name.tableize.singularize
211
- @parent ||= []
212
- @parent = [@parent] unless @parent.is_a? Array
213
-
214
- collection_helper = [@parent, @object.to_s.pluralize, 'url'].flatten.join('_')
215
- collection_args = @parent.map {|n| "@#{object}.#{n}"}.join(', ')
216
- @destroy.redirect ||= "#{collection_helper}(#{collection_args})"
217
-
218
- member_helper = [@parent, @object, 'url'].flatten.join('_')
219
- member_args = [@parent.map {|n| "@#{object}.#{n}"}, "@#{object}"].flatten.join(', ')
220
- @create.redirect ||= "#{member_helper}(#{member_args})"
221
- @update.redirect ||= "#{member_helper}(#{member_args})"
222
- @denied.redirect ||= "new_session_url"
223
- end
224
-
225
- private
226
-
227
- def ensure_valid_members(ary, valid_members, name) # :nodoc:
228
- invalid = ary - valid_members
229
- raise ArgumentError, "Unsupported #{name}: #{invalid.inspect}" unless invalid.empty?
230
- end
107
+ # Name of key in params that references the primary key.
108
+ # Will almost always be :id (default), unless you are using a plugin or have patched rails.
109
+ attr_accessor :identifier
110
+
111
+ # Name of the ActiveRecord class this resource is responsible for. Automatically determined from
112
+ # test class if not explicitly set. UserTest => "User"
113
+ attr_accessor :klass
114
+
115
+ # Name of the instantiated ActiveRecord object that should be used by some of the tests.
116
+ # Defaults to the underscored name of the AR class. CompanyManager => :company_manager
117
+ attr_accessor :object
118
+
119
+ # Name of the parent AR objects. Can be set as parent= or parents=, and can take either
120
+ # the name of the parent resource (if there's only one), or an array of names (if there's
121
+ # more than one).
122
+ #
123
+ # Example:
124
+ # # in the routes...
125
+ # map.resources :companies do
126
+ # map.resources :people do
127
+ # map.resources :limbs
128
+ # end
129
+ # end
130
+ #
131
+ # # in the tests...
132
+ # class PeopleControllerTest < Test::Unit::TestCase
133
+ # should_be_restful do |resource|
134
+ # resource.parent = :companies
135
+ # end
136
+ # end
137
+ #
138
+ # class LimbsControllerTest < Test::Unit::TestCase
139
+ # should_be_restful do |resource|
140
+ # resource.parents = [:companies, :people]
141
+ # end
142
+ # end
143
+ attr_accessor :parent
144
+ alias parents parent
145
+ alias parents= parent=
146
+
147
+ # Actions that should be tested. Must be a subset of VALID_ACTIONS (default).
148
+ # Tests for each actionw will only be generated if the action is listed here.
149
+ # The special value of :all will test all of the REST actions.
150
+ #
151
+ # Example (for a read-only controller):
152
+ # resource.actions = [:show, :index]
153
+ attr_accessor :actions
154
+
155
+ # Formats that should be tested. Must be a subset of VALID_FORMATS (default).
156
+ # Each action will be tested against the formats listed here. The special value
157
+ # of :all will test all of the supported formats.
158
+ #
159
+ # Example:
160
+ # resource.actions = [:html, :xml]
161
+ attr_accessor :formats
162
+
163
+ # ActionOptions object specifying options for the create action.
164
+ attr_accessor :create
165
+
166
+ # ActionOptions object specifying options for the update action.
167
+ attr_accessor :update
168
+
169
+ # ActionOptions object specifying options for the desrtoy action.
170
+ attr_accessor :destroy
171
+
172
+ # DeniedOptions object specifying which actions should return deny a request, and what should happen in that case.
173
+ attr_accessor :denied
174
+
175
+ def initialize # :nodoc:
176
+ @create = ActionOptions.new
177
+ @update = ActionOptions.new
178
+ @destroy = ActionOptions.new
179
+ @denied = DeniedOptions.new
180
+
181
+ @create.flash ||= /created/i
182
+ @update.flash ||= /updated/i
183
+ @destroy.flash ||= /removed/i
184
+ @denied.flash ||= /denied/i
185
+
186
+ @create.params ||= {}
187
+ @update.params ||= {}
188
+
189
+ @actions = VALID_ACTIONS
190
+ @formats = VALID_FORMATS
191
+ @denied.actions = []
192
+ end
193
+
194
+ def normalize!(target) # :nodoc:
195
+ @denied.actions = VALID_ACTIONS if @denied.actions == :all
196
+ @actions = VALID_ACTIONS if @actions == :all
197
+ @formats = VALID_FORMATS if @formats == :all
198
+
199
+ @denied.actions = @denied.actions.map(&:to_sym)
200
+ @actions = @actions.map(&:to_sym)
201
+ @formats = @formats.map(&:to_sym)
202
+
203
+ ensure_valid_members(@actions, VALID_ACTIONS, 'actions')
204
+ ensure_valid_members(@denied.actions, VALID_ACTIONS, 'denied.actions')
205
+ ensure_valid_members(@formats, VALID_FORMATS, 'formats')
206
+
207
+ @identifier ||= :id
208
+ @klass ||= target.name.gsub(/ControllerTest$/, '').singularize.constantize
209
+ @object ||= @klass.name.tableize.singularize
210
+ @parent ||= []
211
+ @parent = [@parent] unless @parent.is_a? Array
212
+
213
+ collection_helper = [@parent, @object.to_s.pluralize, 'url'].flatten.join('_')
214
+ collection_args = @parent.map {|n| "@#{object}.#{n}"}.join(', ')
215
+ @destroy.redirect ||= "#{collection_helper}(#{collection_args})"
216
+
217
+ member_helper = [@parent, @object, 'url'].flatten.join('_')
218
+ member_args = [@parent.map {|n| "@#{object}.#{n}"}, "@#{object}"].flatten.join(', ')
219
+ @create.redirect ||= "#{member_helper}(#{member_args})"
220
+ @update.redirect ||= "#{member_helper}(#{member_args})"
221
+ @denied.redirect ||= "new_session_url"
222
+ end
223
+
224
+ private
225
+
226
+ def ensure_valid_members(ary, valid_members, name) # :nodoc:
227
+ invalid = ary - valid_members
228
+ raise ArgumentError, "Unsupported #{name}: #{invalid.inspect}" unless invalid.empty?
231
229
  end
232
230
  end
233
231
  end
234
232
  end
235
233
 
236
-
@@ -1,10 +1,8 @@
1
- module ThoughtBot # :nodoc:
2
- module Shoulda # :nodoc:
3
- module Helpers
4
- # Prints a message to stdout, tagged with the name of the calling method.
5
- def report!(msg = "")
6
- puts("#{caller.first}: #{msg}")
7
- end
1
+ module Shoulda # :nodoc:
2
+ module Helpers
3
+ # Prints a message to stdout, tagged with the name of the calling method.
4
+ def report!(msg = "")
5
+ puts("#{caller.first}: #{msg}")
8
6
  end
9
7
  end
10
8
  end