draper 1.0.0.beta4 → 1.0.0.beta5

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/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
- `is_decorated?`. [https://github.com/drapergem/draper/commit/834a6fd1f24b5646c333a04a99fe9846a58965d6](https://github.com/drapergem/draper/commit/834a6fd1f24b5646c333a04a99fe9846a58965d6)
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
- gem "sqlite3", :platforms => :ruby
5
+ platforms :ruby do
6
+ gem "sqlite3"
7
+ end
6
8
 
7
- gem "activerecord-jdbcsqlite3-adapter", :platforms => :jruby
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.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.10'
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
- attr_accessor :source, :context, :decorator_class
6
+ attr_reader :source
7
7
  alias_method :to_source, :source
8
8
 
9
- delegate :as_json, *(Array.instance_methods - Object.instance_methods), to: :decorated_collection
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
- # @param [Hash] options (optional)
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.fetch(:with) { self.class.inferred_decorator_class }
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.collect {|item| decorate_item(item) }
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
- "#<CollectionDecorator of #{decorator_class} for #{source.inspect}>"
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
- if decorator_class == :infer
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
- attr_reader :base, :association, :options
4
+ def initialize(owner, association, options)
5
+ options.assert_valid_keys(:with, :scope, :context)
5
6
 
6
- def initialize(base, association, options)
7
- @base = base
7
+ @owner = owner
8
8
  @association = association
9
- options.assert_valid_keys(:with, :scope, :context)
10
- @options = options
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
- decorate
17
+ decorated
16
18
  end
17
19
 
18
- def source
19
- base.source
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(options[:scope]) if options[:scope]
36
+ associated = associated.send(scope) if scope
28
37
  associated
29
38
  end
30
39
  end
31
40
 
32
- def decorate
33
- @decorated ||= decorator_class.send(decorate_method, undecorated, decorator_options)
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 decorator_class
49
- return options[:with] if options[:with]
49
+ def decorator
50
+ return collection_decorator if collection?
50
51
 
51
- if collection?
52
- options[:with] = :infer
53
- Draper::CollectionDecorator
52
+ if decorator_class
53
+ decorator_class.method(:decorate)
54
54
  else
55
- undecorated.decorator_class
55
+ ->(item, options) { item.decorate(options) }
56
56
  end
57
57
  end
58
58
 
59
- def decorator_options
60
- decorator_class # Ensures options[:with] = :infer for unspecified collections
59
+ def collection_decorator
60
+ klass = decorator_class || Draper::CollectionDecorator
61
61
 
62
- dec_options = collection? ? options.slice(:with, :context) : options.slice(:context)
63
- dec_options[:context] = base.context unless dec_options.key?(:context)
64
- if dec_options[:context].respond_to?(:call)
65
- dec_options[:context] = dec_options[:context].call(base.context)
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
@@ -5,11 +5,12 @@ module Draper
5
5
  include Draper::ViewHelpers
6
6
  include ActiveModel::Serialization if defined?(ActiveModel::Serialization)
7
7
 
8
- attr_accessor :source, :context
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,Symbol] :with (self) the class used to decorate
140
- # items, or `:infer` to call each item's `decorate` method instead
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
- self.context = source.context unless options.has_key?(:context)
262
- self.source = source.source
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
@@ -1,3 +1,3 @@
1
1
  module Draper
2
- VERSION = "1.0.0.beta4"
2
+ VERSION = "1.0.0.beta5"
3
3
  end
@@ -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
- subject { Draper::CollectionDecorator.new(source, with: ProductDecorator) }
114
+ let(:subject_class) { Draper::CollectionDecorator }
86
115
 
87
116
  it "uses the :with option" do
88
- subject.decorator_class.should be ProductDecorator
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
- subject { ProductsDecorator.new(source, with: SpecificProductDecorator) }
122
+ let(:subject_class) { ProductsDecorator }
94
123
 
95
124
  it "uses the :with option" do
96
- subject.decorator_class.should be SpecificProductDecorator
125
+ decorator_classes.should == [SpecificProductDecorator, SpecificProductDecorator]
97
126
  end
98
127
  end
99
128
  end
100
129
 
101
- context "when the :with option is not given" do
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
- it "raises an UninferrableDecoratorError" do
104
- expect{Draper::CollectionDecorator.new(source)}.to raise_error Draper::UninferrableDecoratorError
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
- subject { ProductsDecorator.new(source) }
142
+ let(:subject_class) { ProductsDecorator}
110
143
 
