draper 0.18.0 → 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. data/.gitignore +3 -0
  2. data/.travis.yml +5 -0
  3. data/CHANGELOG.markdown +8 -0
  4. data/Gemfile +4 -0
  5. data/Rakefile +50 -31
  6. data/Readme.markdown +42 -48
  7. data/draper.gemspec +2 -1
  8. data/lib/draper.rb +42 -7
  9. data/lib/draper/collection_decorator.rb +88 -0
  10. data/lib/draper/decoratable.rb +44 -0
  11. data/lib/draper/decorated_association.rb +55 -0
  12. data/lib/draper/decorator.rb +208 -0
  13. data/lib/draper/finders.rb +44 -0
  14. data/lib/draper/helper_proxy.rb +16 -0
  15. data/lib/draper/railtie.rb +17 -21
  16. data/lib/draper/security.rb +48 -0
  17. data/lib/draper/tasks/tu.rake +5 -0
  18. data/lib/draper/test/minitest_integration.rb +1 -1
  19. data/lib/draper/test/test_unit_integration.rb +9 -0
  20. data/lib/draper/version.rb +1 -1
  21. data/lib/draper/view_context.rb +6 -13
  22. data/lib/draper/view_helpers.rb +36 -0
  23. data/lib/generators/decorator/decorator_generator.rb +1 -1
  24. data/lib/generators/decorator/templates/decorator.rb +0 -1
  25. data/spec/draper/collection_decorator_spec.rb +240 -0
  26. data/spec/draper/decoratable_spec.rb +164 -0
  27. data/spec/draper/decorated_association_spec.rb +130 -0
  28. data/spec/draper/decorator_spec.rb +497 -0
  29. data/spec/draper/finders_spec.rb +156 -0
  30. data/spec/draper/helper_proxy_spec.rb +12 -0
  31. data/spec/draper/security_spec.rb +158 -0
  32. data/spec/draper/view_helpers_spec.rb +41 -0
  33. data/spec/dummy/.rspec +2 -0
  34. data/spec/dummy/README.rdoc +261 -0
  35. data/spec/dummy/Rakefile +7 -0
  36. data/spec/dummy/app/controllers/application_controller.rb +4 -0
  37. data/spec/dummy/app/controllers/localized_urls.rb +5 -0
  38. data/spec/dummy/app/controllers/posts_controller.rb +17 -0
  39. data/spec/dummy/app/decorators/post_decorator.rb +25 -0
  40. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  41. data/spec/dummy/app/mailers/application_mailer.rb +3 -0
  42. data/spec/dummy/app/mailers/post_mailer.rb +9 -0
  43. data/spec/dummy/app/models/post.rb +3 -0
  44. data/spec/dummy/app/views/layouts/application.html.erb +11 -0
  45. data/spec/dummy/app/views/post_mailer/decorated_email.html.erb +1 -0
  46. data/spec/dummy/app/views/posts/_post.html.erb +19 -0
  47. data/spec/dummy/app/views/posts/show.html.erb +1 -0
  48. data/spec/dummy/config.ru +4 -0
  49. data/spec/dummy/config/application.rb +64 -0
  50. data/spec/dummy/config/boot.rb +10 -0
  51. data/spec/dummy/config/database.yml +25 -0
  52. data/spec/dummy/config/environment.rb +5 -0
  53. data/spec/dummy/config/environments/development.rb +34 -0
  54. data/spec/dummy/config/environments/production.rb +55 -0
  55. data/spec/dummy/config/environments/test.rb +32 -0
  56. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  57. data/spec/dummy/config/initializers/inflections.rb +15 -0
  58. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  59. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  60. data/spec/dummy/config/initializers/session_store.rb +8 -0
  61. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  62. data/spec/dummy/config/locales/en.yml +5 -0
  63. data/spec/dummy/config/routes.rb +7 -0
  64. data/spec/dummy/db/migrate/20121019115657_create_posts.rb +8 -0
  65. data/spec/dummy/db/schema.rb +21 -0
  66. data/spec/dummy/db/seeds.rb +2 -0
  67. data/spec/dummy/lib/tasks/spec.rake +5 -0
  68. data/spec/dummy/public/404.html +26 -0
  69. data/spec/dummy/public/422.html +26 -0
  70. data/spec/dummy/public/500.html +25 -0
  71. data/spec/dummy/public/favicon.ico +0 -0
  72. data/spec/dummy/script/rails +6 -0
  73. data/spec/dummy/spec/decorators/post_decorator_spec.rb +23 -0
  74. data/spec/dummy/spec/mailers/post_mailer_spec.rb +29 -0
  75. data/spec/dummy/spec/spec_helper.rb +9 -0
  76. data/spec/generators/decorator/decorator_generator_spec.rb +3 -4
  77. data/spec/integration/integration_spec.rb +33 -0
  78. data/{performance → spec/performance}/active_record.rb +0 -0
  79. data/{performance/bechmark.rb → spec/performance/benchmark.rb} +0 -0
  80. data/{performance → spec/performance}/decorators.rb +2 -4
  81. data/{performance → spec/performance}/models.rb +0 -0
  82. data/spec/spec_helper.rb +20 -41
  83. data/spec/support/action_controller.rb +12 -0
  84. data/spec/support/active_model.rb +7 -0
  85. data/spec/support/{samples/active_record.rb → active_record.rb} +5 -0
  86. data/spec/support/{samples → decorators}/decorator_with_application_helper.rb +1 -1
  87. data/spec/support/decorators/namespaced_product_decorator.rb +5 -0
  88. data/spec/support/decorators/non_active_model_product_decorator.rb +2 -0
  89. data/spec/support/decorators/product_decorator.rb +19 -0
  90. data/spec/support/{samples → decorators}/products_decorator.rb +1 -1
  91. data/spec/support/decorators/some_thing_decorator.rb +2 -0
  92. data/spec/support/{samples → decorators}/specific_product_decorator.rb +0 -2
  93. data/spec/support/{samples → decorators}/widget_decorator.rb +0 -0
  94. data/spec/support/dummy_app.rb +84 -0
  95. data/spec/support/matchers/have_text.rb +50 -0
  96. data/spec/support/{samples → models}/namespaced_product.rb +1 -3
  97. data/spec/support/{samples → models}/non_active_model_product.rb +1 -0
  98. data/spec/support/{samples → models}/product.rb +13 -2
  99. data/spec/support/models/some_thing.rb +5 -0
  100. data/spec/support/models/uninferrable_decorator_model.rb +3 -0
  101. data/spec/support/{samples → models}/widget.rb +0 -0
  102. metadata +185 -68
  103. data/lib/draper/active_model_support.rb +0 -27
  104. data/lib/draper/base.rb +0 -312
  105. data/lib/draper/decorated_enumerable_proxy.rb +0 -90
  106. data/lib/draper/model_support.rb +0 -25
  107. data/lib/draper/rspec_integration.rb +0 -2
  108. data/lib/draper/system.rb +0 -18
  109. data/spec/draper/base_spec.rb +0 -873
  110. data/spec/draper/decorated_enumerable_proxy_spec.rb +0 -45
  111. data/spec/draper/model_support_spec.rb +0 -48
  112. data/spec/support/samples/decorator.rb +0 -5
  113. data/spec/support/samples/decorator_with_allows.rb +0 -3
  114. data/spec/support/samples/decorator_with_denies.rb +0 -3
  115. data/spec/support/samples/decorator_with_denies_all.rb +0 -3
  116. data/spec/support/samples/decorator_with_multiple_allows.rb +0 -4
  117. data/spec/support/samples/decorator_with_special_methods.rb +0 -13
  118. data/spec/support/samples/enumerable_proxy.rb +0 -3
  119. data/spec/support/samples/namespaced_product_decorator.rb +0 -7
  120. data/spec/support/samples/product_decorator.rb +0 -7
  121. data/spec/support/samples/sequel_product.rb +0 -13
  122. data/spec/support/samples/some_thing.rb +0 -2
  123. data/spec/support/samples/some_thing_decorator.rb +0 -3
