shoulda 2.0.6 → 2.9.0

Sign up to get free protection for your applications and to get access to all the features.
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