draper 1.0.0.beta6 → 1.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.
- data/.travis.yml +6 -0
- data/.yardopts +1 -1
- data/CHANGELOG.md +20 -0
- data/Gemfile +11 -0
- data/README.md +14 -17
- data/Rakefile +5 -3
- data/draper.gemspec +2 -2
- data/lib/draper.rb +2 -1
- data/lib/draper/automatic_delegation.rb +50 -0
- data/lib/draper/collection_decorator.rb +26 -7
- data/lib/draper/decoratable.rb +71 -32
- data/lib/draper/decorated_association.rb +11 -7
- data/lib/draper/decorator.rb +114 -148
- data/lib/draper/delegation.rb +13 -0
- data/lib/draper/finders.rb +9 -6
- data/lib/draper/helper_proxy.rb +4 -3
- data/lib/draper/lazy_helpers.rb +10 -6
- data/lib/draper/railtie.rb +5 -4
- data/lib/draper/tasks/test.rake +22 -0
- data/lib/draper/test/devise_helper.rb +34 -0
- data/lib/draper/test/minitest_integration.rb +2 -3
- data/lib/draper/test/rspec_integration.rb +4 -59
- data/lib/draper/test_case.rb +33 -0
- data/lib/draper/version.rb +1 -1
- data/lib/draper/view_helpers.rb +4 -3
- data/lib/generators/decorator/templates/decorator.rb +7 -25
- data/lib/generators/mini_test/decorator_generator.rb +20 -0
- data/lib/generators/mini_test/templates/decorator_spec.rb +4 -0
- data/lib/generators/mini_test/templates/decorator_test.rb +4 -0
- data/lib/generators/test_unit/templates/decorator_test.rb +1 -1
- data/spec/draper/collection_decorator_spec.rb +25 -3
- data/spec/draper/decorated_association_spec.rb +18 -7
- data/spec/draper/decorator_spec.rb +125 -165
- data/spec/draper/finders_spec.rb +0 -13
- data/spec/dummy/app/controllers/localized_urls.rb +1 -1
- data/spec/dummy/app/controllers/posts_controller.rb +3 -9
- data/spec/dummy/app/decorators/post_decorator.rb +4 -1
- data/spec/dummy/config/application.rb +3 -3
- data/spec/dummy/config/environments/development.rb +4 -4
- data/spec/dummy/config/environments/test.rb +2 -2
- data/spec/dummy/lib/tasks/test.rake +10 -0
- data/spec/dummy/mini_test/mini_test_integration_test.rb +46 -0
- data/spec/dummy/spec/decorators/post_decorator_spec.rb +2 -2
- data/spec/dummy/spec/decorators/rspec_integration_spec.rb +19 -0
- data/spec/dummy/spec/mailers/post_mailer_spec.rb +2 -2
- data/spec/dummy/spec/spec_helper.rb +0 -1
- data/spec/generators/decorator/decorator_generator_spec.rb +43 -2
- data/spec/integration/integration_spec.rb +2 -2
- data/spec/spec_helper.rb +17 -21
- data/spec/support/active_record.rb +0 -13
- data/spec/support/dummy_app.rb +4 -3
- metadata +26 -23
- data/lib/draper/security.rb +0 -48
- data/lib/draper/tasks/tu.rake +0 -5
- data/lib/draper/test/test_unit_integration.rb +0 -18
- data/spec/draper/security_spec.rb +0 -158
- data/spec/dummy/config/initializers/wrap_parameters.rb +0 -14
- data/spec/dummy/lib/tasks/spec.rake +0 -5
- data/spec/minitest-rails/spec_type_spec.rb +0 -63
@@ -1,4 +1,5 @@
|
|
1
1
|
module Draper
|
2
|
+
# @private
|
2
3
|
class DecoratedAssociation
|
3
4
|
|
4
5
|
def initialize(owner, association, options)
|
@@ -24,7 +25,7 @@ module Draper
|
|
24
25
|
|
25
26
|
private
|
26
27
|
|
27
|
-
attr_reader :owner, :association, :
|
28
|
+
attr_reader :owner, :association, :scope
|
28
29
|
|
29
30
|
def source
|
30
31
|
owner.source
|
@@ -48,12 +49,7 @@ module Draper
|
|
48
49
|
|
49
50
|
def decorator
|
50
51
|
return collection_decorator if collection?
|
51
|
-
|
52
|
-
if decorator_class
|
53
|
-
decorator_class.method(:decorate)
|
54
|
-
else
|
55
|
-
->(item, options) { item.decorate(options) }
|
56
|
-
end
|
52
|
+
decorator_class.method(:decorate)
|
57
53
|
end
|
58
54
|
|
59
55
|
def collection_decorator
|
@@ -66,5 +62,13 @@ module Draper
|
|
66
62
|
end
|
67
63
|
end
|
68
64
|
|
65
|
+
def decorator_class
|
66
|
+
@decorator_class || inferred_decorator_class
|
67
|
+
end
|
68
|
+
|
69
|
+
def inferred_decorator_class
|
70
|
+
undecorated.decorator_class if undecorated.respond_to?(:decorator_class)
|
71
|
+
end
|
72
|
+
|
69
73
|
end
|
70
74
|
end
|
data/lib/draper/decorator.rb
CHANGED
@@ -3,83 +3,102 @@ require 'active_support/core_ext/array/extract_options'
|
|
3
3
|
module Draper
|
4
4
|
class Decorator
|
5
5
|
include Draper::ViewHelpers
|
6
|
+
extend Draper::Delegation
|
6
7
|
include ActiveModel::Serialization if defined?(ActiveModel::Serialization)
|
7
8
|
|
9
|
+
# @return the object being decorated.
|
8
10
|
attr_reader :source
|
9
11
|
alias_method :model, :source
|
10
12
|
alias_method :to_source, :source
|
11
13
|
|
14
|
+
# @return [Hash] extra data to be used in user-defined methods.
|
12
15
|
attr_accessor :context
|
13
16
|
|
14
|
-
#
|
15
|
-
# an instance of the source class. Pass in an optional
|
16
|
-
# :context inside the options hash which is available
|
17
|
-
# for later use.
|
17
|
+
# Wraps an object in a new instance of the decorator.
|
18
18
|
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
# You can, however, apply several decorators in a chain but
|
23
|
-
# you will get a warning if the same decorator appears at
|
24
|
-
# multiple places in the chain.
|
19
|
+
# Decorators may be applied to other decorators. However, applying a
|
20
|
+
# decorator to an instance of itself will create a decorator with the same
|
21
|
+
# source as the original, rather than redecorating the other instance.
|
25
22
|
#
|
26
|
-
# @param [Object] source
|
27
|
-
#
|
23
|
+
# @param [Object] source
|
24
|
+
# object to decorate.
|
25
|
+
# @option options [Hash] :context ({})
|
26
|
+
# extra data to be stored in the decorator and used in user-defined
|
27
|
+
# methods.
|
28
28
|
def initialize(source, options = {})
|
29
29
|
options.assert_valid_keys(:context)
|
30
30
|
source.to_a if source.respond_to?(:to_a) # forces evaluation of a lazy query from AR
|
31
31
|
@source = source
|
32
32
|
@context = options.fetch(:context, {})
|
33
|
-
handle_multiple_decoration(options) if source.
|
33
|
+
handle_multiple_decoration(options) if source.instance_of?(self.class)
|
34
34
|
end
|
35
35
|
|
36
36
|
class << self
|
37
37
|
alias_method :decorate, :new
|
38
38
|
end
|
39
39
|
|
40
|
-
#
|
40
|
+
# Automatically delegates instance methods to the source object. Class
|
41
|
+
# methods will be delegated to the {source_class}, if it is set.
|
41
42
|
#
|
42
|
-
# @
|
43
|
-
def self.
|
44
|
-
|
43
|
+
# @return [void]
|
44
|
+
def self.delegate_all
|
45
|
+
include Draper::AutomaticDelegation
|
45
46
|
end
|
46
47
|
|
47
|
-
#
|
48
|
-
#
|
48
|
+
# Sets the source class corresponding to the decorator class.
|
49
|
+
#
|
50
|
+
# @note This is only necessary if you wish to proxy class methods to the
|
51
|
+
# source (including when using {decorates_finders}), and the source class
|
52
|
+
# cannot be inferred from the decorator class (e.g. `ProductDecorator`
|
53
|
+
# maps to `Product`).
|
54
|
+
# @param [String, Symbol, Class] source_class
|
55
|
+
# source class (or class name) that corresponds to this decorator.
|
56
|
+
# @return [void]
|
57
|
+
def self.decorates(source_class)
|
58
|
+
@source_class = source_class.to_s.camelize.constantize
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns the source class corresponding to the decorator class, as set by
|
62
|
+
# {decorates}, or as inferred from the decorator class name (e.g.
|
63
|
+
# `ProductDecorator` maps to `Product`).
|
64
|
+
#
|
65
|
+
# @return [Class] the source class that corresponds to this decorator.
|
49
66
|
def self.source_class
|
50
67
|
@source_class ||= inferred_source_class
|
51
68
|
end
|
52
69
|
|
53
|
-
# Checks whether this decorator class has a corresponding
|
54
|
-
# source class
|
70
|
+
# Checks whether this decorator class has a corresponding {source_class}.
|
55
71
|
def self.source_class?
|
56
72
|
source_class
|
57
73
|
rescue Draper::UninferrableSourceError
|
58
74
|
false
|
59
75
|
end
|
60
76
|
|
61
|
-
# Automatically decorates ActiveRecord finder methods, so that
|
62
|
-
#
|
77
|
+
# Automatically decorates ActiveRecord finder methods, so that you can use
|
78
|
+
# `ProductDecorator.find(id)` instead of
|
63
79
|
# `ProductDecorator.decorate(Product.find(id))`.
|
64
80
|
#
|
65
|
-
#
|
66
|
-
# inferred from the decorator class name.
|
81
|
+
# Finder methods are applied to the {source_class}.
|
67
82
|
#
|
83
|
+
# @return [void]
|
68
84
|
def self.decorates_finders
|
69
85
|
extend Draper::Finders
|
70
86
|
end
|
71
87
|
|
72
|
-
#
|
73
|
-
#
|
74
|
-
#
|
75
|
-
#
|
76
|
-
# @option options [Class] :with
|
77
|
-
#
|
78
|
-
# @option options [
|
79
|
-
#
|
80
|
-
#
|
81
|
-
#
|
82
|
-
# context
|
88
|
+
# Automatically decorate an association.
|
89
|
+
#
|
90
|
+
# @param [Symbol] association
|
91
|
+
# name of the association to decorate (e.g. `:products`).
|
92
|
+
# @option options [Class] :with
|
93
|
+
# the decorator to apply to the association.
|
94
|
+
# @option options [Symbol] :scope
|
95
|
+
# a scope to apply when fetching the association.
|
96
|
+
# @option options [Hash, #call] :context
|
97
|
+
# extra data to be stored in the associated decorator. If omitted, the
|
98
|
+
# associated decorator's context will be the same as the parent
|
99
|
+
# decorator's. If a Proc is given, it will be called with the parent's
|
100
|
+
# context and should return a new context hash for the association.
|
101
|
+
# @return [void]
|
83
102
|
def self.decorates_association(association, options = {})
|
84
103
|
options.assert_valid_keys(:with, :scope, :context)
|
85
104
|
define_method(association) do
|
@@ -88,11 +107,13 @@ module Draper
|
|
88
107
|
end
|
89
108
|
end
|
90
109
|
|
91
|
-
#
|
92
|
-
#
|
93
|
-
#
|
94
|
-
#
|
95
|
-
#
|
110
|
+
# @overload decorates_associations(*associations, options = {})
|
111
|
+
# Automatically decorate multiple associations.
|
112
|
+
# @param [Symbols*] associations
|
113
|
+
# names of the associations to decorate.
|
114
|
+
# @param [Hash] options
|
115
|
+
# see {decorates_association}.
|
116
|
+
# @return [void]
|
96
117
|
def self.decorates_associations(*associations)
|
97
118
|
options = associations.extract_options!
|
98
119
|
associations.each do |association|
|
@@ -100,167 +121,112 @@ module Draper
|
|
100
121
|
end
|
101
122
|
end
|
102
123
|
|
103
|
-
#
|
104
|
-
# the
|
105
|
-
#
|
106
|
-
#
|
107
|
-
# a whitelist with `.allows` or a blacklist with `.denies`
|
108
|
-
#
|
109
|
-
# @param [Symbols*] methods methods to deny like `:find, :find_by_name`
|
110
|
-
def self.denies(*methods)
|
111
|
-
security.denies(*methods)
|
112
|
-
end
|
113
|
-
|
114
|
-
# Specifies that all methods may *not* be proxied to the wrapped object.
|
115
|
-
#
|
116
|
-
# Do not use `.allows` and `.denies` in combination with '.denies_all'
|
117
|
-
def self.denies_all
|
118
|
-
security.denies_all
|
119
|
-
end
|
120
|
-
|
121
|
-
# Specifies a white list of methods which *may* be proxied to
|
122
|
-
# the wrapped object. When `allows` is used, only the listed
|
123
|
-
# methods and methods defined in the decorator itself will be
|
124
|
-
# available.
|
125
|
-
#
|
126
|
-
# Do not use both `.allows` and `.denies` together, either write
|
127
|
-
# a whitelist with `.allows` or a blacklist with `.denies`
|
128
|
-
#
|
129
|
-
# @param [Symbols*] methods methods to allow like `:find, :find_by_name`
|
130
|
-
def self.allows(*methods)
|
131
|
-
security.allows(*methods)
|
132
|
-
end
|
133
|
-
|
134
|
-
# Creates a new CollectionDecorator for the given collection.
|
124
|
+
# Decorates a collection of objects. The class of the collection decorator
|
125
|
+
# is inferred from the decorator class if possible (e.g. `ProductDecorator`
|
126
|
+
# maps to `ProductsDecorator`), but otherwise defaults to
|
127
|
+
# {Draper::CollectionDecorator}.
|
135
128
|
#
|
136
|
-
# @param [Object] source
|
137
|
-
#
|
138
|
-
#
|
139
|
-
#
|
140
|
-
#
|
141
|
-
# @option options [Hash] :context
|
129
|
+
# @param [Object] source
|
130
|
+
# collection to decorate.
|
131
|
+
# @option options [Class, nil] :with (self)
|
132
|
+
# the decorator class used to decorate each item. When `nil`, it is
|
133
|
+
# inferred from each item.
|
134
|
+
# @option options [Hash] :context
|
135
|
+
# extra data to be stored in the collection decorator.
|
142
136
|
def self.decorate_collection(source, options = {})
|
143
137
|
options.assert_valid_keys(:with, :context)
|
144
|
-
|
138
|
+
collection_decorator_class.new(source, options.reverse_merge(with: self))
|
145
139
|
end
|
146
140
|
|
147
|
-
#
|
148
|
-
#
|
149
|
-
# @return [Array] list of decorator classes
|
141
|
+
# @return [Array<Class>] the list of decorators that have been applied to
|
142
|
+
# the object.
|
150
143
|
def applied_decorators
|
151
144
|
chain = source.respond_to?(:applied_decorators) ? source.applied_decorators : []
|
152
145
|
chain << self.class
|
153
146
|
end
|
154
147
|
|
155
|
-
# Checks if a given decorator has been applied.
|
148
|
+
# Checks if a given decorator has been applied to the object.
|
156
149
|
#
|
157
150
|
# @param [Class] decorator_class
|
158
151
|
def decorated_with?(decorator_class)
|
159
152
|
applied_decorators.include?(decorator_class)
|
160
153
|
end
|
161
154
|
|
155
|
+
# Checks if this object is decorated.
|
156
|
+
#
|
157
|
+
# @return [true]
|
162
158
|
def decorated?
|
163
159
|
true
|
164
160
|
end
|
165
161
|
|
166
|
-
#
|
162
|
+
# Delegated to the source object.
|
167
163
|
#
|
168
|
-
# @return [Boolean]
|
164
|
+
# @return [Boolean]
|
169
165
|
def ==(other)
|
170
166
|
source == (other.respond_to?(:source) ? other.source : other)
|
171
167
|
end
|
172
168
|
|
169
|
+
# Checks if `self.kind_of?(klass)` or `source.kind_of?(klass)`
|
170
|
+
#
|
171
|
+
# @param [Class] klass
|
173
172
|
def kind_of?(klass)
|
174
173
|
super || source.kind_of?(klass)
|
175
174
|
end
|
176
175
|
alias_method :is_a?, :kind_of?
|
177
176
|
|
178
|
-
#
|
177
|
+
# Checks if `self.instance_of?(klass)` or `source.instance_of?(klass)`
|
179
178
|
#
|
180
|
-
#
|
181
|
-
|
182
|
-
|
183
|
-
source.present?
|
179
|
+
# @param [Class] klass
|
180
|
+
def instance_of?(klass)
|
181
|
+
super || source.instance_of?(klass)
|
184
182
|
end
|
185
183
|
|
186
|
-
#
|
184
|
+
# In case source is nil
|
185
|
+
delegate :present?
|
186
|
+
|
187
|
+
# ActiveModel compatibility
|
188
|
+
# @private
|
187
189
|
def to_model
|
188
190
|
self
|
189
191
|
end
|
190
192
|
|
191
|
-
#
|
192
|
-
|
193
|
-
source.to_param
|
194
|
-
end
|
193
|
+
# ActiveModel compatibility
|
194
|
+
delegate :to_param, :to_partial_path
|
195
195
|
|
196
|
-
|
197
|
-
|
198
|
-
self.class.define_proxy(method)
|
199
|
-
send(method, *args, &block)
|
200
|
-
else
|
201
|
-
super
|
202
|
-
end
|
203
|
-
end
|
196
|
+
# ActiveModel compatibility
|
197
|
+
singleton_class.delegate :model_name, to: :source_class
|
204
198
|
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
if delegatable_method?(method)
|
211
|
-
source_class.send(method, *args, &block)
|
212
|
-
else
|
213
|
-
super
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
def self.respond_to?(method, include_private = false)
|
218
|
-
super || delegatable_method?(method)
|
199
|
+
# @return [Class] the class created by {decorate_collection}.
|
200
|
+
def self.collection_decorator_class
|
201
|
+
collection_decorator_name.constantize
|
202
|
+
rescue NameError
|
203
|
+
Draper::CollectionDecorator
|
219
204
|
end
|
220
205
|
|
221
206
|
private
|
222
207
|
|
223
|
-
def
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
def self.delegatable_method?(method)
|
228
|
-
source_class? && source_class.respond_to?(method)
|
208
|
+
def self.source_name
|
209
|
+
raise NameError if name.nil? || name.demodulize !~ /.+Decorator$/
|
210
|
+
name.chomp("Decorator")
|
229
211
|
end
|
230
212
|
|
231
213
|
def self.inferred_source_class
|
232
|
-
|
233
|
-
|
234
|
-
begin
|
235
|
-
name.chomp("Decorator").constantize
|
236
|
-
rescue NameError
|
237
|
-
uninferrable_source
|
238
|
-
end
|
239
|
-
end
|
240
|
-
|
241
|
-
def self.uninferrable_source
|
214
|
+
source_name.constantize
|
215
|
+
rescue NameError
|
242
216
|
raise Draper::UninferrableSourceError.new(self)
|
243
217
|
end
|
244
218
|
|
245
|
-
def self.
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
end
|
250
|
-
|
251
|
-
def self.security
|
252
|
-
@security ||= Security.new
|
253
|
-
end
|
254
|
-
|
255
|
-
def allow?(method)
|
256
|
-
self.class.security.allow?(method)
|
219
|
+
def self.collection_decorator_name
|
220
|
+
plural = source_name.pluralize
|
221
|
+
raise NameError if plural == source_name
|
222
|
+
"#{plural}Decorator"
|
257
223
|
end
|
258
224
|
|
259
225
|
def handle_multiple_decoration(options)
|
260
|
-
if source.
|
226
|
+
if source.applied_decorators.last == self.class
|
261
227
|
@context = source.context unless options.has_key?(:context)
|
262
228
|
@source = source.source
|
263
|
-
|
229
|
+
else
|
264
230
|
warn "Reapplying #{self.class} decorator to target that is already decorated with it. Call stack:\n#{caller(1).join("\n")}"
|
265
231
|
end
|
266
232
|
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 `:source` the default delegation target.
|
6
|
+
#
|
7
|
+
# @return [void]
|
8
|
+
def delegate(*methods)
|
9
|
+
options = methods.extract_options!
|
10
|
+
super *methods, options.reverse_merge(to: :source)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/draper/finders.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
1
|
module Draper
|
2
|
+
# Provides automatically-decorated finder methods for your decorators. You
|
3
|
+
# do not have to extend this module directly; it is extended by
|
4
|
+
# {Decorator.decorates_finders}.
|
2
5
|
module Finders
|
3
6
|
|
4
7
|
def find(id, options = {})
|
@@ -17,17 +20,17 @@ module Draper
|
|
17
20
|
decorate(source_class.last, options)
|
18
21
|
end
|
19
22
|
|
23
|
+
# Decorates dynamic finder methods (`find_all_by_` and friends).
|
20
24
|
def method_missing(method, *args, &block)
|
21
|
-
|
25
|
+
return super unless method =~ /^find_(all_|last_|or_(initialize_|create_))?by_/
|
26
|
+
|
27
|
+
result = source_class.send(method, *args, &block)
|
22
28
|
options = args.extract_options!
|
23
29
|
|
24
|
-
|
25
|
-
when /^find_((last_)?by_|or_(initialize|create)_by_)/
|
26
|
-
decorate(result, options)
|
27
|
-
when /^find_all_by_/
|
30
|
+
if method =~ /^find_all/
|
28
31
|
decorate_collection(result, options)
|
29
32
|
else
|
30
|
-
result
|
33
|
+
decorate(result, options)
|
31
34
|
end
|
32
35
|
end
|
33
36
|
end
|