@@ -0,0 +1,5 @@
1
+ if Rake::Task.task_defined?('test:run')
2
+ Rake::Task['test:run'].enhance do
3
+ Rake::Task['test:decorators'].invoke
4
+ end
5
+ end
@@ -1,7 +1,7 @@
1
1
  class MiniTest::Rails::ActiveSupport::TestCase
2
2
  # Use AS::TestCase for the base class when describing a decorator
3
3
  register_spec_type(self) do |desc|
4
- desc < Draper::Base if desc.is_a?(Class)
4
+ desc < Draper::Decorator if desc.is_a?(Class)
5
5
  end
6
6
  register_spec_type(/Decorator( ?Test)?\z/i, self)
7
7
  end
@@ -0,0 +1,9 @@
1
+ require "rake/testtask"
2
+ require "rails/test_unit/sub_test_task"
3
+
4
+ namespace :test do
5
+ Rails::SubTestTask.new(:decorators => "test:prepare") do |t|
6
+ t.libs << "test"
7
+ t.pattern = "test/decorators/**/*_test.rb"
8
+ end
9
+ end
@@ -1,3 +1,3 @@
1
1
  module Draper
2
- VERSION = "0.18.0"
2
+ VERSION = "1.0.0.beta1"
3
3
  end
@@ -1,5 +1,11 @@
1
1
  module Draper
