jamesgolick-draper 1.1.1a

Sign up to get free protection for your applications and to get access to all the features.
Files changed (136) hide show
  1. data/.gitignore +16 -0
  2. data/.rspec +2 -0
  3. data/.travis.yml +22 -0
  4. data/.yardopts +1 -0
  5. data/CHANGELOG.md +150 -0
  6. data/CONTRIBUTING.md +15 -0
  7. data/Gemfile +39 -0
  8. data/Guardfile +26 -0
  9. data/LICENSE +7 -0
  10. data/README.md +417 -0
  11. data/Rakefile +69 -0
  12. data/draper.gemspec +32 -0
  13. data/lib/draper.rb +64 -0
  14. data/lib/draper/automatic_delegation.rb +56 -0
  15. data/lib/draper/collection_decorator.rb +96 -0
  16. data/lib/draper/decoratable.rb +82 -0
  17. data/lib/draper/decoratable/equality.rb +18 -0
  18. data/lib/draper/decorated_association.rb +35 -0
  19. data/lib/draper/decorates_assigned.rb +44 -0
  20. data/lib/draper/decorator.rb +248 -0
  21. data/lib/draper/delegation.rb +13 -0
  22. data/lib/draper/factory.rb +87 -0
  23. data/lib/draper/finders.rb +37 -0
  24. data/lib/draper/helper_proxy.rb +38 -0
  25. data/lib/draper/helper_support.rb +5 -0
  26. data/lib/draper/lazy_helpers.rb +15 -0
  27. data/lib/draper/railtie.rb +63 -0
  28. data/lib/draper/tasks/test.rake +22 -0
  29. data/lib/draper/test/devise_helper.rb +30 -0
  30. data/lib/draper/test/minitest_integration.rb +6 -0
  31. data/lib/draper/test/rspec_integration.rb +16 -0
  32. data/lib/draper/test_case.rb +53 -0
  33. data/lib/draper/version.rb +3 -0
  34. data/lib/draper/view_context.rb +99 -0
  35. data/lib/draper/view_context/build_strategy.rb +48 -0
  36. data/lib/draper/view_helpers.rb +37 -0
  37. data/lib/generators/decorator/decorator_generator.rb +36 -0
  38. data/lib/generators/decorator/templates/decorator.rb +19 -0
  39. data/lib/generators/mini_test/decorator_generator.rb +20 -0
  40. data/lib/generators/mini_test/templates/decorator_spec.rb +4 -0
  41. data/lib/generators/mini_test/templates/decorator_test.rb +4 -0
  42. data/lib/generators/resource_override.rb +12 -0
  43. data/lib/generators/rspec/decorator_generator.rb +9 -0
  44. data/lib/generators/rspec/templates/decorator_spec.rb +4 -0
  45. data/lib/generators/test_unit/decorator_generator.rb +9 -0
  46. data/lib/generators/test_unit/templates/decorator_test.rb +4 -0
  47. data/spec/draper/collection_decorator_spec.rb +279 -0
  48. data/spec/draper/decoratable/equality_spec.rb +10 -0
  49. data/spec/draper/decoratable_spec.rb +176 -0
  50. data/spec/draper/decorated_association_spec.rb +84 -0
  51. data/spec/draper/decorates_assigned_spec.rb +71 -0
  52. data/spec/draper/decorator_spec.rb +634 -0
  53. data/spec/draper/factory_spec.rb +238 -0
  54. data/spec/draper/finders_spec.rb +166 -0
  55. data/spec/draper/helper_proxy_spec.rb +53 -0
  56. data/spec/draper/lazy_helpers_spec.rb +21 -0
  57. data/spec/draper/view_context/build_strategy_spec.rb +116 -0
  58. data/spec/draper/view_context_spec.rb +154 -0
  59. data/spec/draper/view_helpers_spec.rb +8 -0
  60. data/spec/dummy/.rspec +2 -0
  61. data/spec/dummy/Rakefile +7 -0
  62. data/spec/dummy/app/controllers/application_controller.rb +4 -0
  63. data/spec/dummy/app/controllers/localized_urls.rb +5 -0
  64. data/spec/dummy/app/controllers/posts_controller.rb +20 -0
  65. data/spec/dummy/app/decorators/mongoid_post_decorator.rb +2 -0
  66. data/spec/dummy/app/decorators/post_decorator.rb +56 -0
  67. data/spec/dummy/app/helpers/application_helper.rb +5 -0
  68. data/spec/dummy/app/mailers/application_mailer.rb +3 -0
  69. data/spec/dummy/app/mailers/post_mailer.rb +19 -0
  70. data/spec/dummy/app/models/admin.rb +5 -0
  71. data/spec/dummy/app/models/mongoid_post.rb +5 -0
  72. data/spec/dummy/app/models/post.rb +3 -0
  73. data/spec/dummy/app/models/user.rb +5 -0
  74. data/spec/dummy/app/views/layouts/application.html.erb +11 -0
  75. data/spec/dummy/app/views/post_mailer/decorated_email.html.erb +1 -0
  76. data/spec/dummy/app/views/posts/_post.html.erb +34 -0
  77. data/spec/dummy/app/views/posts/show.html.erb +1 -0
  78. data/spec/dummy/bin/rails +4 -0
  79. data/spec/dummy/config.ru +4 -0
  80. data/spec/dummy/config/application.rb +71 -0
  81. data/spec/dummy/config/boot.rb +5 -0
  82. data/spec/dummy/config/database.yml +25 -0
  83. data/spec/dummy/config/environment.rb +5 -0
  84. data/spec/dummy/config/environments/development.rb +33 -0
  85. data/spec/dummy/config/environments/production.rb +57 -0
  86. data/spec/dummy/config/environments/test.rb +31 -0
  87. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  88. data/spec/dummy/config/initializers/inflections.rb +15 -0
  89. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  90. data/spec/dummy/config/initializers/secret_token.rb +8 -0
  91. data/spec/dummy/config/initializers/session_store.rb +8 -0
  92. data/spec/dummy/config/locales/en.yml +5 -0
  93. data/spec/dummy/config/mongoid.yml +80 -0
  94. data/spec/dummy/config/routes.rb +9 -0
  95. data/spec/dummy/db/migrate/20121019115657_create_posts.rb +8 -0
  96. data/spec/dummy/db/schema.rb +21 -0
  97. data/spec/dummy/db/seeds.rb +2 -0
  98. data/spec/dummy/fast_spec/post_decorator_spec.rb +38 -0
  99. data/spec/dummy/lib/tasks/test.rake +16 -0
  100. data/spec/dummy/public/404.html +26 -0
  101. data/spec/dummy/public/422.html +26 -0
  102. data/spec/dummy/public/500.html +25 -0
  103. data/spec/dummy/public/favicon.ico +0 -0
  104. data/spec/dummy/script/rails +6 -0
  105. data/spec/dummy/spec/decorators/active_model_serializers_spec.rb +11 -0
  106. data/spec/dummy/spec/decorators/devise_spec.rb +64 -0
  107. data/spec/dummy/spec/decorators/helpers_spec.rb +21 -0
  108. data/spec/dummy/spec/decorators/post_decorator_spec.rb +58 -0
  109. data/spec/dummy/spec/decorators/spec_type_spec.rb +7 -0
  110. data/spec/dummy/spec/decorators/view_context_spec.rb +22 -0
  111. data/spec/dummy/spec/mailers/post_mailer_spec.rb +33 -0
  112. data/spec/dummy/spec/models/mongoid_post_spec.rb +8 -0
  113. data/spec/dummy/spec/models/post_spec.rb +6 -0
  114. data/spec/dummy/spec/shared_examples/decoratable.rb +24 -0
  115. data/spec/dummy/spec/spec_helper.rb +9 -0
  116. data/spec/dummy/test/decorators/minitest/devise_test.rb +64 -0
  117. data/spec/dummy/test/decorators/minitest/helpers_test.rb +21 -0
  118. data/spec/dummy/test/decorators/minitest/spec_type_test.rb +52 -0
  119. data/spec/dummy/test/decorators/minitest/view_context_test.rb +24 -0
  120. data/spec/dummy/test/decorators/test_unit/devise_test.rb +64 -0
  121. data/spec/dummy/test/decorators/test_unit/helpers_test.rb +21 -0
  122. data/spec/dummy/test/decorators/test_unit/view_context_test.rb +24 -0
  123. data/spec/dummy/test/minitest_helper.rb +4 -0
  124. data/spec/dummy/test/test_helper.rb +3 -0
  125. data/spec/generators/decorator/decorator_generator_spec.rb +130 -0
  126. data/spec/integration/integration_spec.rb +58 -0
  127. data/spec/performance/active_record.rb +4 -0
  128. data/spec/performance/benchmark.rb +55 -0
  129. data/spec/performance/decorators.rb +45 -0
  130. data/spec/performance/models.rb +20 -0
  131. data/spec/spec_helper.rb +37 -0
  132. data/spec/support/dummy_app.rb +85 -0
  133. data/spec/support/matchers/have_text.rb +50 -0
  134. data/spec/support/shared_examples/decoratable_equality.rb +40 -0
  135. data/spec/support/shared_examples/view_helpers.rb +39 -0
  136. metadata +451 -0
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+
3
+ module Draper
4
+ describe DecoratedAssociation do
5
+
6
+ describe "#initialize" do
7
+ it "accepts valid options" do
8
+ valid_options = {with: Decorator, scope: :foo, context: {}}
9
+ expect{DecoratedAssociation.new(Decorator.new(Model.new), :association, valid_options)}.not_to raise_error
10
+ end
11
+
12
+ it "rejects invalid options" do
13
+ expect{DecoratedAssociation.new(Decorator.new(Model.new), :association, foo: "bar")}.to raise_error ArgumentError, /Unknown key/
14
+ end
15
+
16
+ it "creates a factory" do
17
+ options = {with: Decorator, context: {foo: "bar"}}
18
+
19
+ Factory.should_receive(:new).with(options)
20
+ DecoratedAssociation.new(double, :association, options)
21
+ end
22
+
23
+ describe ":with option" do
24
+ it "defaults to nil" do
25
+ Factory.should_receive(:new).with(with: nil, context: anything())
26
+ DecoratedAssociation.new(double, :association, {})
27
+ end
28
+ end
29
+
30
+ describe ":context option" do
31
+ it "defaults to the identity function" do
32
+ Factory.should_receive(:new).with do |options|
33
+ options[:context].call(:anything) == :anything
34
+ end
35
+ DecoratedAssociation.new(double, :association, {})
36
+ end
37
+ end
38
+ end
39
+
40
+ describe "#call" do
41
+ it "calls the factory" do
42
+ factory = double
43
+ Factory.stub new: factory
44
+ associated = double
45
+ owner_context = {foo: "bar"}
46
+ source = double(association: associated)
47
+ owner = double(source: source, context: owner_context)
48
+ decorated_association = DecoratedAssociation.new(owner, :association, {})
49
+ decorated = double
50
+
51
+ factory.should_receive(:decorate).with(associated, context_args: owner_context).and_return(decorated)
52
+ expect(decorated_association.call).to be decorated
53
+ end
54
+
55
+ it "memoizes" do
56
+ factory = double
57
+ Factory.stub new: factory
58
+ owner = double(source: double(association: double), context: {})
59
+ decorated_association = DecoratedAssociation.new(owner, :association, {})
60
+ decorated = double
61
+
62
+ factory.should_receive(:decorate).once.and_return(decorated)
63
+ expect(decorated_association.call).to be decorated
64
+ expect(decorated_association.call).to be decorated
65
+ end
66
+
67
+ context "when the :scope option was given" do
68
+ it "applies the scope before decoration" do
69
+ factory = double
70
+ Factory.stub new: factory
71
+ scoped = double
72
+ source = double(association: double(applied_scope: scoped))
73
+ owner = double(source: source, context: {})
74
+ decorated_association = DecoratedAssociation.new(owner, :association, scope: :applied_scope)
75
+ decorated = double
76
+
77
+ factory.should_receive(:decorate).with(scoped, anything()).and_return(decorated)
78
+ expect(decorated_association.call).to be decorated
79
+ end
80
+ end
81
+ end
82
+
83
+ end
84
+ end
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+
3
+ module Draper
4
+ describe DecoratesAssigned do
5
+ let(:controller_class) do
6
+ Class.new do
7
+ extend DecoratesAssigned
8
+
9
+ def self.helper_method(method)
10
+ helper_methods << method
11
+ end
12
+
13
+ def self.helper_methods
14
+ @helper_methods ||= []
15
+ end
16
+ end
17
+ end
18
+
19
+ describe ".decorates_assigned" do
20
+ it "adds helper methods" do
21
+ controller_class.decorates_assigned :article, :author
22
+
23
+ expect(controller_class.instance_methods).to include :article
24
+ expect(controller_class.instance_methods).to include :author
25
+
26
+ expect(controller_class.helper_methods).to include :article
27
+ expect(controller_class.helper_methods).to include :author
28
+ end
29
+
30
+ it "creates a factory" do
31
+ Factory.should_receive(:new).once
32
+ controller_class.decorates_assigned :article, :author
33
+ end
34
+
35
+ it "passes options to the factory" do
36
+ options = {foo: "bar"}
37
+
38
+ Factory.should_receive(:new).with(options)
39
+ controller_class.decorates_assigned :article, :author, options
40
+ end
41
+
42
+ describe "the generated method" do
43
+ it "decorates the instance variable" do
44
+ source = double
45
+ factory = double
46
+ Factory.stub new: factory
47
+
48
+ controller_class.decorates_assigned :article
49
+ controller = controller_class.new
50
+ controller.instance_variable_set "@article", source
51
+
52
+ factory.should_receive(:decorate).with(source, context_args: controller).and_return(:decorated)
53
+ expect(controller.article).to be :decorated
54
+ end
55
+
56
+ it "memoizes" do
57
+ factory = double
58
+ Factory.stub new: factory
59
+
60
+ controller_class.decorates_assigned :article
61
+ controller = controller_class.new
62
+
63
+ factory.should_receive(:decorate).once
64
+ controller.article
65
+ controller.article
66
+ end
67
+ end
68
+ end
69
+
70
+ end
71
+ end
@@ -0,0 +1,634 @@
1
+ require 'spec_helper'
2
+ require 'support/shared_examples/view_helpers'
3
+
4
+ module Draper
5
+ describe Decorator do
6
+ it_behaves_like "view helpers", Decorator.new(Model.new)
7
+
8
+ describe "#initialize" do
9
+ describe "options validation" do
10
+ it "does not raise error on valid options" do
11
+ valid_options = {context: {}}
12
+ expect{Decorator.new(Model.new, valid_options)}.not_to raise_error
13
+ end
14
+
15
+ it "raises error on invalid options" do
16
+ expect{Decorator.new(Model.new, foo: "bar")}.to raise_error ArgumentError, /Unknown key/
17
+ end
18
+ end
19
+
20
+ it "sets the source" do
21
+ source = Model.new
22
+ decorator = Decorator.new(source)
23
+
24
+ expect(decorator.source).to be source
25
+ end
26
+
27
+ it "stores context" do
28
+ context = {some: "context"}
29
+ decorator = Decorator.new(Model.new, context: context)
30
+
31
+ expect(decorator.context).to be context
32
+ end
33
+
34
+ context "when decorating an instance of itself" do
35
+ it "applies to the source instead" do
36
+ source = Model.new
37
+ decorated = Decorator.new(source)
38
+ redecorated = Decorator.new(decorated)
39
+
40
+ expect(redecorated.source).to be source
41
+ end
42
+
43
+ context "with context" do
44
+ it "overwrites existing context" do
45
+ decorated = Decorator.new(Model.new, context: {some: "context"})
46
+ new_context = {other: "context"}
47
+ redecorated = Decorator.new(decorated, context: new_context)
48
+
49
+ expect(redecorated.context).to be new_context
50
+ end
51
+ end
52
+
53
+ context "without context" do
54
+ it "preserves existing context" do
55
+ old_context = {some: "context"}
56
+ decorated = Decorator.new(Model.new, context: old_context)
57
+ redecorated = Decorator.new(decorated)
58
+
59
+ expect(redecorated.context).to be old_context
60
+ end
61
+ end
62
+ end
63
+
64
+ it "decorates other decorators" do
65
+ decorated = OtherDecorator.new(Model.new)
66
+ redecorated = Decorator.new(decorated)
67
+
68
+ expect(redecorated.source).to be decorated
69
+ end
70
+
71
+ context "when it has been applied previously" do
72
+ it "warns" do
73
+ decorated = OtherDecorator.new(Decorator.new(Model.new))
74
+
75
+ warning_message = nil
76
+ Object.any_instance.stub(:warn) {|message| warning_message = message }
77
+
78
+ expect{Decorator.new(decorated)}.to change{warning_message}
79
+ expect(warning_message).to start_with "Reapplying Draper::Decorator"
80
+ expect(warning_message).to include caller(1).first
81
+ end
82
+
83
+ it "decorates anyway" do
84
+ decorated = OtherDecorator.new(Decorator.new(Model.new))
85
+ Object.any_instance.stub(:warn)
86
+ redecorated = Decorator.decorate(decorated)
87
+
88
+ expect(redecorated.source).to be decorated
89
+ end
90
+ end
91
+ end
92
+
93
+ describe "#context=" do
94
+ it "modifies the context" do
95
+ decorator = Decorator.new(Model.new, context: {some: "context"})
96
+ new_context = {other: "context"}
97
+
98
+ decorator.context = new_context
99
+ expect(decorator.context).to be new_context
100
+ end
101
+ end
102
+
103
+ describe ".decorate_collection" do
104
+ describe "options validation" do
105
+ before { CollectionDecorator.stub(:new) }
106
+
107
+ it "does not raise error on valid options" do
108
+ valid_options = {with: OtherDecorator, context: {}}
109
+ expect{Decorator.decorate_collection([], valid_options)}.not_to raise_error
110
+ end
111
+
112
+ it "raises error on invalid options" do
113
+ expect{Decorator.decorate_collection([], foo: "bar")}.to raise_error ArgumentError, /Unknown key/
114
+ end
115
+ end
116
+
117
+ context "without a custom collection decorator" do
118
+ it "creates a CollectionDecorator using itself for each item" do
119
+ source = [Model.new]
120
+
121
+ CollectionDecorator.should_receive(:new).with(source, with: Decorator)
122
+ Decorator.decorate_collection(source)
123
+ end
124
+
125
+ it "passes options to the collection decorator" do
126
+ options = {with: OtherDecorator, context: {some: "context"}}
127
+
128
+ CollectionDecorator.should_receive(:new).with([], options)
129
+ Decorator.decorate_collection([], options)
130
+ end
131
+ end
132
+
133
+ context "with a custom collection decorator" do
134
+ it "creates a custom collection decorator using itself for each item" do
135
+ source = [Model.new]
136
+
137
+ ProductsDecorator.should_receive(:new).with(source, with: ProductDecorator)
138
+ ProductDecorator.decorate_collection(source)
139
+ end
140
+
141
+ it "passes options to the collection decorator" do
142
+ options = {with: OtherDecorator, context: {some: "context"}}
143
+
144
+ ProductsDecorator.should_receive(:new).with([], options)
145
+ ProductDecorator.decorate_collection([], options)
146
+ end
147
+ end
148
+
149
+ context "when a NameError is thrown" do
150
+ it "re-raises that error" do
151
+ String.any_instance.stub(:constantize).and_return{Draper::DecoratedEnumerableProxy}
152
+ expect{ProductDecorator.decorate_collection([])}.to raise_error NameError, /Draper::DecoratedEnumerableProxy/
153
+ end
154
+ end
155
+ end
156
+
157
+ describe ".decorates" do
158
+ protect_class Decorator
159
+
160
+ it "sets .source_class with a symbol" do
161
+ Decorator.decorates :product
162
+
163
+ expect(Decorator.source_class).to be Product
164
+ end
165
+
166
+ it "sets .source_class with a string" do
167
+ Decorator.decorates "product"
168
+
169
+ expect(Decorator.source_class).to be Product
170
+ end
171
+
172
+ it "sets .source_class with a class" do
173
+ Decorator.decorates Product
174
+
175
+ expect(Decorator.source_class).to be Product
176
+ end
177
+ end
178
+
179
+ describe ".source_class" do
180
+ protect_class ProductDecorator
181
+ protect_class Namespaced::ProductDecorator
182
+
183
+ context "when not set by .decorates" do
184
+ it "raises an UninferrableSourceError for a so-named 'Decorator'" do
185
+ expect{Decorator.source_class}.to raise_error UninferrableSourceError
186
+ end
187
+
188
+ it "raises an UninferrableSourceError for anonymous decorators" do
189
+ expect{Class.new(Decorator).source_class}.to raise_error UninferrableSourceError
190
+ end
191
+
192
+ it "raises an UninferrableSourceError for a decorator without a model" do
193
+ expect{OtherDecorator.source_class}.to raise_error UninferrableSourceError
194
+ end
195
+
196
+ it "raises an UninferrableSourceError for other naming conventions" do
197
+ expect{ProductPresenter.source_class}.to raise_error UninferrableSourceError
198
+ end
199
+
200
+ it "infers the source for '<Model>Decorator'" do
201
+ expect(ProductDecorator.source_class).to be Product
202
+ end
203
+
204
+ it "infers namespaced sources" do
205
+ expect(Namespaced::ProductDecorator.source_class).to be Namespaced::Product
206
+ end
207
+
208
+ context "when an unrelated NameError is thrown" do
209
+ it "re-raises that error" do
210
+ String.any_instance.stub(:constantize).and_return{SomethingThatDoesntExist}
211
+ expect{ProductDecorator.source_class}.to raise_error NameError, /SomethingThatDoesntExist/
212
+ end
213
+ end
214
+ end
215
+ end
216
+
217
+ describe ".source_class?" do
218
+ it "returns truthy when .source_class is set" do
219
+ Decorator.stub(:source_class).and_return(Model)
220
+
221
+ expect(Decorator.source_class?).to be_true
222
+ end
223
+
224
+ it "returns false when .source_class is not inferrable" do
225
+ Decorator.stub(:source_class).and_raise(UninferrableSourceError.new(Decorator))
226
+
227
+ expect(Decorator.source_class?).to be_false
228
+ end
229
+ end
230
+
231
+ describe ".decorates_association" do
232
+ protect_class Decorator
233
+
234
+ describe "options validation" do
235
+ before { DecoratedAssociation.stub(:new).and_return(->{}) }
236
+
237
+ it "does not raise error on valid options" do
238
+ valid_options = {with: Class, scope: :sorted, context: {}}
239
+ expect{Decorator.decorates_association(:children, valid_options)}.not_to raise_error
240
+ end
241
+
242
+ it "raises error on invalid options" do
243
+ expect{Decorator.decorates_association(:children, foo: "bar")}.to raise_error ArgumentError, /Unknown key/
244
+ end
245
+ end
246
+
247
+ describe "defines an association method" do
248
+ it "creates a DecoratedAssociation" do
249
+ options = {with: Class.new, scope: :foo, context: {}}
250
+ Decorator.decorates_association :children, options
251
+ decorator = Decorator.new(Model.new)
252
+
253
+ DecoratedAssociation.should_receive(:new).with(decorator, :children, options).and_return(->{})
254
+ decorator.children
255
+ end
256
+
257
+ it "memoizes the DecoratedAssociation" do
258
+ Decorator.decorates_association :children
259
+ decorator = Decorator.new(Model.new)
260
+
261
+ DecoratedAssociation.should_receive(:new).once.and_return(->{})
262
+ decorator.children
263
+ decorator.children
264
+ end
265
+
266
+ it "calls the DecoratedAssociation" do
267
+ Decorator.decorates_association :children
268
+ decorator = Decorator.new(Model.new)
269
+ decorated_association = ->{}
270
+ DecoratedAssociation.stub(:new).and_return(decorated_association)
271
+
272
+ decorated_association.should_receive(:call).and_return(:decorated)
273
+ expect(decorator.children).to be :decorated
274
+ end
275
+ end
276
+ end
277
+
278
+ describe ".decorates_associations" do
279
+ protect_class Decorator
280
+
281
+ it "decorates each of the associations" do
282
+ Decorator.should_receive(:decorates_association).with(:friends, {})
283
+ Decorator.should_receive(:decorates_association).with(:enemies, {})
284
+ Decorator.decorates_associations :friends, :enemies
285
+ end
286
+
287
+ it "dispatches options" do
288
+ options = {with: Class.new, scope: :foo, context: {}}
289
+
290
+ Decorator.should_receive(:decorates_association).with(:friends, options)
291
+ Decorator.should_receive(:decorates_association).with(:enemies, options)
292
+ Decorator.decorates_associations :friends, :enemies, options
293
+ end
294
+ end
295
+
296
+ describe "#applied_decorators" do
297
+ it "returns a list of decorators applied to a model" do
298
+ decorator = ProductDecorator.new(OtherDecorator.new(Decorator.new(Model.new)))
299
+
300
+ expect(decorator.applied_decorators).to eq [Decorator, OtherDecorator, ProductDecorator]
301
+ end
302
+ end
303
+
304
+ describe "#decorated_with?" do
305
+ it "checks if a decorator has been applied to a model" do
306
+ decorator = ProductDecorator.new(Decorator.new(Model.new))
307
+
308
+ expect(decorator).to be_decorated_with Decorator
309
+ expect(decorator).to be_decorated_with ProductDecorator
310
+ expect(decorator).not_to be_decorated_with OtherDecorator
311
+ end
312
+ end
313
+
314
+ describe "#decorated?" do
315
+ it "returns true" do
316
+ decorator = Decorator.new(Model.new)
317
+
318
+ expect(decorator).to be_decorated
319
+ end
320
+ end
321
+
322
+ describe "#source" do
323
+ it "returns the wrapped object" do
324
+ source = Model.new
325
+ decorator = Decorator.new(source)
326
+
327
+ expect(decorator.source).to be source
328
+ expect(decorator.model).to be source
329
+ expect(decorator.to_source).to be source
330
+ end
331
+ end
332
+
333
+ describe "#to_model" do
334
+ it "returns the decorator" do
335
+ decorator = Decorator.new(Model.new)
336
+
337
+ expect(decorator.to_model).to be decorator
338
+ end
339
+ end
340
+
341
+ describe "#to_param" do
342
+ it "delegates to the source" do
343
+ decorator = Decorator.new(double(to_param: :delegated))
344
+
345
+ expect(decorator.to_param).to be :delegated
346
+ end
347
+ end
348
+
349
+ describe "#present?" do
350
+ it "delegates to the source" do
351
+ decorator = Decorator.new(double(present?: :delegated))
352
+
353
+ expect(decorator.present?).to be :delegated
354
+ end
355
+ end
356
+
357
+ describe "#to_partial_path" do
358
+ it "delegates to the source" do
359
+ decorator = Decorator.new(double(to_partial_path: :delegated))
360
+
361
+ expect(decorator.to_partial_path).to be :delegated
362
+ end
363
+ end
364
+
365
+ describe "#attributes" do
366
+ it "returns only the source's attributes that are implemented by the decorator" do
367
+ decorator = Decorator.new(double(attributes: {foo: "bar", baz: "qux"}))
368
+ decorator.stub(:foo)
369
+
370
+ expect(decorator.attributes).to eq({foo: "bar"})
371
+ end
372
+ end
373
+
374
+ describe ".model_name" do
375
+ it "delegates to the source class" do
376
+ Decorator.stub source_class: double(model_name: :delegated)
377
+
378
+ expect(Decorator.model_name).to be :delegated
379
+ end
380
+ end
381
+
382
+ describe "#==" do
383
+ it "is true when source #== is true" do
384
+ source = Model.new
385
+ decorator = Decorator.new(source)
386
+ other = double(source: Model.new)
387
+
388
+ source.should_receive(:==).with(other).and_return(true)
389
+ expect(decorator == other).to be_true
390
+ end
391
+
392
+ it "is false when source #== is false" do
393
+ source = Model.new
394
+ decorator = Decorator.new(source)
395
+ other = double(source: Model.new)
396
+
397
+ source.should_receive(:==).with(other).and_return(false)
398
+ expect(decorator == other).to be_false
399
+ end
400
+
401
+ end
402
+
403
+ describe "#===" do
404
+ it "is true when #== is true" do
405
+ decorator = Decorator.new(Model.new)
406
+ decorator.stub(:==).with(:anything).and_return(true)
407
+
408
+ expect(decorator === :anything).to be_true
409
+ end
410
+
411
+ it "is false when #== is false" do
412
+ decorator = Decorator.new(Model.new)
413
+ decorator.stub(:==).with(:anything).and_return(false)
414
+
415
+ expect(decorator === :anything).to be_false
416
+ end
417
+ end
418
+
419
+ describe ".delegate" do
420
+ protect_class Decorator
421
+
422
+ it "defaults the :to option to :source" do
423
+ Object.should_receive(:delegate).with(:foo, :bar, to: :source)
424
+ Decorator.delegate :foo, :bar
425
+ end
426
+
427
+ it "does not overwrite the :to option if supplied" do
428
+ Object.should_receive(:delegate).with(:foo, :bar, to: :baz)
429
+ Decorator.delegate :foo, :bar, to: :baz
430
+ end
431
+ end
432
+
433
+ context "with .delegate_all" do
434
+ protect_class Decorator
435
+
436
+ before { Decorator.delegate_all }
437
+
438
+ describe "#method_missing" do
439
+ it "delegates missing methods that exist on the source" do
440
+ decorator = Decorator.new(double(hello_world: :delegated))
441
+
442
+ expect(decorator.hello_world).to be :delegated
443
+ end
444
+
445
+ it "adds delegated methods to the decorator when they are used" do
446
+ decorator = Decorator.new(double(hello_world: :delegated))
447
+
448
+ expect(decorator.methods).not_to include :hello_world
449
+ decorator.hello_world
450
+ expect(decorator.methods).to include :hello_world
451
+ end
452
+
453
+ it "passes blocks to delegated methods" do
454
+ source = Model.new
455
+ source.stub(:hello_world).and_return{|*args, &block| block.call}
456
+ decorator = Decorator.new(source)
457
+
458
+ expect(decorator.hello_world{:yielded}).to be :yielded
459
+ end
460
+
461
+ it "does not confuse Kernel#Array" do
462
+ decorator = Decorator.new(Model.new)
463
+
464
+ expect(Array(decorator)).to be_an Array
465
+ end
466
+
467
+ it "delegates already-delegated methods" do
468
+ source = Class.new{ delegate :bar, to: :foo }.new
469
+ source.stub foo: double(bar: :delegated)
470
+ decorator = Decorator.new(source)
471
+
472
+ expect(decorator.bar).to be :delegated
473
+ end
474
+
475
+ it "does not delegate private methods" do
476
+ source = Class.new{ private; def hello_world; end }.new
477
+ decorator = Decorator.new(source)
478
+
479
+ expect{decorator.hello_world}.to raise_error NoMethodError
480
+ end
481
+
482
+ it "does not delegate methods that do not exist on the source" do
483
+ decorator = Decorator.new(Model.new)
484
+
485
+ expect(decorator.methods).not_to include :hello_world
486
+ expect{decorator.hello_world}.to raise_error NoMethodError
487
+ expect(decorator.methods).not_to include :hello_world
488
+ end
489
+ end
490
+
491
+ context ".method_missing" do
492
+ context "without a source class" do
493
+ it "raises a NoMethodError on missing methods" do
494
+ expect{Decorator.hello_world}.to raise_error NoMethodError
495
+ end
496
+ end
497
+
498
+ context "with a source class" do
499
+ it "delegates methods that exist on the source class" do
500
+ source_class = Class.new
501
+ source_class.stub hello_world: :delegated
502
+ Decorator.stub source_class: source_class
503
+
504
+ expect(Decorator.hello_world).to be :delegated
505
+ end
506
+
507
+ it "does not delegate methods that do not exist on the source class" do
508
+ Decorator.stub source_class: Class.new
509
+
510
+ expect{Decorator.hello_world}.to raise_error NoMethodError
511
+ end
512
+ end
513
+ end
514
+
515
+ describe "#respond_to?" do
516
+ it "returns true for its own methods" do
517
+ Decorator.class_eval{def hello_world; end}
518
+ decorator = Decorator.new(Model.new)
519
+
520
+ expect(decorator).to respond_to :hello_world
521
+ end
522
+
523
+ it "returns true for the source's methods" do
524
+ decorator = Decorator.new(double(hello_world: :delegated))
525
+
526
+ expect(decorator).to respond_to :hello_world
527
+ end
528
+
529
+ context "with include_private" do
530
+ it "returns true for its own private methods" do
531
+ Decorator.class_eval{private; def hello_world; end}
532
+ decorator = Decorator.new(Model.new)
533
+
534
+ expect(decorator.respond_to?(:hello_world, true)).to be_true
535
+ end
536
+
537
+ it "returns false for the source's private methods" do
538
+ source = Class.new{private; def hello_world; end}.new
539
+ decorator = Decorator.new(source)
540
+
541
+ expect(decorator.respond_to?(:hello_world, true)).to be_false
542
+ end
543
+ end
544
+ end
545
+
546
+ describe ".respond_to?" do
547
+ context "without a source class" do
548
+ it "returns true for its own class methods" do
549
+ Decorator.class_eval{def self.hello_world; end}
550
+
551
+ expect(Decorator).to respond_to :hello_world
552
+ end
553
+
554
+ it "returns false for other class methods" do
555
+ expect(Decorator).not_to respond_to :goodnight_moon
556
+ end
557
+ end
558
+
559
+ context "with a source class" do
560
+ it "returns true for its own class methods" do
561
+ Decorator.class_eval{def self.hello_world; end}
562
+ Decorator.stub source_class: Class.new
563
+
564
+ expect(Decorator).to respond_to :hello_world
565
+ end
566
+
567
+ it "returns true for the source's class methods" do
568
+ Decorator.stub source_class: double(hello_world: :delegated)
569
+
570
+ expect(Decorator).to respond_to :hello_world
571
+ end
572
+ end
573
+ end
574
+
575
+ describe "#respond_to_missing?" do
576
+ it "allows #method to be called on delegated methods" do
577
+ source = Class.new{def hello_world; end}.new
578
+ decorator = Decorator.new(source)
579
+
580
+ expect { decorator.method(:hello_world) }.not_to raise_error NameError
581
+ expect(decorator.method(:hello_world)).not_to be_nil
582
+ end
583
+ end
584
+
585
+ describe ".respond_to_missing?" do
586
+ it "allows .method to be called on delegated class methods" do
587
+ Decorator.stub source_class: double(hello_world: :delegated)
588
+
589
+ expect { Decorator.method(:hello_world) }.not_to raise_error NameError
590
+ expect(Decorator.method(:hello_world)).not_to be_nil
591
+ end
592
+ end
593
+ end
594
+
595
+ describe "class spoofing" do
596
+ it "pretends to be a kind of the source class" do
597
+ decorator = Decorator.new(Model.new)
598
+
599
+ expect(decorator.kind_of?(Model)).to be_true
600
+ expect(decorator.is_a?(Model)).to be_true
601
+ end
602
+
603
+ it "is still a kind of its own class" do
604
+ decorator = Decorator.new(Model.new)
605
+
606
+ expect(decorator.kind_of?(Decorator)).to be_true
607
+ expect(decorator.is_a?(Decorator)).to be_true
608
+ end
609
+
610
+ it "pretends to be an instance of the source class" do
611
+ decorator = Decorator.new(Model.new)
612
+
613
+ expect(decorator.instance_of?(Model)).to be_true
614
+ end
615
+
616
+ it "is still an instance of its own class" do
617
+ decorator = Decorator.new(Model.new)
618
+
619
+ expect(decorator.instance_of?(Decorator)).to be_true
620
+ end
621
+ end
622
+
623
+ describe ".decorates_finders" do
624
+ protect_class Decorator
625
+
626
+ it "extends the Finders module" do
627
+ expect(Decorator).not_to be_a_kind_of Finders
628
+ Decorator.decorates_finders
629
+ expect(Decorator).to be_a_kind_of Finders
630
+ end
631
+ end
632
+
633
+ end
634
+ end