draper 0.18.0 → 1.1.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 (155) hide show
  1. data/.gitignore +3 -0
  2. data/.travis.yml +11 -0
  3. data/.yardopts +1 -1
  4. data/CHANGELOG.md +164 -0
  5. data/CONTRIBUTING.md +15 -0
  6. data/Gemfile +34 -0
  7. data/LICENSE +7 -0
  8. data/README.md +416 -0
  9. data/Rakefile +53 -31
  10. data/draper.gemspec +11 -9
  11. data/lib/draper/automatic_delegation.rb +50 -0
  12. data/lib/draper/collection_decorator.rb +107 -0
  13. data/lib/draper/decoratable/equality.rb +14 -0
  14. data/lib/draper/decoratable.rb +79 -0
  15. data/lib/draper/decorated_association.rb +74 -0
  16. data/lib/draper/decorator.rb +235 -0
  17. data/lib/draper/delegation.rb +13 -0
  18. data/lib/draper/finders.rb +37 -0
  19. data/lib/draper/helper_proxy.rb +36 -0
  20. data/lib/draper/lazy_helpers.rb +10 -6
  21. data/lib/draper/railtie.rb +27 -21
  22. data/lib/draper/tasks/test.rake +22 -0
  23. data/lib/draper/test/devise_helper.rb +30 -0
  24. data/lib/draper/test/minitest_integration.rb +2 -3
  25. data/lib/draper/test/rspec_integration.rb +7 -13
  26. data/lib/draper/test_case.rb +53 -0
  27. data/lib/draper/version.rb +1 -1
  28. data/lib/draper/view_context/build_strategy.rb +48 -0
  29. data/lib/draper/view_context.rb +81 -24
  30. data/lib/draper/view_helpers.rb +37 -0
  31. data/lib/draper.rb +50 -8
  32. data/lib/generators/decorator/decorator_generator.rb +10 -2
  33. data/lib/generators/decorator/templates/decorator.rb +12 -27
  34. data/lib/generators/mini_test/decorator_generator.rb +20 -0
  35. data/lib/generators/mini_test/templates/decorator_spec.rb +4 -0
  36. data/lib/generators/mini_test/templates/decorator_test.rb +4 -0
  37. data/lib/generators/test_unit/templates/decorator_test.rb +1 -1
  38. data/spec/draper/collection_decorator_spec.rb +259 -0
  39. data/spec/draper/decoratable/equality_spec.rb +10 -0
  40. data/spec/draper/decoratable_spec.rb +167 -0
  41. data/spec/draper/decorated_association_spec.rb +145 -0
  42. data/spec/draper/decorator_spec.rb +598 -0
  43. data/spec/draper/finders_spec.rb +166 -0
  44. data/spec/draper/helper_proxy_spec.rb +42 -0
  45. data/spec/draper/view_context/build_strategy_spec.rb +116 -0
  46. data/spec/draper/view_context_spec.rb +154 -0
  47. data/spec/draper/view_helpers_spec.rb +8 -0
  48. data/spec/dummy/.rspec +2 -0
  49. data/spec/dummy/Rakefile +7 -0
  50. data/spec/dummy/app/controllers/application_controller.rb +4 -0
  51. data/spec/dummy/app/controllers/localized_urls.rb +5 -0
  52. data/spec/dummy/app/controllers/posts_controller.rb +18 -0
  53. data/spec/dummy/app/decorators/post_decorator.rb +56 -0
  54. data/spec/dummy/app/helpers/application_helper.rb +5 -0
  55. data/spec/dummy/app/mailers/application_mailer.rb +3 -0
  56. data/spec/dummy/app/mailers/post_mailer.rb +19 -0
  57. data/spec/dummy/app/models/admin.rb +5 -0
  58. data/spec/dummy/app/models/mongoid_post.rb +5 -0
  59. data/spec/dummy/app/models/post.rb +3 -0
  60. data/spec/dummy/app/models/user.rb +5 -0
  61. data/spec/dummy/app/views/layouts/application.html.erb +11 -0
  62. data/spec/dummy/app/views/post_mailer/decorated_email.html.erb +1 -0
  63. data/spec/dummy/app/views/posts/_post.html.erb +34 -0
  64. data/spec/dummy/app/views/posts/show.html.erb +1 -0
  65. data/spec/dummy/bin/rails +4 -0
  66. data/spec/dummy/config/application.rb +70 -0
  67. data/spec/dummy/config/boot.rb +5 -0
  68. data/spec/dummy/config/database.yml +25 -0
  69. data/spec/dummy/config/environment.rb +5 -0
  70. data/spec/dummy/config/environments/development.rb +33 -0
  71. data/spec/dummy/config/environments/production.rb +57 -0
  72. data/spec/dummy/config/environments/test.rb +31 -0
  73. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  74. data/spec/dummy/config/initializers/inflections.rb +15 -0
  75. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  76. data/spec/dummy/config/initializers/secret_token.rb +8 -0
  77. data/spec/dummy/config/initializers/session_store.rb +8 -0
  78. data/spec/dummy/config/locales/en.yml +5 -0
  79. data/spec/dummy/config/mongoid.yml +80 -0
  80. data/spec/dummy/config/routes.rb +9 -0
  81. data/spec/dummy/config.ru +4 -0
  82. data/spec/dummy/db/migrate/20121019115657_create_posts.rb +8 -0
  83. data/spec/dummy/db/schema.rb +21 -0
  84. data/spec/dummy/db/seeds.rb +2 -0
  85. data/spec/dummy/fast_spec/post_decorator_spec.rb +38 -0
  86. data/spec/dummy/lib/tasks/test.rake +16 -0
  87. data/spec/dummy/log/.gitkeep +0 -0
  88. data/spec/dummy/public/404.html +26 -0
  89. data/spec/dummy/public/422.html +26 -0
  90. data/spec/dummy/public/500.html +25 -0
  91. data/spec/dummy/public/favicon.ico +0 -0
  92. data/spec/dummy/script/rails +6 -0
  93. data/spec/dummy/spec/decorators/devise_spec.rb +64 -0
  94. data/spec/dummy/spec/decorators/helpers_spec.rb +21 -0
  95. data/spec/dummy/spec/decorators/post_decorator_spec.rb +46 -0
  96. data/spec/dummy/spec/decorators/spec_type_spec.rb +7 -0
  97. data/spec/dummy/spec/decorators/view_context_spec.rb +22 -0
  98. data/spec/dummy/spec/mailers/post_mailer_spec.rb +33 -0
  99. data/spec/dummy/spec/models/mongoid_post_spec.rb +10 -0
  100. data/spec/dummy/spec/models/post_spec.rb +14 -0
  101. data/spec/dummy/spec/spec_helper.rb +9 -0
  102. data/spec/dummy/test/decorators/minitest/devise_test.rb +64 -0
  103. data/spec/dummy/test/decorators/minitest/helpers_test.rb +21 -0
  104. data/spec/dummy/test/decorators/minitest/spec_type_test.rb +52 -0
  105. data/spec/dummy/test/decorators/minitest/view_context_test.rb +24 -0
  106. data/spec/dummy/test/decorators/test_unit/devise_test.rb +64 -0
  107. data/spec/dummy/test/decorators/test_unit/helpers_test.rb +21 -0
  108. data/spec/dummy/test/decorators/test_unit/view_context_test.rb +24 -0
  109. data/spec/dummy/test/minitest_helper.rb +4 -0
  110. data/spec/dummy/test/test_helper.rb +3 -0
  111. data/spec/generators/decorator/decorator_generator_spec.rb +48 -7
  112. data/spec/integration/integration_spec.rb +58 -0
  113. data/{performance → spec/performance}/decorators.rb +2 -4
  114. data/spec/spec_helper.rb +25 -43
  115. data/spec/support/dummy_app.rb +85 -0
  116. data/spec/support/matchers/have_text.rb +50 -0
  117. data/spec/support/shared_examples/decoratable_equality.rb +40 -0
  118. data/spec/support/shared_examples/view_helpers.rb +39 -0
  119. metadata +253 -87
  120. data/CHANGELOG.markdown +0 -57
  121. data/Readme.markdown +0 -387
  122. data/lib/draper/active_model_support.rb +0 -27
  123. data/lib/draper/base.rb +0 -312
  124. data/lib/draper/decorated_enumerable_proxy.rb +0 -90
  125. data/lib/draper/model_support.rb +0 -25
  126. data/lib/draper/rspec_integration.rb +0 -2
  127. data/lib/draper/system.rb +0 -18
  128. data/spec/draper/base_spec.rb +0 -873
  129. data/spec/draper/decorated_enumerable_proxy_spec.rb +0 -45
  130. data/spec/draper/model_support_spec.rb +0 -48
  131. data/spec/minitest-rails/spec_type_spec.rb +0 -63
  132. data/spec/support/samples/active_record.rb +0 -17
  133. data/spec/support/samples/decorator.rb +0 -5
  134. data/spec/support/samples/decorator_with_allows.rb +0 -3
  135. data/spec/support/samples/decorator_with_application_helper.rb +0 -25
  136. data/spec/support/samples/decorator_with_denies.rb +0 -3
  137. data/spec/support/samples/decorator_with_denies_all.rb +0 -3
  138. data/spec/support/samples/decorator_with_multiple_allows.rb +0 -4
  139. data/spec/support/samples/decorator_with_special_methods.rb +0 -13
  140. data/spec/support/samples/enumerable_proxy.rb +0 -3
  141. data/spec/support/samples/namespaced_product.rb +0 -51
  142. data/spec/support/samples/namespaced_product_decorator.rb +0 -7
  143. data/spec/support/samples/non_active_model_product.rb +0 -2
  144. data/spec/support/samples/product.rb +0 -80
  145. data/spec/support/samples/product_decorator.rb +0 -7
  146. data/spec/support/samples/products_decorator.rb +0 -10
  147. data/spec/support/samples/sequel_product.rb +0 -13
  148. data/spec/support/samples/some_thing.rb +0 -2
  149. data/spec/support/samples/some_thing_decorator.rb +0 -3
  150. data/spec/support/samples/specific_product_decorator.rb +0 -4
  151. data/spec/support/samples/widget.rb +0 -2
  152. data/spec/support/samples/widget_decorator.rb +0 -2
  153. /data/{performance → spec/performance}/active_record.rb +0 -0
  154. /data/{performance/bechmark.rb → spec/performance/benchmark.rb} +0 -0
  155. /data/{performance → spec/performance}/models.rb +0 -0
