draper_new 3.0.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 (144) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +15 -0
  5. data/.yardopts +1 -0
  6. data/CHANGELOG.md +230 -0
  7. data/CONTRIBUTING.md +20 -0
  8. data/Gemfile +16 -0
  9. data/Guardfile +26 -0
  10. data/LICENSE +7 -0
  11. data/README.md +587 -0
  12. data/Rakefile +69 -0
  13. data/draper_new.gemspec +31 -0
  14. data/gemfiles/4.0.gemfile +3 -0
  15. data/gemfiles/4.1.gemfile +3 -0
  16. data/gemfiles/4.2.6.gemfile +3 -0
  17. data/gemfiles/4.2.gemfile +3 -0
  18. data/lib/draper.rb +63 -0
  19. data/lib/draper/automatic_delegation.rb +56 -0
  20. data/lib/draper/collection_decorator.rb +100 -0
  21. data/lib/draper/decoratable.rb +96 -0
  22. data/lib/draper/decoratable/equality.rb +26 -0
  23. data/lib/draper/decorated_association.rb +35 -0
  24. data/lib/draper/decorates_assigned.rb +44 -0
  25. data/lib/draper/decorator.rb +293 -0
  26. data/lib/draper/delegation.rb +13 -0
  27. data/lib/draper/factory.rb +91 -0
  28. data/lib/draper/finders.rb +37 -0
  29. data/lib/draper/helper_proxy.rb +44 -0
  30. data/lib/draper/helper_support.rb +5 -0
  31. data/lib/draper/lazy_helpers.rb +15 -0
  32. data/lib/draper/railtie.rb +70 -0
  33. data/lib/draper/tasks/test.rake +22 -0
  34. data/lib/draper/test/devise_helper.rb +30 -0
  35. data/lib/draper/test/minitest_integration.rb +6 -0
  36. data/lib/draper/test/rspec_integration.rb +20 -0
  37. data/lib/draper/test_case.rb +42 -0
  38. data/lib/draper/undecorate.rb +9 -0
  39. data/lib/draper/version.rb +3 -0
  40. data/lib/draper/view_context.rb +104 -0
  41. data/lib/draper/view_context/build_strategy.rb +48 -0
  42. data/lib/draper/view_helpers.rb +37 -0
  43. data/lib/generators/controller_override.rb +17 -0
  44. data/lib/generators/mini_test/decorator_generator.rb +20 -0
  45. data/lib/generators/mini_test/templates/decorator_spec.rb +4 -0
  46. data/lib/generators/mini_test/templates/decorator_test.rb +4 -0
  47. data/lib/generators/rails/decorator_generator.rb +36 -0
  48. data/lib/generators/rails/templates/decorator.rb +19 -0
  49. data/lib/generators/rspec/decorator_generator.rb +9 -0
  50. data/lib/generators/rspec/templates/decorator_spec.rb +4 -0
  51. data/lib/generators/test_unit/decorator_generator.rb +9 -0
  52. data/lib/generators/test_unit/templates/decorator_test.rb +4 -0
  53. data/spec/draper/collection_decorator_spec.rb +307 -0
  54. data/spec/draper/decoratable/equality_spec.rb +10 -0
  55. data/spec/draper/decoratable_spec.rb +202 -0
  56. data/spec/draper/decorated_association_spec.rb +84 -0
  57. data/spec/draper/decorates_assigned_spec.rb +71 -0
  58. data/spec/draper/decorator_spec.rb +816 -0
  59. data/spec/draper/factory_spec.rb +251 -0
  60. data/spec/draper/finders_spec.rb +166 -0
  61. data/spec/draper/helper_proxy_spec.rb +61 -0
  62. data/spec/draper/lazy_helpers_spec.rb +21 -0
  63. data/spec/draper/undecorate_spec.rb +19 -0
  64. data/spec/draper/view_context/build_strategy_spec.rb +116 -0
  65. data/spec/draper/view_context_spec.rb +154 -0
  66. data/spec/draper/view_helpers_spec.rb +8 -0
  67. data/spec/dummy/.rspec +2 -0
  68. data/spec/dummy/Rakefile +7 -0
  69. data/spec/dummy/app/controllers/application_controller.rb +4 -0
  70. data/spec/dummy/app/controllers/localized_urls.rb +5 -0
  71. data/spec/dummy/app/controllers/posts_controller.rb +20 -0
  72. data/spec/dummy/app/decorators/mongoid_post_decorator.rb +4 -0
  73. data/spec/dummy/app/decorators/post_decorator.rb +60 -0
  74. data/spec/dummy/app/helpers/application_helper.rb +5 -0
  75. data/spec/dummy/app/mailers/application_mailer.rb +3 -0
  76. data/spec/dummy/app/mailers/post_mailer.rb +19 -0
  77. data/spec/dummy/app/models/admin.rb +5 -0
  78. data/spec/dummy/app/models/mongoid_post.rb +5 -0
  79. data/spec/dummy/app/models/post.rb +3 -0
  80. data/spec/dummy/app/models/user.rb +5 -0
  81. data/spec/dummy/app/views/layouts/application.html.erb +11 -0
  82. data/spec/dummy/app/views/post_mailer/decorated_email.html.erb +1 -0
  83. data/spec/dummy/app/views/posts/_post.html.erb +40 -0
  84. data/spec/dummy/app/views/posts/show.html.erb +1 -0
  85. data/spec/dummy/bin/rails +4 -0
  86. data/spec/dummy/config.ru +4 -0
  87. data/spec/dummy/config/application.rb +71 -0
  88. data/spec/dummy/config/boot.rb +5 -0
  89. data/spec/dummy/config/database.yml +25 -0
  90. data/spec/dummy/config/environment.rb +5 -0
  91. data/spec/dummy/config/environments/development.rb +33 -0
  92. data/spec/dummy/config/environments/production.rb +57 -0
  93. data/spec/dummy/config/environments/test.rb +31 -0
  94. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  95. data/spec/dummy/config/initializers/inflections.rb +15 -0
  96. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  97. data/spec/dummy/config/initializers/secret_token.rb +8 -0
  98. data/spec/dummy/config/initializers/session_store.rb +8 -0
  99. data/spec/dummy/config/locales/en.yml +5 -0
  100. data/spec/dummy/config/mongoid.yml +79 -0
  101. data/spec/dummy/config/routes.rb +9 -0
  102. data/spec/dummy/db/migrate/20121019115657_create_posts.rb +8 -0
  103. data/spec/dummy/db/schema.rb +21 -0
  104. data/spec/dummy/db/seeds.rb +2 -0
  105. data/spec/dummy/fast_spec/post_decorator_spec.rb +37 -0
  106. data/spec/dummy/lib/tasks/test.rake +16 -0
  107. data/spec/dummy/public/404.html +26 -0
  108. data/spec/dummy/public/422.html +26 -0
  109. data/spec/dummy/public/500.html +25 -0
  110. data/spec/dummy/public/favicon.ico +0 -0
  111. data/spec/dummy/script/rails +6 -0
  112. data/spec/dummy/spec/decorators/active_model_serializers_spec.rb +16 -0
  113. data/spec/dummy/spec/decorators/devise_spec.rb +64 -0
  114. data/spec/dummy/spec/decorators/helpers_spec.rb +21 -0
  115. data/spec/dummy/spec/decorators/post_decorator_spec.rb +66 -0
  116. data/spec/dummy/spec/decorators/spec_type_spec.rb +7 -0
  117. data/spec/dummy/spec/decorators/view_context_spec.rb +22 -0
  118. data/spec/dummy/spec/mailers/post_mailer_spec.rb +33 -0
  119. data/spec/dummy/spec/models/mongoid_post_spec.rb +8 -0
  120. data/spec/dummy/spec/models/post_spec.rb +6 -0
  121. data/spec/dummy/spec/shared_examples/decoratable.rb +24 -0
  122. data/spec/dummy/spec/spec_helper.rb +8 -0
  123. data/spec/dummy/test/decorators/minitest/devise_test.rb +64 -0
  124. data/spec/dummy/test/decorators/minitest/helpers_test.rb +21 -0
  125. data/spec/dummy/test/decorators/minitest/spec_type_test.rb +52 -0
  126. data/spec/dummy/test/decorators/minitest/view_context_test.rb +24 -0
  127. data/spec/dummy/test/decorators/test_unit/devise_test.rb +64 -0
  128. data/spec/dummy/test/decorators/test_unit/helpers_test.rb +21 -0
  129. data/spec/dummy/test/decorators/test_unit/view_context_test.rb +24 -0
  130. data/spec/dummy/test/minitest_helper.rb +2 -0
  131. data/spec/dummy/test/test_helper.rb +3 -0
  132. data/spec/generators/controller/controller_generator_spec.rb +22 -0
  133. data/spec/generators/decorator/decorator_generator_spec.rb +92 -0
  134. data/spec/integration/integration_spec.rb +66 -0
  135. data/spec/performance/active_record.rb +4 -0
  136. data/spec/performance/benchmark.rb +55 -0
  137. data/spec/performance/decorators.rb +45 -0
  138. data/spec/performance/models.rb +20 -0
  139. data/spec/spec_helper.rb +41 -0
  140. data/spec/support/dummy_app.rb +85 -0
  141. data/spec/support/matchers/have_text.rb +50 -0
  142. data/spec/support/shared_examples/decoratable_equality.rb +40 -0
  143. data/spec/support/shared_examples/view_helpers.rb +39 -0
  144. metadata +420 -0
