strong_presenter 0.1.0 → 0.2.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/CHANGELOG.md +14 -6
- data/README.md +107 -30
- data/lib/strong_presenter/associable.rb +47 -7
- data/lib/strong_presenter/collection_presenter.rb +16 -0
- data/lib/strong_presenter/permissible.rb +9 -32
- data/lib/strong_presenter/permissions.rb +78 -75
- data/lib/strong_presenter/presenter.rb +2 -3
- data/lib/strong_presenter/presenter_helper_constructor.rb +1 -1
- data/lib/strong_presenter/version.rb +1 -1
- data/spec/dummy/app/controllers/posts_controller.rb +1 -1
- data/spec/dummy/app/mailers/post_mailer.rb +1 -1
- data/spec/strong_presenter/associable_spec.rb +197 -18
- data/spec/strong_presenter/collection_presenter_spec.rb +38 -6
- data/spec/strong_presenter/controller_additions_spec.rb +115 -0
- data/spec/strong_presenter/helper_proxy_spec.rb +53 -0
- data/spec/strong_presenter/permissible_spec.rb +6 -6
- data/spec/strong_presenter/permissions_spec.rb +81 -67
- data/spec/strong_presenter/presenter_spec.rb +83 -1
- data/spec/strong_presenter/view_context/build_strategy_spec.rb +116 -0
- data/spec/strong_presenter/view_context_spec.rb +154 -0
- data/spec/strong_presenter/view_helpers_spec.rb +8 -0
- data/spec/support/shared_examples/view_helpers.rb +39 -0
- data/strong_presenter.gemspec +2 -2
- metadata +74 -100
- checksums.yaml +0 -7
@@ -2,56 +2,61 @@ module StrongPresenter
|
|
2
2
|
# @private
|
3
3
|
#
|
4
4
|
# Storage format:
|
5
|
-
#
|
6
|
-
#
|
7
|
-
# of symbols in a Set. There is one top level presenter - the one which initialized the
|
8
|
-
# Permissions object.
|
5
|
+
# Each attribute path is stored as an array of objects, usually strings in a Set. For indifferent access,
|
6
|
+
# all symbols are converted to strings.
|
9
7
|
#
|
10
|
-
# When a presenter checks for permissions, the attribute path relative to the top
|
11
|
-
# presenter is prepended to each attribute path, and its existence checked in the Set.
|
12
|
-
#
|
13
8
|
# Arguments can also be part of permissions control. They are simply additional elements in the attribute path array,
|
14
|
-
# and need not be
|
15
|
-
#
|
9
|
+
# and need not be strings or symbols. Permitting a string or symbol argument automatically permits both.
|
10
|
+
#
|
11
|
+
# When checking if paths with tainted strings/elements are permitted, only exact matches are allowed
|
16
12
|
class Permissions
|
17
13
|
|
18
|
-
|
19
|
-
|
14
|
+
def prefix_path
|
15
|
+
@prefix_path || []
|
16
|
+
end
|
17
|
+
|
18
|
+
# Initialize, optionally with link to permitted paths, and the prefix to that (with copy on write semantics)
|
19
|
+
def initialize(permissions = nil, prefix_path = [])
|
20
|
+
unless permissions.nil?
|
21
|
+
@permitted_paths = permissions.permitted_paths
|
22
|
+
@prefix_path = permissions.prefix_path + canonicalize(prefix_path) # copy on write
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Checks whether everything is permitted. Considers :*, which permits all methods
|
27
|
+
# but not association methods to be complete.
|
20
28
|
# @return [Boolean]
|
21
29
|
def complete?
|
22
|
-
|
30
|
+
permitted? prefix_path + [:*]
|
23
31
|
end
|
24
32
|
|
25
|
-
# Permits
|
26
|
-
#
|
33
|
+
# Permits wildcard method, but not association methods.
|
27
34
|
# @return self
|
28
35
|
def permit_all!
|
29
|
-
|
30
|
-
permitted_paths << []
|
36
|
+
copy_on_write!
|
37
|
+
permitted_paths << [:*]
|
31
38
|
self
|
32
39
|
end
|
33
40
|
|
34
|
-
# @overload permitted? prefix_path = nil, attribute_path
|
35
|
-
#
|
36
41
|
# Checks if the attribute path is permitted. This is the case if
|
37
42
|
# any array prefix has been permitted.
|
38
|
-
#
|
39
|
-
# @param [
|
40
|
-
# @param [Symbol, Array<Symbol,Object>] attribute_path
|
43
|
+
# @param [Object, Array<Object>] prefix_path
|
44
|
+
# @param [Object, Array<Object>] attribute_path
|
41
45
|
# @return [Boolean]
|
42
|
-
def permitted?
|
43
|
-
|
46
|
+
def permitted? attribute_path
|
47
|
+
attribute_path = canonicalize(attribute_path)
|
48
|
+
return true if permitted_paths.include? prefix_path + attribute_path # exact match
|
49
|
+
!path_tainted?(attribute_path) and permitted_by_wildcard?(prefix_path + attribute_path) # wildcard match only if not tainted
|
44
50
|
end
|
45
51
|
|
46
52
|
# Selects the attribute paths which are permitted.
|
47
|
-
#
|
48
53
|
# @param [Array] prefix_path
|
49
54
|
# namespace in which each of the given attribute paths are in
|
50
|
-
# @param [[
|
55
|
+
# @param [[Object, Array<Object>]*] *attribute_paths
|
51
56
|
# each attribute path is a symbol or array of symbols
|
52
|
-
# @return [Array<
|
53
|
-
def select_permitted
|
54
|
-
|
57
|
+
# @return [Array<Object, Array<Object>>] array of attribute paths permitted
|
58
|
+
def select_permitted *attribute_paths
|
59
|
+
attribute_paths.select { |attribute_path| permitted?(attribute_path) }
|
55
60
|
end
|
56
61
|
|
57
62
|
# Rejects the attribute paths which are permitted. Opposite of select_permitted.
|
@@ -59,38 +64,27 @@ module StrongPresenter
|
|
59
64
|
#
|
60
65
|
# @param [Array] prefix_path
|
61
66
|
# namespace in which each of the given attribute paths are in
|
62
|
-
# @param [[
|
63
|
-
# each attribute path is
|
64
|
-
# @return [Array<
|
65
|
-
def reject_permitted
|
66
|
-
|
67
|
+
# @param [[Object, Array<Object>]*] *attribute_paths
|
68
|
+
# each attribute path is an object(string) or array
|
69
|
+
# @return [Array<Object, Array<Object>>] array of attribute paths remaining
|
70
|
+
def reject_permitted *attribute_paths
|
71
|
+
attribute_paths.reject { |attribute_path| permitted?(attribute_path) }
|
67
72
|
end
|
68
73
|
|
69
74
|
# Permits some attribute paths
|
70
75
|
#
|
71
|
-
# @param [Array
|
76
|
+
# @param [Array] prefix_path
|
72
77
|
# path to prepend to each attribute path
|
73
|
-
# @param [[
|
74
|
-
def permit
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
permitted_paths << prefix_path
|
78
|
+
# @param [[Object, Array]*] *attribute_paths
|
79
|
+
def permit *attribute_paths
|
80
|
+
copy_on_write!
|
81
|
+
attribute_paths = attribute_paths.map{|path|canonicalize(path).taint} # exact match
|
82
|
+
reject_permitted(*attribute_paths).each do |attribute_path| # don't permit if already permitted
|
83
|
+
permitted_paths << attribute_path # prefix_path = [] because of copy on write
|
79
84
|
end
|
80
85
|
self
|
81
86
|
end
|
82
87
|
|
83
|
-
# Merges the permissions from another Permissions object
|
84
|
-
#
|
85
|
-
# @param [Permissions] permissions
|
86
|
-
# @param [Array<Symbol>] prefix
|
87
|
-
# prefix to prepend to paths in permissions
|
88
|
-
# @return self
|
89
|
-
def merge permissions, prefix = []
|
90
|
-
permitted_paths.merge permissions.permitted_paths.map{|path| prefix+path} if permissions.is_a? self.class
|
91
|
-
self
|
92
|
-
end
|
93
|
-
|
94
88
|
protected
|
95
89
|
|
96
90
|
def permitted_paths
|
@@ -98,40 +92,49 @@ module StrongPresenter
|
|
98
92
|
end
|
99
93
|
|
100
94
|
private
|
101
|
-
#
|
102
|
-
def
|
103
|
-
|
104
|
-
permitted_partial?([], prefix_path + Array(attribute_path))
|
95
|
+
# Is this still referencing another objects permissions?
|
96
|
+
def reference?
|
97
|
+
!@prefix_path.nil?
|
105
98
|
end
|
106
99
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
100
|
+
# Make a copy if this still references something else, since we are planning on writing soon
|
101
|
+
def copy_on_write!
|
102
|
+
if prefix_path == []
|
103
|
+
@permitted_paths = permitted_paths.dup
|
104
|
+
elsif reference?
|
105
|
+
@permitted_paths, old_set = Set.new, permitted_paths
|
106
|
+
old_set.each do |path|
|
107
|
+
@permitted_paths << path[prefix_path.size...path.size] if path[0...prefix_path.size] == prefix_path
|
108
|
+
end
|
111
109
|
end
|
110
|
+
@prefix_path = nil
|
112
111
|
end
|
113
112
|
|
114
|
-
def
|
115
|
-
|
116
|
-
attribute_paths.select do |attribute_path|
|
117
|
-
permitted_partial? prefix_path.dup, attribute_path
|
118
|
-
end
|
113
|
+
def path_tainted? attribute_path
|
114
|
+
attribute_path.tainted? or attribute_path.any? { |element| element.tainted? }
|
119
115
|
end
|
120
116
|
|
121
|
-
#
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
117
|
+
# Caution: Will mutate path
|
118
|
+
def permitted_by_wildcard? path
|
119
|
+
unless path.empty?
|
120
|
+
path[-1] = :*
|
121
|
+
return true if permitted_paths.include? path
|
122
|
+
end
|
123
|
+
until path.empty?
|
124
|
+
path[-1] = :**
|
125
|
+
return true if permitted_paths.include? path
|
126
|
+
path.pop
|
129
127
|
end
|
128
|
+
false
|
130
129
|
end
|
131
130
|
|
132
|
-
#
|
133
|
-
def
|
134
|
-
array
|
131
|
+
# Converts symbols to strings (except for wildcard symbol)
|
132
|
+
def canonicalize array
|
133
|
+
array = Array(array)
|
134
|
+
canonical_array = array.map{|e|e.is_a?(Symbol) ? e.to_s : e}
|
135
|
+
canonical_array[-1] = array.last if [:*, :**].include? array.last
|
136
|
+
canonical_array.taint if array.tainted?
|
137
|
+
canonical_array
|
135
138
|
end
|
136
139
|
|
137
140
|
end
|
@@ -84,6 +84,7 @@ module StrongPresenter
|
|
84
84
|
#
|
85
85
|
def presents *attributes
|
86
86
|
select_permitted(*attributes).map do |args|
|
87
|
+
args = Array(args)
|
87
88
|
obj = self # drill into associations
|
88
89
|
while (args.size > 1) && self.class.send(:presenter_associations).include?(args[0]) do
|
89
90
|
obj = obj.public_send args.slice!(0)
|
@@ -146,7 +147,7 @@ module StrongPresenter
|
|
146
147
|
|
147
148
|
def set_presenter_collection
|
148
149
|
collection_presenter = get_collection_presenter
|
149
|
-
const_set "Collection", collection_presenter
|
150
|
+
const_set "Collection", collection_presenter # will overwrite if constant only defined in superclass
|
150
151
|
end
|
151
152
|
|
152
153
|
private
|
@@ -174,8 +175,6 @@ module StrongPresenter
|
|
174
175
|
# Checks whether this presenter class has a corresponding {object_class}.
|
175
176
|
def object_class?
|
176
177
|
!!(@object_class ||= Inferrer.new(name).chomp("Presenter").inferred_class)
|
177
|
-
rescue NameError
|
178
|
-
false
|
179
178
|
end
|
180
179
|
|
181
180
|
# Sets the model presented by the class
|
@@ -54,7 +54,7 @@ module StrongPresenter
|
|
54
54
|
|
55
55
|
# wrap model with presenter and return
|
56
56
|
def wrapped_object(controller)
|
57
|
-
factory.wrap(controller.send :instance_variable_get, @object) { |presenter| self.instance_exec presenter, &block }
|
57
|
+
factory.wrap(controller.send :instance_variable_get, @object) { |presenter| self.instance_exec presenter, &block unless block.nil? }
|
58
58
|
end
|
59
59
|
end
|
60
60
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
class PostsController < ApplicationController
|
2
2
|
presents :post, :with => StrongPresenter::Presenter, :only => :show
|
3
3
|
presents :post, :with => PostPresenter, :only => :show do |presenter|
|
4
|
-
presenter.permit(:permit_to_present, :peek_a_boo)
|
4
|
+
presenter.permit!(:permit_to_present, :peek_a_boo)
|
5
5
|
end
|
6
6
|
presents :post, :with => StrongPresenter::Presenter, :only => [:index]
|
7
7
|
presents :post, :with => StrongPresenter::Presenter, :except => [:show, :new]
|
@@ -7,12 +7,10 @@ module StrongPresenter
|
|
7
7
|
protect_class ProductPresenter
|
8
8
|
|
9
9
|
before(:each) do
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
def name(*args); "Presented #{object.name}#{(args+[""])[0]}"; end
|
15
|
-
end
|
10
|
+
stub_const('Manufacturer', Class.new(Model))
|
11
|
+
Manufacturer.send(:define_method, :name) {"Factory"}
|
12
|
+
stub_const('ManufacturerPresenter', Class.new(StrongPresenter::Presenter))
|
13
|
+
ManufacturerPresenter.send(:define_method, :name) { |*args| "Presented #{object.name}#{(args+[""])[0]}" }
|
16
14
|
Product.send(:define_method, :manufacturer) { @manufacturer ||= Manufacturer.new }
|
17
15
|
Product.send(:define_method, :name) { "Product" }
|
18
16
|
ProductPresenter.presents_association :manufacturer
|
@@ -25,10 +23,10 @@ module StrongPresenter
|
|
25
23
|
expect(@product_presenter.manufacturer.name).to eq "Presented Factory"
|
26
24
|
end
|
27
25
|
|
28
|
-
it 'does not allow presenting without permit' do
|
26
|
+
it 'does not allow presenting without permit!' do
|
29
27
|
expect(@product_presenter.presents :manufacturer).to be_empty
|
30
28
|
expect(@product_presenter.presents [:manufacturer, :name]).to be_empty
|
31
|
-
@product_presenter.permit :name
|
29
|
+
@product_presenter.permit! :name
|
32
30
|
expect(@product_presenter.presents [:manufacturer, :name]).to be_empty
|
33
31
|
end
|
34
32
|
|
@@ -36,21 +34,23 @@ module StrongPresenter
|
|
36
34
|
expect(@product_presenter.manufacturer.presents :name).to be_empty
|
37
35
|
end
|
38
36
|
|
39
|
-
context 'with association permitted' do
|
37
|
+
context 'with association methods permitted' do
|
40
38
|
before(:each) do
|
41
|
-
@product_presenter.permit :manufacturer
|
39
|
+
@product_presenter.permit! [:manufacturer, :*]
|
42
40
|
end
|
43
41
|
|
44
|
-
it 'allows presenting association' do
|
42
|
+
it 'allows presenting association if exact match' do
|
43
|
+
expect(@product_presenter.present(:manufacturer)).to be_nil
|
44
|
+
@product_presenter.permit! :manufacturer
|
45
45
|
expect(@product_presenter.present(:manufacturer).class).to be ManufacturerPresenter
|
46
46
|
end
|
47
47
|
|
48
|
-
it 'allows presenting association
|
48
|
+
it 'allows presenting association methods' do
|
49
49
|
expect(@product_presenter.present [:manufacturer, :name]).to eq "Presented Factory"
|
50
50
|
end
|
51
51
|
|
52
|
-
it '
|
53
|
-
expect(@product_presenter.present [:manufacturer, :name, " arg"]).to
|
52
|
+
it 'cannot present method with arguments' do
|
53
|
+
expect(@product_presenter.present [:manufacturer, :name, " arg"]).to be_nil
|
54
54
|
end
|
55
55
|
|
56
56
|
it 'allows presenting from association' do
|
@@ -59,19 +59,41 @@ module StrongPresenter
|
|
59
59
|
end
|
60
60
|
|
61
61
|
it 'rejects full path from association' do
|
62
|
-
@product_presenter.permit [:manufacturer, :name]
|
62
|
+
@product_presenter.permit! [:manufacturer, :name]
|
63
63
|
expect(@product_presenter.manufacturer.presents [:manufacturer, :name]).to be_empty
|
64
64
|
end
|
65
65
|
end
|
66
66
|
|
67
|
+
context 'with missing constants' do
|
68
|
+
protect_class Product
|
69
|
+
protect_class ProductPresenter
|
70
|
+
|
71
|
+
it 'can declare association without ActiveRecord' do
|
72
|
+
hide_const('ActiveRecord')
|
73
|
+
Product.send(:define_method, :inverse) { @inverse ||= Product.new }
|
74
|
+
ProductPresenter.presents_association :inverse
|
75
|
+
@product_presenter = ProductPresenter.new(Product.new)
|
76
|
+
expect(@product_presenter.inverse.class).to be ProductPresenter
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'can declare association with empty ActiveRecord' do
|
80
|
+
stub_const('ActiveRecord', Module.new)
|
81
|
+
Product.send(:define_method, :inverse) { @inverse ||= Product.new }
|
82
|
+
ProductPresenter.presents_association :inverse
|
83
|
+
@product_presenter = ProductPresenter.new(Product.new)
|
84
|
+
expect(@product_presenter.inverse.class).to be ProductPresenter
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
67
88
|
context 'with CollectionPresenter' do
|
68
89
|
protect_class Product
|
69
90
|
protect_class ProductPresenter
|
70
91
|
before(:each) do
|
92
|
+
stub_const('ProductList', Class.new(Model))
|
71
93
|
Product.send(:define_method, :initialize) { |name| @name = name }
|
72
94
|
Product.send(:attr_reader, :name)
|
73
95
|
ProductPresenter.send(:define_method, :name) { object.name }
|
74
|
-
class ProductList
|
96
|
+
class ProductList
|
75
97
|
attr_accessor :products
|
76
98
|
def initialize; @products = [Product.new("X"), Product.new("Y"), Product.new("Z")]; end
|
77
99
|
end
|
@@ -104,19 +126,176 @@ module StrongPresenter
|
|
104
126
|
expect(presenter.wheels.class).to be WheelPresenter::Collection
|
105
127
|
end
|
106
128
|
|
107
|
-
it '
|
129
|
+
it 'cannot infer without presenter' do
|
130
|
+
hide_const('WheelPresenter')
|
131
|
+
CarPresenter.presents_association :wheels
|
132
|
+
|
133
|
+
car = Car.new
|
134
|
+
car.wheels << Wheel.new
|
135
|
+
presenter = CarPresenter.new(car)
|
136
|
+
expect(presenter.wheels.class).to be StrongPresenter::CollectionPresenter
|
137
|
+
expect{presenter.wheels[0]}.to raise_error StrongPresenter::UninferrablePresenterError
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'infers collection presenter on factory construction' do
|
141
|
+
hide_const('WheelPresenter')
|
142
|
+
stub_const('WheelsPresenter', Class.new(StrongPresenter::CollectionPresenter))
|
143
|
+
WheelsPresenter.send(:define_method, :number_of){object.size}
|
144
|
+
CarPresenter.presents_association :wheels
|
145
|
+
|
146
|
+
car = Car.new
|
147
|
+
car.wheels = [Wheel.new, Wheel.new, Wheel.new]
|
148
|
+
presenter = CarPresenter.new(car)
|
149
|
+
expect(presenter.wheels.class).to be WheelsPresenter
|
150
|
+
expect(presenter.wheels.number_of).to be 3
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'infers presenter of polymorphic association' do
|
108
154
|
WheelPresenter.presents_association :vehicle
|
109
155
|
|
110
156
|
wheel = Wheel.new
|
111
157
|
wheel.vehicle = Car.new
|
112
158
|
presenter = WheelPresenter.new(wheel)
|
113
159
|
expect(presenter.vehicle.class).to be CarPresenter
|
160
|
+
end
|
114
161
|
|
115
|
-
|
162
|
+
it 'infers new association after reload' do
|
163
|
+
WheelPresenter.presents_association :vehicle
|
164
|
+
wheel = Wheel.new
|
165
|
+
wheel.vehicle = Car.new
|
166
|
+
presenter = WheelPresenter.new(wheel)
|
167
|
+
presenter.vehicle
|
116
168
|
wheel.vehicle = Wheel.new
|
169
|
+
expect(presenter.vehicle.class).to be CarPresenter
|
170
|
+
expect(presenter.reload!.vehicle.class).to be WheelPresenter
|
117
171
|
expect(presenter.vehicle.class).to be WheelPresenter
|
118
172
|
end
|
119
173
|
end
|
174
|
+
|
175
|
+
describe 'without suitable presenter' do
|
176
|
+
protect_class Product
|
177
|
+
it 'throws uninferrable presenter' do
|
178
|
+
Product.send(:attr_accessor, :string)
|
179
|
+
ProductPresenter.presents_association :string
|
180
|
+
Product.send(:define_method, :initialize) { self.string = "String"}
|
181
|
+
|
182
|
+
presenter = ProductPresenter.new(Product.new)
|
183
|
+
expect{presenter.string}.to raise_error(StrongPresenter::UninferrablePresenterError)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
describe 'Permissible' do
|
188
|
+
protect_class Product
|
189
|
+
protect_class ProductPresenter
|
190
|
+
before (:each) do
|
191
|
+
Product.send(:attr_accessor, :name, :description, :price, :subproducts, :other)
|
192
|
+
Product.send(:define_method, :initialize) do |name, description = "Description", price = 1, subproducts = []|
|
193
|
+
self.name = name
|
194
|
+
self.description = description
|
195
|
+
self.price = price
|
196
|
+
self.subproducts = Array(subproducts)
|
197
|
+
self.other = Wheel.new
|
198
|
+
end
|
199
|
+
ProductPresenter.presents_association :subproducts
|
200
|
+
ProductPresenter.presents_association :other
|
201
|
+
ProductPresenter.delegate :name, :description, :price
|
202
|
+
@product = Product.new("Main", "I, Main", 4.2, [Product.new("Sub A", "Small", 8), Product.new("Component B")])
|
203
|
+
@product_presenter = ProductPresenter.new(@product)
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'collection association inherits permissions' do
|
207
|
+
@product_presenter.permit! [:subproducts, :name]
|
208
|
+
expect(@product_presenter.subproducts.select_permitted(:name, :price)).to eq [:name]
|
209
|
+
end
|
210
|
+
|
211
|
+
it 'association inherits permissions' do
|
212
|
+
@product_presenter.permit! [:other, :stuff]
|
213
|
+
expect(@product_presenter.other.select_permitted(:other, :stuff)).to eq [:stuff]
|
214
|
+
end
|
215
|
+
|
216
|
+
it 'collection item inherits permissions' do
|
217
|
+
@product_presenter.permit! [:subproducts, :name]
|
218
|
+
expect(@product_presenter.subproducts[0].presents(:name, :price)).to eq ["Sub A"]
|
219
|
+
end
|
220
|
+
|
221
|
+
it 'forwards permit to associations' do
|
222
|
+
other_presenter = @product_presenter.other
|
223
|
+
@product_presenter.permit! [:other, :stuff]
|
224
|
+
expect(other_presenter.select_permitted(:other, :stuff)).to eq [:stuff]
|
225
|
+
end
|
226
|
+
|
227
|
+
it 'forwards permit to association collection items' do
|
228
|
+
subproduct_presenter = @product_presenter.subproducts[0]
|
229
|
+
@product_presenter.permit! [:subproducts, :name]
|
230
|
+
expect(subproduct_presenter.presents(:name, :price)).to eq ["Sub A"]
|
231
|
+
end
|
232
|
+
|
233
|
+
it 'does not feed permissions to siblings' do
|
234
|
+
@product_presenter.subproducts[0].permit! :name
|
235
|
+
expect(@product_presenter.subproducts[0].presents(:name, :price)).to eq ["Sub A"]
|
236
|
+
expect(@product_presenter.subproducts[1].presents(:name, :price)).to be_empty
|
237
|
+
end
|
238
|
+
|
239
|
+
it 'appears to clear permissions on reload' do
|
240
|
+
@product_presenter.subproducts[0].permit! :name
|
241
|
+
expect(@product_presenter.reload!.subproducts[0].presents(:name, :price)).to be_empty
|
242
|
+
end
|
243
|
+
|
244
|
+
it 'can add to association permissions' do
|
245
|
+
@product_presenter.permit! [:subproducts, :name]
|
246
|
+
@product_presenter.subproducts[0].permit! :price
|
247
|
+
expect(@product_presenter.subproducts[0].presents(:name, :price)).to eq ["Sub A", 8]
|
248
|
+
end
|
249
|
+
|
250
|
+
it 'can add to collection permissions' do
|
251
|
+
@product_presenter.permit! [:subproducts, :name]
|
252
|
+
@product_presenter.subproducts.permit! :description
|
253
|
+
expect(@product_presenter.subproducts[0].presents(:name, :price, :description)).to eq ["Sub A", "Small"]
|
254
|
+
end
|
255
|
+
|
256
|
+
it 'can add to item permissions' do
|
257
|
+
@product_presenter.subproducts.permit! :description
|
258
|
+
@product_presenter.subproducts[0].permit! :price
|
259
|
+
expect(@product_presenter.subproducts[0].presents(:name, :price, :description)).to eq [8, "Small"]
|
260
|
+
expect(@product_presenter.subproducts[1].presents(:name, :price, :description)).to eq ["Description"]
|
261
|
+
end
|
262
|
+
|
263
|
+
it 'adds propagated permissions' do
|
264
|
+
@product_presenter.subproducts[0].permit! :price
|
265
|
+
@product_presenter.permit! [:subproducts, :name]
|
266
|
+
expect(@product_presenter.subproducts[0].presents(:name, :price, :description)).to eq ["Sub A", 8]
|
267
|
+
end
|
268
|
+
|
269
|
+
it 'propagates double wildcard' do
|
270
|
+
@product_presenter.subproducts[0].permit! :price
|
271
|
+
@product_presenter.permit! :**
|
272
|
+
expect(@product_presenter.subproducts[0].presents(:name, "price", :description)).to eq ["Sub A", 8, "Small"]
|
273
|
+
end
|
274
|
+
|
275
|
+
it 'does not propagate single wildcard' do
|
276
|
+
@product_presenter.subproducts[0].permit! :price
|
277
|
+
@product_presenter.permit! :*
|
278
|
+
expect(@product_presenter.subproducts[0].presents(:name, :price, :description)).to eq [8]
|
279
|
+
end
|
280
|
+
|
281
|
+
it 'can propagate string path to collection' do
|
282
|
+
@product_presenter.subproducts.permit! :price
|
283
|
+
@product_presenter.permit! ["subproducts", "name"]
|
284
|
+
expect(@product_presenter.subproducts[0].presents(:name, :price, :description)).to eq ["Sub A", 8]
|
285
|
+
end
|
286
|
+
|
287
|
+
it 'propagates wildcard ending' do
|
288
|
+
@product_presenter.subproducts[0].permit! :price
|
289
|
+
@product_presenter.permit! [:subproducts, :*]
|
290
|
+
expect(@product_presenter.subproducts[0].presents(:name, "price", :description)).to eq ["Sub A", 8, "Small"]
|
291
|
+
end
|
292
|
+
|
293
|
+
it 'handles taint with mixture of wildcard and exact matches' do
|
294
|
+
@product_presenter.subproducts[0].permit! :price
|
295
|
+
@product_presenter.permit! [:subproducts, :*]
|
296
|
+
expect(@product_presenter.subproducts[0].presents(:name, "price".taint, "description".taint)).to eq ["Sub A", 8]
|
297
|
+
end
|
298
|
+
end
|
120
299
|
end
|
121
300
|
end
|
122
301
|
|