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 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