2
2
  module ViewContext
3
+ def view_context
4
+ super.tap do |context|
5
+ Draper::ViewContext.current = context
6
+ end
7
+ end
8
+
3
9
  def self.current_controller
4
10
  Thread.current[:current_controller] || ApplicationController.new
5
11
  end
@@ -16,21 +22,8 @@ module Draper
16
22
  Thread.current[:current_view_context] = context
17
23
  end
18
24
 
19
- def view_context
20
- super.tap do |context|
21
- Draper::ViewContext.current = context
22
- end
23
- end
24
-
25
- private
26
-
27
25
  def self.build_view_context
28
26
  current_controller.view_context.tap do |context|
29
- context.instance_eval do
30
- def url_options
31
- ActionMailer::Base.default_url_options
32
- end
33
- end unless context.request
34
27
  if defined?(ActionController::TestRequest)
35
28
  context.controller.request ||= ActionController::TestRequest.new
36
29
  context.request ||= context.controller.request
@@ -0,0 +1,36 @@
1
+ module Draper
2
+ module ViewHelpers
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+
7
+ # Access the helpers proxy to call built-in and user-defined
8
+ # Rails helpers from a class context.
9
+ #
10
+ # @return [HelperProxy] the helpers proxy
11
+ def helpers
12
+ @helpers ||= Draper::HelperProxy.new
13
+ end
14
+ alias_method :h, :helpers
15
+
16
+ end
17
+
18
+ # Access the helpers proxy to call built-in and user-defined
19
+ # Rails helpers. Aliased to `h` for convenience.
20
+ #
21
+ # @return [HelperProxy] the helpers proxy
22
+ def helpers
23
+ self.class.helpers
24
+ end
25
+ alias_method :h, :helpers
26
+
27
+ # Localize is something that's used quite often. Even though
28
+ # it's available through helpers, that's annoying. Aliased
29
+ # to `l` for convenience.
30
+ def localize(*args)
31
+ helpers.localize(*args)
32
+ end
33
+ alias_method :l, :localize
34
+
35
+ end
36
+ end
@@ -20,7 +20,7 @@ module Rails
20
20
  elsif defined?(ApplicationDecorator)
21
21
  "ApplicationDecorator"
22
22
  else
23
- "Draper::Base"
23
+ "Draper::Decorator"
24
24
  end
25
25
  end
26
26
  end
@@ -1,6 +1,5 @@
1
1
  <% module_namespacing do -%>
2
2
  class <%= class_name %>Decorator < <%= parent_class_name %>
3
- decorates :<%= singular_name %>
4
3
 
5
4
  # Accessing Helpers
6
5
  # You can access any helper via a proxy
