draper 0.18.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +3 -0
- data/.travis.yml +11 -0
- data/.yardopts +1 -1
- data/CHANGELOG.md +164 -0
- data/CONTRIBUTING.md +15 -0
- data/Gemfile +34 -0
- data/LICENSE +7 -0
- data/README.md +416 -0
- data/Rakefile +53 -31
- data/draper.gemspec +11 -9
- data/lib/draper/automatic_delegation.rb +50 -0
- data/lib/draper/collection_decorator.rb +107 -0
- data/lib/draper/decoratable/equality.rb +14 -0
- data/lib/draper/decoratable.rb +79 -0
- data/lib/draper/decorated_association.rb +74 -0
- data/lib/draper/decorator.rb +235 -0
- data/lib/draper/delegation.rb +13 -0
- data/lib/draper/finders.rb +37 -0
- data/lib/draper/helper_proxy.rb +36 -0
- data/lib/draper/lazy_helpers.rb +10 -6
- data/lib/draper/railtie.rb +27 -21
- data/lib/draper/tasks/test.rake +22 -0
- data/lib/draper/test/devise_helper.rb +30 -0
- data/lib/draper/test/minitest_integration.rb +2 -3
- data/lib/draper/test/rspec_integration.rb +7 -13
- data/lib/draper/test_case.rb +53 -0
- data/lib/draper/version.rb +1 -1
- data/lib/draper/view_context/build_strategy.rb +48 -0
- data/lib/draper/view_context.rb +81 -24
- data/lib/draper/view_helpers.rb +37 -0
- data/lib/draper.rb +50 -8
- data/lib/generators/decorator/decorator_generator.rb +10 -2
- data/lib/generators/decorator/templates/decorator.rb +12 -27
- data/lib/generators/mini_test/decorator_generator.rb +20 -0
- data/lib/generators/mini_test/templates/decorator_spec.rb +4 -0
- data/lib/generators/mini_test/templates/decorator_test.rb +4 -0
- data/lib/generators/test_unit/templates/decorator_test.rb +1 -1
- data/spec/draper/collection_decorator_spec.rb +259 -0
- data/spec/draper/decoratable/equality_spec.rb +10 -0
- data/spec/draper/decoratable_spec.rb +167 -0
- data/spec/draper/decorated_association_spec.rb +145 -0
- data/spec/draper/decorator_spec.rb +598 -0
- data/spec/draper/finders_spec.rb +166 -0
- data/spec/draper/helper_proxy_spec.rb +42 -0
- data/spec/draper/view_context/build_strategy_spec.rb +116 -0
- data/spec/draper/view_context_spec.rb +154 -0
- data/spec/draper/view_helpers_spec.rb +8 -0
- data/spec/dummy/.rspec +2 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/controllers/application_controller.rb +4 -0
- data/spec/dummy/app/controllers/localized_urls.rb +5 -0
- data/spec/dummy/app/controllers/posts_controller.rb +18 -0
- data/spec/dummy/app/decorators/post_decorator.rb +56 -0
- data/spec/dummy/app/helpers/application_helper.rb +5 -0
- data/spec/dummy/app/mailers/application_mailer.rb +3 -0
- data/spec/dummy/app/mailers/post_mailer.rb +19 -0
- data/spec/dummy/app/models/admin.rb +5 -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 +11 -0
- data/spec/dummy/app/views/post_mailer/decorated_email.html.erb +1 -0
- data/spec/dummy/app/views/posts/_post.html.erb +34 -0
- data/spec/dummy/app/views/posts/show.html.erb +1 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/config/application.rb +70 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +33 -0
- data/spec/dummy/config/environments/production.rb +57 -0
- data/spec/dummy/config/environments/test.rb +31 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +8 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/mongoid.yml +80 -0
- data/spec/dummy/config/routes.rb +9 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/migrate/20121019115657_create_posts.rb +8 -0
- data/spec/dummy/db/schema.rb +21 -0
- data/spec/dummy/db/seeds.rb +2 -0
- data/spec/dummy/fast_spec/post_decorator_spec.rb +38 -0
- data/spec/dummy/lib/tasks/test.rake +16 -0
- data/spec/dummy/log/.gitkeep +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -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 +46 -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 +10 -0
- data/spec/dummy/spec/models/post_spec.rb +14 -0
- data/spec/dummy/spec/spec_helper.rb +9 -0
- data/spec/dummy/test/decorators/minitest/devise_test.rb +64 -0
- data/spec/dummy/test/decorators/minitest/helpers_test.rb +21 -0
- data/spec/dummy/test/decorators/minitest/spec_type_test.rb +52 -0
- data/spec/dummy/test/decorators/minitest/view_context_test.rb +24 -0
- data/spec/dummy/test/decorators/test_unit/devise_test.rb +64 -0
- data/spec/dummy/test/decorators/test_unit/helpers_test.rb +21 -0
- data/spec/dummy/test/decorators/test_unit/view_context_test.rb +24 -0
- data/spec/dummy/test/minitest_helper.rb +4 -0
- data/spec/dummy/test/test_helper.rb +3 -0
- data/spec/generators/decorator/decorator_generator_spec.rb +48 -7
- data/spec/integration/integration_spec.rb +58 -0
- data/{performance → spec/performance}/decorators.rb +2 -4
- data/spec/spec_helper.rb +25 -43
- data/spec/support/dummy_app.rb +85 -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 +253 -87
- data/CHANGELOG.markdown +0 -57
- data/Readme.markdown +0 -387
- data/lib/draper/active_model_support.rb +0 -27
- data/lib/draper/base.rb +0 -312
- data/lib/draper/decorated_enumerable_proxy.rb +0 -90
- data/lib/draper/model_support.rb +0 -25
- data/lib/draper/rspec_integration.rb +0 -2
- data/lib/draper/system.rb +0 -18
- data/spec/draper/base_spec.rb +0 -873
- data/spec/draper/decorated_enumerable_proxy_spec.rb +0 -45
- data/spec/draper/model_support_spec.rb +0 -48
- data/spec/minitest-rails/spec_type_spec.rb +0 -63
- data/spec/support/samples/active_record.rb +0 -17
- data/spec/support/samples/decorator.rb +0 -5
- data/spec/support/samples/decorator_with_allows.rb +0 -3
- data/spec/support/samples/decorator_with_application_helper.rb +0 -25
- data/spec/support/samples/decorator_with_denies.rb +0 -3
- data/spec/support/samples/decorator_with_denies_all.rb +0 -3
- data/spec/support/samples/decorator_with_multiple_allows.rb +0 -4
- data/spec/support/samples/decorator_with_special_methods.rb +0 -13
- data/spec/support/samples/enumerable_proxy.rb +0 -3
- data/spec/support/samples/namespaced_product.rb +0 -51
- data/spec/support/samples/namespaced_product_decorator.rb +0 -7
- data/spec/support/samples/non_active_model_product.rb +0 -2
- data/spec/support/samples/product.rb +0 -80
- data/spec/support/samples/product_decorator.rb +0 -7
- data/spec/support/samples/products_decorator.rb +0 -10
- data/spec/support/samples/sequel_product.rb +0 -13
- data/spec/support/samples/some_thing.rb +0 -2
- data/spec/support/samples/some_thing_decorator.rb +0 -3
- data/spec/support/samples/specific_product_decorator.rb +0 -4
- data/spec/support/samples/widget.rb +0 -2
- data/spec/support/samples/widget_decorator.rb +0 -2
- /data/{performance → spec/performance}/active_record.rb +0 -0
- /data/{performance/bechmark.rb → spec/performance/benchmark.rb} +0 -0
- /data/{performance → spec/performance}/models.rb +0 -0
|
@@ -1,34 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
<%- module_namespacing do -%>
|
|
2
|
+
<%- if parent_class_name.present? -%>
|
|
2
3
|
class <%= class_name %>Decorator < <%= parent_class_name %>
|
|
3
|
-
|
|
4
|
+
<%- else -%>
|
|
5
|
+
class <%= class_name %>
|
|
6
|
+
<%- end -%>
|
|
7
|
+
delegate_all
|
|
4
8
|
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# Normal Usage: helpers.number_to_currency(2)
|
|
9
|
-
# Abbreviated : h.number_to_currency(2)
|
|
10
|
-
#
|
|
11
|
-
# Or, optionally enable "lazy helpers" by including this module:
|
|
12
|
-
# include Draper::LazyHelpers
|
|
13
|
-
# Then use the helpers with no proxy:
|
|
14
|
-
# number_to_currency(2)
|
|
15
|
-
|
|
16
|
-
# Defining an Interface
|
|
17
|
-
# Control access to the wrapped subject's methods using one of the following:
|
|
18
|
-
#
|
|
19
|
-
# To allow only the listed methods (whitelist):
|
|
20
|
-
# allows :method1, :method2
|
|
21
|
-
#
|
|
22
|
-
# To allow everything except the listed methods (blacklist):
|
|
23
|
-
# denies :method1, :method2
|
|
24
|
-
|
|
25
|
-
# Presentation Methods
|
|
26
|
-
# Define your own instance methods, even overriding accessors
|
|
27
|
-
# generated by ActiveRecord:
|
|
9
|
+
# Define presentation-specific methods here. Helpers are accessed through
|
|
10
|
+
# `helpers` (aka `h`). You can override attributes, for example:
|
|
28
11
|
#
|
|
29
12
|
# def created_at
|
|
30
|
-
#
|
|
31
|
-
#
|
|
13
|
+
# helpers.content_tag :span, class: 'time' do
|
|
14
|
+
# source.created_at.strftime("%a %m/%d/%y")
|
|
15
|
+
# end
|
|
32
16
|
# end
|
|
17
|
+
|
|
33
18
|
end
|
|
34
19
|
<% end -%>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require 'generators/mini_test'
|
|
2
|
+
|
|
3
|
+
module MiniTest
|
|
4
|
+
module Generators
|
|
5
|
+
class DecoratorGenerator < Base
|
|
6
|
+
def self.source_root
|
|
7
|
+
File.expand_path('../templates', __FILE__)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class_option :spec, :type => :boolean, :default => false, :desc => "Use MiniTest::Spec DSL"
|
|
11
|
+
|
|
12
|
+
check_class_collision suffix: "DecoratorTest"
|
|
13
|
+
|
|
14
|
+
def create_test_file
|
|
15
|
+
template_type = options[:spec] ? "spec" : "test"
|
|
16
|
+
template "decorator_#{template_type}.rb", File.join("test/decorators", class_path, "#{singular_name}_decorator_test.rb")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'support/shared_examples/view_helpers'
|
|
3
|
+
|
|
4
|
+
module Draper
|
|
5
|
+
describe CollectionDecorator do
|
|
6
|
+
it_behaves_like "view helpers", CollectionDecorator.new([])
|
|
7
|
+
|
|
8
|
+
describe "#initialize" do
|
|
9
|
+
describe "options validation" do
|
|
10
|
+
|
|
11
|
+
it "does not raise error on valid options" do
|
|
12
|
+
valid_options = {with: Decorator, context: {}}
|
|
13
|
+
expect{CollectionDecorator.new([], valid_options)}.not_to raise_error
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "raises error on invalid options" do
|
|
17
|
+
expect{CollectionDecorator.new([], foo: "bar")}.to raise_error ArgumentError, /Unknown key/
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
context "with context" do
|
|
23
|
+
it "stores the context itself" do
|
|
24
|
+
context = {some: "context"}
|
|
25
|
+
decorator = CollectionDecorator.new([], context: context)
|
|
26
|
+
|
|
27
|
+
expect(decorator.context).to be context
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "passes context to the individual decorators" do
|
|
31
|
+
context = {some: "context"}
|
|
32
|
+
decorator = CollectionDecorator.new([Product.new, Product.new], context: context)
|
|
33
|
+
|
|
34
|
+
decorator.each do |item|
|
|
35
|
+
expect(item.context).to be context
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
describe "#context=" do
|
|
41
|
+
it "updates the stored context" do
|
|
42
|
+
decorator = CollectionDecorator.new([], context: {some: "context"})
|
|
43
|
+
new_context = {other: "context"}
|
|
44
|
+
|
|
45
|
+
decorator.context = new_context
|
|
46
|
+
expect(decorator.context).to be new_context
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
context "when the collection is already decorated" do
|
|
50
|
+
it "updates the items' context" do
|
|
51
|
+
decorator = CollectionDecorator.new([Product.new, Product.new], context: {some: "context"})
|
|
52
|
+
decorator.decorated_collection # trigger decoration
|
|
53
|
+
new_context = {other: "context"}
|
|
54
|
+
|
|
55
|
+
decorator.context = new_context
|
|
56
|
+
decorator.each do |item|
|
|
57
|
+
expect(item.context).to be new_context
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
context "when the collection has not yet been decorated" do
|
|
63
|
+
it "does not trigger decoration" do
|
|
64
|
+
decorator = CollectionDecorator.new([])
|
|
65
|
+
|
|
66
|
+
decorator.should_not_receive(:decorated_collection)
|
|
67
|
+
decorator.context = {other: "context"}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "sets context after decoration is triggered" do
|
|
71
|
+
decorator = CollectionDecorator.new([Product.new, Product.new], context: {some: "context"})
|
|
72
|
+
new_context = {other: "context"}
|
|
73
|
+
|
|
74
|
+
decorator.context = new_context
|
|
75
|
+
decorator.each do |item|
|
|
76
|
+
expect(item.context).to be new_context
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
describe "item decoration" do
|
|
83
|
+
it "sets decorated items' source models" do
|
|
84
|
+
collection = [Product.new, Product.new]
|
|
85
|
+
decorator = CollectionDecorator.new(collection)
|
|
86
|
+
|
|
87
|
+
decorator.zip collection do |item, source|
|
|
88
|
+
expect(item.source).to be source
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
context "when the item decorator is inferrable from the collection decorator" do
|
|
93
|
+
context "when the :with option was given" do
|
|
94
|
+
it "uses the :with option" do
|
|
95
|
+
decorator = ProductsDecorator.new([Product.new], with: OtherDecorator)
|
|
96
|
+
|
|
97
|
+
expect(*decorator).to be_decorated_with OtherDecorator
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
context "when the :with option was not given" do
|
|
102
|
+
it "infers the item decorator from the collection decorator" do
|
|
103
|
+
decorator = ProductsDecorator.new([Product.new])
|
|
104
|
+
|
|
105
|
+
expect(*decorator).to be_decorated_with ProductDecorator
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
context "when the item decorator is not inferrable from the collection decorator" do
|
|
111
|
+
context "when the :with option was given" do
|
|
112
|
+
it "uses the :with option" do
|
|
113
|
+
decorator = CollectionDecorator.new([Product.new], with: OtherDecorator)
|
|
114
|
+
|
|
115
|
+
expect(*decorator).to be_decorated_with OtherDecorator
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
context "when the :with option was not given" do
|
|
120
|
+
it "infers the item decorator from each item" do
|
|
121
|
+
decorator = CollectionDecorator.new([double(decorate: :inferred_decorator)])
|
|
122
|
+
|
|
123
|
+
expect(*decorator).to be :inferred_decorator
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
describe ".delegate" do
|
|
130
|
+
protect_class ProductsDecorator
|
|
131
|
+
|
|
132
|
+
it "defaults the :to option to :source" do
|
|
133
|
+
Object.should_receive(:delegate).with(:foo, :bar, to: :source)
|
|
134
|
+
ProductsDecorator.delegate :foo, :bar
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
it "does not overwrite the :to option if supplied" do
|
|
138
|
+
Object.should_receive(:delegate).with(:foo, :bar, to: :baz)
|
|
139
|
+
ProductsDecorator.delegate :foo, :bar, to: :baz
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
describe "#find" do
|
|
144
|
+
context "with a block" do
|
|
145
|
+
it "decorates Enumerable#find" do
|
|
146
|
+
decorator = CollectionDecorator.new([])
|
|
147
|
+
|
|
148
|
+
decorator.decorated_collection.should_receive(:find).and_return(:delegated)
|
|
149
|
+
expect(decorator.find{|p| p.title == "title"}).to be :delegated
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
context "without a block" do
|
|
154
|
+
it "decorates Model.find" do
|
|
155
|
+
item_decorator = Class.new
|
|
156
|
+
decorator = CollectionDecorator.new([], with: item_decorator)
|
|
157
|
+
|
|
158
|
+
item_decorator.should_receive(:find).with(1).and_return(:delegated)
|
|
159
|
+
expect(decorator.find(1)).to be :delegated
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
describe "#to_ary" do
|
|
165
|
+
# required for `render @collection` in Rails
|
|
166
|
+
it "delegates to the decorated collection" do
|
|
167
|
+
decorator = CollectionDecorator.new([])
|
|
168
|
+
|
|
169
|
+
decorator.decorated_collection.should_receive(:to_ary).and_return(:delegated)
|
|
170
|
+
expect(decorator.to_ary).to be :delegated
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
it "delegates array methods to the decorated collection" do
|
|
175
|
+
decorator = CollectionDecorator.new([])
|
|
176
|
+
|
|
177
|
+
decorator.decorated_collection.should_receive(:[]).with(42).and_return(:delegated)
|
|
178
|
+
expect(decorator[42]).to be :delegated
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
describe "#==" do
|
|
182
|
+
context "when comparing to a collection decorator with the same source" do
|
|
183
|
+
it "returns true" do
|
|
184
|
+
source = [Product.new, Product.new]
|
|
185
|
+
decorator = CollectionDecorator.new(source)
|
|
186
|
+
other = ProductsDecorator.new(source)
|
|
187
|
+
|
|
188
|
+
expect(decorator == other).to be_true
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
context "when comparing to a collection decorator with a different source" do
|
|
193
|
+
it "returns false" do
|
|
194
|
+
decorator = CollectionDecorator.new([Product.new, Product.new])
|
|
195
|
+
other = ProductsDecorator.new([Product.new, Product.new])
|
|
196
|
+
|
|
197
|
+
expect(decorator == other).to be_false
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
context "when comparing to a collection of the same items" do
|
|
202
|
+
it "returns true" do
|
|
203
|
+
source = [Product.new, Product.new]
|
|
204
|
+
decorator = CollectionDecorator.new(source)
|
|
205
|
+
other = source.dup
|
|
206
|
+
|
|
207
|
+
expect(decorator == other).to be_true
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
context "when comparing to a collection of different items" do
|
|
212
|
+
it "returns false" do
|
|
213
|
+
decorator = CollectionDecorator.new([Product.new, Product.new])
|
|
214
|
+
other = [Product.new, Product.new]
|
|
215
|
+
|
|
216
|
+
expect(decorator == other).to be_false
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
context "when the decorated collection has been modified" do
|
|
221
|
+
it "is no longer equal to the source" do
|
|
222
|
+
source = [Product.new, Product.new]
|
|
223
|
+
decorator = CollectionDecorator.new(source)
|
|
224
|
+
other = source.dup
|
|
225
|
+
|
|
226
|
+
decorator << Product.new.decorate
|
|
227
|
+
expect(decorator == other).to be_false
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
describe "#to_s" do
|
|
233
|
+
context "when :with option was given" do
|
|
234
|
+
it "returns a string representation of the collection decorator" do
|
|
235
|
+
decorator = CollectionDecorator.new(["a", "b", "c"], with: ProductDecorator)
|
|
236
|
+
|
|
237
|
+
expect(decorator.to_s).to eq '#<Draper::CollectionDecorator of ProductDecorator for ["a", "b", "c"]>'
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
context "when :with option was not given" do
|
|
242
|
+
it "returns a string representation of the collection decorator" do
|
|
243
|
+
decorator = CollectionDecorator.new(["a", "b", "c"])
|
|
244
|
+
|
|
245
|
+
expect(decorator.to_s).to eq '#<Draper::CollectionDecorator of inferred decorators for ["a", "b", "c"]>'
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
context "for a custom subclass" do
|
|
250
|
+
it "uses the custom class name" do
|
|
251
|
+
decorator = ProductsDecorator.new([])
|
|
252
|
+
|
|
253
|
+
expect(decorator.to_s).to match /ProductsDecorator/
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
end
|
|
259
|
+
end
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'support/shared_examples/decoratable_equality'
|
|
3
|
+
|
|
4
|
+
module Draper
|
|
5
|
+
describe Decoratable do
|
|
6
|
+
|
|
7
|
+
describe "#decorate" do
|
|
8
|
+
it "returns a decorator for self" do
|
|
9
|
+
product = Product.new
|
|
10
|
+
decorator = product.decorate
|
|
11
|
+
|
|
12
|
+
expect(decorator).to be_a ProductDecorator
|
|
13
|
+
expect(decorator.source).to be product
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "accepts context" do
|
|
17
|
+
context = {some: "context"}
|
|
18
|
+
decorator = Product.new.decorate(context: context)
|
|
19
|
+
|
|
20
|
+
expect(decorator.context).to be context
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "uses the #decorator_class" do
|
|
24
|
+
product = Product.new
|
|
25
|
+
product.stub decorator_class: OtherDecorator
|
|
26
|
+
|
|
27
|
+
expect(product.decorate).to be_an_instance_of OtherDecorator
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe "#applied_decorators" do
|
|
32
|
+
it "returns an empty list" do
|
|
33
|
+
expect(Product.new.applied_decorators).to eq []
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe "#decorated_with?" do
|
|
38
|
+
it "returns false" do
|
|
39
|
+
expect(Product.new).not_to be_decorated_with Decorator
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
describe "#decorated?" do
|
|
44
|
+
it "returns false" do
|
|
45
|
+
expect(Product.new).not_to be_decorated
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
describe "#decorator_class" do
|
|
50
|
+
it "delegates to .decorator_class" do
|
|
51
|
+
product = Product.new
|
|
52
|
+
|
|
53
|
+
Product.should_receive(:decorator_class).and_return(:some_decorator)
|
|
54
|
+
expect(product.decorator_class).to be :some_decorator
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
describe "#==" do
|
|
59
|
+
it_behaves_like "decoration-aware #==", Product.new
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
describe "#===" do
|
|
63
|
+
it "is true when #== is true" do
|
|
64
|
+
product = Product.new
|
|
65
|
+
|
|
66
|
+
product.should_receive(:==).and_return(true)
|
|
67
|
+
expect(product === :anything).to be_true
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "is false when #== is false" do
|
|
71
|
+
product = Product.new
|
|
72
|
+
|
|
73
|
+
product.should_receive(:==).and_return(false)
|
|
74
|
+
expect(product === :anything).to be_false
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
describe ".====" do
|
|
79
|
+
it "is true for an instance" do
|
|
80
|
+
expect(Product === Product.new).to be_true
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "is true for a derived instance" do
|
|
84
|
+
expect(Product === Class.new(Product).new).to be_true
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it "is false for an unrelated instance" do
|
|
88
|
+
expect(Product === Model.new).to be_false
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it "is true for a decorated instance" do
|
|
92
|
+
decorator = double(source: Product.new)
|
|
93
|
+
|
|
94
|
+
expect(Product === decorator).to be_true
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it "is true for a decorated derived instance" do
|
|
98
|
+
decorator = double(source: Class.new(Product).new)
|
|
99
|
+
|
|
100
|
+
expect(Product === decorator).to be_true
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it "is false for a decorated unrelated instance" do
|
|
104
|
+
decorator = double(source: Model.new)
|
|
105
|
+
|
|
106
|
+
expect(Product === decorator).to be_false
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
describe ".decorate" do
|
|
111
|
+
it "calls #decorate_collection on .decorator_class" do
|
|
112
|
+
scoped = [Product.new]
|
|
113
|
+
Product.stub scoped: scoped
|
|
114
|
+
|
|
115
|
+
Product.decorator_class.should_receive(:decorate_collection).with(scoped, {}).and_return(:decorated_collection)
|
|
116
|
+
expect(Product.decorate).to be :decorated_collection
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it "accepts options" do
|
|
120
|
+
options = {context: {some: "context"}}
|
|
121
|
+
Product.stub scoped: []
|
|
122
|
+
|
|
123
|
+
Product.decorator_class.should_receive(:decorate_collection).with([], options)
|
|
124
|
+
Product.decorate(options)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
describe ".decorator_class" do
|
|
129
|
+
context "for classes" do
|
|
130
|
+
it "infers the decorator from the class" do
|
|
131
|
+
expect(Product.decorator_class).to be ProductDecorator
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
context "for ActiveModel classes" do
|
|
136
|
+
it "infers the decorator from the model name" do
|
|
137
|
+
Product.stub(:model_name).and_return("Other")
|
|
138
|
+
|
|
139
|
+
expect(Product.decorator_class).to be OtherDecorator
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
context "in a namespace" do
|
|
144
|
+
context "for classes" do
|
|
145
|
+
it "infers the decorator from the class" do
|
|
146
|
+
expect(Namespaced::Product.decorator_class).to be Namespaced::ProductDecorator
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
context "for ActiveModel classes" do
|
|
151
|
+
it "infers the decorator from the model name" do
|
|
152
|
+
Namespaced::Product.stub(:model_name).and_return("Namespaced::Other")
|
|
153
|
+
|
|
154
|
+
expect(Namespaced::Product.decorator_class).to be Namespaced::OtherDecorator
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
context "when the decorator can't be inferred" do
|
|
160
|
+
it "throws an UninferrableDecoratorError" do
|
|
161
|
+
expect{Model.decorator_class}.to raise_error UninferrableDecoratorError
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
end
|
|
167
|
+
end
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module Draper
|
|
4
|
+
describe DecoratedAssociation do
|
|
5
|
+
|
|
6
|
+
describe "#initialize" do
|
|
7
|
+
describe "options validation" do
|
|
8
|
+
it "does not raise error on valid options" do
|
|
9
|
+
valid_options = {with: Decorator, scope: :foo, context: {}}
|
|
10
|
+
expect{DecoratedAssociation.new(Decorator.new(Model.new), :association, valid_options)}.not_to raise_error
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "raises error on invalid options" do
|
|
14
|
+
expect{DecoratedAssociation.new(Decorator.new(Model.new), :association, foo: "bar")}.to raise_error ArgumentError, /Unknown key/
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
describe "#call" do
|
|
20
|
+
let(:context) { {some: "context"} }
|
|
21
|
+
let(:options) { {} }
|
|
22
|
+
|
|
23
|
+
let(:decorated_association) do
|
|
24
|
+
owner = double(context: nil, source: double(association: associated))
|
|
25
|
+
|
|
26
|
+
DecoratedAssociation.new(owner, :association, options).tap do |decorated_association|
|
|
27
|
+
decorated_association.stub context: context
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
context "for a singular association" do
|
|
32
|
+
let(:associated) { Model.new }
|
|
33
|
+
|
|
34
|
+
context "when :with option was given" do
|
|
35
|
+
let(:options) { {with: Decorator} }
|
|
36
|
+
|
|
37
|
+
it "uses the specified decorator" do
|
|
38
|
+
Decorator.should_receive(:decorate).with(associated, context: context).and_return(:decorated)
|
|
39
|
+
expect(decorated_association.call).to be :decorated
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
context "when :with option was not given" do
|
|
44
|
+
it "infers the decorator" do
|
|
45
|
+
associated.stub decorator_class: OtherDecorator
|
|
46
|
+
|
|
47
|
+
OtherDecorator.should_receive(:decorate).with(associated, context: context).and_return(:decorated)
|
|
48
|
+
expect(decorated_association.call).to be :decorated
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
context "for a collection association" do
|
|
54
|
+
let(:associated) { [] }
|
|
55
|
+
|
|
56
|
+
context "when :with option is a collection decorator" do
|
|
57
|
+
let(:options) { {with: ProductsDecorator} }
|
|
58
|
+
|
|
59
|
+
it "uses the specified decorator" do
|
|
60
|
+
ProductsDecorator.should_receive(:decorate).with(associated, context: context).and_return(:decorated_collection)
|
|
61
|
+
expect(decorated_association.call).to be :decorated_collection
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
context "when :with option is a singular decorator" do
|
|
66
|
+
let(:options) { {with: ProductDecorator} }
|
|
67
|
+
|
|
68
|
+
it "uses a CollectionDecorator of the specified decorator" do
|
|
69
|
+
ProductDecorator.should_receive(:decorate_collection).with(associated, context: context).and_return(:decorated_collection)
|
|
70
|
+
expect(decorated_association.call).to be :decorated_collection
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
context "when :with option was not given" do
|
|
75
|
+
context "when the collection itself is decoratable" do
|
|
76
|
+
before { associated.stub decorator_class: ProductsDecorator }
|
|
77
|
+
|
|
78
|
+
it "infers the decorator" do
|
|
79
|
+
ProductsDecorator.should_receive(:decorate).with(associated, context: context).and_return(:decorated_collection)
|
|
80
|
+
expect(decorated_association.call).to be :decorated_collection
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
context "when the collection is not decoratable" do
|
|
85
|
+
it "uses a CollectionDecorator of inferred decorators" do
|
|
86
|
+
CollectionDecorator.should_receive(:decorate).with(associated, context: context).and_return(:decorated_collection)
|
|
87
|
+
expect(decorated_association.call).to be :decorated_collection
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
context "with a scope" do
|
|
94
|
+
let(:options) { {scope: :foo} }
|
|
95
|
+
let(:associated) { double(foo: scoped) }
|
|
96
|
+
let(:scoped) { Product.new }
|
|
97
|
+
|
|
98
|
+
it "applies the scope before decoration" do
|
|
99
|
+
expect(decorated_association.call.source).to be scoped
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
describe "#context" do
|
|
105
|
+
let(:owner_context) { {some: "context"} }
|
|
106
|
+
let(:options) { {} }
|
|
107
|
+
let(:decorated_association) do
|
|
108
|
+
owner = double(context: owner_context)
|
|
109
|
+
DecoratedAssociation.new(owner, :association, options)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
context "when :context option was given" do
|
|
113
|
+
let(:options) { {context: context} }
|
|
114
|
+
|
|
115
|
+
context "and is callable" do
|
|
116
|
+
let(:context) { ->(*){ :dynamic_context } }
|
|
117
|
+
|
|
118
|
+
it "calls it with the owner's context" do
|
|
119
|
+
context.should_receive(:call).with(owner_context)
|
|
120
|
+
decorated_association.context
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
it "returns the lambda's return value" do
|
|
124
|
+
expect(decorated_association.context).to be :dynamic_context
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
context "and is not callable" do
|
|
129
|
+
let(:context) { {other: "context"} }
|
|
130
|
+
|
|
131
|
+
it "returns the specified value" do
|
|
132
|
+
expect(decorated_association.context).to be context
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
context "when :context option was not given" do
|
|
138
|
+
it "returns the owner's context" do
|
|
139
|
+
expect(decorated_association.context).to be owner_context
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
end
|
|
145
|
+
end
|