@@ -1,34 +1,19 @@
1
- <% module_namespacing do -%>
1
+ <%- module_namespacing do -%>
2
+ <%- if parent_class_name.present? -%>
2
3
  class <%= class_name %>Decorator < <%= parent_class_name %>
3
- decorates :<%= singular_name %>
4
+ <%- else -%>
5
+ class <%= class_name %>
6
+ <%- end -%>
7
+ delegate_all
4
8
 
5
- # Accessing Helpers
6
- # You can access any helper via a proxy
7
- #
8
- # Normal Usage: helpers.number_to_currency(2)
9
- # Abbreviated : h.number_to_currency(2)
10
- #
11
- # Or, optionally enable "lazy helpers" by including this module:
12
- # include Draper::LazyHelpers
13
- # Then use the helpers with no proxy:
14
- # number_to_currency(2)
15
-
16
- # Defining an Interface
17
- # Control access to the wrapped subject's methods using one of the following:
18
- #
19
- # To allow only the listed methods (whitelist):
20
- # allows :method1, :method2
21
- #
22
- # To allow everything except the listed methods (blacklist):
23
- # denies :method1, :method2
24
-
25
- # Presentation Methods
26
- # Define your own instance methods, even overriding accessors
27
- # generated by ActiveRecord:
9
+ # Define presentation-specific methods here. Helpers are accessed through
10
+ # `helpers` (aka `h`). You can override attributes, for example:
28
11
  #
