draper 1.2.0 → 1.2.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +216 -56
- data/lib/draper/automatic_delegation.rb +3 -3
- data/lib/draper/collection_decorator.rb +14 -10
- data/lib/draper/decoratable.rb +1 -1
- data/lib/draper/decoratable/equality.rb +1 -1
- data/lib/draper/decorated_association.rb +1 -1
- data/lib/draper/decorator.rb +46 -40
- data/lib/draper/delegation.rb +2 -2
- data/lib/draper/factory.rb +18 -14
- data/lib/draper/finders.rb +5 -5
- data/lib/draper/railtie.rb +1 -1
- data/lib/draper/version.rb +1 -1
- data/lib/generators/decorator/decorator_generator.rb +2 -2
- data/lib/generators/decorator/templates/decorator.rb +1 -1
- data/spec/draper/collection_decorator_spec.rb +41 -22
- data/spec/draper/decoratable_spec.rb +4 -4
- data/spec/draper/decorated_association_spec.rb +5 -5
- data/spec/draper/decorates_assigned_spec.rb +3 -3
- data/spec/draper/decorator_spec.rb +130 -91
- data/spec/draper/factory_spec.rb +30 -28
- data/spec/draper/finders_spec.rb +4 -4
- data/spec/dummy/app/decorators/post_decorator.rb +2 -2
- data/spec/dummy/fast_spec/post_decorator_spec.rb +2 -2
- data/spec/dummy/spec/decorators/post_decorator_spec.rb +7 -7
- data/spec/dummy/test/minitest_helper.rb +1 -3
- data/spec/generators/decorator/decorator_generator_spec.rb +1 -0
- data/spec/support/shared_examples/decoratable_equality.rb +8 -8
- metadata +2 -2
@@ -18,7 +18,7 @@ module Draper
|
|
18
18
|
|
19
19
|
# @private
|
20
20
|
def delegatable?(method)
|
21
|
-
|
21
|
+
object.respond_to?(method)
|
22
22
|
end
|
23
23
|
|
24
24
|
module ClassMethods
|
@@ -26,7 +26,7 @@ module Draper
|
|
26
26
|
def method_missing(method, *args, &block)
|
27
27
|
return super unless delegatable?(method)
|
28
28
|
|
29
|
-
|
29
|
+
object_class.send(method, *args, &block)
|
30
30
|
end
|
31
31
|
|
32
32
|
# Checks if the decorator responds to a class method, or is able to proxy
|
@@ -37,7 +37,7 @@ module Draper
|
|
37
37
|
|
38
38
|
# @private
|
39
39
|
def delegatable?(method)
|
40
|
-
|
40
|
+
object_class? && object_class.respond_to?(method)
|
41
41
|
end
|
42
42
|
|
43
43
|
# @private
|
@@ -15,19 +15,17 @@ module Draper
|
|
15
15
|
array_methods = Array.instance_methods - Object.instance_methods
|
16
16
|
delegate :==, :as_json, *array_methods, to: :decorated_collection
|
17
17
|
|
18
|
-
# @param [Enumerable]
|
18
|
+
# @param [Enumerable] object
|
19
19
|
# collection to decorate.
|
20
20
|
# @option options [Class, nil] :with (nil)
|
21
|
-
# the decorator class used to decorate each item. When `nil`,
|
22
|
-
# inferred from the collection decorator class if possible (e.g.
|
23
|
-
# `ProductsDecorator` maps to `ProductDecorator`), otherwise each item's
|
21
|
+
# the decorator class used to decorate each item. When `nil`, each item's
|
24
22
|
# {Decoratable#decorate decorate} method will be used.
|
25
23
|
# @option options [Hash] :context ({})
|
26
24
|
# extra data to be stored in the collection decorator and used in
|
27
25
|
# user-defined methods, and passed to each item's decorator.
|
28
|
-
def initialize(
|
26
|
+
def initialize(object, options = {})
|
29
27
|
options.assert_valid_keys(:with, :context)
|
30
|
-
@
|
28
|
+
@object = object
|
31
29
|
@decorator_class = options[:with]
|
32
30
|
@context = options.fetch(:context, {})
|
33
31
|
end
|
@@ -38,7 +36,7 @@ module Draper
|
|
38
36
|
|
39
37
|
# @return [Array] the decorated items.
|
40
38
|
def decorated_collection
|
41
|
-
@decorated_collection ||=
|
39
|
+
@decorated_collection ||= object.map{|item| decorate_item(item)}
|
42
40
|
end
|
43
41
|
|
44
42
|
# Delegated to the decorated collection when using the block form
|
@@ -48,12 +46,13 @@ module Draper
|
|
48
46
|
if block_given?
|
49
47
|
decorated_collection.find(*args, &block)
|
50
48
|
else
|
51
|
-
|
49
|
+
ActiveSupport::Deprecation.warn("Using ActiveRecord's `find` on a CollectionDecorator is deprecated. Call `find` on a model, and then decorate the result", caller)
|
50
|
+
decorate_item(object.find(*args))
|
52
51
|
end
|
53
52
|
end
|
54
53
|
|
55
54
|
def to_s
|
56
|
-
"#<#{self.class.name} of #{decorator_class || "inferred decorators"} for #{
|
55
|
+
"#<#{self.class.name} of #{decorator_class || "inferred decorators"} for #{object.inspect}>"
|
57
56
|
end
|
58
57
|
|
59
58
|
def context=(value)
|
@@ -73,10 +72,15 @@ module Draper
|
|
73
72
|
end
|
74
73
|
alias_method :is_a?, :kind_of?
|
75
74
|
|
75
|
+
def replace(other)
|
76
|
+
decorated_collection.replace(other)
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
76
80
|
protected
|
77
81
|
|
78
82
|
# @return the collection being decorated.
|
79
|
-
attr_reader :
|
83
|
+
attr_reader :object
|
80
84
|
|
81
85
|
# Decorates the given item.
|
82
86
|
def decorate_item(item)
|
data/lib/draper/decoratable.rb
CHANGED
@@ -19,7 +19,7 @@ module Draper
|
|
19
19
|
# @private
|
20
20
|
def self.test_for_decorator(object, other)
|
21
21
|
other.respond_to?(:decorated?) && other.decorated? &&
|
22
|
-
other.respond_to?(:
|
22
|
+
other.respond_to?(:object) && test(object, other.object)
|
23
23
|
end
|
24
24
|
end
|
25
25
|
end
|
@@ -25,7 +25,7 @@ module Draper
|
|
25
25
|
attr_reader :factory, :owner, :association, :scope
|
26
26
|
|
27
27
|
def decorate
|
28
|
-
associated = owner.
|
28
|
+
associated = owner.object.send(association)
|
29
29
|
associated = associated.send(scope) if scope
|
30
30
|
|
31
31
|
@decorated = factory.decorate(associated, context_args: owner.context)
|
data/lib/draper/decorator.rb
CHANGED
@@ -8,9 +8,10 @@ module Draper
|
|
8
8
|
include ActiveModel::Serializers::Xml
|
9
9
|
|
10
10
|
# @return the object being decorated.
|
11
|
-
attr_reader :
|
12
|
-
alias_method :model, :
|
13
|
-
alias_method :
|
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
|
14
15
|
|
15
16
|
# @return [Hash] extra data to be used in user-defined methods.
|
16
17
|
attr_accessor :context
|
@@ -21,16 +22,16 @@ module Draper
|
|
21
22
|
# decorator to an instance of itself will create a decorator with the same
|
22
23
|
# source as the original, rather than redecorating the other instance.
|
23
24
|
#
|
24
|
-
# @param [Object]
|
25
|
+
# @param [Object] object
|
25
26
|
# object to decorate.
|
26
27
|
# @option options [Hash] :context ({})
|
27
28
|
# extra data to be stored in the decorator and used in user-defined
|
28
29
|
# methods.
|
29
|
-
def initialize(
|
30
|
+
def initialize(object, options = {})
|
30
31
|
options.assert_valid_keys(:context)
|
31
|
-
@
|
32
|
+
@object = object
|
32
33
|
@context = options.fetch(:context, {})
|
33
|
-
handle_multiple_decoration(options) if
|
34
|
+
handle_multiple_decoration(options) if object.instance_of?(self.class)
|
34
35
|
end
|
35
36
|
|
36
37
|
class << self
|
@@ -38,7 +39,7 @@ module Draper
|
|
38
39
|
end
|
39
40
|
|
40
41
|
# Automatically delegates instance methods to the source object. Class
|
41
|
-
# methods will be delegated to the {
|
42
|
+
# methods will be delegated to the {object_class}, if it is set.
|
42
43
|
#
|
43
44
|
# @return [void]
|
44
45
|
def self.delegate_all
|
@@ -51,11 +52,11 @@ module Draper
|
|
51
52
|
# source (including when using {decorates_finders}), and the source class
|
52
53
|
# cannot be inferred from the decorator class (e.g. `ProductDecorator`
|
53
54
|
# maps to `Product`).
|
54
|
-
# @param [String, Symbol, Class]
|
55
|
+
# @param [String, Symbol, Class] object_class
|
55
56
|
# source class (or class name) that corresponds to this decorator.
|
56
57
|
# @return [void]
|
57
|
-
def self.decorates(
|
58
|
-
@
|
58
|
+
def self.decorates(object_class)
|
59
|
+
@object_class = object_class.to_s.camelize.constantize
|
59
60
|
end
|
60
61
|
|
61
62
|
# Returns the source class corresponding to the decorator class, as set by
|
@@ -63,22 +64,27 @@ module Draper
|
|
63
64
|
# `ProductDecorator` maps to `Product`).
|
64
65
|
#
|
65
66
|
# @return [Class] the source class that corresponds to this decorator.
|
66
|
-
def self.
|
67
|
-
@
|
67
|
+
def self.object_class
|
68
|
+
@object_class ||= inferred_object_class
|
68
69
|
end
|
69
70
|
|
70
|
-
# Checks whether this decorator class has a corresponding {
|
71
|
-
def self.
|
72
|
-
|
71
|
+
# Checks whether this decorator class has a corresponding {object_class}.
|
72
|
+
def self.object_class?
|
73
|
+
object_class
|
73
74
|
rescue Draper::UninferrableSourceError
|
74
75
|
false
|
75
76
|
end
|
76
77
|
|
78
|
+
class << self # TODO deprecate this
|
79
|
+
alias_method :source_class, :object_class
|
80
|
+
alias_method :source_class?, :object_class?
|
81
|
+
end
|
82
|
+
|
77
83
|
# Automatically decorates ActiveRecord finder methods, so that you can use
|
78
84
|
# `ProductDecorator.find(id)` instead of
|
79
85
|
# `ProductDecorator.decorate(Product.find(id))`.
|
80
86
|
#
|
81
|
-
# Finder methods are applied to the {
|
87
|
+
# Finder methods are applied to the {object_class}.
|
82
88
|
#
|
83
89
|
# @return [void]
|
84
90
|
def self.decorates_finders
|
@@ -126,22 +132,22 @@ module Draper
|
|
126
132
|
# maps to `ProductsDecorator`), but otherwise defaults to
|
127
133
|
# {Draper::CollectionDecorator}.
|
128
134
|
#
|
129
|
-
# @param [Object]
|
135
|
+
# @param [Object] object
|
130
136
|
# collection to decorate.
|
131
137
|
# @option options [Class, nil] :with (self)
|
132
138
|
# the decorator class used to decorate each item. When `nil`, it is
|
133
139
|
# inferred from each item.
|
134
140
|
# @option options [Hash] :context
|
135
141
|
# extra data to be stored in the collection decorator.
|
136
|
-
def self.decorate_collection(
|
142
|
+
def self.decorate_collection(object, options = {})
|
137
143
|
options.assert_valid_keys(:with, :context)
|
138
|
-
collection_decorator_class.new(
|
144
|
+
collection_decorator_class.new(object, options.reverse_merge(with: self))
|
139
145
|
end
|
140
146
|
|
141
147
|
# @return [Array<Class>] the list of decorators that have been applied to
|
142
148
|
# the object.
|
143
149
|
def applied_decorators
|
144
|
-
chain =
|
150
|
+
chain = object.respond_to?(:applied_decorators) ? object.applied_decorators : []
|
145
151
|
chain << self.class
|
146
152
|
end
|
147
153
|
|
@@ -159,30 +165,30 @@ module Draper
|
|
159
165
|
true
|
160
166
|
end
|
161
167
|
|
162
|
-
# Compares the source with a possibly-decorated object.
|
168
|
+
# Compares the source object with a possibly-decorated object.
|
163
169
|
#
|
164
170
|
# @return [Boolean]
|
165
171
|
def ==(other)
|
166
|
-
Draper::Decoratable::Equality.test(
|
172
|
+
Draper::Decoratable::Equality.test(object, other)
|
167
173
|
end
|
168
174
|
|
169
|
-
# Checks if `self.kind_of?(klass)` or `
|
175
|
+
# Checks if `self.kind_of?(klass)` or `object.kind_of?(klass)`
|
170
176
|
#
|
171
177
|
# @param [Class] klass
|
172
178
|
def kind_of?(klass)
|
173
|
-
super ||
|
179
|
+
super || object.kind_of?(klass)
|
174
180
|
end
|
175
181
|
alias_method :is_a?, :kind_of?
|
176
182
|
|
177
|
-
# Checks if `self.instance_of?(klass)` or `
|
183
|
+
# Checks if `self.instance_of?(klass)` or `object.instance_of?(klass)`
|
178
184
|
#
|
179
185
|
# @param [Class] klass
|
180
186
|
def instance_of?(klass)
|
181
|
-
super ||
|
187
|
+
super || object.instance_of?(klass)
|
182
188
|
end
|
183
189
|
|
184
|
-
# In case
|
185
|
-
delegate :present?
|
190
|
+
# In case object is nil
|
191
|
+
delegate :present?, :blank?
|
186
192
|
|
187
193
|
# ActiveModel compatibility
|
188
194
|
# @private
|
@@ -190,17 +196,17 @@ module Draper
|
|
190
196
|
self
|
191
197
|
end
|
192
198
|
|
193
|
-
# @return [Hash] the
|
199
|
+
# @return [Hash] the object's attributes, sliced to only include those
|
194
200
|
# implemented by the decorator.
|
195
201
|
def attributes
|
196
|
-
|
202
|
+
object.attributes.select {|attribute, _| respond_to?(attribute) }
|
197
203
|
end
|
198
204
|
|
199
205
|
# ActiveModel compatibility
|
200
206
|
delegate :to_param, :to_partial_path
|
201
207
|
|
202
208
|
# ActiveModel compatibility
|
203
|
-
singleton_class.delegate :model_name, to: :
|
209
|
+
singleton_class.delegate :model_name, to: :object_class
|
204
210
|
|
205
211
|
# @return [Class] the class created by {decorate_collection}.
|
206
212
|
def self.collection_decorator_class
|
@@ -213,13 +219,13 @@ module Draper
|
|
213
219
|
|
214
220
|
private
|
215
221
|
|
216
|
-
def self.
|
222
|
+
def self.object_class_name
|
217
223
|
raise NameError if name.nil? || name.demodulize !~ /.+Decorator$/
|
218
224
|
name.chomp("Decorator")
|
219
225
|
end
|
220
226
|
|
221
|
-
def self.
|
222
|
-
name =
|
227
|
+
def self.inferred_object_class
|
228
|
+
name = object_class_name
|
223
229
|
name.constantize
|
224
230
|
rescue NameError => error
|
225
231
|
raise if name && !error.missing_name?(name)
|
@@ -227,15 +233,15 @@ module Draper
|
|
227
233
|
end
|
228
234
|
|
229
235
|
def self.collection_decorator_name
|
230
|
-
plural =
|
231
|
-
raise NameError if plural ==
|
236
|
+
plural = object_class_name.pluralize
|
237
|
+
raise NameError if plural == object_class_name
|
232
238
|
"#{plural}Decorator"
|
233
239
|
end
|
234
240
|
|
235
241
|
def handle_multiple_decoration(options)
|
236
|
-
if
|
237
|
-
@context =
|
238
|
-
@
|
242
|
+
if object.applied_decorators.last == self.class
|
243
|
+
@context = object.context unless options.has_key?(:context)
|
244
|
+
@object = object.object
|
239
245
|
else
|
240
246
|
warn "Reapplying #{self.class} decorator to target that is already decorated with it. Call stack:\n#{caller(1).join("\n")}"
|
241
247
|
end
|
data/lib/draper/delegation.rb
CHANGED
@@ -2,12 +2,12 @@ module Draper
|
|
2
2
|
module Delegation
|
3
3
|
# @overload delegate(*methods, options = {})
|
4
4
|
# Overrides {http://api.rubyonrails.org/classes/Module.html#method-i-delegate Module.delegate}
|
5
|
-
# to make `:
|
5
|
+
# to make `:object` the default delegation target.
|
6
6
|
#
|
7
7
|
# @return [void]
|
8
8
|
def delegate(*methods)
|
9
9
|
options = methods.extract_options!
|
10
|
-
super *methods, options.reverse_merge(to: :
|
10
|
+
super *methods, options.reverse_merge(to: :object)
|
11
11
|
end
|
12
12
|
end
|
13
13
|
end
|
data/lib/draper/factory.rb
CHANGED
@@ -18,7 +18,7 @@ module Draper
|
|
18
18
|
# Decorates an object, inferring whether to create a singular or collection
|
19
19
|
# decorator from the type of object passed.
|
20
20
|
#
|
21
|
-
# @param [Object]
|
21
|
+
# @param [Object] object
|
22
22
|
# object to decorate.
|
23
23
|
# @option options [Hash] context
|
24
24
|
# extra data to be stored in the decorator. Overrides any context passed
|
@@ -26,9 +26,9 @@ module Draper
|
|
26
26
|
# @option options [Object, Array] context_args (nil)
|
27
27
|
# argument(s) to be passed to the context proc.
|
28
28
|
# @return [Decorator, CollectionDecorator] the decorated object.
|
29
|
-
def decorate(
|
30
|
-
return nil if
|
31
|
-
Worker.new(decorator_class,
|
29
|
+
def decorate(object, options = {})
|
30
|
+
return nil if object.nil?
|
31
|
+
Worker.new(decorator_class, object).call(options.reverse_merge(default_options))
|
32
32
|
end
|
33
33
|
|
34
34
|
private
|
@@ -37,29 +37,33 @@ module Draper
|
|
37
37
|
|
38
38
|
# @private
|
39
39
|
class Worker
|
40
|
-
def initialize(decorator_class,
|
40
|
+
def initialize(decorator_class, object)
|
41
41
|
@decorator_class = decorator_class
|
42
|
-
@
|
42
|
+
@object = object
|
43
43
|
end
|
44
44
|
|
45
45
|
def call(options)
|
46
46
|
update_context options
|
47
|
-
decorator.call(
|
47
|
+
decorator.call(object, options)
|
48
48
|
end
|
49
49
|
|
50
50
|
def decorator
|
51
51
|
return decorator_method(decorator_class) if decorator_class
|
52
|
-
return
|
52
|
+
return object_decorator if decoratable?
|
53
53
|
return decorator_method(Draper::CollectionDecorator) if collection?
|
54
|
-
raise Draper::UninferrableDecoratorError.new(
|
54
|
+
raise Draper::UninferrableDecoratorError.new(object.class)
|
55
55
|
end
|
56
56
|
|
57
57
|
private
|
58
58
|
|
59
|
-
attr_reader :decorator_class, :
|
59
|
+
attr_reader :decorator_class, :object
|
60
60
|
|
61
|
-
def
|
62
|
-
|
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
|
63
67
|
end
|
64
68
|
|
65
69
|
def decorator_method(klass)
|
@@ -71,11 +75,11 @@ module Draper
|
|
71
75
|
end
|
72
76
|
|
73
77
|
def collection?
|
74
|
-
|
78
|
+
object.respond_to?(:first)
|
75
79
|
end
|
76
80
|
|
77
81
|
def decoratable?
|
78
|
-
|
82
|
+
object.respond_to?(:decorate)
|
79
83
|
end
|
80
84
|
|
81
85
|
def update_context(options)
|
data/lib/draper/finders.rb
CHANGED
@@ -5,26 +5,26 @@ module Draper
|
|
5
5
|
module Finders
|
6
6
|
|
7
7
|
def find(id, options = {})
|
8
|
-
decorate(
|
8
|
+
decorate(object_class.find(id), options)
|
9
9
|
end
|
10
10
|
|
11
11
|
def all(options = {})
|
12
|
-
decorate_collection(
|
12
|
+
decorate_collection(object_class.all, options)
|
13
13
|
end
|
14
14
|
|
15
15
|
def first(options = {})
|
16
|
-
decorate(
|
16
|
+
decorate(object_class.first, options)
|
17
17
|
end
|
18
18
|
|
19
19
|
def last(options = {})
|
20
|
-
decorate(
|
20
|
+
decorate(object_class.last, options)
|
21
21
|
end
|
22
22
|
|
23
23
|
# Decorates dynamic finder methods (`find_all_by_` and friends).
|
24
24
|
def method_missing(method, *args, &block)
|
25
25
|
return super unless method =~ /^find_(all_|last_|or_(initialize_|create_))?by_/
|
26
26
|
|
27
|
-
result =
|
27
|
+
result = object_class.send(method, *args, &block)
|
28
28
|
options = args.extract_options!
|
29
29
|
|
30
30
|
if method =~ /^find_all/
|