drape 1.0.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.codeclimate.yml +27 -0
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/.rubocop.yml +1159 -0
- data/.travis.yml +14 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +230 -0
- data/CONTRIBUTING.md +20 -0
- data/Gemfile +16 -0
- data/Guardfile +26 -0
- data/LICENSE +7 -0
- data/README.md +592 -0
- data/Rakefile +76 -0
- data/drape.gemspec +31 -0
- data/gemfiles/5.0.gemfile +8 -0
- data/lib/drape.rb +63 -0
- data/lib/drape/automatic_delegation.rb +55 -0
- data/lib/drape/collection_decorator.rb +102 -0
- data/lib/drape/decoratable.rb +93 -0
- data/lib/drape/decoratable/equality.rb +26 -0
- data/lib/drape/decorated_association.rb +33 -0
- data/lib/drape/decorates_assigned.rb +44 -0
- data/lib/drape/decorator.rb +282 -0
- data/lib/drape/delegation.rb +13 -0
- data/lib/drape/factory.rb +91 -0
- data/lib/drape/finders.rb +36 -0
- data/lib/drape/helper_proxy.rb +43 -0
- data/lib/drape/helper_support.rb +5 -0
- data/lib/drape/lazy_helpers.rb +13 -0
- data/lib/drape/railtie.rb +69 -0
- data/lib/drape/tasks/test.rake +9 -0
- data/lib/drape/test/devise_helper.rb +30 -0
- data/lib/drape/test/minitest_integration.rb +6 -0
- data/lib/drape/test/rspec_integration.rb +20 -0
- data/lib/drape/test_case.rb +42 -0
- data/lib/drape/undecorate.rb +9 -0
- data/lib/drape/version.rb +3 -0
- data/lib/drape/view_context.rb +104 -0
- data/lib/drape/view_context/build_strategy.rb +46 -0
- data/lib/drape/view_helpers.rb +34 -0
- data/lib/generators/controller_override.rb +17 -0
- data/lib/generators/mini_test/decorator_generator.rb +20 -0
- data/lib/generators/mini_test/templates/decorator_spec.rb +4 -0
- data/lib/generators/mini_test/templates/decorator_test.rb +4 -0
- data/lib/generators/rails/decorator_generator.rb +36 -0
- data/lib/generators/rails/templates/decorator.rb +19 -0
- data/lib/generators/rspec/decorator_generator.rb +9 -0
- data/lib/generators/rspec/templates/decorator_spec.rb +4 -0
- data/lib/generators/test_unit/decorator_generator.rb +9 -0
- data/lib/generators/test_unit/templates/decorator_test.rb +4 -0
- data/spec/draper/collection_decorator_spec.rb +305 -0
- data/spec/draper/decoratable/equality_spec.rb +10 -0
- data/spec/draper/decoratable_spec.rb +203 -0
- data/spec/draper/decorated_association_spec.rb +85 -0
- data/spec/draper/decorates_assigned_spec.rb +70 -0
- data/spec/draper/decorator_spec.rb +828 -0
- data/spec/draper/factory_spec.rb +250 -0
- data/spec/draper/finders_spec.rb +165 -0
- data/spec/draper/helper_proxy_spec.rb +61 -0
- data/spec/draper/lazy_helpers_spec.rb +21 -0
- data/spec/draper/undecorate_spec.rb +19 -0
- data/spec/draper/view_context/build_strategy_spec.rb +116 -0
- data/spec/draper/view_context_spec.rb +160 -0
- data/spec/draper/view_helpers_spec.rb +8 -0
- data/spec/dummy/.gitignore +21 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/config/manifest.js +3 -0
- data/spec/dummy/app/assets/images/.keep +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +16 -0
- data/spec/dummy/app/assets/javascripts/cable.js +13 -0
- data/spec/dummy/app/assets/javascripts/channels/.keep +0 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/channels/application_cable/channel.rb +4 -0
- data/spec/dummy/app/channels/application_cable/connection.rb +5 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/controllers/concerns/.keep +0 -0
- data/spec/dummy/app/controllers/posts_controller.rb +21 -0
- data/spec/dummy/app/decorators/mongoid_post_decorator.rb +4 -0
- data/spec/dummy/app/decorators/post_decorator.rb +59 -0
- data/spec/dummy/app/helpers/application_helper.rb +5 -0
- data/spec/dummy/app/jobs/application_job.rb +2 -0
- data/spec/dummy/app/mailers/application_mailer.rb +4 -0
- data/spec/dummy/app/mailers/post_mailer.rb +18 -0
- data/spec/dummy/app/models/admin.rb +5 -0
- data/spec/dummy/app/models/application_record.rb +3 -0
- data/spec/dummy/app/models/concerns/.keep +0 -0
- data/spec/dummy/app/models/mongoid_post.rb +5 -0
- data/spec/dummy/app/models/post.rb +3 -0
- data/spec/dummy/app/models/user.rb +5 -0
- data/spec/dummy/app/views/layouts/application.html.erb +9 -0
- data/spec/dummy/app/views/layouts/mailer.html.erb +13 -0
- data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
- data/spec/dummy/app/views/post_mailer/decorated_email.html.erb +31 -0
- data/spec/dummy/app/views/posts/_post.html.erb +40 -0
- data/spec/dummy/app/views/posts/show.html.erb +1 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +9 -0
- data/spec/dummy/bin/rake +9 -0
- data/spec/dummy/bin/setup +34 -0
- data/spec/dummy/bin/spring +15 -0
- data/spec/dummy/bin/update +29 -0
- data/spec/dummy/config.ru +5 -0
- data/spec/dummy/config/application.rb +21 -0
- data/spec/dummy/config/boot.rb +2 -0
- data/spec/dummy/config/cable.yml +10 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +51 -0
- data/spec/dummy/config/environments/production.rb +91 -0
- data/spec/dummy/config/environments/test.rb +42 -0
- data/spec/dummy/config/initializers/active_record_belongs_to_required_by_default.rb +6 -0
- data/spec/dummy/config/initializers/application_controller_renderer.rb +6 -0
- data/spec/dummy/config/initializers/assets.rb +11 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/callback_terminator.rb +6 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
- data/spec/dummy/config/initializers/devise.rb +3 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/per_form_csrf_tokens.rb +4 -0
- data/spec/dummy/config/initializers/request_forgery_protection.rb +4 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/ssl_options.rb +4 -0
- data/spec/dummy/config/initializers/to_time_preserves_timezone.rb +10 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/mongoid.yml +16 -0
- data/spec/dummy/config/puma.rb +47 -0
- data/spec/dummy/config/routes.rb +8 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/config/spring.rb +6 -0
- data/spec/dummy/db/migrate/20121019115657_create_posts.rb +7 -0
- data/spec/dummy/db/schema.rb +19 -0
- data/spec/dummy/db/seeds.rb +2 -0
- data/spec/dummy/lib/assets/.keep +0 -0
- data/spec/dummy/lib/tasks/.keep +0 -0
- data/spec/dummy/lib/tasks/test.rake +16 -0
- data/spec/dummy/log/.keep +0 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
- data/spec/dummy/public/apple-touch-icon.png +0 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/public/robots.txt +5 -0
- data/spec/dummy/spec/decorators/active_model_serializers_spec.rb +12 -0
- data/spec/dummy/spec/decorators/devise_spec.rb +64 -0
- data/spec/dummy/spec/decorators/helpers_spec.rb +21 -0
- data/spec/dummy/spec/decorators/post_decorator_spec.rb +66 -0
- data/spec/dummy/spec/decorators/spec_type_spec.rb +7 -0
- data/spec/dummy/spec/decorators/view_context_spec.rb +22 -0
- data/spec/dummy/spec/mailers/post_mailer_spec.rb +33 -0
- data/spec/dummy/spec/models/mongoid_post_spec.rb +8 -0
- data/spec/dummy/spec/models/post_spec.rb +6 -0
- data/spec/dummy/spec/shared_examples/decoratable.rb +26 -0
- data/spec/dummy/spec/spec_helper.rb +8 -0
- data/spec/dummy/test/controllers/.keep +0 -0
- data/spec/dummy/test/fixtures/.keep +0 -0
- data/spec/dummy/test/fixtures/files/.keep +0 -0
- data/spec/dummy/test/helpers/.keep +0 -0
- data/spec/dummy/test/integration/.keep +0 -0
- data/spec/dummy/test/mailers/.keep +0 -0
- data/spec/dummy/test/models/.keep +0 -0
- data/spec/dummy/test/test_helper.rb +10 -0
- data/spec/dummy/vendor/assets/javascripts/.keep +0 -0
- data/spec/dummy/vendor/assets/stylesheets/.keep +0 -0
- data/spec/generators/controller/controller_generator_spec.rb +24 -0
- data/spec/generators/decorator/decorator_generator_spec.rb +94 -0
- data/spec/integration/integration_spec.rb +68 -0
- data/spec/performance/active_record.rb +4 -0
- data/spec/performance/benchmark.rb +57 -0
- data/spec/performance/decorators.rb +41 -0
- data/spec/performance/models.rb +20 -0
- data/spec/spec_helper.rb +41 -0
- data/spec/support/dummy_app.rb +88 -0
- data/spec/support/matchers/have_text.rb +50 -0
- data/spec/support/shared_examples/decoratable_equality.rb +40 -0
- data/spec/support/shared_examples/view_helpers.rb +39 -0
- metadata +497 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'support/shared_examples/decoratable_equality'
|
|
3
|
+
|
|
4
|
+
module Drape
|
|
5
|
+
describe Decoratable do
|
|
6
|
+
describe '#decorate' do
|
|
7
|
+
it 'returns a decorator for self' do
|
|
8
|
+
product = Product.new
|
|
9
|
+
decorator = product.decorate
|
|
10
|
+
|
|
11
|
+
expect(decorator).to be_a ProductDecorator
|
|
12
|
+
expect(decorator.object).to be product
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'accepts context' do
|
|
16
|
+
context = { some: 'context' }
|
|
17
|
+
decorator = Product.new.decorate(context: context)
|
|
18
|
+
|
|
19
|
+
expect(decorator.context).to be context
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'uses the #decorator_class' do
|
|
23
|
+
product = Product.new
|
|
24
|
+
product.stub decorator_class: OtherDecorator
|
|
25
|
+
|
|
26
|
+
expect(product.decorate).to be_an_instance_of OtherDecorator
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
describe '#applied_decorators' do
|
|
31
|
+
it 'returns an empty list' do
|
|
32
|
+
expect(Product.new.applied_decorators).to eq []
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
describe '#decorated_with?' do
|
|
37
|
+
it 'returns false' do
|
|
38
|
+
expect(Product.new).not_to be_decorated_with Decorator
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe '#decorated?' do
|
|
43
|
+
it 'returns false' do
|
|
44
|
+
expect(Product.new).not_to be_decorated
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
describe '#decorator_class?' do
|
|
49
|
+
it 'returns true for decoratable model' do
|
|
50
|
+
expect(Product.new.decorator_class?).to be_truthy
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'returns false for non-decoratable model' do
|
|
54
|
+
expect(Model.new.decorator_class?).to be_falsey
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
describe '.decorator_class?' do
|
|
59
|
+
it 'returns true for decoratable model' do
|
|
60
|
+
expect(Product.decorator_class?).to be_truthy
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it 'returns false for non-decoratable model' do
|
|
64
|
+
expect(Model.decorator_class?).to be_falsey
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
describe '#decorator_class' do
|
|
69
|
+
it 'delegates to .decorator_class' do
|
|
70
|
+
product = Product.new
|
|
71
|
+
|
|
72
|
+
Product.should_receive(:decorator_class).and_return(:some_decorator)
|
|
73
|
+
expect(product.decorator_class).to be :some_decorator
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
describe '#==' do
|
|
78
|
+
it_behaves_like 'decoration-aware #==', Product.new
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
describe '#===' do
|
|
82
|
+
it 'is true when #== is true' do
|
|
83
|
+
product = Product.new
|
|
84
|
+
|
|
85
|
+
product.should_receive(:==).and_return(true)
|
|
86
|
+
expect(product === :anything).to be_truthy
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it 'is false when #== is false' do
|
|
90
|
+
product = Product.new
|
|
91
|
+
|
|
92
|
+
product.should_receive(:==).and_return(false)
|
|
93
|
+
expect(product === :anything).to be_falsey
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
describe '.====' do
|
|
98
|
+
it 'is true for an instance' do
|
|
99
|
+
expect(Product === Product.new).to be_truthy
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it 'is true for a derived instance' do
|
|
103
|
+
expect(Product === Class.new(Product).new).to be_truthy
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it 'is false for an unrelated instance' do
|
|
107
|
+
expect(Product === Model.new).to be_falsey
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it 'is true for a decorated instance' do
|
|
111
|
+
decorator = double(object: Product.new)
|
|
112
|
+
|
|
113
|
+
expect(Product === decorator).to be_truthy
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it 'is true for a decorated derived instance' do
|
|
117
|
+
decorator = double(object: Class.new(Product).new)
|
|
118
|
+
|
|
119
|
+
expect(Product === decorator).to be_truthy
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it 'is false for a decorated unrelated instance' do
|
|
123
|
+
decorator = double(object: Model.new)
|
|
124
|
+
|
|
125
|
+
expect(Product === decorator).to be_falsey
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
describe '.decorate' do
|
|
130
|
+
let(:scoping_method) { Rails::VERSION::MAJOR >= 4 ? :all : :scoped }
|
|
131
|
+
|
|
132
|
+
it 'calls #decorate_collection on .decorator_class' do
|
|
133
|
+
scoped = [Product.new]
|
|
134
|
+
Product.stub scoping_method => scoped
|
|
135
|
+
|
|
136
|
+
Product.decorator_class
|
|
137
|
+
.should_receive(:decorate_collection).with(scoped, with: nil)
|
|
138
|
+
.and_return(:decorated_collection)
|
|
139
|
+
|
|
140
|
+
expect(Product.decorate).to be :decorated_collection
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it 'accepts options' do
|
|
144
|
+
options = { with: ProductDecorator, context: { some: 'context' } }
|
|
145
|
+
Product.stub scoping_method => []
|
|
146
|
+
|
|
147
|
+
Product.decorator_class.should_receive(:decorate_collection).with([], options)
|
|
148
|
+
Product.decorate(options)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
describe '.decorator_class' do
|
|
153
|
+
context 'for classes' do
|
|
154
|
+
it 'infers the decorator from the class' do
|
|
155
|
+
expect(Product.decorator_class).to be ProductDecorator
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
context 'without a decorator on its own' do
|
|
159
|
+
it 'infers the decorator from a superclass' do
|
|
160
|
+
expect(SpecialProduct.decorator_class).to be ProductDecorator
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
context 'for ActiveModel classes' do
|
|
166
|
+
it 'infers the decorator from the model name' do
|
|
167
|
+
allow(Product).to receive(:model_name) { 'Other' }
|
|
168
|
+
|
|
169
|
+
expect(Product.decorator_class).to be OtherDecorator
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
context 'in a namespace' do
|
|
174
|
+
context 'for classes' do
|
|
175
|
+
it 'infers the decorator from the class' do
|
|
176
|
+
expect(Namespaced::Product.decorator_class).to be Namespaced::ProductDecorator
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
context 'for ActiveModel classes' do
|
|
181
|
+
it 'infers the decorator from the model name' do
|
|
182
|
+
Namespaced::Product.stub(:model_name).and_return('Namespaced::Other')
|
|
183
|
+
|
|
184
|
+
expect(Namespaced::Product.decorator_class).to be Namespaced::OtherDecorator
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
context "when the decorator can't be inferred" do
|
|
190
|
+
it 'throws an UninferrableDecoratorError' do
|
|
191
|
+
expect { Model.decorator_class }.to raise_error UninferrableDecoratorError
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
context 'when an unrelated NameError is thrown' do
|
|
196
|
+
it 're-raises that error' do
|
|
197
|
+
String.any_instance.stub(:constantize) { Drape::Base }
|
|
198
|
+
expect { Product.decorator_class }.to raise_error NameError, /Drape::Base/
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module Drape
|
|
4
|
+
describe DecoratedAssociation do
|
|
5
|
+
describe '#initialize' do
|
|
6
|
+
it 'accepts valid options' do
|
|
7
|
+
valid_options = { with: Decorator, scope: :foo, context: {} }
|
|
8
|
+
expect { DecoratedAssociation.new(Decorator.new(Model.new), :association, valid_options) }
|
|
9
|
+
.not_to raise_error
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it 'rejects invalid options' do
|
|
13
|
+
expect { DecoratedAssociation.new(Decorator.new(Model.new), :association, foo: 'bar') }
|
|
14
|
+
.to raise_error ArgumentError, /Unknown key/
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'creates a factory' do
|
|
18
|
+
options = { with: Decorator, context: { foo: 'bar' } }
|
|
19
|
+
|
|
20
|
+
Factory.should_receive(:new).with(options)
|
|
21
|
+
DecoratedAssociation.new(double, :association, options)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
describe ':with option' do
|
|
25
|
+
it 'defaults to nil' do
|
|
26
|
+
Factory.should_receive(:new).with(with: nil, context: anything)
|
|
27
|
+
DecoratedAssociation.new(double, :association, {})
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe ':context option' do
|
|
32
|
+
it 'defaults to the identity function' do
|
|
33
|
+
Factory.should_receive(:new) do |options|
|
|
34
|
+
options[:context].call(:anything) == :anything
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
DecoratedAssociation.new(double, :association, {})
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe '#call' do
|
|
43
|
+
it 'calls the factory' do
|
|
44
|
+
factory = double
|
|
45
|
+
Factory.stub new: factory
|
|
46
|
+
associated = double
|
|
47
|
+
owner_context = { foo: 'bar' }
|
|
48
|
+
object = double(association: associated)
|
|
49
|
+
owner = double(object: object, context: owner_context)
|
|
50
|
+
decorated_association = DecoratedAssociation.new(owner, :association, {})
|
|
51
|
+
decorated = double
|
|
52
|
+
|
|
53
|
+
factory.should_receive(:decorate).with(associated, context_args: owner_context).and_return(decorated)
|
|
54
|
+
expect(decorated_association.call).to be decorated
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it 'memoizes' do
|
|
58
|
+
factory = double
|
|
59
|
+
Factory.stub new: factory
|
|
60
|
+
owner = double(object: double(association: double), context: {})
|
|
61
|
+
decorated_association = DecoratedAssociation.new(owner, :association, {})
|
|
62
|
+
decorated = double
|
|
63
|
+
|
|
64
|
+
factory.should_receive(:decorate).once.and_return(decorated)
|
|
65
|
+
expect(decorated_association.call).to be decorated
|
|
66
|
+
expect(decorated_association.call).to be decorated
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
context 'when the :scope option was given' do
|
|
70
|
+
it 'applies the scope before decoration' do
|
|
71
|
+
factory = double
|
|
72
|
+
Factory.stub new: factory
|
|
73
|
+
scoped = double
|
|
74
|
+
object = double(association: double(applied_scope: scoped))
|
|
75
|
+
owner = double(object: object, context: {})
|
|
76
|
+
decorated_association = DecoratedAssociation.new(owner, :association, scope: :applied_scope)
|
|
77
|
+
decorated = double
|
|
78
|
+
|
|
79
|
+
factory.should_receive(:decorate).with(scoped, anything).and_return(decorated)
|
|
80
|
+
expect(decorated_association.call).to be decorated
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module Drape
|
|
4
|
+
describe DecoratesAssigned do
|
|
5
|
+
let(:controller_class) do
|
|
6
|
+
Class.new do
|
|
7
|
+
extend DecoratesAssigned
|
|
8
|
+
|
|
9
|
+
def self.helper_method(method)
|
|
10
|
+
helper_methods << method
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.helper_methods
|
|
14
|
+
@helper_methods ||= []
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
describe '.decorates_assigned' do
|
|
20
|
+
it 'adds helper methods' do
|
|
21
|
+
controller_class.decorates_assigned :article, :author
|
|
22
|
+
|
|
23
|
+
expect(controller_class.instance_methods).to include :article
|
|
24
|
+
expect(controller_class.instance_methods).to include :author
|
|
25
|
+
|
|
26
|
+
expect(controller_class.helper_methods).to include :article
|
|
27
|
+
expect(controller_class.helper_methods).to include :author
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'creates a factory' do
|
|
31
|
+
Factory.should_receive(:new).once
|
|
32
|
+
controller_class.decorates_assigned :article, :author
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'passes options to the factory' do
|
|
36
|
+
options = { foo: 'bar' }
|
|
37
|
+
|
|
38
|
+
Factory.should_receive(:new).with(options)
|
|
39
|
+
controller_class.decorates_assigned :article, :author, options
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe 'the generated method' do
|
|
43
|
+
it 'decorates the instance variable' do
|
|
44
|
+
object = double
|
|
45
|
+
factory = double
|
|
46
|
+
Factory.stub new: factory
|
|
47
|
+
|
|
48
|
+
controller_class.decorates_assigned :article
|
|
49
|
+
controller = controller_class.new
|
|
50
|
+
controller.instance_variable_set '@article', object
|
|
51
|
+
|
|
52
|
+
factory.should_receive(:decorate).with(object, context_args: controller).and_return(:decorated)
|
|
53
|
+
expect(controller.article).to be :decorated
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'memoizes' do
|
|
57
|
+
factory = double
|
|
58
|
+
Factory.stub new: factory
|
|
59
|
+
|
|
60
|
+
controller_class.decorates_assigned :article
|
|
61
|
+
controller = controller_class.new
|
|
62
|
+
|
|
63
|
+
factory.should_receive(:decorate).once
|
|
64
|
+
controller.article
|
|
65
|
+
controller.article
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,828 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'support/shared_examples/view_helpers'
|
|
3
|
+
|
|
4
|
+
module Drape
|
|
5
|
+
describe Decorator do
|
|
6
|
+
it_behaves_like 'view helpers', Decorator.new(Model.new)
|
|
7
|
+
|
|
8
|
+
describe '#initialize' do
|
|
9
|
+
describe 'options validation' do
|
|
10
|
+
it 'does not raise error on valid options' do
|
|
11
|
+
valid_options = { context: {} }
|
|
12
|
+
expect { Decorator.new(Model.new, valid_options) }.not_to raise_error
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'raises error on invalid options' do
|
|
16
|
+
expect { Decorator.new(Model.new, foo: 'bar') }.to raise_error ArgumentError, /Unknown key/
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'sets the object' do
|
|
21
|
+
object = Model.new
|
|
22
|
+
decorator = Decorator.new(object)
|
|
23
|
+
|
|
24
|
+
expect(decorator.object).to be object
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'stores context' do
|
|
28
|
+
context = { some: 'context' }
|
|
29
|
+
decorator = Decorator.new(Model.new, context: context)
|
|
30
|
+
|
|
31
|
+
expect(decorator.context).to be context
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
context 'when decorating an instance of itself' do
|
|
35
|
+
it 'applies to the object instead' do
|
|
36
|
+
object = Model.new
|
|
37
|
+
decorated = Decorator.new(object)
|
|
38
|
+
redecorated = Decorator.new(decorated)
|
|
39
|
+
|
|
40
|
+
expect(redecorated.object).to be object
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
context 'with context' do
|
|
44
|
+
it 'overwrites existing context' do
|
|
45
|
+
decorated = Decorator.new(Model.new, context: { some: 'context' })
|
|
46
|
+
new_context = { other: 'context' }
|
|
47
|
+
redecorated = Decorator.new(decorated, context: new_context)
|
|
48
|
+
|
|
49
|
+
expect(redecorated.context).to be new_context
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
context 'without context' do
|
|
54
|
+
it 'preserves existing context' do
|
|
55
|
+
old_context = { some: 'context' }
|
|
56
|
+
decorated = Decorator.new(Model.new, context: old_context)
|
|
57
|
+
redecorated = Decorator.new(decorated)
|
|
58
|
+
|
|
59
|
+
expect(redecorated.context).to be old_context
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it 'decorates other decorators' do
|
|
65
|
+
decorated = OtherDecorator.new(Model.new)
|
|
66
|
+
redecorated = Decorator.new(decorated)
|
|
67
|
+
|
|
68
|
+
expect(redecorated.object).to be decorated
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
context 'when it has been applied previously' do
|
|
72
|
+
it 'warns' do
|
|
73
|
+
decorated = OtherDecorator.new(Decorator.new(Model.new))
|
|
74
|
+
|
|
75
|
+
warning_message = nil
|
|
76
|
+
Object.any_instance.stub(:warn) { |_instance, message| warning_message = message }
|
|
77
|
+
|
|
78
|
+
expect { Decorator.new(decorated) }.to change { warning_message }
|
|
79
|
+
expect(warning_message).to start_with 'Reapplying Drape::Decorator'
|
|
80
|
+
expect(warning_message).to include caller(1).first
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it 'decorates anyway' do
|
|
84
|
+
decorated = OtherDecorator.new(Decorator.new(Model.new))
|
|
85
|
+
Object.any_instance.stub(:warn)
|
|
86
|
+
redecorated = Decorator.decorate(decorated)
|
|
87
|
+
|
|
88
|
+
expect(redecorated.object).to be decorated
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
describe '#context=' do
|
|
94
|
+
it 'modifies the context' do
|
|
95
|
+
decorator = Decorator.new(Model.new, context: { some: 'context' })
|
|
96
|
+
new_context = { other: 'context' }
|
|
97
|
+
|
|
98
|
+
decorator.context = new_context
|
|
99
|
+
expect(decorator.context).to be new_context
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
describe '.decorate_collection' do
|
|
104
|
+
describe 'options validation' do
|
|
105
|
+
before { CollectionDecorator.stub(:new) }
|
|
106
|
+
|
|
107
|
+
it 'does not raise error on valid options' do
|
|
108
|
+
valid_options = { with: OtherDecorator, context: {} }
|
|
109
|
+
expect { Decorator.decorate_collection([], valid_options) }.not_to raise_error
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it 'raises error on invalid options' do
|
|
113
|
+
expect { Decorator.decorate_collection([], foo: 'bar') }.to raise_error ArgumentError, /Unknown key/
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
context 'without a custom collection decorator' do
|
|
118
|
+
it 'creates a CollectionDecorator using itself for each item' do
|
|
119
|
+
object = [Model.new]
|
|
120
|
+
|
|
121
|
+
CollectionDecorator.should_receive(:new).with(object, with: Decorator)
|
|
122
|
+
Decorator.decorate_collection(object)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it 'passes options to the collection decorator' do
|
|
126
|
+
options = { with: OtherDecorator, context: { some: 'context' } }
|
|
127
|
+
|
|
128
|
+
CollectionDecorator.should_receive(:new).with([], options)
|
|
129
|
+
Decorator.decorate_collection([], options)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
context 'with a custom collection decorator' do
|
|
134
|
+
it 'creates a custom collection decorator using itself for each item' do
|
|
135
|
+
object = [Model.new]
|
|
136
|
+
|
|
137
|
+
ProductsDecorator.should_receive(:new).with(object, with: ProductDecorator)
|
|
138
|
+
ProductDecorator.decorate_collection(object)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
it 'passes options to the collection decorator' do
|
|
142
|
+
options = { with: OtherDecorator, context: { some: 'context' } }
|
|
143
|
+
|
|
144
|
+
ProductsDecorator.should_receive(:new).with([], options)
|
|
145
|
+
ProductDecorator.decorate_collection([], options)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
context 'when a NameError is thrown' do
|
|
150
|
+
it 're-raises that error' do
|
|
151
|
+
String.any_instance.stub(:constantize) { Drape::DecoratedEnumerableProxy }
|
|
152
|
+
expect { ProductDecorator.decorate_collection([]) }.to raise_error NameError, /Drape::DecoratedEnumerableProxy/
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
describe '.decorates' do
|
|
158
|
+
protect_class Decorator
|
|
159
|
+
|
|
160
|
+
it 'sets .object_class with a symbol' do
|
|
161
|
+
Decorator.decorates :product
|
|
162
|
+
|
|
163
|
+
expect(Decorator.object_class).to be Product
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
it 'sets .object_class with a string' do
|
|
167
|
+
Decorator.decorates 'product'
|
|
168
|
+
|
|
169
|
+
expect(Decorator.object_class).to be Product
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
it 'sets .object_class with a class' do
|
|
173
|
+
Decorator.decorates Product
|
|
174
|
+
|
|
175
|
+
expect(Decorator.object_class).to be Product
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
describe '.object_class' do
|
|
180
|
+
protect_class ProductDecorator
|
|
181
|
+
protect_class Namespaced::ProductDecorator
|
|
182
|
+
|
|
183
|
+
context 'when not set by .decorates' do
|
|
184
|
+
it "raises an UninferrableSourceError for a so-named 'Decorator'" do
|
|
185
|
+
expect { Decorator.object_class }.to raise_error UninferrableSourceError
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
it 'raises an UninferrableSourceError for anonymous decorators' do
|
|
189
|
+
expect { Class.new(Decorator).object_class }.to raise_error UninferrableSourceError
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
it 'raises an UninferrableSourceError for a decorator without a model' do
|
|
193
|
+
skip
|
|
194
|
+
expect { OtherDecorator.object_class }.to raise_error UninferrableSourceError
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
it 'raises an UninferrableSourceError for other naming conventions' do
|
|
198
|
+
expect { ProductPresenter.object_class }.to raise_error UninferrableSourceError
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
it "infers the source for '<Model>Decorator'" do
|
|
202
|
+
expect(ProductDecorator.object_class).to be Product
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
it 'infers namespaced sources' do
|
|
206
|
+
expect(Namespaced::ProductDecorator.object_class).to be Namespaced::Product
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
context 'when an unrelated NameError is thrown' do
|
|
210
|
+
it 're-raises that error' do
|
|
211
|
+
String.any_instance.stub(:constantize) { SomethingThatDoesntExist }
|
|
212
|
+
expect { ProductDecorator.object_class }.to raise_error NameError, /SomethingThatDoesntExist/
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
it 'is aliased to .source_class' do
|
|
218
|
+
expect(ProductDecorator.source_class).to be Product
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
describe '.object_class?' do
|
|
223
|
+
it 'returns truthy when .object_class is set' do
|
|
224
|
+
Decorator.stub(:object_class).and_return(Model)
|
|
225
|
+
|
|
226
|
+
expect(Decorator.object_class?).to be_truthy
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
it 'returns false when .object_class is not inferrable' do
|
|
230
|
+
Decorator.stub(:object_class).and_raise(UninferrableSourceError.new(Decorator))
|
|
231
|
+
|
|
232
|
+
expect(Decorator.object_class?).to be_falsey
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
it 'is aliased to .source_class?' do
|
|
236
|
+
Decorator.stub(:object_class).and_return(Model)
|
|
237
|
+
|
|
238
|
+
expect(Decorator.source_class?).to be_truthy
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
describe '.decorates_association' do
|
|
243
|
+
protect_class Decorator
|
|
244
|
+
|
|
245
|
+
describe 'options validation' do
|
|
246
|
+
before { DecoratedAssociation.stub(:new).and_return(-> {}) }
|
|
247
|
+
|
|
248
|
+
it 'does not raise error on valid options' do
|
|
249
|
+
valid_options = { with: Class, scope: :sorted, context: {} }
|
|
250
|
+
expect { Decorator.decorates_association(:children, valid_options) }.not_to raise_error
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
it 'raises error on invalid options' do
|
|
254
|
+
expect { Decorator.decorates_association(:children, foo: 'bar') }.to raise_error ArgumentError, /Unknown key/
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
describe 'defines an association method' do
|
|
259
|
+
it 'creates a DecoratedAssociation' do
|
|
260
|
+
options = { with: Class.new, scope: :foo, context: {} }
|
|
261
|
+
Decorator.decorates_association :children, options
|
|
262
|
+
decorator = Decorator.new(Model.new)
|
|
263
|
+
|
|
264
|
+
DecoratedAssociation.should_receive(:new).with(decorator, :children, options).and_return(-> {})
|
|
265
|
+
decorator.children
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
it 'memoizes the DecoratedAssociation' do
|
|
269
|
+
Decorator.decorates_association :children
|
|
270
|
+
decorator = Decorator.new(Model.new)
|
|
271
|
+
|
|
272
|
+
DecoratedAssociation.should_receive(:new).once.and_return(-> {})
|
|
273
|
+
decorator.children
|
|
274
|
+
decorator.children
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
it 'calls the DecoratedAssociation' do
|
|
278
|
+
Decorator.decorates_association :children
|
|
279
|
+
decorator = Decorator.new(Model.new)
|
|
280
|
+
decorated_association = -> {}
|
|
281
|
+
DecoratedAssociation.stub(:new).and_return(decorated_association)
|
|
282
|
+
|
|
283
|
+
decorated_association.should_receive(:call).and_return(:decorated)
|
|
284
|
+
expect(decorator.children).to be :decorated
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
describe '.decorates_associations' do
|
|
290
|
+
protect_class Decorator
|
|
291
|
+
|
|
292
|
+
it 'decorates each of the associations' do
|
|
293
|
+
Decorator.should_receive(:decorates_association).with(:friends, {})
|
|
294
|
+
Decorator.should_receive(:decorates_association).with(:enemies, {})
|
|
295
|
+
Decorator.decorates_associations :friends, :enemies
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
it 'dispatches options' do
|
|
299
|
+
options = { with: Class.new, scope: :foo, context: {} }
|
|
300
|
+
|
|
301
|
+
Decorator.should_receive(:decorates_association).with(:friends, options)
|
|
302
|
+
Decorator.should_receive(:decorates_association).with(:enemies, options)
|
|
303
|
+
Decorator.decorates_associations :friends, :enemies, options
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
describe '#applied_decorators' do
|
|
308
|
+
it 'returns a list of decorators applied to a model' do
|
|
309
|
+
decorator = ProductDecorator.new(OtherDecorator.new(Decorator.new(Model.new)))
|
|
310
|
+
|
|
311
|
+
expect(decorator.applied_decorators).to eq [Decorator, OtherDecorator, ProductDecorator]
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
describe '#decorated_with?' do
|
|
316
|
+
it 'checks if a decorator has been applied to a model' do
|
|
317
|
+
decorator = ProductDecorator.new(Decorator.new(Model.new))
|
|
318
|
+
|
|
319
|
+
expect(decorator).to be_decorated_with Decorator
|
|
320
|
+
expect(decorator).to be_decorated_with ProductDecorator
|
|
321
|
+
expect(decorator).not_to be_decorated_with OtherDecorator
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
describe '#decorated?' do
|
|
326
|
+
it 'returns true' do
|
|
327
|
+
decorator = Decorator.new(Model.new)
|
|
328
|
+
|
|
329
|
+
expect(decorator).to be_decorated
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
describe '#object' do
|
|
334
|
+
it 'returns the wrapped object' do
|
|
335
|
+
object = Model.new
|
|
336
|
+
decorator = Decorator.new(object)
|
|
337
|
+
|
|
338
|
+
expect(decorator.object).to be object
|
|
339
|
+
expect(decorator.model).to be object
|
|
340
|
+
expect(decorator.to_source).to be object
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
it 'is aliased to #model' do
|
|
344
|
+
object = Model.new
|
|
345
|
+
decorator = Decorator.new(object)
|
|
346
|
+
|
|
347
|
+
expect(decorator.model).to be object
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
it 'is aliased to #source' do
|
|
351
|
+
object = Model.new
|
|
352
|
+
decorator = Decorator.new(object)
|
|
353
|
+
|
|
354
|
+
expect(decorator.source).to be object
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
it 'is aliased to #to_source' do
|
|
358
|
+
object = Model.new
|
|
359
|
+
decorator = Decorator.new(object)
|
|
360
|
+
|
|
361
|
+
expect(decorator.to_source).to be object
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
describe 'aliasing object to object class name' do
|
|
366
|
+
context 'when object_class is inferrable from the decorator name' do
|
|
367
|
+
it 'aliases object to the object class name' do
|
|
368
|
+
object = double
|
|
369
|
+
decorator = ProductDecorator.new(object)
|
|
370
|
+
|
|
371
|
+
expect(decorator.product).to be object
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
context 'when object_class is set by decorates' do
|
|
376
|
+
it 'aliases object to the object class name' do
|
|
377
|
+
decorator_class = Class.new(Decorator) { decorates Product }
|
|
378
|
+
object = double
|
|
379
|
+
decorator = decorator_class.new(object)
|
|
380
|
+
|
|
381
|
+
expect(decorator.product).to be object
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
context "when object_class's name is several words long" do
|
|
386
|
+
it 'underscores the method name' do
|
|
387
|
+
stub_const 'LongWindedModel', Class.new
|
|
388
|
+
decorator_class = Class.new(Decorator) { decorates LongWindedModel }
|
|
389
|
+
object = double
|
|
390
|
+
decorator = decorator_class.new(object)
|
|
391
|
+
|
|
392
|
+
expect(decorator.long_winded_model).to be object
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
context 'when object_class is not set' do
|
|
397
|
+
it 'does not alias object' do
|
|
398
|
+
decorator_class = Class.new(Decorator)
|
|
399
|
+
|
|
400
|
+
expect(decorator_class.instance_methods).to eq Decorator.instance_methods
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
describe '#to_model' do
|
|
406
|
+
it 'returns the decorator' do
|
|
407
|
+
decorator = Decorator.new(Model.new)
|
|
408
|
+
|
|
409
|
+
expect(decorator.to_model).to be decorator
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
describe '#to_param' do
|
|
414
|
+
it 'delegates to the object' do
|
|
415
|
+
decorator = Decorator.new(double(to_param: :delegated))
|
|
416
|
+
|
|
417
|
+
expect(decorator.to_param).to be :delegated
|
|
418
|
+
end
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
describe '#present?' do
|
|
422
|
+
it 'delegates to the object' do
|
|
423
|
+
decorator = Decorator.new(double(present?: :delegated))
|
|
424
|
+
|
|
425
|
+
expect(decorator.present?).to be :delegated
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
describe '#blank?' do
|
|
430
|
+
it 'delegates to the object' do
|
|
431
|
+
decorator = Decorator.new(double(blank?: :delegated))
|
|
432
|
+
|
|
433
|
+
expect(decorator.blank?).to be :delegated
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
describe '#to_partial_path' do
|
|
438
|
+
it 'delegates to the object' do
|
|
439
|
+
decorator = Decorator.new(double(to_partial_path: :delegated))
|
|
440
|
+
|
|
441
|
+
expect(decorator.to_partial_path).to be :delegated
|
|
442
|
+
end
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
describe '#to_s' do
|
|
446
|
+
it 'delegates to the object' do
|
|
447
|
+
decorator = Decorator.new(double(to_s: :delegated))
|
|
448
|
+
|
|
449
|
+
expect(decorator.to_s).to be :delegated
|
|
450
|
+
end
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
describe '#inspect' do
|
|
454
|
+
it 'returns a detailed description of the decorator' do
|
|
455
|
+
decorator = ProductDecorator.new(double)
|
|
456
|
+
|
|
457
|
+
expect(decorator.inspect).to match /#<ProductDecorator:0x\h+ .+>/
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
it 'includes the object' do
|
|
461
|
+
decorator = Decorator.new(double(inspect: '#<the object>'))
|
|
462
|
+
|
|
463
|
+
expect(decorator.inspect).to include '@object=#<the object>'
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
it 'includes the context' do
|
|
467
|
+
decorator = Decorator.new(double, context: { foo: 'bar' })
|
|
468
|
+
|
|
469
|
+
expect(decorator.inspect).to include '@context={:foo=>"bar"}'
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
it 'includes other instance variables' do
|
|
473
|
+
decorator = Decorator.new(double)
|
|
474
|
+
decorator.instance_variable_set :@foo, 'bar'
|
|
475
|
+
|
|
476
|
+
expect(decorator.inspect).to include '@foo="bar"'
|
|
477
|
+
end
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
describe '#attributes' do
|
|
481
|
+
it "returns only the object's attributes that are implemented by the decorator" do
|
|
482
|
+
decorator = Decorator.new(double(attributes: { foo: 'bar', baz: 'qux' }))
|
|
483
|
+
decorator.stub(:foo)
|
|
484
|
+
|
|
485
|
+
expect(decorator.attributes).to eq({ foo: 'bar' })
|
|
486
|
+
end
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
describe '.model_name' do
|
|
490
|
+
it 'delegates to the source class' do
|
|
491
|
+
Decorator.stub object_class: double(model_name: :delegated)
|
|
492
|
+
|
|
493
|
+
expect(Decorator.model_name).to be :delegated
|
|
494
|
+
end
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
describe '#==' do
|
|
498
|
+
it 'works for a object that does not include Decoratable' do
|
|
499
|
+
object = Object.new
|
|
500
|
+
decorator = Decorator.new(object)
|
|
501
|
+
|
|
502
|
+
expect(decorator).to eq Decorator.new(object)
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
it 'works for a multiply-decorated object that does not include Decoratable' do
|
|
506
|
+
object = Object.new
|
|
507
|
+
decorator = Decorator.new(object)
|
|
508
|
+
|
|
509
|
+
expect(decorator).to eq ProductDecorator.new(Decorator.new(object))
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
it 'is true when object #== is true' do
|
|
513
|
+
object = Model.new
|
|
514
|
+
decorator = Decorator.new(object)
|
|
515
|
+
other = double(object: Model.new)
|
|
516
|
+
|
|
517
|
+
object.should_receive(:==).with(other).and_return(true)
|
|
518
|
+
expect(decorator == other).to be_truthy
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
it 'is false when object #== is false' do
|
|
522
|
+
object = Model.new
|
|
523
|
+
decorator = Decorator.new(object)
|
|
524
|
+
other = double(object: Model.new)
|
|
525
|
+
|
|
526
|
+
object.should_receive(:==).with(other).and_return(false)
|
|
527
|
+
expect(decorator == other).to be_falsey
|
|
528
|
+
end
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
describe '#===' do
|
|
532
|
+
it 'is true when #== is true' do
|
|
533
|
+
decorator = Decorator.new(Model.new)
|
|
534
|
+
allow(decorator).to receive(:==) { true }
|
|
535
|
+
|
|
536
|
+
expect(decorator === :anything).to be_truthy
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
it 'is false when #== is false' do
|
|
540
|
+
decorator = Decorator.new(Model.new)
|
|
541
|
+
decorator.stub(:==).with(:anything).and_return(false)
|
|
542
|
+
|
|
543
|
+
expect(decorator === :anything).to be_falsey
|
|
544
|
+
end
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
describe '#eql?' do
|
|
548
|
+
it 'is true when #eql? is true' do
|
|
549
|
+
first = Decorator.new('foo')
|
|
550
|
+
second = Decorator.new('foo')
|
|
551
|
+
|
|
552
|
+
expect(first.eql? second).to be
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
it 'is false when #eql? is false' do
|
|
556
|
+
first = Decorator.new('foo')
|
|
557
|
+
second = Decorator.new('bar')
|
|
558
|
+
|
|
559
|
+
expect(first.eql? second).to_not be
|
|
560
|
+
end
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
describe '#hash' do
|
|
564
|
+
it 'is consistent for equal objects' do
|
|
565
|
+
object = Model.new
|
|
566
|
+
first = Decorator.new(object)
|
|
567
|
+
second = Decorator.new(object)
|
|
568
|
+
|
|
569
|
+
expect(first.hash == second.hash).to be
|
|
570
|
+
end
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
describe '.delegate' do
|
|
574
|
+
protect_class Decorator
|
|
575
|
+
|
|
576
|
+
it 'defaults the :to option to :object' do
|
|
577
|
+
Object.should_receive(:delegate).with(:foo, :bar, to: :object)
|
|
578
|
+
Decorator.delegate :foo, :bar
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
it 'does not overwrite the :to option if supplied' do
|
|
582
|
+
Object.should_receive(:delegate).with(:foo, :bar, to: :baz)
|
|
583
|
+
Decorator.delegate :foo, :bar, to: :baz
|
|
584
|
+
end
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
context 'with .delegate_all' do
|
|
588
|
+
protect_class Decorator
|
|
589
|
+
|
|
590
|
+
before { Decorator.delegate_all }
|
|
591
|
+
|
|
592
|
+
describe '#method_missing' do
|
|
593
|
+
it 'delegates missing methods that exist on the object' do
|
|
594
|
+
decorator = Decorator.new(double(hello_world: :delegated))
|
|
595
|
+
|
|
596
|
+
expect(decorator.hello_world).to be :delegated
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
it 'adds delegated methods to the decorator when they are used' do
|
|
600
|
+
decorator = Decorator.new(double(hello_world: :delegated))
|
|
601
|
+
|
|
602
|
+
expect(decorator.methods).not_to include :hello_world
|
|
603
|
+
decorator.hello_world
|
|
604
|
+
expect(decorator.methods).to include :hello_world
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
it 'passes blocks to delegated methods' do
|
|
608
|
+
object = Model.new
|
|
609
|
+
object.stub(:hello_world) { |*_args, &block| block.call }
|
|
610
|
+
decorator = Decorator.new(object)
|
|
611
|
+
|
|
612
|
+
expect(decorator.hello_world { :yielded }).to be :yielded
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
it 'does not confuse Kernel#Array' do
|
|
616
|
+
decorator = Decorator.new(Model.new)
|
|
617
|
+
|
|
618
|
+
expect(Array(decorator)).to be_an Array
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
it 'delegates already-delegated methods' do
|
|
622
|
+
object = Class.new { delegate :bar, to: :foo }.new
|
|
623
|
+
object.stub foo: double(bar: :delegated)
|
|
624
|
+
decorator = Decorator.new(object)
|
|
625
|
+
|
|
626
|
+
expect(decorator.bar).to be :delegated
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
it 'does not delegate private methods' do
|
|
630
|
+
object = Class.new do
|
|
631
|
+
private
|
|
632
|
+
|
|
633
|
+
def hello_world; end
|
|
634
|
+
end.new
|
|
635
|
+
decorator = Decorator.new(object)
|
|
636
|
+
|
|
637
|
+
expect { decorator.hello_world }.to raise_error NoMethodError
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
it 'does not delegate methods that do not exist on the object' do
|
|
641
|
+
decorator = Decorator.new(Model.new)
|
|
642
|
+
|
|
643
|
+
expect(decorator.methods).not_to include :hello_world
|
|
644
|
+
expect { decorator.hello_world }.to raise_error NoMethodError
|
|
645
|
+
expect(decorator.methods).not_to include :hello_world
|
|
646
|
+
end
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
context '.method_missing' do
|
|
650
|
+
context 'without a source class' do
|
|
651
|
+
it 'raises a NoMethodError on missing methods' do
|
|
652
|
+
expect { Decorator.hello_world }.to raise_error NoMethodError
|
|
653
|
+
end
|
|
654
|
+
end
|
|
655
|
+
|
|
656
|
+
context 'with a source class' do
|
|
657
|
+
it 'delegates methods that exist on the source class' do
|
|
658
|
+
object_class = Class.new
|
|
659
|
+
object_class.stub hello_world: :delegated
|
|
660
|
+
Decorator.stub object_class: object_class
|
|
661
|
+
|
|
662
|
+
expect(Decorator.hello_world).to be :delegated
|
|
663
|
+
end
|
|
664
|
+
|
|
665
|
+
it 'does not delegate methods that do not exist on the source class' do
|
|
666
|
+
Decorator.stub object_class: Class.new
|
|
667
|
+
|
|
668
|
+
expect { Decorator.hello_world }.to raise_error NoMethodError
|
|
669
|
+
end
|
|
670
|
+
end
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
describe '#respond_to?' do
|
|
674
|
+
it 'returns true for its own methods' do
|
|
675
|
+
Decorator.class_eval { def hello_world; end }
|
|
676
|
+
decorator = Decorator.new(Model.new)
|
|
677
|
+
|
|
678
|
+
expect(decorator).to respond_to :hello_world
|
|
679
|
+
end
|
|
680
|
+
|
|
681
|
+
it "returns true for the object's methods" do
|
|
682
|
+
decorator = Decorator.new(double(hello_world: :delegated))
|
|
683
|
+
|
|
684
|
+
expect(decorator).to respond_to :hello_world
|
|
685
|
+
end
|
|
686
|
+
|
|
687
|
+
context 'with include_private' do
|
|
688
|
+
it 'returns true for its own private methods' do
|
|
689
|
+
Decorator.class_eval do
|
|
690
|
+
private
|
|
691
|
+
|
|
692
|
+
def hello_world; end
|
|
693
|
+
end
|
|
694
|
+
decorator = Decorator.new(Model.new)
|
|
695
|
+
|
|
696
|
+
expect(decorator.respond_to?(:hello_world, true)).to be_truthy
|
|
697
|
+
end
|
|
698
|
+
|
|
699
|
+
it "returns false for the object's private methods" do
|
|
700
|
+
object = Class.new do
|
|
701
|
+
private
|
|
702
|
+
|
|
703
|
+
def hello_world; end
|
|
704
|
+
end.new
|
|
705
|
+
decorator = Decorator.new(object)
|
|
706
|
+
|
|
707
|
+
expect(decorator.respond_to?(:hello_world, true)).to be_falsey
|
|
708
|
+
end
|
|
709
|
+
end
|
|
710
|
+
end
|
|
711
|
+
|
|
712
|
+
describe '.respond_to?' do
|
|
713
|
+
context 'without a source class' do
|
|
714
|
+
it 'returns true for its own class methods' do
|
|
715
|
+
Decorator.class_eval { def self.hello_world; end }
|
|
716
|
+
|
|
717
|
+
expect(Decorator).to respond_to :hello_world
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
it 'returns false for other class methods' do
|
|
721
|
+
expect(Decorator).not_to respond_to :goodnight_moon
|
|
722
|
+
end
|
|
723
|
+
end
|
|
724
|
+
|
|
725
|
+
context 'with a source class' do
|
|
726
|
+
it 'returns true for its own class methods' do
|
|
727
|
+
Decorator.class_eval { def self.hello_world; end }
|
|
728
|
+
Decorator.stub object_class: Class.new
|
|
729
|
+
|
|
730
|
+
expect(Decorator).to respond_to :hello_world
|
|
731
|
+
end
|
|
732
|
+
|
|
733
|
+
it "returns true for the source's class methods" do
|
|
734
|
+
Decorator.stub object_class: double(hello_world: :delegated)
|
|
735
|
+
|
|
736
|
+
expect(Decorator).to respond_to :hello_world
|
|
737
|
+
end
|
|
738
|
+
end
|
|
739
|
+
end
|
|
740
|
+
|
|
741
|
+
describe '#respond_to_missing?' do
|
|
742
|
+
it 'allows #method to be called on delegated methods' do
|
|
743
|
+
object = Class.new { def hello_world; end }.new
|
|
744
|
+
decorator = Decorator.new(object)
|
|
745
|
+
|
|
746
|
+
expect(decorator.method(:hello_world)).not_to be_nil
|
|
747
|
+
end
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
describe '.respond_to_missing?' do
|
|
751
|
+
it 'allows .method to be called on delegated class methods' do
|
|
752
|
+
Decorator.stub object_class: double(hello_world: :delegated)
|
|
753
|
+
|
|
754
|
+
expect(Decorator.method(:hello_world)).not_to be_nil
|
|
755
|
+
end
|
|
756
|
+
end
|
|
757
|
+
end
|
|
758
|
+
|
|
759
|
+
describe 'class spoofing' do
|
|
760
|
+
it 'pretends to be a kind of the source class' do
|
|
761
|
+
decorator = Decorator.new(Model.new)
|
|
762
|
+
|
|
763
|
+
expect(decorator.is_a?(Model)).to be_truthy
|
|
764
|
+
expect(decorator.is_a?(Model)).to be_truthy
|
|
765
|
+
end
|
|
766
|
+
|
|
767
|
+
it 'is still a kind of its own class' do
|
|
768
|
+
decorator = Decorator.new(Model.new)
|
|
769
|
+
|
|
770
|
+
expect(decorator.is_a?(Decorator)).to be_truthy
|
|
771
|
+
expect(decorator.is_a?(Decorator)).to be_truthy
|
|
772
|
+
end
|
|
773
|
+
|
|
774
|
+
it 'pretends to be an instance of the source class' do
|
|
775
|
+
decorator = Decorator.new(Model.new)
|
|
776
|
+
|
|
777
|
+
expect(decorator.instance_of?(Model)).to be_truthy
|
|
778
|
+
end
|
|
779
|
+
|
|
780
|
+
it 'is still an instance of its own class' do
|
|
781
|
+
decorator = Decorator.new(Model.new)
|
|
782
|
+
|
|
783
|
+
expect(decorator.instance_of?(Decorator)).to be_truthy
|
|
784
|
+
end
|
|
785
|
+
end
|
|
786
|
+
|
|
787
|
+
describe '.decorates_finders' do
|
|
788
|
+
protect_class Decorator
|
|
789
|
+
|
|
790
|
+
it 'extends the Finders module' do
|
|
791
|
+
expect(Decorator).not_to be_a_kind_of Finders
|
|
792
|
+
Decorator.decorates_finders
|
|
793
|
+
expect(Decorator).to be_a_kind_of Finders
|
|
794
|
+
end
|
|
795
|
+
end
|
|
796
|
+
|
|
797
|
+
describe 'Enumerable hash and equality functionality' do
|
|
798
|
+
describe '#uniq' do
|
|
799
|
+
it 'removes duplicate objects with same decorator' do
|
|
800
|
+
object = Model.new
|
|
801
|
+
array = [Decorator.new(object), Decorator.new(object)]
|
|
802
|
+
|
|
803
|
+
expect(array.uniq.count).to eq(1)
|
|
804
|
+
end
|
|
805
|
+
|
|
806
|
+
it 'separates different objects with identical decorators' do
|
|
807
|
+
array = [Decorator.new('foo'), Decorator.new('bar')]
|
|
808
|
+
|
|
809
|
+
expect(array.uniq.count).to eq(2)
|
|
810
|
+
end
|
|
811
|
+
|
|
812
|
+
it 'separates identical objects with different decorators' do
|
|
813
|
+
object = Model.new
|
|
814
|
+
array = [Decorator.new(object), OtherDecorator.new(object)]
|
|
815
|
+
|
|
816
|
+
expect(array.uniq.count).to eq(2)
|
|
817
|
+
end
|
|
818
|
+
|
|
819
|
+
it 'distinguishes between an objects and its decorated version' do
|
|
820
|
+
object = Model.new
|
|
821
|
+
array = [Decorator.new(object), object]
|
|
822
|
+
|
|
823
|
+
expect(array.uniq.count).to eq(2)
|
|
824
|
+
end
|
|
825
|
+
end
|
|
826
|
+
end
|
|
827
|
+
end
|
|
828
|
+
end
|