29
12
  # def created_at
30
- # h.content_tag :span, attributes["created_at"].strftime("%a %m/%d/%y"),
31
- # :class => 'timestamp'
13
+ # helpers.content_tag :span, class: 'time' do
14
+ # source.created_at.strftime("%a %m/%d/%y")
15
+ # end
32
16
  # end
17
+
33
18
  end
34
19
  <% end -%>
@@ -0,0 +1,20 @@
1
+ require 'generators/mini_test'
2
+
3
+ module MiniTest
4
+ module Generators
5
+ class DecoratorGenerator < Base
6
+ def self.source_root
7
+ File.expand_path('../templates', __FILE__)
8
+ end
9
+
10
+ class_option :spec, :type => :boolean, :default => false, :desc => "Use MiniTest::Spec DSL"
11
+
12
+ check_class_collision suffix: "DecoratorTest"
13
+
14
+ def create_test_file
15
+ template_type = options[:spec] ? "spec" : "test"
16
+ template "decorator_#{template_type}.rb", File.join("test/decorators", class_path, "#{singular_name}_decorator_test.rb")
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,4 @@
1
+ require 'minitest_helper'
2
+
3
+ describe <%= class_name %>Decorator do
4
+ end
@@ -0,0 +1,4 @@
1
+ require 'minitest_helper'
2
+
3
+ class <%= class_name %>DecoratorTest < Draper::TestCase
4
+ end
@@ -1,4 +1,4 @@
1
1
  require 'test_helper'
