strong_presenter 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|