@@ -0,0 +1,240 @@
1
+ require 'spec_helper'
2
+
3
+ describe Draper::CollectionDecorator do
4
+ before { ApplicationController.new.view_context }
5
+ subject { Draper::CollectionDecorator.new(source, with: ProductDecorator) }
6
+ let(:source){ [Product.new, Product.new] }
7
+ let(:non_active_model_source){ NonActiveModelProduct.new }
8
+
9
+ it "decorates a collection's items" do
10
+ subject.each do |item|
11
+ item.should be_decorated_with ProductDecorator
12
+ end
13
+ end
14
+
15
+ it "sets the decorated items' source models" do
16
+ subject.map{|item| item.source}.should == source
17
+ end
18
+
19
+ context "with options" do
20
+ subject { Draper::CollectionDecorator.new(source, with: ProductDecorator, some: "options") }
21
+
22
+ its(:options) { should == {some: "options"} }
23
+
24
+ it "passes options to the individual decorators" do
25
+ subject.each do |item|
26
+ item.options.should == {some: "options"}
27
+ end
28
+ end
29
+
30
+ describe "#options=" do
31
+ it "updates the options on the collection decorator" do
32
+ subject.options = {other: "options"}
33
+ subject.options.should == {other: "options"}
34
+ end
35
+
36
+ it "updates the options on the individual decorators" do
37
+ subject.options = {other: "options"}
38
+ subject.each do |item|
39
+ item.options.should == {other: "options"}
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ describe "#initialize" do
46
+ context "when the :with option is given" do
47
+ context "and the decorator can't be inferred from the class" do
48
+ subject { Draper::CollectionDecorator.new(source, with: ProductDecorator) }
49
+
50
+ it "uses the :with option" do
51
+ subject.decorator_class.should be ProductDecorator
52
+ end
53
+ end
54
+
55
+ context "and the decorator is inferrable from the class" do
56
+ subject { ProductsDecorator.new(source, with: SpecificProductDecorator) }
57
+
58
+ it "uses the :with option" do
59
+ subject.decorator_class.should be SpecificProductDecorator
60
+ end
61
+ end
62
+ end
63
+
64
+ context "when the :with option is not given" do
65
+ context "and the decorator can't be inferred from the class" do
66
+ it "raises an UninferrableDecoratorError" do
67
+ expect{Draper::CollectionDecorator.new(source)}.to raise_error Draper::UninferrableDecoratorError
68
+ end
69
+ end
70
+
71
+ context "and the decorator is inferrable from the class" do
72
+ subject { ProductsDecorator.new(source) }
73
+
74
+ it "infers the decorator" do
75
+ subject.decorator_class.should be ProductDecorator
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ describe "#source" do
82
+ it "returns the source collection" do
83
+ subject.source.should be source
84
+ end
85
+
86
+ it "is aliased to #to_source" do
87
+ subject.to_source.should be source
88
+ end
89
+ end
90
+
91
+ describe "#find" do
92
+ context "with a block" do
93
+ it "decorates Enumerable#find" do
94
+ subject.decorated_collection.should_receive(:find)
95
+ subject.find {|p| p.title == "title" }
96
+ end
97
+ end
98
+
99
+ context "without a block" do
100
+ it "decorates Model.find" do
101
+ source.should_not_receive(:find)
102
+ Product.should_receive(:find).with(1).and_return(:product)
103
+ subject.find(1).should == ProductDecorator.new(:product)
104
+ end
105
+ end
106
+ end
107
+
108
+ describe "#helpers" do
109
+ it "returns a HelperProxy" do
110
+ subject.helpers.should be_a Draper::HelperProxy
111
+ end
112
+
113
+ it "is aliased to #h" do
114
+ subject.h.should be subject.helpers
115
+ end
116
+
117
+ it "initializes the wrapper only once" do
118
+ helper_proxy = subject.helpers
119
+ helper_proxy.stub(:test_method) { "test_method" }
120
+ subject.helpers.test_method.should == "test_method"
121
+ subject.helpers.test_method.should == "test_method"
122
+ end
123
+ end
124
+
125
+ describe "#localize" do
126
+ before { subject.helpers.should_receive(:localize).with(:an_object, {some: "options"}) }
127
+
128
+ it "delegates to helpers" do
129
+ subject.localize(:an_object, some: "options")
130
+ end
131
+
132
+ it "is aliased to #l" do
133
+ subject.l(:an_object, some: "options")
134
+ end
135
+ end
136
+
137
+ describe ".helpers" do
138
+ it "returns a HelperProxy" do
139
+ subject.class.helpers.should be_a Draper::HelperProxy
140
+ end
141
+
142
+ it "is aliased to .h" do
143
+ subject.class.h.should be subject.class.helpers
144
+ end
145
+ end
146
+
147
+ describe "#to_ary" do
148
+ # required for `render @collection` in Rails
149
+ it "delegates to the decorated collection" do
150
+ subject.decorated_collection.should_receive(:to_ary).and_return(:an_array)
151
+ subject.to_ary.should == :an_array
152
+ end
153
+ end
154
+
155
+ describe "#respond_to?" do
156
+ it "returns true for its own methods" do
157
+ subject.should respond_to :decorated_collection
158
+ end
159
+
160
+ it "returns true for the wrapped collection's methods" do
161
+ source.stub(:respond_to?).with(:whatever, true).and_return(true)
162
+ subject.respond_to?(:whatever, true).should be_true
163
+ end
164
+ end
165
+
166
+ context "Array methods" do
167
+ describe "#include?" do
168
+ it "delegates to the decorated collection" do
169
+ subject.decorated_collection.should_receive(:include?).with(:something).and_return(true)
170
+ subject.should include :something
171
+ end
172
+ end
173
+
174
+ describe "#[]" do
175
+ it "delegates to the decorated collection" do
176
+ subject.decorated_collection.should_receive(:[]).with(42).and_return(:something)
177
+ subject[42].should == :something
178
+ end
179
+ end
180
+ end
181
+
182
+ describe "#==" do
183
+ context "when comparing to a collection decorator with the same source" do
184
+ it "returns true" do
185
+ a = Draper::CollectionDecorator.new(source, with: ProductDecorator)
186
+ b = ProductsDecorator.new(source)
187
+ a.should == b
188
+ end
189
+ end
190
+
191
+ context "when comparing to a collection decorator with a different source" do
192
+ it "returns false" do
193
+ a = Draper::CollectionDecorator.new(source, with: ProductDecorator)
194
+ b = ProductsDecorator.new([Product.new])
195
+ a.should_not == b
196
+ end
197
+ end
198
+
199
+ context "when comparing to a collection of the same items" do
200
+ it "returns true" do
201
+ a = Draper::CollectionDecorator.new(source, with: ProductDecorator)
202
+ b = source.dup
203
+ a.should == b
204
+ end
205
+ end
206
+
207
+ context "when comparing to a collection of different items" do
208
+ it "returns true" do
209
+ a = Draper::CollectionDecorator.new(source, with: ProductDecorator)
210
+ b = [Product.new]
211
+ a.should_not == b
212
+ end
213
+ end
214
+ end
215
+
216
+ it "pretends to be the source class" do
217
+ subject.kind_of?(source.class).should be_true
218
+ subject.is_a?(source.class).should be_true
219
+ end
220
+
221
+ it "is still its own class" do
222
+ subject.kind_of?(subject.class).should be_true
223
+ subject.is_a?(subject.class).should be_true
224
+ end
225
+
226
+ describe "#method_missing" do
227
+ before do
228
+ class << source
229
+ def page_number
230
+ 42
231
+ end
232
+ end
233
+ end
234
+
235
+ it "proxies unknown methods to the source collection" do
236
+ subject.page_number.should == 42
237
+ end
238
+ end
239
+
240
+ end
@@ -0,0 +1,164 @@
1
+ require 'spec_helper'
2
+
3
+ describe Draper::Decoratable do
4
+ subject { Product.new }
5
+
6
+ describe "#decorate" do
7
+ it "returns a decorator for self" do
8
+ subject.decorate.should be_a ProductDecorator
9
+ subject.decorate.source.should be subject
10
+ end
11
+
12
+ it "accepts options" do
13
+ decorator = subject.decorate(some: "options")
14
+ decorator.options.should == {some: "options"}
15
+ end
16
+
17
+ it "is not memoized" do
18
+ subject.decorate.should_not be subject.decorate
19
+ end
20
+ end
21
+
22
+ describe "#applied_decorators" do
23
+ it "returns an empty list" do
24
+ subject.applied_decorators.should be_empty
25
+ end
26
+ end
27
+
28
+ describe "#decorated_with?" do
29
+ it "returns false" do
30
+ subject.should_not be_decorated_with ProductDecorator
31
+ end
32
+ end
33
+
34
+ describe "#decorated?" do
35
+ it "returns false" do
36
+ subject.should_not be_decorated
37
+ end
38
+ end
39
+
40
+ describe "#decorator_class" do
41
+ it "delegates to .decorator_class" do
42
+ Product.stub(:decorator_class).and_return(WidgetDecorator)
43
+ product = Product.new
44
+ product.decorator_class.should be WidgetDecorator
45
+ end
46
+ end
47
+
48
+ describe "#===" do
49
+ context "with itself" do
50
+ it "returns true" do
51
+ (subject === subject).should be_true
52
+ end
53
+ end
54
+
55
+ context "with another instance" do
56
+ it "returns false" do
57
+ (subject === Product.new).should be_false
58
+ end
59
+ end
60
+
61
+ context "with a decorated version of itself" do
62
+ it "returns true" do
63
+ decorator = double(source: subject)
64
+ (subject === decorator).should be_true
65
+ end
66
+ end
67
+
68
+ context "with a decorated other instance" do
69
+ it "returns false" do
70
+ decorator = double(source: Product.new)
71
+ (subject === decorator).should be_false
72
+ end
73
+ end
74
+ end
75
+
76
+ describe ".====" do
77
+ context "with an instance" do
78
+ it "returns true" do
79
+ (Product === Product.new).should be_true
80
+ end
81
+ end
82
+
83
+ context "with a derived instance" do
84
+ it "returns true" do
85
+ (Product === Widget.new).should be_true
86
+ end
87
+ end
88
+
89
+ context "with an unrelated instance" do
90
+ it "returns false" do
91
+ (Product === Object.new).should be_false
92
+ end
93
+ end
94
+
95
+ context "with a decorated instance" do
96
+ it "returns true" do
97
+ decorator = double(source: Product.new)
98
+ (Product === decorator).should be_true
99
+ end
100
+ end
101
+
102
+ context "with a decorated derived instance" do
103
+ it "returns true" do
104
+ decorator = double(source: Widget.new)
105
+ (Product === decorator).should be_true
106
+ end
107
+ end
108
+
109
+ context "with a decorated unrelated instance" do
110
+ it "returns false" do
111
+ decorator = double(source: Object.new)
112
+ (Product === decorator).should be_false
113
+ end
114
+ end
115
+ end
116
+
117
+ describe ".decorate" do
118
+ it "returns a collection decorator" do
119
+ Product.stub(:scoped).and_return([Product.new])
120
+ Product.stub(:decorator_class).and_return(WidgetDecorator)
121
+ decorator = Product.decorate
122
+
123
+ decorator.should be_a Draper::CollectionDecorator
124
+ decorator.decorator_class.should be WidgetDecorator
125
+ decorator.source.should be Product.scoped
126
+ end
127
+
128
+ it "accepts options" do
129
+ decorator = Product.decorate(some: "options")
130
+ decorator.options.should == {some: "options"}
131
+ end
132
+
133
+ it "is not memoized" do
134
+ Product.decorate.should_not be Product.decorate
135
+ end
136
+ end
137
+
138
+ describe ".decorator_class" do
139
+ context "for non-ActiveModel classes" do
140
+ it "infers the decorator from the class" do
141
+ NonActiveModelProduct.decorator_class.should be NonActiveModelProductDecorator
142
+ end
143
+ end
144
+
145
+ context "for ActiveModel classes" do
146
+ it "infers the decorator from the model name" do
147
+ Product.stub(:model_name).and_return("Widget")
148
+ Product.decorator_class.should be WidgetDecorator
149
+ end
150
+ end
151
+
152
+ context "for namespaced ActiveModel classes" do
153
+ it "infers the decorator from the model name" do
154
+ Namespace::Product.decorator_class.should be Namespace::ProductDecorator
155
+ end
156
+ end
157
+
158
+ context "when the decorator can't be inferred" do
159
+ it "throws an UninferrableDecoratorError" do
160
+ expect{UninferrableDecoratorModel.decorator_class}.to raise_error Draper::UninferrableDecoratorError
161
+ end
162
+ end
163
+ end
164
+ end