2
2
 
3
- class <%= class_name %>DecoratorTest < ActiveSupport::TestCase
3
+ class <%= class_name %>DecoratorTest < Draper::TestCase
4
4
  end
@@ -0,0 +1,259 @@
1
+ require 'spec_helper'
2
+ require 'support/shared_examples/view_helpers'
3
+
4
+ module Draper
5
+ describe CollectionDecorator do
6
+ it_behaves_like "view helpers", CollectionDecorator.new([])
7
+
8
+ describe "#initialize" do
9
+ describe "options validation" do
10
+
11
+ it "does not raise error on valid options" do
12
+ valid_options = {with: Decorator, context: {}}
13
+ expect{CollectionDecorator.new([], valid_options)}.not_to raise_error
14
+ end
15
+
16
+ it "raises error on invalid options" do
17
+ expect{CollectionDecorator.new([], foo: "bar")}.to raise_error ArgumentError, /Unknown key/
18
+ end
19
+ end
20
+ end
21
+
22
+ context "with context" do
23
+ it "stores the context itself" do
24
+ context = {some: "context"}
25
+ decorator = CollectionDecorator.new([], context: context)
26
+
27
+ expect(decorator.context).to be context
28
+ end
29
+
30
+ it "passes context to the individual decorators" do
31
+ context = {some: "context"}
32
+ decorator = CollectionDecorator.new([Product.new, Product.new], context: context)
33
+
34
+ decorator.each do |item|
35
+ expect(item.context).to be context
36
+ end
37
+ end
38
+ end
39
+
40
+ describe "#context=" do
41
+ it "updates the stored context" do
42
+ decorator = CollectionDecorator.new([], context: {some: "context"})
43
+ new_context = {other: "context"}
44
+
45
+ decorator.context = new_context
46
+ expect(decorator.context).to be new_context
47
+ end
48
+
49
+ context "when the collection is already decorated" do
50
+ it "updates the items' context" do
51
+ decorator = CollectionDecorator.new([Product.new, Product.new], context: {some: "context"})
52
+ decorator.decorated_collection # trigger decoration
53
+ new_context = {other: "context"}
54
+
55
+ decorator.context = new_context
56
+ decorator.each do |item|
57
+ expect(item.context).to be new_context
58
+ end
59
+ end
60
+ end
61
+
62
+ context "when the collection has not yet been decorated" do
63
+ it "does not trigger decoration" do
64
+ decorator = CollectionDecorator.new([])
65
+
66
+ decorator.should_not_receive(:decorated_collection)
67
+ decorator.context = {other: "context"}
68
+ end
69
+
70
+ it "sets context after decoration is triggered" do
71
+ decorator = CollectionDecorator.new([Product.new, Product.new], context: {some: "context"})
72
+ new_context = {other: "context"}
73
+
74
+ decorator.context = new_context
75
+ decorator.each do |item|
76
+ expect(item.context).to be new_context
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ describe "item decoration" do
83
+ it "sets decorated items' source models" do
84
+ collection = [Product.new, Product.new]
85
+ decorator = CollectionDecorator.new(collection)
86
+
87
+ decorator.zip collection do |item, source|
88
+ expect(item.source).to be source
89
+ end
90
+ end
91
+
92
+ context "when the item decorator is inferrable from the collection decorator" do
93
+ context "when the :with option was given" do
94
+ it "uses the :with option" do
95
+ decorator = ProductsDecorator.new([Product.new], with: OtherDecorator)
96
+
97
+ expect(*decorator).to be_decorated_with OtherDecorator
98
+ end
99
+ end
100
+
101
+ context "when the :with option was not given" do
102
+ it "infers the item decorator from the collection decorator" do
103
+ decorator = ProductsDecorator.new([Product.new])
104
+
105
+ expect(*decorator).to be_decorated_with ProductDecorator
106
+ end
107
+ end
108
+ end
109
+
110
+ context "when the item decorator is not inferrable from the collection decorator" do
111
+ context "when the :with option was given" do
112
+ it "uses the :with option" do
113
+ decorator = CollectionDecorator.new([Product.new], with: OtherDecorator)
114
+
115
+ expect(*decorator).to be_decorated_with OtherDecorator
116
+ end
117
+ end
118
+
119
+ context "when the :with option was not given" do
120
+ it "infers the item decorator from each item" do
121
+ decorator = CollectionDecorator.new([double(decorate: :inferred_decorator)])
122
+
123
+ expect(*decorator).to be :inferred_decorator
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ describe ".delegate" do
130
+ protect_class ProductsDecorator
131
+
132
+ it "defaults the :to option to :source" do
133
+ Object.should_receive(:delegate).with(:foo, :bar, to: :source)
134
+ ProductsDecorator.delegate :foo, :bar
135
+ end
136
+
137
+ it "does not overwrite the :to option if supplied" do
138
+ Object.should_receive(:delegate).with(:foo, :bar, to: :baz)
139
+ ProductsDecorator.delegate :foo, :bar, to: :baz
140
+ end
141
+ end
142
+
143
+ describe "#find" do
144
+ context "with a block" do
145
+ it "decorates Enumerable#find" do
146
+ decorator = CollectionDecorator.new([])
147
+
148
+ decorator.decorated_collection.should_receive(:find).and_return(:delegated)
149
+ expect(decorator.find{|p| p.title == "title"}).to be :delegated
150
+ end
151
+ end
152
+
153
+ context "without a block" do
154
+ it "decorates Model.find" do
155
+ item_decorator = Class.new
156
+ decorator = CollectionDecorator.new([], with: item_decorator)
157
+
158
+ item_decorator.should_receive(:find).with(1).and_return(:delegated)
159
+ expect(decorator.find(1)).to be :delegated
160
+ end
161
+ end
162
+ end
163
+
164
+ describe "#to_ary" do
165
+ # required for `render @collection` in Rails
166
+ it "delegates to the decorated collection" do
167
+ decorator = CollectionDecorator.new([])
168
+
169
+ decorator.decorated_collection.should_receive(:to_ary).and_return(:delegated)
170
+ expect(decorator.to_ary).to be :delegated
171
+ end
172
+ end
173
+
174
+ it "delegates array methods to the decorated collection" do
175
+ decorator = CollectionDecorator.new([])
176
+
177
+ decorator.decorated_collection.should_receive(:[]).with(42).and_return(:delegated)
178
+ expect(decorator[42]).to be :delegated
179
+ end
180
+
181
+ describe "#==" do
182
+ context "when comparing to a collection decorator with the same source" do
183
+ it "returns true" do
184
+ source = [Product.new, Product.new]
185
+ decorator = CollectionDecorator.new(source)
186
+ other = ProductsDecorator.new(source)
187
+
188
+ expect(decorator == other).to be_true
189
+ end
190
+ end
191
+
192
+ context "when comparing to a collection decorator with a different source" do
193
+ it "returns false" do
194
+ decorator = CollectionDecorator.new([Product.new, Product.new])
195
+ other = ProductsDecorator.new([Product.new, Product.new])
196
+
197
+ expect(decorator == other).to be_false
198
+ end
199
+ end
200
+
201
+ context "when comparing to a collection of the same items" do
202
+ it "returns true" do
203
+ source = [Product.new, Product.new]
204
+ decorator = CollectionDecorator.new(source)
205
+ other = source.dup
206
+
207
+ expect(decorator == other).to be_true
208
+ end
209
+ end
210
+
211
+ context "when comparing to a collection of different items" do
212
+ it "returns false" do
213
+ decorator = CollectionDecorator.new([Product.new, Product.new])
214
+ other = [Product.new, Product.new]
215
+
216
+ expect(decorator == other).to be_false
217
+ end
218
+ end
219
+
220
+ context "when the decorated collection has been modified" do
221
+ it "is no longer equal to the source" do
222
+ source = [Product.new, Product.new]
223
+ decorator = CollectionDecorator.new(source)
224
+ other = source.dup
225
+
226
+ decorator << Product.new.decorate
227
+ expect(decorator == other).to be_false
228
+ end
229
+ end
230
+ end
231
+
232
+ describe "#to_s" do
233
+ context "when :with option was given" do
234
+ it "returns a string representation of the collection decorator" do
235
+ decorator = CollectionDecorator.new(["a", "b", "c"], with: ProductDecorator)
236
+
237
+ expect(decorator.to_s).to eq '#<Draper::CollectionDecorator of ProductDecorator for ["a", "b", "c"]>'
238
+ end
239
+ end
240
+
241
+ context "when :with option was not given" do
242
+ it "returns a string representation of the collection decorator" do
243
+ decorator = CollectionDecorator.new(["a", "b", "c"])
244
+
245
+ expect(decorator.to_s).to eq '#<Draper::CollectionDecorator of inferred decorators for ["a", "b", "c"]>'
246
+ end
247
+ end
248
+
249
+ context "for a custom subclass" do
250
+ it "uses the custom class name" do
251
+ decorator = ProductsDecorator.new([])
252
+
253
+ expect(decorator.to_s).to match /ProductsDecorator/
254
+ end
255
+ end
256
+ end
257
+
258
+ end
259
+ end
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+ require 'support/shared_examples/decoratable_equality'
3
+
4
+ module Draper
5
+ describe Decoratable::Equality do
6
+ describe "#==" do
7
+ it_behaves_like "decoration-aware #==", Object.new.extend(Decoratable::Equality)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,167 @@
1
+ require 'spec_helper'
2
+ require 'support/shared_examples/decoratable_equality'
3
+
4
+ module Draper
5
+ describe Decoratable do
6
+
7
+ describe "#decorate" do
8
+ it "returns a decorator for self" do
9
+ product = Product.new
10
+ decorator = product.decorate
11
+
12
+ expect(decorator).to be_a ProductDecorator
13
+ expect(decorator.source).to be product
14
+ end
15
+
16
+ it "accepts context" do
17
+ context = {some: "context"}
18
+ decorator = Product.new.decorate(context: context)
19
+
20
+ expect(decorator.context).to be context
21
+ end
22
+
23
+ it "uses the #decorator_class" do
24
+ product = Product.new
25
+ product.stub decorator_class: OtherDecorator
26
+
27
+ expect(product.decorate).to be_an_instance_of OtherDecorator
28
+ end
29
+ end
30
+
31
+ describe "#applied_decorators" do
32
+ it "returns an empty list" do
33
+ expect(Product.new.applied_decorators).to eq []
34
+ end
35
+ end
36
+
37
+ describe "#decorated_with?" do
38
+ it "returns false" do
39
+ expect(Product.new).not_to be_decorated_with Decorator
40
+ end
41
+ end
42
+
43
+ describe "#decorated?" do
44
+ it "returns false" do
45
+ expect(Product.new).not_to be_decorated
46
+ end
47
+ end
48
+
49
+ describe "#decorator_class" do
50
+ it "delegates to .decorator_class" do
51
+ product = Product.new
52
+
53
+ Product.should_receive(:decorator_class).and_return(:some_decorator)
54
+ expect(product.decorator_class).to be :some_decorator
55
+ end
56
+ end
57
+
58
+ describe "#==" do
59
+ it_behaves_like "decoration-aware #==", Product.new
60
+ end
61
+
62
+ describe "#===" do
63
+ it "is true when #== is true" do
64
+ product = Product.new
65
+
66
+ product.should_receive(:==).and_return(true)
67
+ expect(product === :anything).to be_true
68
+ end
69
+
70
+ it "is false when #== is false" do
71
+ product = Product.new
72
+
73
+ product.should_receive(:==).and_return(false)
74
+ expect(product === :anything).to be_false
75
+ end
76
+ end
77
+
78
+ describe ".====" do
79
+ it "is true for an instance" do
80
+ expect(Product === Product.new).to be_true
81
+ end
82
+
83
+ it "is true for a derived instance" do
84
+ expect(Product === Class.new(Product).new).to be_true
85
+ end
86
+
87
+ it "is false for an unrelated instance" do
88
+ expect(Product === Model.new).to be_false
89
+ end
90
+
91
+ it "is true for a decorated instance" do
92
+ decorator = double(source: Product.new)
93
+
94
+ expect(Product === decorator).to be_true
95
+ end
96
+
97
+ it "is true for a decorated derived instance" do
98
+ decorator = double(source: Class.new(Product).new)
99
+
100
+ expect(Product === decorator).to be_true
101
+ end
102
+
103
+ it "is false for a decorated unrelated instance" do
104
+ decorator = double(source: Model.new)
105
+
106
+ expect(Product === decorator).to be_false
107
+ end
108
+ end
109
+
110
+ describe ".decorate" do
111
+ it "calls #decorate_collection on .decorator_class" do
112
+ scoped = [Product.new]
113
+ Product.stub scoped: scoped
114
+
115
+ Product.decorator_class.should_receive(:decorate_collection).with(scoped, {}).and_return(:decorated_collection)
116
+ expect(Product.decorate).to be :decorated_collection
117
+ end
118
+
119
+ it "accepts options" do
120
+ options = {context: {some: "context"}}
121
+ Product.stub scoped: []
122
+
123
+ Product.decorator_class.should_receive(:decorate_collection).with([], options)
124
+ Product.decorate(options)
125
+ end
126
+ end
127
+
128
+ describe ".decorator_class" do
129
+ context "for classes" do
130
+ it "infers the decorator from the class" do
131
+ expect(Product.decorator_class).to be ProductDecorator
132
+ end
133
+ end
134
+
135
+ context "for ActiveModel classes" do
136
+ it "infers the decorator from the model name" do
137
+ Product.stub(:model_name).and_return("Other")
138
+
139
+ expect(Product.decorator_class).to be OtherDecorator
140
+ end
141
+ end
142
+
143
+ context "in a namespace" do
144
+ context "for classes" do
145
+ it "infers the decorator from the class" do
146
+ expect(Namespaced::Product.decorator_class).to be Namespaced::ProductDecorator
147
+ end
148
+ end
149
+
150
+ context "for ActiveModel classes" do
151
+ it "infers the decorator from the model name" do
152
+ Namespaced::Product.stub(:model_name).and_return("Namespaced::Other")
153
+
154
+ expect(Namespaced::Product.decorator_class).to be Namespaced::OtherDecorator
155
+ end
156
+ end
157
+ end
158
+
159
+ context "when the decorator can't be inferred" do
160
+ it "throws an UninferrableDecoratorError" do
161
+ expect{Model.decorator_class}.to raise_error UninferrableDecoratorError
162
+ end
163
+ end
164
+ end
165
+
166
+ end
167
+ end
@@ -0,0 +1,145 @@
1
+ require 'spec_helper'
2
+
3
+ module Draper
4
+ describe DecoratedAssociation do
5
+
6
+ describe "#initialize" do
7
+ describe "options validation" do
8
+ it "does not raise error on valid options" do
9
+ valid_options = {with: Decorator, scope: :foo, context: {}}
10
+ expect{DecoratedAssociation.new(Decorator.new(Model.new), :association, valid_options)}.not_to raise_error
11
+ end
12
+
13
+ it "raises error on invalid options" do
14
+ expect{DecoratedAssociation.new(Decorator.new(Model.new), :association, foo: "bar")}.to raise_error ArgumentError, /Unknown key/
15
+ end
16
+ end
17
+ end
18
+
19
+ describe "#call" do
20
+ let(:context) { {some: "context"} }
21
+ let(:options) { {} }
22
+
23
+ let(:decorated_association) do
24
+ owner = double(context: nil, source: double(association: associated))
25
+
26
+ DecoratedAssociation.new(owner, :association, options).tap do |decorated_association|
27
+ decorated_association.stub context: context
28
+ end
29
+ end
30
+
31
+ context "for a singular association" do
32
+ let(:associated) { Model.new }
33
+
34
+ context "when :with option was given" do
35
+ let(:options) { {with: Decorator} }
36
+
37
+ it "uses the specified decorator" do
38
+ Decorator.should_receive(:decorate).with(associated, context: context).and_return(:decorated)
39
+ expect(decorated_association.call).to be :decorated
40
+ end
41
+ end
42
+
43
+ context "when :with option was not given" do
44
+ it "infers the decorator" do
45
+ associated.stub decorator_class: OtherDecorator
46
+
47
+ OtherDecorator.should_receive(:decorate).with(associated, context: context).and_return(:decorated)
48
+ expect(decorated_association.call).to be :decorated
49
+ end
50
+ end
51
+ end
52
+
53
+ context "for a collection association" do
54
+ let(:associated) { [] }
55
+
56
+ context "when :with option is a collection decorator" do
57
+ let(:options) { {with: ProductsDecorator} }
58
+
59
+ it "uses the specified decorator" do
60
+ ProductsDecorator.should_receive(:decorate).with(associated, context: context).and_return(:decorated_collection)
61
+ expect(decorated_association.call).to be :decorated_collection
62
+ end
63
+ end
64
+
65
+ context "when :with option is a singular decorator" do
66
+ let(:options) { {with: ProductDecorator} }
67
+
68
+ it "uses a CollectionDecorator of the specified decorator" do
69
+ ProductDecorator.should_receive(:decorate_collection).with(associated, context: context).and_return(:decorated_collection)
70
+ expect(decorated_association.call).to be :decorated_collection
71
+ end
72
+ end
73
+
74
+ context "when :with option was not given" do
75
+ context "when the collection itself is decoratable" do
76
+ before { associated.stub decorator_class: ProductsDecorator }
77
+
78
+ it "infers the decorator" do
79
+ ProductsDecorator.should_receive(:decorate).with(associated, context: context).and_return(:decorated_collection)
80
+ expect(decorated_association.call).to be :decorated_collection
81
+ end
82
+ end
83
+
84
+ context "when the collection is not decoratable" do
85
+ it "uses a CollectionDecorator of inferred decorators" do
86
+ CollectionDecorator.should_receive(:decorate).with(associated, context: context).and_return(:decorated_collection)
87
+ expect(decorated_association.call).to be :decorated_collection
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ context "with a scope" do
94
+ let(:options) { {scope: :foo} }
95
+ let(:associated) { double(foo: scoped) }
96
+ let(:scoped) { Product.new }
97
+
98
+ it "applies the scope before decoration" do
99
+ expect(decorated_association.call.source).to be scoped
100
+ end
101
+ end
102
+ end
103
+
104
+ describe "#context" do
105
+ let(:owner_context) { {some: "context"} }
106
+ let(:options) { {} }
107
+ let(:decorated_association) do
108
+ owner = double(context: owner_context)
109
+ DecoratedAssociation.new(owner, :association, options)
110
+ end
111
+
112
+ context "when :context option was given" do
113
+ let(:options) { {context: context} }
114
+
115
+ context "and is callable" do
116
+ let(:context) { ->(*){ :dynamic_context } }
117
+
118
+ it "calls it with the owner's context" do
119
+ context.should_receive(:call).with(owner_context)
120
+ decorated_association.context
121
+ end
122
+
123
+ it "returns the lambda's return value" do
124
+ expect(decorated_association.context).to be :dynamic_context
125
+ end
126
+ end
127
+
128
+ context "and is not callable" do
129
+ let(:context) { {other: "context"} }
130
+
131
+ it "returns the specified value" do
132
+ expect(decorated_association.context).to be context
133
+ end
134
+ end
135
+ end
136
+
137
+ context "when :context option was not given" do
138
+ it "returns the owner's context" do
139
+ expect(decorated_association.context).to be owner_context
140
+ end
141
+ end
142
+ end
143
+
144
+ end
145
+ end