111
144
  it "infers the decorator" do
112
- subject.decorator_class.should be ProductDecorator
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.should_receive(:to_ary).and_return(:an_array)
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
- describe "#respond_to?" do
193
- it "returns true for its own methods" do
194
- subject.should respond_to :decorated_collection
195
- end
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
- it "pretends to be the source class" do
254
- subject.kind_of?(source.class).should be_true
255
- subject.is_a?(source.class).should be_true
256
- end
256
+ describe "#to_s" do
257
+ subject { Draper::CollectionDecorator.new(source, options) }
258
+ let(:source) { ["a", "b", "c"] }
257
259
 
258
- it "is still its own class" do
259
- subject.kind_of?(subject.class).should be_true
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
- describe "#method_missing" do
264
- before do
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
- it "proxies unknown methods to the source collection" do
273
- subject.page_number.should == 42
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 be Product.scoped
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(base, association, options) }
4
+ let(:decorated_association) { Draper::DecoratedAssociation.new(owner, :association, options) }
5
5
  let(:source) { Product.new }
6
- let(:base) { source.decorate }
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(base, association, valid_options) }.to_not raise_error
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(base, association, valid_options.merge(foo: 'bar')) }.to raise_error(ArgumentError, 'Unknown key: foo')
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
- subject { decorated_association.call }
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
- context "when the association is empty" do
67
- it "returns an empty collection decorator" do
68
- source.stub(:similar_products).and_return([])
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 non-ActiveModel collection associations" do
77
- let(:association) { :poro_similar_products }
32
+ context "for a singular association" do
33
+ let(:associated) { Product.new }
78
34
 
79
- context "when the association is not empty" do
80
- it "decorates the collection" do
81
- subject.should be_a Draper::CollectionDecorator
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
- context "when the association is present" do
103
- it "decorates the association" do
104
- subject.should be_decorated_with ProductDecorator
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 the association is absent" do
109
- it "doesn't decorate the association" do
110
- source.stub(:previous_version).and_return(nil)
111
- subject.should be_nil
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 non-ActiveModel singular association" do
117
- let(:association) { :poro_previous_version }
53
+ context "for a collection association" do
54
+ let(:associated) { [Product.new, Widget.new] }
118
55
 
119
- context "when the association is present" do
120
- it "decorates the association" do
121
- subject.should be_decorated_with ProductDecorator
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
- context "when the association is absent" do
126
- it "doesn't decorate the association" do
127
- source.stub(:poro_previous_version).and_return(nil)
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 "for a singular association" do
137
- let(:association) { :previous_version }
66
+ context "when :with option is a singular decorator" do
67
+ let(:options) { {with: decorator} }
68
+ let(:decorator) { SpecificProductDecorator }
138
69
 
139
- it "decorates with the specified decorator" do
140
- subject.should be_decorated_with SpecificProductDecorator
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 "for a collection association" do
145
- let(:association) { :similar_products}
146
-
147
- it "decorates with a collection of the specifed decorators" do
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(:association) { :thing }
85
+ let(:associated) { [] }
165
86
  let(:options) { {scope: :foo} }
166
87
 
167
88
  it "applies the scope before decoration" do
168
- scoped = [SomeThing.new]
169
- SomeThing.any_instance.should_receive(:foo).and_return(scoped)
170
- subject.source.should be scoped
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 "#decorator_options" do
207
- subject { decorated_association.send(:decorator_options) }
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
- context "no options" do
213
- it "should return default options" do
214
- should == {with: :infer, context: {}}
215
- end
99
+ context "when :context option was given" do
100
+ let(:options) { {context: context} }
216
101
 
217
- it "should set with: to :infer" do
218
- decorated_association.send(:options).should == options
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
- context "option with: ProductDecorator" do
225
- let(:options) { {with: ProductDecorator} }
226
- it "should pass with: from options" do
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
- context "option scope: :to_a" do
232
- let(:options) { {scope: :to_a} }
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 "base has context" do
240
- let(:base) { source.decorate(context: {some: 'context'}) }
115
+ context "and is not callable" do
116
+ let(:context) { :static_context }
241
117
 
242
- context "no options" do
243
- it "should return context from base" do
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 "singular association" do
265
- let(:association) { :previous_version }
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 be source
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
 
@@ -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 be found
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.beta4
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-18 00:00:00.000000000 Z
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.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.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.10'
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.10'
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