draper 1.0.0.beta4 → 1.0.0.beta5
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.markdown +9 -1
- data/Gemfile +7 -2
- data/draper.gemspec +3 -3
- data/lib/draper/collection_decorator.rb +31 -27
- data/lib/draper/decorated_association.rb +32 -32
- data/lib/draper/decorator.rb +7 -7
- data/lib/draper/version.rb +1 -1
- data/spec/draper/collection_decorator_spec.rb +62 -62
- data/spec/draper/decoratable_spec.rb +1 -1
- data/spec/draper/decorated_association_spec.rb +62 -244
- data/spec/draper/decorator_spec.rb +1 -10
- data/spec/draper/finders_spec.rb +2 -2
- metadata +22 -6
data/CHANGELOG.markdown
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
# Draper Changelog
|
2
2
|
|
3
|
+
## 1.0.0.beta5
|
4
|
+
|
5
|
+
* Change CollectionDecorator to freeze its collection [https://github.com/drapergem/draper/commit/04d779615c43580409083a71661489e1bbf91ad4](https://github.com/drapergem/draper/commit/04d779615c43580409083a71661489e1bbf91ad4)
|
6
|
+
|
7
|
+
* Bugfix on `CollectionDecorator#to_s` [https://github.com/drapergem/draper/commit/eefd7d09cac97d531b9235246378c3746d153f08](https://github.com/drapergem/draper/commit/eefd7d09cac97d531b9235246378c3746d153f08)
|
8
|
+
|
9
|
+
* Upgrade `request_store` dependency to take advantage of a bugfix [https://github.com/drapergem/draper/commit/9f17212fd1fb656ef1314327d60fe45e0acf60a2](https://github.com/drapergem/draper/commit/9f17212fd1fb656ef1314327d60fe45e0acf60a2)
|
10
|
+
|
3
11
|
## 1.0.0.beta4
|
4
12
|
|
5
13
|
* Fixed a race condition with capybara integration. [https://github.com/drapergem/draper/commit/e79464931e7b98c85ed5d78ed9ca38d51f43006e](https://github.com/drapergem/draper/commit/e79464931e7b98c85ed5d78ed9ca38d51f43006e)
|
@@ -44,7 +52,7 @@
|
|
44
52
|
Rails app in both development and production mode to help ensure that we
|
45
53
|
don't make changes that break Draper. [https://github.com/drapergem/draper/commit/90a4859085cab158658d23d77cd3108b6037e36f](https://github.com/drapergem/draper/commit/90a4859085cab158658d23d77cd3108b6037e36f)
|
46
54
|
* Add `#decorated?` method. This gives us a free RSpec matcher,
|
47
|
-
`
|
55
|
+
`be_decorated`. [https://github.com/drapergem/draper/commit/834a6fd1f24b5646c333a04a99fe9846a58965d6](https://github.com/drapergem/draper/commit/834a6fd1f24b5646c333a04a99fe9846a58965d6)
|
48
56
|
* `#decorates` is no longer needed inside your models, and should be removed.
|
49
57
|
Decorators automatically infer the class they decorate. [https://github.com/drapergem/draper/commit/e1214d97b62f2cab45227cc650029734160dcdfe](https://github.com/drapergem/draper/commit/e1214d97b62f2cab45227cc650029734160dcdfe)
|
50
58
|
* Decorators do not automatically come with 'finders' by default. If you'd like
|
data/Gemfile
CHANGED
@@ -2,6 +2,11 @@ source :rubygems
|
|
2
2
|
|
3
3
|
gemspec
|
4
4
|
|
5
|
-
|
5
|
+
platforms :ruby do
|
6
|
+
gem "sqlite3"
|
7
|
+
end
|
6
8
|
|
7
|
-
|
9
|
+
platforms :jruby do
|
10
|
+
gem "minitest", ">= 3.0"
|
11
|
+
gem "activerecord-jdbcsqlite3-adapter", "~> 1.2.2.1"
|
12
|
+
end
|
data/draper.gemspec
CHANGED
@@ -18,13 +18,13 @@ Gem::Specification.new do |s|
|
|
18
18
|
|
19
19
|
s.add_dependency 'activesupport', '>= 3.0'
|
20
20
|
s.add_dependency 'actionpack', '>= 3.0'
|
21
|
-
s.add_dependency 'request_store', '~> 1.0.
|
21
|
+
s.add_dependency 'request_store', '~> 1.0.3'
|
22
22
|
|
23
23
|
s.add_development_dependency 'ammeter'
|
24
24
|
s.add_development_dependency 'rake', '~> 0.9.2'
|
25
|
-
s.add_development_dependency 'rspec', '~> 2.
|
25
|
+
s.add_development_dependency 'rspec', '~> 2.12'
|
26
|
+
s.add_development_dependency 'rspec-mocks', '>= 2.12.1'
|
26
27
|
s.add_development_dependency 'yard'
|
27
28
|
s.add_development_dependency 'minitest-rails', '~> 0.2'
|
28
|
-
s.add_development_dependency 'minitest', '~> 3.0' if RUBY_PLATFORM == "java"
|
29
29
|
s.add_development_dependency 'capybara'
|
30
30
|
end
|
@@ -3,20 +3,21 @@ module Draper
|
|
3
3
|
include Enumerable
|
4
4
|
include ViewHelpers
|
5
5
|
|
6
|
-
|
6
|
+
attr_reader :source
|
7
7
|
alias_method :to_source, :source
|
8
8
|
|
9
|
-
|
9
|
+
attr_accessor :context
|
10
|
+
|
11
|
+
array_methods = Array.instance_methods - Object.instance_methods
|
12
|
+
delegate :as_json, *array_methods, to: :decorated_collection
|
10
13
|
|
11
14
|
# @param source collection to decorate
|
12
|
-
# @
|
13
|
-
# @option options [Class, Symbol] :with the class used to decorate
|
14
|
-
# items, or `:infer` to call each item's `decorate` method instead
|
15
|
+
# @option options [Class] :with the class used to decorate items
|
15
16
|
# @option options [Hash] :context context available to each item's decorator
|
16
17
|
def initialize(source, options = {})
|
17
18
|
options.assert_valid_keys(:with, :context)
|
18
|
-
@source = source
|
19
|
-
@decorator_class = options
|
19
|
+
@source = source.dup.freeze
|
20
|
+
@decorator_class = options[:with]
|
20
21
|
@context = options.fetch(:context, {})
|
21
22
|
end
|
22
23
|
|
@@ -25,7 +26,7 @@ module Draper
|
|
25
26
|
end
|
26
27
|
|
27
28
|
def decorated_collection
|
28
|
-
@decorated_collection ||= source.
|
29
|
+
@decorated_collection ||= source.map{|item| decorate_item(item)}.freeze
|
29
30
|
end
|
30
31
|
|
31
32
|
def find(*args, &block)
|
@@ -36,25 +37,18 @@ module Draper
|
|
36
37
|
end
|
37
38
|
end
|
38
39
|
|
39
|
-
def method_missing(method, *args, &block)
|
40
|
-
source.send(method, *args, &block)
|
41
|
-
end
|
42
|
-
|
43
|
-
def respond_to?(method, include_private = false)
|
44
|
-
super || source.respond_to?(method, include_private)
|
45
|
-
end
|
46
|
-
|
47
|
-
def kind_of?(klass)
|
48
|
-
super || source.kind_of?(klass)
|
49
|
-
end
|
50
|
-
alias_method :is_a?, :kind_of?
|
51
|
-
|
52
40
|
def ==(other)
|
53
41
|
source == (other.respond_to?(:source) ? other.source : other)
|
54
42
|
end
|
55
43
|
|
56
44
|
def to_s
|
57
|
-
|
45
|
+
klass = begin
|
46
|
+
decorator_class
|
47
|
+
rescue Draper::UninferrableDecoratorError
|
48
|
+
"inferred decorators"
|
49
|
+
end
|
50
|
+
|
51
|
+
"#<CollectionDecorator of #{klass} for #{source.inspect}>"
|
58
52
|
end
|
59
53
|
|
60
54
|
def context=(value)
|
@@ -62,14 +56,14 @@ module Draper
|
|
62
56
|
each {|item| item.context = value } if @decorated_collection
|
63
57
|
end
|
64
58
|
|
59
|
+
def decorator_class
|
60
|
+
@decorator_class ||= self.class.inferred_decorator_class
|
61
|
+
end
|
62
|
+
|
65
63
|
protected
|
66
64
|
|
67
65
|
def decorate_item(item)
|
68
|
-
|
69
|
-
item.decorate(context: context)
|
70
|
-
else
|
71
|
-
decorator_class.decorate(item, context: context)
|
72
|
-
end
|
66
|
+
item_decorator.call(item, context: context)
|
73
67
|
end
|
74
68
|
|
75
69
|
def self.inferred_decorator_class
|
@@ -85,5 +79,15 @@ module Draper
|
|
85
79
|
def self.decorator_uninferrable
|
86
80
|
raise Draper::UninferrableDecoratorError.new(self)
|
87
81
|
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def item_decorator
|
86
|
+
@item_decorator ||= begin
|
87
|
+
decorator_class.method(:decorate)
|
88
|
+
rescue Draper::UninferrableDecoratorError
|
89
|
+
->(item, options) { item.decorate(options) }
|
90
|
+
end
|
91
|
+
end
|
88
92
|
end
|
89
93
|
end
|
@@ -1,70 +1,70 @@
|
|
1
1
|
module Draper
|
2
2
|
class DecoratedAssociation
|
3
3
|
|
4
|
-
|
4
|
+
def initialize(owner, association, options)
|
5
|
+
options.assert_valid_keys(:with, :scope, :context)
|
5
6
|
|
6
|
-
|
7
|
-
@base = base
|
7
|
+
@owner = owner
|
8
8
|
@association = association
|
9
|
-
|
10
|
-
@
|
9
|
+
|
10
|
+
@decorator_class = options[:with]
|
11
|
+
@scope = options[:scope]
|
12
|
+
@context = options.fetch(:context, owner.context)
|
11
13
|
end
|
12
14
|
|
13
15
|
def call
|
14
16
|
return undecorated if undecorated.nil?
|
15
|
-
|
17
|
+
decorated
|
16
18
|
end
|
17
19
|
|
18
|
-
def
|
19
|
-
|
20
|
+
def context
|
21
|
+
return @context.call(owner.context) if @context.respond_to?(:call)
|
22
|
+
@context
|
20
23
|
end
|
21
24
|
|
22
25
|
private
|
23
26
|
|
27
|
+
attr_reader :owner, :association, :decorator_class, :scope
|
28
|
+
|
29
|
+
def source
|
30
|
+
owner.source
|
31
|
+
end
|
32
|
+
|
24
33
|
def undecorated
|
25
34
|
@undecorated ||= begin
|
26
35
|
associated = source.send(association)
|
27
|
-
associated = associated.send(
|
36
|
+
associated = associated.send(scope) if scope
|
28
37
|
associated
|
29
38
|
end
|
30
39
|
end
|
31
40
|
|
32
|
-
def
|
33
|
-
@decorated ||=
|
34
|
-
end
|
35
|
-
|
36
|
-
def decorate_method
|
37
|
-
if collection? && decorator_class.respond_to?(:decorate_collection)
|
38
|
-
:decorate_collection
|
39
|
-
else
|
40
|
-
:decorate
|
41
|
-
end
|
41
|
+
def decorated
|
42
|
+
@decorated ||= decorator.call(undecorated, context: context)
|
42
43
|
end
|
43
44
|
|
44
45
|
def collection?
|
45
46
|
undecorated.respond_to?(:first)
|
46
47
|
end
|
47
48
|
|
48
|
-
def
|
49
|
-
return
|
49
|
+
def decorator
|
50
|
+
return collection_decorator if collection?
|
50
51
|
|
51
|
-
if
|
52
|
-
|
53
|
-
Draper::CollectionDecorator
|
52
|
+
if decorator_class
|
53
|
+
decorator_class.method(:decorate)
|
54
54
|
else
|
55
|
-
|
55
|
+
->(item, options) { item.decorate(options) }
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
|
-
def
|
60
|
-
|
59
|
+
def collection_decorator
|
60
|
+
klass = decorator_class || Draper::CollectionDecorator
|
61
61
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
62
|
+
if klass.respond_to?(:decorate_collection)
|
63
|
+
klass.method(:decorate_collection)
|
64
|
+
else
|
65
|
+
klass.method(:decorate)
|
66
66
|
end
|
67
|
-
dec_options
|
68
67
|
end
|
68
|
+
|
69
69
|
end
|
70
70
|
end
|
data/lib/draper/decorator.rb
CHANGED
@@ -5,11 +5,12 @@ module Draper
|
|
5
5
|
include Draper::ViewHelpers
|
6
6
|
include ActiveModel::Serialization if defined?(ActiveModel::Serialization)
|
7
7
|
|
8
|
-
|
9
|
-
|
8
|
+
attr_reader :source
|
10
9
|
alias_method :model, :source
|
11
10
|
alias_method :to_source, :source
|
12
11
|
|
12
|
+
attr_accessor :context
|
13
|
+
|
13
14
|
# Initialize a new decorator instance by passing in
|
14
15
|
# an instance of the source class. Pass in an optional
|
15
16
|
# :context inside the options hash which is available
|
@@ -23,7 +24,6 @@ module Draper
|
|
23
24
|
# multiple places in the chain.
|
24
25
|
#
|
25
26
|
# @param [Object] source object to decorate
|
26
|
-
# @param [Hash] options (optional)
|
27
27
|
# @option options [Hash] :context context available to the decorator
|
28
28
|
def initialize(source, options = {})
|
29
29
|
options.assert_valid_keys(:context)
|
@@ -136,8 +136,8 @@ module Draper
|
|
136
136
|
# @param [Object] source collection to decorate
|
137
137
|
# @param [Hash] options passed to each item's decorator (except
|
138
138
|
# for the keys listed below)
|
139
|
-
# @option options [Class
|
140
|
-
# items
|
139
|
+
# @option options [Class] :with (self) the class used to decorate
|
140
|
+
# items
|
141
141
|
# @option options [Hash] :context context available to decorated items
|
142
142
|
def self.decorate_collection(source, options = {})
|
143
143
|
options.assert_valid_keys(:with, :context)
|
@@ -258,8 +258,8 @@ module Draper
|
|
258
258
|
|
259
259
|
def handle_multiple_decoration(options)
|
260
260
|
if source.instance_of?(self.class)
|
261
|
-
|
262
|
-
|
261
|
+
@context = source.context unless options.has_key?(:context)
|
262
|
+
@source = source.source
|
263
263
|
elsif source.decorated_with?(self.class)
|
264
264
|
warn "Reapplying #{self.class} decorator to target that is already decorated with it. Call stack:\n#{caller(1).join("\n")}"
|
265
265
|
end
|
data/lib/draper/version.rb
CHANGED
@@ -16,6 +16,27 @@ describe Draper::CollectionDecorator do
|
|
16
16
|
subject.map{|item| item.source}.should == source
|
17
17
|
end
|
18
18
|
|
19
|
+
describe "#source" do
|
20
|
+
it "duplicates the source collection" do
|
21
|
+
subject.source.should == source
|
22
|
+
subject.source.should_not be source
|
23
|
+
end
|
24
|
+
|
25
|
+
it "is frozen" do
|
26
|
+
subject.source.should be_frozen
|
27
|
+
end
|
28
|
+
|
29
|
+
it "is aliased to #to_source" do
|
30
|
+
subject.to_source.should == source
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#decorated_collection" do
|
35
|
+
it "is frozen" do
|
36
|
+
subject.decorated_collection.should be_frozen
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
19
40
|
context "with context" do
|
20
41
|
subject { Draper::CollectionDecorator.new(source, with: ProductDecorator, context: {some: 'context'}) }
|
21
42
|
|
@@ -79,55 +100,58 @@ describe Draper::CollectionDecorator do
|
|
79
100
|
expect { Draper::CollectionDecorator.new(source, valid_options.merge(foo: 'bar')) }.to raise_error(ArgumentError, 'Unknown key: foo')
|
80
101
|
end
|
81
102
|
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "item decoration" do
|
106
|
+
subject { subject_class.new(source, options) }
|
107
|
+
let(:decorator_classes) { subject.decorated_collection.map(&:class) }
|
108
|
+
let(:source) { [Product.new, Widget.new] }
|
109
|
+
|
110
|
+
context "when the :with option was given" do
|
111
|
+
let(:options) { {with: SpecificProductDecorator} }
|
82
112
|
|
83
|
-
context "when the :with option is given" do
|
84
113
|
context "and the decorator can't be inferred from the class" do
|
85
|
-
|
114
|
+
let(:subject_class) { Draper::CollectionDecorator }
|
86
115
|
|
87
116
|
it "uses the :with option" do
|
88
|
-
|
117
|
+
decorator_classes.should == [SpecificProductDecorator, SpecificProductDecorator]
|
89
118
|
end
|
90
119
|
end
|
91
120
|
|
92
121
|
context "and the decorator is inferrable from the class" do
|
93
|
-
|
122
|
+
let(:subject_class) { ProductsDecorator }
|
94
123
|
|
95
124
|
it "uses the :with option" do
|
96
|
-
|
125
|
+
decorator_classes.should == [SpecificProductDecorator, SpecificProductDecorator]
|
97
126
|
end
|
98
127
|
end
|
99
128
|
end
|
100
129
|
|
101
|
-
context "when the :with option
|
130
|
+
context "when the :with option was not given" do
|
131
|
+
let(:options) { {} }
|
132
|
+
|
102
133
|
context "and the decorator can't be inferred from the class" do
|
103
|
-
|
104
|
-
|
134
|
+
let(:subject_class) { Draper::CollectionDecorator }
|
135
|
+
|
136
|
+
it "infers the decorator from each item" do
|
137
|
+
decorator_classes.should == [ProductDecorator, WidgetDecorator]
|
105
138
|
end
|
106
139
|
end
|
107
140
|
|
108
141
|
context "and the decorator is inferrable from the class" do
|
109
|
-
|
142
|
+
let(:subject_class) { ProductsDecorator}
|
110
143
|
|
111
144
|
it "infers the decorator" do
|
112
|
-
|
145
|
+
decorator_classes.should == [ProductDecorator, ProductDecorator]
|
113
146
|
end
|
114
147
|
end
|
115
148
|
end
|
116
149
|
end
|
117
150
|
|
118
|
-
describe "#source" do
|
119
|
-
it "returns the source collection" do
|
120
|
-
subject.source.should be source
|
121
|
-
end
|
122
|
-
|
123
|
-
it "is aliased to #to_source" do
|
124
|
-
subject.to_source.should be source
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
151
|
describe "#find" do
|
129
152
|
context "with a block" do
|
130
153
|
it "decorates Enumerable#find" do
|
154
|
+
subject.stub decorated_collection: []
|
131
155
|
subject.decorated_collection.should_receive(:find)
|
132
156
|
subject.find {|p| p.title == "title" }
|
133
157
|
end
|
@@ -184,36 +208,15 @@ describe Draper::CollectionDecorator do
|
|
184
208
|
describe "#to_ary" do
|
185
209
|
# required for `render @collection` in Rails
|
186
210
|
it "delegates to the decorated collection" do
|
187
|
-
subject.decorated_collection
|
211
|
+
subject.stub decorated_collection: double(to_ary: :an_array)
|
188
212
|
subject.to_ary.should == :an_array
|
189
213
|
end
|
190
214
|
end
|
191
215
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
it "returns true for the wrapped collection's methods" do
|
198
|
-
source.stub(:respond_to?).with(:whatever, true).and_return(true)
|
199
|
-
subject.respond_to?(:whatever, true).should be_true
|
200
|
-
end
|
201
|
-
end
|
202
|
-
|
203
|
-
context "Array methods" do
|
204
|
-
describe "#include?" do
|
205
|
-
it "delegates to the decorated collection" do
|
206
|
-
subject.decorated_collection.should_receive(:include?).with(:something).and_return(true)
|
207
|
-
subject.should include :something
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
|
-
describe "#[]" do
|
212
|
-
it "delegates to the decorated collection" do
|
213
|
-
subject.decorated_collection.should_receive(:[]).with(42).and_return(:something)
|
214
|
-
subject[42].should == :something
|
215
|
-
end
|
216
|
-
end
|
216
|
+
it "delegates array methods to the decorated collection" do
|
217
|
+
subject.stub decorated_collection: []
|
218
|
+
subject.decorated_collection.should_receive(:[]).with(42).and_return(:the_answer)
|
219
|
+
subject[42].should == :the_answer
|
217
220
|
end
|
218
221
|
|
219
222
|
describe "#==" do
|
@@ -250,27 +253,24 @@ describe Draper::CollectionDecorator do
|
|
250
253
|
end
|
251
254
|
end
|
252
255
|
|
253
|
-
|
254
|
-
subject.
|
255
|
-
|
256
|
-
end
|
256
|
+
describe "#to_s" do
|
257
|
+
subject { Draper::CollectionDecorator.new(source, options) }
|
258
|
+
let(:source) { ["a", "b", "c"] }
|
257
259
|
|
258
|
-
|
259
|
-
|
260
|
-
subject.is_a?(subject.class).should be_true
|
261
|
-
end
|
260
|
+
context "when :with option was given" do
|
261
|
+
let(:options) { {with: ProductDecorator} }
|
262
262
|
|
263
|
-
|
264
|
-
|
265
|
-
class << source
|
266
|
-
def page_number
|
267
|
-
42
|
268
|
-
end
|
263
|
+
it "returns a string representation of the CollectionDecorator" do
|
264
|
+
subject.to_s.should == '#<CollectionDecorator of ProductDecorator for ["a", "b", "c"]>'
|
269
265
|
end
|
270
266
|
end
|
271
267
|
|
272
|
-
|
273
|
-
|
268
|
+
context "when :with option was not given" do
|
269
|
+
let(:options) { {} }
|
270
|
+
|
271
|
+
it "returns a string representation of the CollectionDecorator" do
|
272
|
+
subject.to_s.should == '#<CollectionDecorator of inferred decorators for ["a", "b", "c"]>'
|
273
|
+
end
|
274
274
|
end
|
275
275
|
end
|
276
276
|
|
@@ -150,7 +150,7 @@ describe Draper::Decoratable do
|
|
150
150
|
|
151
151
|
decorator.should be_a Draper::CollectionDecorator
|
152
152
|
decorator.decorator_class.should be WidgetDecorator
|
153
|
-
decorator.source.should
|
153
|
+
decorator.source.should == Product.scoped
|
154
154
|
end
|
155
155
|
|
156
156
|
it "accepts context" do
|
@@ -1,313 +1,131 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Draper::DecoratedAssociation do
|
4
|
-
let(:decorated_association) { Draper::DecoratedAssociation.new(
|
4
|
+
let(:decorated_association) { Draper::DecoratedAssociation.new(owner, :association, options) }
|
5
5
|
let(:source) { Product.new }
|
6
|
-
let(:
|
6
|
+
let(:owner) { source.decorate }
|
7
7
|
let(:options) { {} }
|
8
8
|
|
9
9
|
describe "#initialize" do
|
10
10
|
describe "options validation" do
|
11
|
-
let(:association) { :similar_products }
|
12
11
|
let(:valid_options) { {with: ProductDecorator, scope: :foo, context: {}} }
|
13
12
|
|
14
13
|
it "does not raise error on valid options" do
|
15
|
-
expect { Draper::DecoratedAssociation.new(
|
14
|
+
expect { Draper::DecoratedAssociation.new(owner, :association, valid_options) }.to_not raise_error
|
16
15
|
end
|
17
16
|
|
18
17
|
it "raises error on invalid options" do
|
19
|
-
expect { Draper::DecoratedAssociation.new(
|
18
|
+
expect { Draper::DecoratedAssociation.new(owner, :association, valid_options.merge(foo: 'bar')) }.to raise_error(ArgumentError, 'Unknown key: foo')
|
20
19
|
end
|
21
20
|
end
|
22
21
|
end
|
23
22
|
|
24
|
-
describe "#base" do
|
25
|
-
subject { decorated_association.base }
|
26
|
-
let(:association) { :similar_products }
|
27
|
-
|
28
|
-
it "returns the base decorator" do
|
29
|
-
should be base
|
30
|
-
end
|
31
|
-
|
32
|
-
it "returns a Decorator" do
|
33
|
-
subject.class.should == ProductDecorator
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
describe "#source" do
|
38
|
-
subject { decorated_association.source }
|
39
|
-
let(:association) { :similar_products }
|
40
|
-
|
41
|
-
it "returns the base decorator's source" do
|
42
|
-
should be base.source
|
43
|
-
end
|
44
|
-
|
45
|
-
it "returns a Model" do
|
46
|
-
subject.class.should == Product
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
23
|
describe "#call" do
|
51
|
-
|
52
|
-
|
53
|
-
context "for an ActiveModel collection association" do
|
54
|
-
let(:association) { :similar_products }
|
55
|
-
|
56
|
-
context "when the association is not empty" do
|
57
|
-
it "decorates the collection" do
|
58
|
-
subject.should be_a Draper::CollectionDecorator
|
59
|
-
end
|
60
|
-
|
61
|
-
it "infers the decorator" do
|
62
|
-
subject.decorator_class.should be :infer
|
63
|
-
end
|
64
|
-
end
|
24
|
+
let(:context) { {foo: "bar"} }
|
25
|
+
let(:expected_options) { {context: context} }
|
65
26
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
subject.should be_a Draper::CollectionDecorator
|
70
|
-
subject.should be_empty
|
71
|
-
subject.first.should be_nil
|
72
|
-
end
|
73
|
-
end
|
27
|
+
before do
|
28
|
+
source.stub association: associated
|
29
|
+
decorated_association.stub context: context
|
74
30
|
end
|
75
31
|
|
76
|
-
context "for
|
77
|
-
let(:
|
32
|
+
context "for a singular association" do
|
33
|
+
let(:associated) { Product.new }
|
78
34
|
|
79
|
-
context "when
|
80
|
-
|
81
|
-
|
82
|
-
end
|
83
|
-
|
84
|
-
it "infers the decorator" do
|
85
|
-
subject.decorator_class.should be :infer
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
context "when the association is empty" do
|
90
|
-
it "returns an empty collection decorator" do
|
91
|
-
source.stub(:poro_similar_products).and_return([])
|
92
|
-
subject.should be_a Draper::CollectionDecorator
|
93
|
-
subject.should be_empty
|
94
|
-
subject.first.should be_nil
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
context "for an ActiveModel singular association" do
|
100
|
-
let(:association) { :previous_version }
|
35
|
+
context "when :with option was given" do
|
36
|
+
let(:options) { {with: decorator} }
|
37
|
+
let(:decorator) { SpecificProductDecorator }
|
101
38
|
|
102
|
-
|
103
|
-
|
104
|
-
|
39
|
+
it "uses the specified decorator" do
|
40
|
+
decorator.should_receive(:decorate).with(associated, expected_options).and_return(:decorated)
|
41
|
+
decorated_association.call.should be :decorated
|
105
42
|
end
|
106
43
|
end
|
107
44
|
|
108
|
-
context "when
|
109
|
-
it "
|
110
|
-
|
111
|
-
|
45
|
+
context "when :with option was not given" do
|
46
|
+
it "infers the decorator" do
|
47
|
+
associated.should_receive(:decorate).with(expected_options).and_return(:decorated)
|
48
|
+
decorated_association.call.should be :decorated
|
112
49
|
end
|
113
50
|
end
|
114
51
|
end
|
115
52
|
|
116
|
-
context "for a
|
117
|
-
let(:
|
53
|
+
context "for a collection association" do
|
54
|
+
let(:associated) { [Product.new, Widget.new] }
|
118
55
|
|
119
|
-
context "when
|
120
|
-
|
121
|
-
|
122
|
-
end
|
123
|
-
end
|
56
|
+
context "when :with option is a collection decorator" do
|
57
|
+
let(:options) { {with: collection_decorator} }
|
58
|
+
let(:collection_decorator) { ProductsDecorator }
|
124
59
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
subject.should be_nil
|
60
|
+
it "uses the specified decorator" do
|
61
|
+
collection_decorator.should_receive(:decorate).with(associated, expected_options).and_return(:decorated_collection)
|
62
|
+
decorated_association.call.should be :decorated_collection
|
129
63
|
end
|
130
64
|
end
|
131
|
-
end
|
132
|
-
|
133
|
-
context "when a decorator is specified" do
|
134
|
-
let(:options) { {with: SpecificProductDecorator} }
|
135
65
|
|
136
|
-
context "
|
137
|
-
let(:
|
66
|
+
context "when :with option is a singular decorator" do
|
67
|
+
let(:options) { {with: decorator} }
|
68
|
+
let(:decorator) { SpecificProductDecorator }
|
138
69
|
|
139
|
-
it "
|
140
|
-
|
70
|
+
it "uses a CollectionDecorator of the specified decorator" do
|
71
|
+
decorator.should_receive(:decorate_collection).with(associated, expected_options).and_return(:decorated_collection)
|
72
|
+
decorated_association.call.should be :decorated_collection
|
141
73
|
end
|
142
74
|
end
|
143
75
|
|
144
|
-
context "
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
subject.should be_a Draper::CollectionDecorator
|
149
|
-
subject.decorator_class.should be SpecificProductDecorator
|
76
|
+
context "when :with option was not given" do
|
77
|
+
it "uses a CollectionDecorator of inferred decorators" do
|
78
|
+
Draper::CollectionDecorator.should_receive(:decorate).with(associated, expected_options).and_return(:decorated_collection)
|
79
|
+
decorated_association.call.should be :decorated_collection
|
150
80
|
end
|
151
81
|
end
|
152
82
|
end
|
153
83
|
|
154
|
-
context "when a collection decorator is specified" do
|
155
|
-
let(:association) { :similar_products }
|
156
|
-
let(:options) { {with: ProductsDecorator} }
|
157
|
-
|
158
|
-
it "decorates with the specified decorator" do
|
159
|
-
subject.should be_a ProductsDecorator
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
84
|
context "with a scope" do
|
164
|
-
let(:
|
85
|
+
let(:associated) { [] }
|
165
86
|
let(:options) { {scope: :foo} }
|
166
87
|
|
167
88
|
it "applies the scope before decoration" do
|
168
|
-
scoped = [
|
169
|
-
|
170
|
-
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
|
-
context "base has context" do
|
175
|
-
let(:association) { :similar_products }
|
176
|
-
let(:base) { source.decorate(context: {some: 'context'}) }
|
177
|
-
|
178
|
-
context "when no context is specified" do
|
179
|
-
it "it should inherit context from base" do
|
180
|
-
subject.context.should == {some: 'context'}
|
181
|
-
end
|
182
|
-
|
183
|
-
it "it should share context hash with base" do
|
184
|
-
subject.context.should be base.context
|
185
|
-
end
|
186
|
-
end
|
187
|
-
|
188
|
-
context "when static context is specified" do
|
189
|
-
let(:options) { {context: {other: 'context'}} }
|
190
|
-
|
191
|
-
it "it should get context from static option" do
|
192
|
-
subject.context.should == {other: 'context'}
|
193
|
-
end
|
194
|
-
end
|
195
|
-
|
196
|
-
context "when lambda context is specified" do
|
197
|
-
let(:options) { {context: lambda {|context| context.merge(other: 'protext')}} }
|
198
|
-
|
199
|
-
it "it should get generated context" do
|
200
|
-
subject.context.should == {some: 'context', other: 'protext'}
|
201
|
-
end
|
89
|
+
scoped = [:scoped]
|
90
|
+
associated.should_receive(:foo).and_return(scoped)
|
91
|
+
decorated_association.call.source.should == scoped
|
202
92
|
end
|
203
93
|
end
|
204
94
|
end
|
205
95
|
|
206
|
-
describe "#
|
207
|
-
|
208
|
-
|
209
|
-
context "collection association" do
|
210
|
-
let(:association) { :similar_products }
|
96
|
+
describe "#context" do
|
97
|
+
before { owner.stub context: :owner_context }
|
211
98
|
|
212
|
-
|
213
|
-
|
214
|
-
should == {with: :infer, context: {}}
|
215
|
-
end
|
99
|
+
context "when :context option was given" do
|
100
|
+
let(:options) { {context: context} }
|
216
101
|
|
217
|
-
|
218
|
-
|
219
|
-
subject
|
220
|
-
decorated_association.send(:options).should == {with: :infer}
|
221
|
-
end
|
222
|
-
end
|
102
|
+
context "and is callable" do
|
103
|
+
let(:context) { ->(*){ :dynamic_context } }
|
223
104
|
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
should == {with: ProductDecorator, context: {}}
|
105
|
+
it "calls it with the owner's context" do
|
106
|
+
context.should_receive(:call).with(:owner_context)
|
107
|
+
decorated_association.context
|
228
108
|
end
|
229
|
-
end
|
230
109
|
|
231
|
-
|
232
|
-
|
233
|
-
it "should strip scope: from options" do
|
234
|
-
decorated_association.send(:options).should == options
|
235
|
-
should == {with: :infer, context: {}}
|
110
|
+
it "returns the lambda's return value" do
|
111
|
+
decorated_association.context.should be :dynamic_context
|
236
112
|
end
|
237
113
|
end
|
238
114
|
|
239
|
-
context "
|
240
|
-
let(:
|
115
|
+
context "and is not callable" do
|
116
|
+
let(:context) { :static_context }
|
241
117
|
|
242
|
-
|
243
|
-
|
244
|
-
should == {with: :infer, context: {some: 'context'}}
|
245
|
-
end
|
246
|
-
end
|
247
|
-
|
248
|
-
context "option context: {other: 'context'}" do
|
249
|
-
let(:options) { {context: {other: 'context'}} }
|
250
|
-
it "should return specified context" do
|
251
|
-
should == {with: :infer, context: {other: 'context'}}
|
252
|
-
end
|
253
|
-
end
|
254
|
-
|
255
|
-
context "option context: lambda" do
|
256
|
-
let(:options) { {context: lambda {|context| context.merge(other: 'protext')}} }
|
257
|
-
it "should return specified context" do
|
258
|
-
should == {with: :infer, context: {some: 'context', other: 'protext'}}
|
259
|
-
end
|
118
|
+
it "returns the specified value" do
|
119
|
+
decorated_association.context.should be :static_context
|
260
120
|
end
|
261
121
|
end
|
262
122
|
end
|
263
123
|
|
264
|
-
context "
|
265
|
-
|
266
|
-
|
267
|
-
context "no options" do
|
268
|
-
it "should return default options" do
|
269
|
-
should == {context: {}}
|
270
|
-
end
|
271
|
-
end
|
272
|
-
|
273
|
-
context "option with: ProductDecorator" do
|
274
|
-
let(:options) { {with: ProductDecorator} }
|
275
|
-
it "should strip with: from options" do
|
276
|
-
should == {context: {}}
|
277
|
-
end
|
278
|
-
end
|
279
|
-
|
280
|
-
context "option scope: :decorate" do
|
281
|
-
let(:options) { {scope: :decorate} }
|
282
|
-
it "should strip scope: from options" do
|
283
|
-
decorated_association.send(:options).should == options
|
284
|
-
should == {context: {}}
|
285
|
-
end
|
286
|
-
end
|
287
|
-
|
288
|
-
context "base has context" do
|
289
|
-
let(:base) { source.decorate(context: {some: 'context'}) }
|
290
|
-
|
291
|
-
context "no options" do
|
292
|
-
it "should return context from base" do
|
293
|
-
should == {context: {some: 'context'}}
|
294
|
-
end
|
295
|
-
end
|
296
|
-
|
297
|
-
context "option context: {other: 'context'}" do
|
298
|
-
let(:options) { {context: {other: 'context'}} }
|
299
|
-
it "should return specified context" do
|
300
|
-
should == {context: {other: 'context'}}
|
301
|
-
end
|
302
|
-
end
|
303
|
-
|
304
|
-
context "option context: lambda" do
|
305
|
-
let(:options) { {context: lambda {|context| context.merge(other: 'protext')}} }
|
306
|
-
it "should return specified context" do
|
307
|
-
should == {context: {some: 'context', other: 'protext'}}
|
308
|
-
end
|
309
|
-
end
|
124
|
+
context "when :context option was not given" do
|
125
|
+
it "returns the owner's context" do
|
126
|
+
decorated_association.context.should be :owner_context
|
310
127
|
end
|
311
128
|
end
|
312
129
|
end
|
130
|
+
|
313
131
|
end
|
@@ -94,22 +94,13 @@ describe Draper::Decorator do
|
|
94
94
|
|
95
95
|
it "returns a collection decorator" do
|
96
96
|
subject.should be_a Draper::CollectionDecorator
|
97
|
-
subject.source.should
|
97
|
+
subject.source.should == source
|
98
98
|
end
|
99
99
|
|
100
100
|
it "uses itself as the item decorator by default" do
|
101
101
|
subject.each {|item| item.should be_a ProductDecorator}
|
102
102
|
end
|
103
103
|
|
104
|
-
context "when given :with => :infer" do
|
105
|
-
subject { ProductDecorator.decorate_collection(source, with: :infer) }
|
106
|
-
|
107
|
-
it "infers the item decorators" do
|
108
|
-
subject.first.should be_a ProductDecorator
|
109
|
-
subject.last.should be_a WidgetDecorator
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
104
|
context "with context" do
|
114
105
|
subject { ProductDecorator.decorate_collection(source, with: :infer, context: {some: 'context'}) }
|
115
106
|
|
data/spec/draper/finders_spec.rb
CHANGED
@@ -64,7 +64,7 @@ describe Draper::Finders do
|
|
64
64
|
|
65
65
|
describe ".find_all_by_" do
|
66
66
|
it "proxies to the model class" do
|
67
|
-
Product.should_receive(:find_all_by_name_and_size).with("apples", "large")
|
67
|
+
Product.should_receive(:find_all_by_name_and_size).with("apples", "large").and_return([])
|
68
68
|
ProductDecorator.find_all_by_name_and_size("apples", "large")
|
69
69
|
end
|
70
70
|
|
@@ -73,7 +73,7 @@ describe Draper::Finders do
|
|
73
73
|
Product.stub(:find_all_by_name).and_return(found)
|
74
74
|
decorator = ProductDecorator.find_all_by_name("apples")
|
75
75
|
decorator.should be_a Draper::CollectionDecorator
|
76
|
-
decorator.source.should
|
76
|
+
decorator.source.should == found
|
77
77
|
end
|
78
78
|
end
|
79
79
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: draper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.
|
4
|
+
version: 1.0.0.beta5
|
5
5
|
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2012-12-
|
13
|
+
date: 2012-12-27 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activesupport
|
@@ -51,7 +51,7 @@ dependencies:
|
|
51
51
|
requirements:
|
52
52
|
- - ~>
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: 1.0.
|
54
|
+
version: 1.0.3
|
55
55
|
type: :runtime
|
56
56
|
prerelease: false
|
57
57
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -59,7 +59,7 @@ dependencies:
|
|
59
59
|
requirements:
|
60
60
|
- - ~>
|
61
61
|
- !ruby/object:Gem::Version
|
62
|
-
version: 1.0.
|
62
|
+
version: 1.0.3
|
63
63
|
- !ruby/object:Gem::Dependency
|
64
64
|
name: ammeter
|
65
65
|
requirement: !ruby/object:Gem::Requirement
|
@@ -99,7 +99,7 @@ dependencies:
|
|
99
99
|
requirements:
|
100
100
|
- - ~>
|
101
101
|
- !ruby/object:Gem::Version
|
102
|
-
version: '2.
|
102
|
+
version: '2.12'
|
103
103
|
type: :development
|
104
104
|
prerelease: false
|
105
105
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -107,7 +107,23 @@ dependencies:
|
|
107
107
|
requirements:
|
108
108
|
- - ~>
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version: '2.
|
110
|
+
version: '2.12'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rspec-mocks
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
none: false
|
115
|
+
requirements:
|
116
|
+
- - ! '>='
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: 2.12.1
|
119
|
+
type: :development
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
none: false
|
123
|
+
requirements:
|
124
|
+
- - ! '>='
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: 2.12.1
|
111
127
|
- !ruby/object:Gem::Dependency
|
112
128
|
name: yard
|
113
129
|
requirement: !ruby/object:Gem::Requirement
|