@@ -0,0 +1,35 @@
1
+ module Draper
2
+ # @private
3
+ class DecoratedAssociation
4
+
5
+ def initialize(owner, association, options)
6
+ options.assert_valid_keys(:with, :scope, :context)
7
+
8
+ @owner = owner
9
+ @association = association
10
+
11
+ @scope = options[:scope]
12
+
13
+ decorator_class = options[:with]
14
+ context = options.fetch(:context, ->(context){ context })
15
+ @factory = Draper::Factory.new(with: decorator_class, context: context)
16
+ end
17
+
18
+ def call
19
+ decorate unless defined?(@decorated)
20
+ @decorated
21
+ end
22
+
23
+ private
24
+
25
+ attr_reader :factory, :owner, :association, :scope
26
+
27
+ def decorate
28
+ associated = owner.object.send(association)
29
+ associated = associated.send(scope) if scope
30
+
31
+ @decorated = factory.decorate(associated, context_args: owner.context)
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,44 @@
1
+ module Draper
2
+ module DecoratesAssigned
3
+ # @overload decorates_assigned(*variables, options = {})
4
+ # Defines a helper method to access decorated instance variables.
5
+ #
6
+ # @example
7
+ # # app/controllers/articles_controller.rb
8
+ # class ArticlesController < ApplicationController
9
+ # decorates_assigned :article
10
+ #
11
+ # def show
12
+ # @article = Article.find(params[:id])
13
+ # end
14
+ # end
15
+ #
16
+ # # app/views/articles/show.html.erb
17
+ # <%= article.decorated_title %>
18
+ #
19
+ # @param [Symbols*] variables
20
+ # names of the instance variables to decorate (without the `@`).
21
+ # @param [Hash] options
22
+ # @option options [Decorator, CollectionDecorator] :with (nil)
23
+ # decorator class to use. If nil, it is inferred from the instance
24
+ # variable.
25
+ # @option options [Hash, #call] :context
26
+ # extra data to be stored in the decorator. If a Proc is given, it will
27
+ # be passed the controller and should return a new context hash.
28
+ def decorates_assigned(*variables)
29
+ factory = Draper::Factory.new(variables.extract_options!)
30
+
31
+ variables.each do |variable|
32
+ undecorated = "@#{variable}"
33
+ decorated = "@decorated_#{variable}"
34
+
35
+ define_method variable do
36
+ return instance_variable_get(decorated) if instance_variable_defined?(decorated)
37
+ instance_variable_set decorated, factory.decorate(instance_variable_get(undecorated), context_args: self)
38
+ end
39
+
40
+ helper_method variable
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,293 @@
1
+ module Draper
2
+ class Decorator
3
+ include Draper::ViewHelpers
4
+ extend Draper::Delegation
5
+
6
+ include ActiveModel::Serialization
7
+ include ActiveModel::Serializers::JSON
8
+ include ActiveModel::Serializers::Xml
9
+
10
+ # @return the object being decorated.
11
+ attr_reader :object
12
+ alias_method :model, :object
13
+ alias_method :source, :object # TODO: deprecate this
14
+ alias_method :to_source, :object # TODO: deprecate this
15
+
16
+ # @return [Hash] extra data to be used in user-defined methods.
17
+ attr_accessor :context
18
+
19
+ # Wraps an object in a new instance of the decorator.
20
+ #
21
+ # Decorators may be applied to other decorators. However, applying a
22
+ # decorator to an instance of itself will create a decorator with the same
23
+ # source as the original, rather than redecorating the other instance.
24
+ #
25
+ # @param [Object] object
26
+ # object to decorate.
27
+ # @option options [Hash] :context ({})
28
+ # extra data to be stored in the decorator and used in user-defined
29
+ # methods.
30
+ def initialize(object, options = {})
31
+ options.assert_valid_keys(:context)
32
+ @object = object
33
+ @context = options.fetch(:context, {})
34
+ handle_multiple_decoration(options) if object.instance_of?(self.class)
35
+ end
36
+
37
+ class << self
38
+ alias_method :decorate, :new
39
+ end
40
+
41
+ # Automatically delegates instance methods to the source object. Class
42
+ # methods will be delegated to the {object_class}, if it is set.
43
+ #
44
+ # @return [void]
45
+ def self.delegate_all
46
+ include Draper::AutomaticDelegation
47
+ end
48
+
49
+ # Sets the source class corresponding to the decorator class.
50
+ #
51
+ # @note This is only necessary if you wish to proxy class methods to the
52
+ # source (including when using {decorates_finders}), and the source class
53
+ # cannot be inferred from the decorator class (e.g. `ProductDecorator`
54
+ # maps to `Product`).
55
+ # @param [String, Symbol, Class] object_class
56
+ # source class (or class name) that corresponds to this decorator.
57
+ # @return [void]
58
+ def self.decorates(object_class)
59
+ @object_class = object_class.to_s.camelize.constantize
60
+ alias_object_to_object_class_name
61
+ end
62
+
63
+ # Returns the source class corresponding to the decorator class, as set by
64
+ # {decorates}, or as inferred from the decorator class name (e.g.
65
+ # `ProductDecorator` maps to `Product`).
66
+ #
67
+ # @return [Class] the source class that corresponds to this decorator.
68
+ def self.object_class
69
+ @object_class ||= inferred_object_class
70
+ end
71
+
72
+ # Checks whether this decorator class has a corresponding {object_class}.
73
+ def self.object_class?
74
+ object_class
75
+ rescue Draper::UninferrableSourceError
76
+ false
77
+ end
78
+
79
+ class << self # TODO deprecate this
80
+ alias_method :source_class, :object_class
81
+ alias_method :source_class?, :object_class?
82
+ end
83
+
84
+ # Automatically decorates ActiveRecord finder methods, so that you can use
85
+ # `ProductDecorator.find(id)` instead of
86
+ # `ProductDecorator.decorate(Product.find(id))`.
87
+ #
88
+ # Finder methods are applied to the {object_class}.
89
+ #
90
+ # @return [void]
91
+ def self.decorates_finders
92
+ extend Draper::Finders
93
+ end
94
+
95
+ # Automatically decorate an association.
96
+ #
97
+ # @param [Symbol] association
98
+ # name of the association to decorate (e.g. `:products`).
99
+ # @option options [Class] :with
100
+ # the decorator to apply to the association.
101
+ # @option options [Symbol] :scope
102
+ # a scope to apply when fetching the association.
103
+ # @option options [Hash, #call] :context
104
+ # extra data to be stored in the associated decorator. If omitted, the
105
+ # associated decorator's context will be the same as the parent
106
+ # decorator's. If a Proc is given, it will be called with the parent's
107
+ # context and should return a new context hash for the association.
108
+ # @return [void]
109
+ def self.decorates_association(association, options = {})
110
+ options.assert_valid_keys(:with, :scope, :context)
111
+ define_method(association) do
112
+ decorated_associations[association] ||= Draper::DecoratedAssociation.new(self, association, options)
113
+ decorated_associations[association].call
114
+ end
115
+ end
116
+
117
+ # @overload decorates_associations(*associations, options = {})
118
+ # Automatically decorate multiple associations.
119
+ # @param [Symbols*] associations
120
+ # names of the associations to decorate.
121
+ # @param [Hash] options
122
+ # see {decorates_association}.
123
+ # @return [void]
124
+ def self.decorates_associations(*associations)
125
+ options = associations.extract_options!
126
+ associations.each do |association|
127
+ decorates_association(association, options)
128
+ end
129
+ end
130
+
131
+ # Decorates a collection of objects. The class of the collection decorator
132
+ # is inferred from the decorator class if possible (e.g. `ProductDecorator`
133
+ # maps to `ProductsDecorator`), but otherwise defaults to
134
+ # {Draper::CollectionDecorator}.
135
+ #
136
+ # @param [Object] object
137
+ # collection to decorate.
138
+ # @option options [Class, nil] :with (self)
139
+ # the decorator class used to decorate each item. When `nil`, it is
140
+ # inferred from each item.
141
+ # @option options [Hash] :context
142
+ # extra data to be stored in the collection decorator.
143
+ def self.decorate_collection(object, options = {})
144
+ options.assert_valid_keys(:with, :context)
145
+ collection_decorator_class.new(object, options.reverse_merge(with: self))
146
+ end
147
+
148
+ # @return [Array<Class>] the list of decorators that have been applied to
149
+ # the object.
150
+ def applied_decorators
151
+ chain = object.respond_to?(:applied_decorators) ? object.applied_decorators : []
152
+ chain << self.class
153
+ end
154
+
155
+ # Checks if a given decorator has been applied to the object.
156
+ #
157
+ # @param [Class] decorator_class
158
+ def decorated_with?(decorator_class)
159
+ applied_decorators.include?(decorator_class)
160
+ end
161
+
162
+ # Checks if this object is decorated.
163
+ #
164
+ # @return [true]
165
+ def decorated?
166
+ true
167
+ end
168
+
169
+ # Compares the source object with a possibly-decorated object.
170
+ #
171
+ # @return [Boolean]
172
+ def ==(other)
173
+ Draper::Decoratable::Equality.test(object, other)
174
+ end
175
+
176
+ # Delegates equality to :== as expected
177
+ #
178
+ # @return [Boolean]
179
+ def eql?(other)
180
+ self == other
181
+ end
182
+
183
+ # Returns a unique hash for a decorated object based on
184
+ # the decorator class and the object being decorated.
185
+ #
186
+ # @return [Fixnum]
187
+ def hash
188
+ self.class.hash ^ object.hash
189
+ end
190
+
191
+ # Checks if `self.kind_of?(klass)` or `object.kind_of?(klass)`
192
+ #
193
+ # @param [Class] klass
194
+ def kind_of?(klass)
195
+ super || object.kind_of?(klass)
196
+ end
197
+ alias_method :is_a?, :kind_of?
198
+
199
+ # Checks if `self.instance_of?(klass)` or `object.instance_of?(klass)`
200
+ #
201
+ # @param [Class] klass
202
+ def instance_of?(klass)
203
+ super || object.instance_of?(klass)
204
+ end
205
+
206
+ if RUBY_VERSION < "2.0"
207
+ # nasty hack to stop 1.9.x using the delegated `to_s` in `inspect`
208
+ alias_method :_to_s, :to_s
209
+
210
+ def inspect
211
+ ivars = instance_variables.map do |name|
212
+ "#{name}=#{instance_variable_get(name).inspect}"
213
+ end
214
+ _to_s.insert(-2, " #{ivars.join(", ")}")
215
+ end
216
+ end
217
+
218
+ delegate :to_s
219
+
220
+ # In case object is nil
221
+ delegate :present?, :blank?
222
+
223
+ # ActiveModel compatibility
224
+ # @private
225
+ def to_model
226
+ self
227
+ end
228
+
229
+ # @return [Hash] the object's attributes, sliced to only include those
230
+ # implemented by the decorator.
231
+ def attributes
232
+ object.attributes.select {|attribute, _| respond_to?(attribute) }
233
+ end
234
+
235
+ # ActiveModel compatibility
236
+ delegate :to_param, :to_partial_path
237
+
238
+ # ActiveModel compatibility
239
+ singleton_class.delegate :model_name, to: :object_class
240
+
241
+ # @return [Class] the class created by {decorate_collection}.
242
+ def self.collection_decorator_class
243
+ name = collection_decorator_name
244
+ name.constantize
245
+ rescue NameError => error
246
+ raise if name && !error.missing_name?(name)
247
+ Draper::CollectionDecorator
248
+ end
249
+
250
+ private
251
+
252
+ def self.inherited(subclass)
253
+ subclass.alias_object_to_object_class_name
254
+ super
255
+ end
256
+
257
+ def self.alias_object_to_object_class_name
258
+ alias_method object_class.name.underscore, :object if object_class?
259
+ end
260
+
261
+ def self.object_class_name
262
+ raise NameError if name.nil? || name.demodulize !~ /.+Decorator$/
263
+ name.chomp("Decorator")
264
+ end
265
+
266
+ def self.inferred_object_class
267
+ name = object_class_name
268
+ name.constantize
269
+ rescue NameError => error
270
+ raise if name && !error.missing_name?(name)
271
+ raise Draper::UninferrableSourceError.new(self)
272
+ end
273
+
274
+ def self.collection_decorator_name
275
+ plural = object_class_name.pluralize
276
+ raise NameError if plural == object_class_name
277
+ "#{plural}Decorator"
278
+ end
279
+
280
+ def handle_multiple_decoration(options)
281
+ if object.applied_decorators.last == self.class
282
+ @context = object.context unless options.has_key?(:context)
283
+ @object = object.object
284
+ else
285
+ warn "Reapplying #{self.class} decorator to target that is already decorated with it. Call stack:\n#{caller(1).join("\n")}"
286
+ end
287
+ end
288
+
289
+ def decorated_associations
290
+ @decorated_associations ||= {}
291
+ end
292
+ end
293
+ end
@@ -0,0 +1,13 @@
1
+ module Draper
2
+ module Delegation
3
+ # @overload delegate(*methods, options = {})
4
+ # Overrides {http://api.rubyonrails.org/classes/Module.html#method-i-delegate Module.delegate}
5
+ # to make `:object` the default delegation target.
6
+ #
7
+ # @return [void]
8
+ def delegate(*methods)
9
+ options = methods.extract_options!
10
+ super *methods, options.reverse_merge(to: :object)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,91 @@
1
+ module Draper
2
+ class Factory
3
+ # Creates a decorator factory.
4
+ #
5
+ # @option options [Decorator, CollectionDecorator] :with (nil)
6
+ # decorator class to use. If nil, it is inferred from the object
7
+ # passed to {#decorate}.
8
+ # @option options [Hash, #call] context
9
+ # extra data to be stored in created decorators. If a proc is given, it
10
+ # will be called each time {#decorate} is called and its return value
11
+ # will be used as the context.
12
+ def initialize(options = {})
13
+ options.assert_valid_keys(:with, :context)
14
+ @decorator_class = options.delete(:with)
15
+ @default_options = options
16
+ end
17
+
18
+ # Decorates an object, inferring whether to create a singular or collection
19
+ # decorator from the type of object passed.
20
+ #
21
+ # @param [Object] object
22
+ # object to decorate.
23
+ # @option options [Hash] context
24
+ # extra data to be stored in the decorator. Overrides any context passed
25
+ # to the constructor.
26
+ # @option options [Object, Array] context_args (nil)
27
+ # argument(s) to be passed to the context proc.
28
+ # @return [Decorator, CollectionDecorator] the decorated object.
29
+ def decorate(object, options = {})
30
+ return nil if object.nil?
31
+ Worker.new(decorator_class, object).call(options.reverse_merge(default_options))
32
+ end
33
+
34
+ private
35
+
36
+ attr_reader :decorator_class, :default_options
37
+
38
+ # @private
39
+ class Worker
40
+ def initialize(decorator_class, object)
41
+ @decorator_class = decorator_class
42
+ @object = object
43
+ end
44
+
45
+ def call(options)
46
+ update_context options
47
+ decorator.call(object, options)
48
+ end
49
+
50
+ def decorator
51
+ return decorator_method(decorator_class) if decorator_class
52
+ return object_decorator if decoratable?
53
+ return decorator_method(Draper::CollectionDecorator) if collection?
54
+ raise Draper::UninferrableDecoratorError.new(object.class)
55
+ end
56
+
57
+ private
58
+
59
+ attr_reader :decorator_class, :object
60
+
61
+ def object_decorator
62
+ if collection?
63
+ ->(object, options) { object.decorator_class.decorate_collection(object, options.reverse_merge(with: nil))}
64
+ else
65
+ ->(object, options) { object.decorate(options) }
66
+ end
67
+ end
68
+
69
+ def decorator_method(klass)
70
+ if collection? && klass.respond_to?(:decorate_collection)
71
+ klass.method(:decorate_collection)
72
+ else
73
+ klass.method(:decorate)
74
+ end
75
+ end
76
+
77
+ def collection?
78
+ object.respond_to?(:first) && !object.is_a?(Struct)
79
+ end
80
+
81
+ def decoratable?
82
+ object.respond_to?(:decorate)
83
+ end
84
+
85
+ def update_context(options)
86
+ args = options.delete(:context_args)
87
+ options[:context] = options[:context].call(*Array.wrap(args)) if options[:context].respond_to?(:call)
88
+ end
89
+ end
90
+ end
91
+ end