brainstem 1.1.1 → 1.3.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +81 -4
- data/Gemfile.lock +9 -9
- data/README.md +134 -37
- data/brainstem.gemspec +1 -1
- data/lib/brainstem/api_docs/endpoint.rb +40 -18
- data/lib/brainstem/api_docs/formatters/markdown/endpoint_formatter.rb +27 -22
- data/lib/brainstem/api_docs/formatters/markdown/helper.rb +9 -0
- data/lib/brainstem/api_docs/formatters/markdown/presenter_formatter.rb +14 -6
- data/lib/brainstem/api_docs/presenter.rb +3 -7
- data/lib/brainstem/concerns/controller_dsl.rb +138 -14
- data/lib/brainstem/concerns/presenter_dsl.rb +39 -6
- data/lib/brainstem/dsl/array_block_field.rb +25 -0
- data/lib/brainstem/dsl/block_field.rb +69 -0
- data/lib/brainstem/dsl/configuration.rb +13 -5
- data/lib/brainstem/dsl/field.rb +15 -1
- data/lib/brainstem/dsl/fields_block.rb +20 -2
- data/lib/brainstem/dsl/hash_block_field.rb +30 -0
- data/lib/brainstem/presenter.rb +10 -6
- data/lib/brainstem/presenter_validator.rb +20 -11
- data/lib/brainstem/version.rb +1 -1
- data/spec/brainstem/api_docs/endpoint_spec.rb +347 -14
- data/spec/brainstem/api_docs/formatters/markdown/endpoint_formatter_spec.rb +106 -13
- data/spec/brainstem/api_docs/formatters/markdown/helper_spec.rb +19 -0
- data/spec/brainstem/api_docs/formatters/markdown/presenter_formatter_spec.rb +150 -37
- data/spec/brainstem/api_docs/presenter_spec.rb +85 -18
- data/spec/brainstem/concerns/controller_dsl_spec.rb +615 -31
- data/spec/brainstem/concerns/inheritable_configuration_spec.rb +32 -9
- data/spec/brainstem/concerns/presenter_dsl_spec.rb +99 -25
- data/spec/brainstem/dsl/array_block_field_spec.rb +43 -0
- data/spec/brainstem/dsl/block_field_spec.rb +188 -0
- data/spec/brainstem/dsl/field_spec.rb +86 -20
- data/spec/brainstem/dsl/hash_block_field_spec.rb +166 -0
- data/spec/brainstem/presenter_collection_spec.rb +24 -24
- data/spec/brainstem/presenter_spec.rb +233 -9
- data/spec/brainstem/query_strategies/filter_and_search_spec.rb +1 -1
- data/spec/spec_helpers/presenters.rb +8 -0
- data/spec/spec_helpers/schema.rb +13 -0
- metadata +15 -6
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'brainstem/concerns/inheritable_configuration'
|
2
2
|
require 'brainstem/dsl/association'
|
3
3
|
require 'brainstem/dsl/field'
|
4
|
+
require 'brainstem/dsl/array_block_field'
|
5
|
+
require 'brainstem/dsl/hash_block_field'
|
4
6
|
require 'brainstem/dsl/conditional'
|
5
7
|
|
6
8
|
require 'brainstem/dsl/base_block'
|
@@ -50,6 +52,13 @@ module Brainstem
|
|
50
52
|
configuration[:nodoc] = true
|
51
53
|
end
|
52
54
|
|
55
|
+
#
|
56
|
+
# Temporary implementation to track controllers that have been documented.
|
57
|
+
#
|
58
|
+
def documented!
|
59
|
+
configuration[:documented] = true
|
60
|
+
end
|
61
|
+
|
53
62
|
# Declare a helper module or block whose methods will be available in dynamic fields and associations.
|
54
63
|
def helper(mod = nil, &block)
|
55
64
|
if mod
|
@@ -114,15 +123,17 @@ module Brainstem
|
|
114
123
|
|
115
124
|
|
116
125
|
#
|
117
|
-
# @overload filter(name, options = {})
|
126
|
+
# @overload filter(name, type, options = {})
|
118
127
|
# @param [Symbol] name The name of the scope that may be applied as a
|
119
128
|
# filter.
|
129
|
+
# @param [Symbol] type The type of the value that filter holds.
|
120
130
|
# @option options [Object] :default If set, causes this filter to be
|
121
131
|
# applied to every request. If the filter accepts parameters, the
|
122
132
|
# value given here will be passed to the filter when it is applied.
|
123
133
|
# @option options [String] :info Docstring for the filter.
|
134
|
+
# @option options [Array] :items List of assignable values for the filter.
|
124
135
|
#
|
125
|
-
# @overload filter(name, options = {}, &block)
|
136
|
+
# @overload filter(name, type, options = {}, &block)
|
126
137
|
# @param [Symbol] name The filter can be requested using this name.
|
127
138
|
# @yieldparam scope [ActiveRecord::Relation] The scope that the
|
128
139
|
# filter should use as a base.
|
@@ -131,16 +142,25 @@ module Brainstem
|
|
131
142
|
# @yieldreturn [ActiveRecord::Relation] A new scope that filters the
|
132
143
|
# scope that was yielded.
|
133
144
|
#
|
134
|
-
def filter(name, options = {}, &block)
|
135
|
-
|
145
|
+
def filter(name, type = DEFAULT_FILTER_DATA_TYPE, options = {}, &block)
|
146
|
+
if type.is_a?(Hash)
|
147
|
+
options = type if options.blank?
|
148
|
+
type = DEFAULT_FILTER_DATA_TYPE
|
149
|
+
|
150
|
+
deprecated_type_warning
|
151
|
+
elsif type.to_s == 'array'
|
152
|
+
options[:item_type] = options[:item_type].to_s.presence || DEFAULT_FILTER_DATA_TYPE
|
153
|
+
end
|
154
|
+
|
155
|
+
valid_options = %w(default info include_params nodoc items item_type)
|
136
156
|
options.select! { |k, v| valid_options.include?(k.to_s) }
|
137
157
|
|
138
158
|
configuration[:filters][name] = options.merge({
|
139
|
-
value: (block_given? ? block : nil)
|
159
|
+
value: (block_given? ? block : nil),
|
160
|
+
type: type.to_s,
|
140
161
|
})
|
141
162
|
end
|
142
163
|
|
143
|
-
|
144
164
|
def search(&block)
|
145
165
|
configuration[:search] = block
|
146
166
|
end
|
@@ -166,6 +186,19 @@ module Brainstem
|
|
166
186
|
configuration.nonheritable!(:description)
|
167
187
|
configuration.nonheritable!(:nodoc)
|
168
188
|
end
|
189
|
+
|
190
|
+
DEFAULT_FILTER_DATA_TYPE = 'string'
|
191
|
+
private_constant :DEFAULT_FILTER_DATA_TYPE
|
192
|
+
|
193
|
+
def deprecated_type_warning
|
194
|
+
ActiveSupport::Deprecation.warn(
|
195
|
+
'Please specify the `type` of the filter as the second argument. If not specified, '\
|
196
|
+
'it will default to `:string`. This default behavior will be deprecated in the next major '\
|
197
|
+
'version and will need to be explicitly specified. '\
|
198
|
+
'e.g. `filter :status, :string, items: ["Started", "Completed"]`',
|
199
|
+
caller
|
200
|
+
)
|
201
|
+
end
|
169
202
|
end
|
170
203
|
end
|
171
204
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'brainstem/dsl/configuration'
|
2
|
+
require 'brainstem/dsl/block_field'
|
3
|
+
|
4
|
+
module Brainstem
|
5
|
+
module DSL
|
6
|
+
class ArrayBlockField < BlockField
|
7
|
+
def run_on(model, context, helper_instance = Object.new)
|
8
|
+
evaluated_models = evaluate_value_on(model, context, helper_instance)
|
9
|
+
|
10
|
+
evaluated_models.map do |evaluated_model|
|
11
|
+
result = {}
|
12
|
+
|
13
|
+
configuration.each do |field_name, field|
|
14
|
+
next unless field.presentable?(model, context)
|
15
|
+
|
16
|
+
model_for_field = use_parent_value?(field) ? evaluated_model : model
|
17
|
+
result[field_name] = field.run_on(model_for_field, context, context[:helper_instance])
|
18
|
+
end
|
19
|
+
|
20
|
+
result
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'brainstem/dsl/configuration'
|
2
|
+
require 'brainstem/dsl/field'
|
3
|
+
|
4
|
+
module Brainstem
|
5
|
+
module DSL
|
6
|
+
class BlockField < Field
|
7
|
+
attr_reader :configuration
|
8
|
+
|
9
|
+
delegate :to_h, :keys, :has_key?, :each, to: :configuration
|
10
|
+
|
11
|
+
def initialize(name, type, options, parent_field = nil)
|
12
|
+
super(name, type, options)
|
13
|
+
@parent_field = parent_field
|
14
|
+
end
|
15
|
+
|
16
|
+
def configuration
|
17
|
+
@configuration ||= begin
|
18
|
+
if @parent_field && @parent_field.respond_to?(:configuration)
|
19
|
+
DSL::Configuration.new(@parent_field.configuration)
|
20
|
+
else
|
21
|
+
DSL::Configuration.new
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def [](key)
|
27
|
+
configuration[key]
|
28
|
+
end
|
29
|
+
|
30
|
+
def run_on(model, context, helper_instance = Object.new)
|
31
|
+
raise NotImplementedError.new("Override this method in a sub class")
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.for(name, type, options, parent_field = nil)
|
35
|
+
case type.to_s
|
36
|
+
when 'array'
|
37
|
+
DSL::ArrayBlockField.new(name, type, options, parent_field)
|
38
|
+
when 'hash'
|
39
|
+
DSL::HashBlockField.new(name, type, options, parent_field)
|
40
|
+
else
|
41
|
+
raise "Unknown Brainstem Block Field type encountered: #{type}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def evaluate_value_on(model, context, helper_instance = Object.new)
|
46
|
+
if options[:lookup]
|
47
|
+
run_on_with_lookup(model, context, helper_instance)
|
48
|
+
elsif options[:dynamic]
|
49
|
+
proc = options[:dynamic]
|
50
|
+
if proc.arity == 1
|
51
|
+
helper_instance.instance_exec(model, &proc)
|
52
|
+
else
|
53
|
+
helper_instance.instance_exec(&proc)
|
54
|
+
end
|
55
|
+
elsif options[:via]
|
56
|
+
model.send(options[:via])
|
57
|
+
else
|
58
|
+
raise "Block field #{name} can only be evaluated if :dynamic, :lookup, :via options are specified."
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def use_parent_value?(field)
|
63
|
+
return true unless field.options.has_key?(:use_parent_value)
|
64
|
+
|
65
|
+
field.options[:use_parent_value]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -78,8 +78,8 @@ module Brainstem
|
|
78
78
|
# keys
|
79
79
|
#
|
80
80
|
def nonheritable!(key)
|
81
|
-
|
82
|
-
self.nonheritable_keys <<
|
81
|
+
formatted_key = format_key(key)
|
82
|
+
self.nonheritable_keys << formatted_key unless self.nonheritable_keys.include?(formatted_key)
|
83
83
|
end
|
84
84
|
|
85
85
|
|
@@ -104,7 +104,7 @@ module Brainstem
|
|
104
104
|
# @return [Hash] the hash, less nonheritable pairs.
|
105
105
|
#
|
106
106
|
def pairs_visible_to_children
|
107
|
-
to_h.select {|k, v| keys_visible_to_children.include?(k
|
107
|
+
to_h.select {|k, v| keys_visible_to_children.include?(format_key(k)) }
|
108
108
|
end
|
109
109
|
|
110
110
|
|
@@ -150,7 +150,7 @@ module Brainstem
|
|
150
150
|
# @param [Symbol,String] key the key to check for nonheritability.
|
151
151
|
#
|
152
152
|
def key_nonheritable_in_parent?(*key)
|
153
|
-
parent_nonheritable_keys.include?(key.first
|
153
|
+
parent_nonheritable_keys.include?(format_key(key.first))
|
154
154
|
end
|
155
155
|
|
156
156
|
|
@@ -164,7 +164,7 @@ module Brainstem
|
|
164
164
|
# @param [Symbol,String] key the key to check for heritability.
|
165
165
|
#
|
166
166
|
def key_inheritable_in_parent?(*key)
|
167
|
-
!key_nonheritable_in_parent?(key.first
|
167
|
+
!key_nonheritable_in_parent?(format_key(key.first))
|
168
168
|
end
|
169
169
|
|
170
170
|
|
@@ -258,6 +258,14 @@ module Brainstem
|
|
258
258
|
end
|
259
259
|
end
|
260
260
|
|
261
|
+
# @api private
|
262
|
+
#
|
263
|
+
# Stringifies key if key is not a Proc.
|
264
|
+
#
|
265
|
+
def format_key(key)
|
266
|
+
key.respond_to?(:call) ? key : key.to_s
|
267
|
+
end
|
268
|
+
|
261
269
|
# An Array-like object that provides `push`, `concat`, `each`, `empty?`, and `to_a` methods that act the combination
|
262
270
|
# of its own entries and those of a parent InheritableAppendSet, if present.
|
263
271
|
class InheritableAppendSet
|
data/lib/brainstem/dsl/field.rb
CHANGED
@@ -9,7 +9,7 @@ module Brainstem
|
|
9
9
|
|
10
10
|
def initialize(name, type, options)
|
11
11
|
@name = name.to_s
|
12
|
-
@type = type
|
12
|
+
@type = type.to_s
|
13
13
|
@conditionals = [options[:if]].flatten.compact
|
14
14
|
@options = options
|
15
15
|
end
|
@@ -38,7 +38,12 @@ module Brainstem
|
|
38
38
|
options[:optional]
|
39
39
|
end
|
40
40
|
|
41
|
+
# Please override in sub classes to compute value of field with the given arguments.
|
41
42
|
def run_on(model, context, helper_instance = Object.new)
|
43
|
+
evaluate_value_on(model, context, helper_instance)
|
44
|
+
end
|
45
|
+
|
46
|
+
def evaluate_value_on(model, context, helper_instance = Object.new)
|
42
47
|
if options[:lookup]
|
43
48
|
run_on_with_lookup(model, context, helper_instance)
|
44
49
|
elsif options[:dynamic]
|
@@ -53,6 +58,15 @@ module Brainstem
|
|
53
58
|
end
|
54
59
|
end
|
55
60
|
|
61
|
+
def presentable?(model, context)
|
62
|
+
optioned?(context[:optional_fields]) && conditionals_match?(
|
63
|
+
model,
|
64
|
+
context[:conditionals],
|
65
|
+
context[:helper_instance],
|
66
|
+
context[:conditional_cache]
|
67
|
+
)
|
68
|
+
end
|
69
|
+
|
56
70
|
def conditionals_match?(model, presenter_conditionals, helper_instance = Object.new, conditional_cache = { model: {}, request: {} })
|
57
71
|
return true unless conditional?
|
58
72
|
|
@@ -6,18 +6,36 @@ module Brainstem
|
|
6
6
|
configuration[name] = DSL::Field.new(name, type, smart_merge(block_options, format_options(options)))
|
7
7
|
end
|
8
8
|
|
9
|
-
def fields(name, &block)
|
10
|
-
|
9
|
+
def fields(name, type = :hash, options = {}, &block)
|
10
|
+
nested_field = DSL::BlockField.for(name, type, smart_merge(block_options, format_options(options)), configuration[name])
|
11
|
+
configuration[name] = nested_field
|
12
|
+
|
13
|
+
descend self.class, nested_field.configuration, merge_parent_options(block_options, options), &block
|
11
14
|
end
|
12
15
|
|
13
16
|
private
|
14
17
|
|
18
|
+
NON_INHERITABLE_FIELD_OPTIONS = [:dynamic, :via, :lookup, :lookup_fetch, :info, :type, :item_type]
|
19
|
+
private_constant :NON_INHERITABLE_FIELD_OPTIONS
|
20
|
+
|
21
|
+
def merge_parent_options(block_options, parent_options)
|
22
|
+
inheritable_options = parent_options.except(*NON_INHERITABLE_FIELD_OPTIONS)
|
23
|
+
inheritable_options[:use_parent_value] = true unless inheritable_options.has_key?(:use_parent_value)
|
24
|
+
|
25
|
+
block_options.deep_dup.merge(inheritable_options)
|
26
|
+
end
|
27
|
+
|
15
28
|
def smart_merge(block_options, options)
|
16
29
|
if_clause = ([block_options[:if]] + [options[:if]]).flatten(2).compact.uniq
|
17
30
|
block_options.merge(options).tap do |opts|
|
18
31
|
opts.merge!(if: if_clause) if if_clause.present?
|
19
32
|
end
|
20
33
|
end
|
34
|
+
|
35
|
+
def format_options(options)
|
36
|
+
options[:item_type] = options[:item_type].to_s if options.has_key?(:item_type)
|
37
|
+
super
|
38
|
+
end
|
21
39
|
end
|
22
40
|
end
|
23
41
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'brainstem/dsl/configuration'
|
2
|
+
require 'brainstem/dsl/block_field'
|
3
|
+
|
4
|
+
module Brainstem
|
5
|
+
module DSL
|
6
|
+
class HashBlockField < BlockField
|
7
|
+
def run_on(model, context, helper_instance = Object.new)
|
8
|
+
evaluated_model = nil
|
9
|
+
evaluated_model = evaluate_value_on(model, context, helper_instance) if executable?
|
10
|
+
|
11
|
+
result = {}
|
12
|
+
configuration.each do |field_name, field|
|
13
|
+
next unless field.presentable?(model, context)
|
14
|
+
|
15
|
+
model_for_field = (executable? && use_parent_value?(field)) ? evaluated_model : model
|
16
|
+
result[field_name] = field.run_on(model_for_field, context, context[:helper_instance]) if model_for_field
|
17
|
+
end
|
18
|
+
|
19
|
+
result
|
20
|
+
end
|
21
|
+
|
22
|
+
def executable?
|
23
|
+
(options.keys & EXECUTABLE_OPTIONS).present?
|
24
|
+
end
|
25
|
+
|
26
|
+
EXECUTABLE_OPTIONS = [:dynamic, :via, :lookup]
|
27
|
+
private_constant :EXECUTABLE_OPTIONS
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/brainstem/presenter.rb
CHANGED
@@ -308,15 +308,19 @@ module Brainstem
|
|
308
308
|
def present_fields(model, context, fields, result = {})
|
309
309
|
fields.each do |name, field|
|
310
310
|
case field
|
311
|
-
when DSL::
|
312
|
-
if field.
|
311
|
+
when DSL::HashBlockField
|
312
|
+
next if field.executable? && !field.presentable?(model, context)
|
313
|
+
|
314
|
+
# This preserves backwards compatibility
|
315
|
+
# In the case of a hash field, the individual attributes will call presentable
|
316
|
+
# If none of the individual attributes are presentable we will receive an empty hash
|
317
|
+
result[name] = field.run_on(model, context, context[:helper_instance])
|
318
|
+
when DSL::ArrayBlockField, DSL::Field
|
319
|
+
if field.presentable?(model, context)
|
313
320
|
result[name] = field.run_on(model, context, context[:helper_instance])
|
314
321
|
end
|
315
|
-
when DSL::Configuration
|
316
|
-
result[name] ||= {}
|
317
|
-
present_fields(model, context, field, result[name])
|
318
322
|
else
|
319
|
-
raise "Unknown
|
323
|
+
raise "Unknown Brainstem Field type encountered: #{field}"
|
320
324
|
end
|
321
325
|
end
|
322
326
|
result
|
@@ -33,15 +33,17 @@ module Brainstem
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def fields_exist(fields = presenter_class.configuration[:fields])
|
36
|
-
fields.each do |name,
|
37
|
-
case
|
36
|
+
fields.each do |name, field|
|
37
|
+
case field
|
38
|
+
when DSL::HashBlockField
|
39
|
+
fields_exist(field) if presenter_class.presents.any? { !field.executable? }
|
38
40
|
when DSL::Field
|
39
|
-
method_name =
|
41
|
+
method_name = field.method_name
|
40
42
|
if method_name && presenter_class.presents.any? { |klass| !klass.new.respond_to?(method_name) }
|
41
43
|
errors.add(:fields, "'#{name}' is not valid because not all presented classes respond to '#{method_name}'")
|
42
44
|
end
|
43
|
-
|
44
|
-
|
45
|
+
else
|
46
|
+
errors.add(:fields, "'#{name}' is an unknown Brainstem field type")
|
45
47
|
end
|
46
48
|
end
|
47
49
|
end
|
@@ -61,16 +63,23 @@ module Brainstem
|
|
61
63
|
end
|
62
64
|
|
63
65
|
def conditionals_exist(fields = presenter_class.configuration[:fields])
|
64
|
-
fields.each do |name,
|
65
|
-
case
|
66
|
+
fields.each do |name, field|
|
67
|
+
case field
|
68
|
+
when DSL::HashBlockField
|
69
|
+
if field.options[:if].present?
|
70
|
+
if Array.wrap(field.options[:if]).any? { |conditional| presenter_class.configuration[:conditionals][conditional].nil? }
|
71
|
+
errors.add(:fields, "'#{name}' is not valid because one or more of the specified conditionals does not exist")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
conditionals_exist(field) if presenter_class.presents.any? { |klass| !field.executable? }
|
66
75
|
when DSL::Field
|
67
|
-
if
|
68
|
-
if Array.wrap(
|
76
|
+
if field.options[:if].present?
|
77
|
+
if Array.wrap(field.options[:if]).any? { |conditional| presenter_class.configuration[:conditionals][conditional].nil? }
|
69
78
|
errors.add(:fields, "'#{name}' is not valid because one or more of the specified conditionals does not exist")
|
70
79
|
end
|
71
80
|
end
|
72
|
-
|
73
|
-
|
81
|
+
else
|
82
|
+
errors.add(:fields, "'#{name}' is an unknown Brainstem field type")
|
74
83
|
end
|
75
84
|
end
|
76
85
|
end
|
data/lib/brainstem/version.rb
CHANGED
@@ -143,65 +143,398 @@ module Brainstem
|
|
143
143
|
end
|
144
144
|
|
145
145
|
|
146
|
-
describe "#
|
147
|
-
let(:
|
148
|
-
let(:proc_nested_param) { { title: { nodoc: nodoc, root: Proc.new { |klass| klass.brainstem_model_name } } } }
|
149
|
-
let(:root_param) { { title: { nodoc: nodoc } } }
|
150
|
-
let(:default_config) { { valid_params: which_param } }
|
146
|
+
describe "#params_configuration_tree" do
|
147
|
+
let(:default_config) { { valid_params: which_param } }
|
151
148
|
|
152
149
|
context "non-nested params" do
|
150
|
+
let(:root_param) { { Proc.new { 'title' } => { nodoc: nodoc, type: 'string' } } }
|
153
151
|
let(:which_param) { root_param }
|
154
152
|
|
155
153
|
context "when nodoc" do
|
156
154
|
let(:nodoc) { true }
|
157
155
|
|
158
156
|
it "rejects the key" do
|
159
|
-
expect(subject.
|
157
|
+
expect(subject.params_configuration_tree).to be_empty
|
160
158
|
end
|
161
159
|
end
|
162
160
|
|
163
161
|
context "when not nodoc" do
|
162
|
+
let(:nodoc) { false }
|
163
|
+
|
164
164
|
it "lists it as a root param" do
|
165
|
-
expect(subject.
|
165
|
+
expect(subject.params_configuration_tree).to eq(
|
166
|
+
{
|
167
|
+
title: {
|
168
|
+
_config: { nodoc: nodoc, type: 'string' }
|
169
|
+
}
|
170
|
+
}.with_indifferent_access
|
171
|
+
)
|
172
|
+
end
|
173
|
+
|
174
|
+
context "when param has an item" do
|
175
|
+
let(:which_param) { { only: { nodoc: nodoc, type: 'array', item: 'integer' } } }
|
176
|
+
|
177
|
+
it "lists it as a root param" do
|
178
|
+
expect(subject.params_configuration_tree).to eq(
|
179
|
+
{
|
180
|
+
only: {
|
181
|
+
_config: { nodoc: nodoc, type: 'array', item: 'integer' }
|
182
|
+
}
|
183
|
+
}.with_indifferent_access
|
184
|
+
)
|
185
|
+
end
|
166
186
|
end
|
167
187
|
end
|
168
188
|
end
|
169
189
|
|
170
|
-
|
171
190
|
context "nested params" do
|
191
|
+
let(:root_proc) { Proc.new { 'sprocket' } }
|
192
|
+
let(:nested_param) {
|
193
|
+
{ Proc.new { 'title' } => { nodoc: nodoc, type: 'string', root: root_proc, ancestors: [root_proc] } }
|
194
|
+
}
|
172
195
|
let(:which_param) { nested_param }
|
173
196
|
|
174
197
|
context "when nodoc" do
|
175
198
|
let(:nodoc) { true }
|
176
199
|
|
177
200
|
it "rejects the key" do
|
178
|
-
expect(subject.
|
201
|
+
expect(subject.params_configuration_tree).to be_empty
|
179
202
|
end
|
180
203
|
end
|
181
204
|
|
182
205
|
context "when not nodoc" do
|
183
206
|
it "lists it as a nested param" do
|
184
|
-
expect(subject.
|
207
|
+
expect(subject.params_configuration_tree).to eq(
|
208
|
+
{
|
209
|
+
sprocket: {
|
210
|
+
_config: {
|
211
|
+
type: 'hash',
|
212
|
+
},
|
213
|
+
title: {
|
214
|
+
_config: {
|
215
|
+
nodoc: nodoc,
|
216
|
+
type: 'string'
|
217
|
+
}
|
218
|
+
}
|
219
|
+
}
|
220
|
+
}.with_indifferent_access
|
221
|
+
)
|
222
|
+
end
|
223
|
+
|
224
|
+
context "when nested param has an item" do
|
225
|
+
let(:which_param) {
|
226
|
+
{
|
227
|
+
Proc.new { 'ids' } => { nodoc: nodoc, type: 'array', item: 'integer', root: root_proc, ancestors: [root_proc] }
|
228
|
+
}
|
229
|
+
}
|
230
|
+
|
231
|
+
it "lists it as a nested param" do
|
232
|
+
expect(subject.params_configuration_tree).to eq(
|
233
|
+
{
|
234
|
+
sprocket: {
|
235
|
+
_config: {
|
236
|
+
type: 'hash'
|
237
|
+
},
|
238
|
+
ids: {
|
239
|
+
_config: {
|
240
|
+
nodoc: nodoc,
|
241
|
+
type: 'array',
|
242
|
+
item: 'integer'
|
243
|
+
}
|
244
|
+
}
|
245
|
+
}
|
246
|
+
}.with_indifferent_access
|
247
|
+
)
|
248
|
+
end
|
185
249
|
end
|
186
250
|
end
|
187
251
|
end
|
188
252
|
|
189
|
-
|
190
253
|
context "proc nested params" do
|
191
|
-
let(:
|
254
|
+
let!(:root_proc) { Proc.new { |klass| klass.brainstem_model_name } }
|
255
|
+
let(:proc_nested_param) {
|
256
|
+
{ Proc.new { 'title' } => { nodoc: nodoc, type: 'string', root: root_proc, ancestors: [root_proc] } }
|
257
|
+
}
|
258
|
+
let(:which_param) { proc_nested_param }
|
192
259
|
|
193
260
|
context "when nodoc" do
|
194
261
|
let(:nodoc) { true }
|
195
262
|
|
196
263
|
it "rejects the key" do
|
197
|
-
expect(subject.
|
264
|
+
expect(subject.params_configuration_tree).to be_empty
|
198
265
|
end
|
199
266
|
end
|
200
267
|
|
201
268
|
context "when not nodoc" do
|
202
269
|
it "evaluates the proc in the controller's context and lists it as a nested param" do
|
203
270
|
mock.proxy(const).brainstem_model_name
|
204
|
-
|
271
|
+
|
272
|
+
result = subject.params_configuration_tree
|
273
|
+
expect(result.keys).to eq(%w(widget))
|
274
|
+
|
275
|
+
children_of_the_root = result[:widget].except(:_config)
|
276
|
+
expect(children_of_the_root.keys).to eq(%w(title))
|
277
|
+
|
278
|
+
title_param = children_of_the_root[:title][:_config]
|
279
|
+
expect(title_param.keys).to eq(%w(nodoc type))
|
280
|
+
expect(title_param[:nodoc]).to eq(nodoc)
|
281
|
+
expect(title_param[:type]).to eq('string')
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
context "multi nested params" do
|
287
|
+
let(:project_proc) { Proc.new { 'project' } }
|
288
|
+
let(:id_proc) { Proc.new { 'id' } }
|
289
|
+
let(:task_proc) { Proc.new { 'task' } }
|
290
|
+
let(:title_proc) { Proc.new { 'title' } }
|
291
|
+
let(:checklist_proc) { Proc.new { 'checklist' } }
|
292
|
+
let(:name_proc) { Proc.new { 'name' } }
|
293
|
+
|
294
|
+
context "has a root & ancestors" do
|
295
|
+
let(:which_param) {
|
296
|
+
{
|
297
|
+
id_proc => {
|
298
|
+
type: 'integer'
|
299
|
+
},
|
300
|
+
task_proc => {
|
301
|
+
type: 'hash',
|
302
|
+
root: project_proc,
|
303
|
+
ancestors: [project_proc]
|
304
|
+
},
|
305
|
+
title_proc => {
|
306
|
+
type: 'string',
|
307
|
+
ancestors: [project_proc, task_proc]
|
308
|
+
},
|
309
|
+
checklist_proc => {
|
310
|
+
type: 'array',
|
311
|
+
item: 'hash',
|
312
|
+
ancestors: [project_proc, task_proc]
|
313
|
+
},
|
314
|
+
name_proc => {
|
315
|
+
type: 'string',
|
316
|
+
ancestors: [project_proc, task_proc, checklist_proc]
|
317
|
+
}
|
318
|
+
}
|
319
|
+
}
|
320
|
+
|
321
|
+
context "when a leaf param has no doc" do
|
322
|
+
before do
|
323
|
+
which_param[name_proc][:nodoc] = true
|
324
|
+
end
|
325
|
+
|
326
|
+
it "rejects the key" do
|
327
|
+
expect(subject.params_configuration_tree).to eq(
|
328
|
+
{
|
329
|
+
id: {
|
330
|
+
_config: {
|
331
|
+
type: 'integer',
|
332
|
+
}
|
333
|
+
},
|
334
|
+
project: {
|
335
|
+
_config: {
|
336
|
+
type: 'hash',
|
337
|
+
},
|
338
|
+
task: {
|
339
|
+
_config: {
|
340
|
+
type: 'hash',
|
341
|
+
},
|
342
|
+
title: {
|
343
|
+
_config: {
|
344
|
+
type: 'string'
|
345
|
+
}
|
346
|
+
},
|
347
|
+
checklist: {
|
348
|
+
_config: {
|
349
|
+
type: 'array',
|
350
|
+
item: 'hash',
|
351
|
+
}
|
352
|
+
},
|
353
|
+
},
|
354
|
+
},
|
355
|
+
}.with_indifferent_access
|
356
|
+
)
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
context "when nodoc on a parent param" do
|
361
|
+
before do
|
362
|
+
which_param[checklist_proc][:nodoc] = true
|
363
|
+
which_param[name_proc][:nodoc] = true # This will be inherited from the parent when the param is defined.
|
364
|
+
end
|
365
|
+
|
366
|
+
it "rejects the parent key and its children" do
|
367
|
+
expect(subject.params_configuration_tree).to eq(
|
368
|
+
{
|
369
|
+
id: {
|
370
|
+
_config: {
|
371
|
+
type: 'integer'
|
372
|
+
}
|
373
|
+
},
|
374
|
+
project: {
|
375
|
+
_config: {
|
376
|
+
type: 'hash'
|
377
|
+
},
|
378
|
+
task: {
|
379
|
+
_config: {
|
380
|
+
type: 'hash',
|
381
|
+
},
|
382
|
+
title: {
|
383
|
+
_config: {
|
384
|
+
type: 'string'
|
385
|
+
}
|
386
|
+
},
|
387
|
+
},
|
388
|
+
},
|
389
|
+
}.with_indifferent_access
|
390
|
+
)
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
context "when not nodoc" do
|
395
|
+
it "evaluates the proc in the controller's context and lists it as a nested param" do
|
396
|
+
expect(subject.params_configuration_tree).to eq(
|
397
|
+
{
|
398
|
+
id: {
|
399
|
+
_config: {
|
400
|
+
type: 'integer'
|
401
|
+
}
|
402
|
+
},
|
403
|
+
project: {
|
404
|
+
_config: {
|
405
|
+
type: 'hash',
|
406
|
+
},
|
407
|
+
task: {
|
408
|
+
_config: {
|
409
|
+
type: 'hash',
|
410
|
+
},
|
411
|
+
title: {
|
412
|
+
_config: {
|
413
|
+
type: 'string'
|
414
|
+
}
|
415
|
+
},
|
416
|
+
checklist: {
|
417
|
+
_config: {
|
418
|
+
type: 'array',
|
419
|
+
item: 'hash'
|
420
|
+
},
|
421
|
+
name: {
|
422
|
+
_config: {
|
423
|
+
type: 'string'
|
424
|
+
}
|
425
|
+
},
|
426
|
+
},
|
427
|
+
},
|
428
|
+
},
|
429
|
+
}.with_indifferent_access
|
430
|
+
)
|
431
|
+
end
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
context "has only ancestors" do
|
436
|
+
let(:which_param) {
|
437
|
+
{
|
438
|
+
task_proc => {
|
439
|
+
type: 'hash',
|
440
|
+
},
|
441
|
+
title_proc => {
|
442
|
+
type: 'string',
|
443
|
+
ancestors: [task_proc]
|
444
|
+
},
|
445
|
+
checklist_proc => {
|
446
|
+
type: 'array',
|
447
|
+
item: 'hash',
|
448
|
+
ancestors: [task_proc]
|
449
|
+
},
|
450
|
+
name_proc => {
|
451
|
+
type: 'string',
|
452
|
+
ancestors: [task_proc, checklist_proc]
|
453
|
+
}
|
454
|
+
}
|
455
|
+
}
|
456
|
+
|
457
|
+
context "when a leaf param has no doc" do
|
458
|
+
before do
|
459
|
+
which_param[name_proc][:nodoc] = true
|
460
|
+
end
|
461
|
+
|
462
|
+
it "rejects the key" do
|
463
|
+
expect(subject.params_configuration_tree).to eq(
|
464
|
+
{
|
465
|
+
task: {
|
466
|
+
_config: {
|
467
|
+
type: 'hash'
|
468
|
+
},
|
469
|
+
title: {
|
470
|
+
_config: {
|
471
|
+
type: 'string'
|
472
|
+
}
|
473
|
+
},
|
474
|
+
checklist: {
|
475
|
+
_config: {
|
476
|
+
type: 'array',
|
477
|
+
item: 'hash'
|
478
|
+
}
|
479
|
+
},
|
480
|
+
},
|
481
|
+
}.with_indifferent_access
|
482
|
+
)
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
context "when parent param has nodoc" do
|
487
|
+
before do
|
488
|
+
which_param[checklist_proc][:nodoc] = true
|
489
|
+
which_param[name_proc][:nodoc] = true # This will be inherited from the parent when the param is defined.
|
490
|
+
end
|
491
|
+
|
492
|
+
it "rejects the parent key and its children" do
|
493
|
+
expect(subject.params_configuration_tree).to eq(
|
494
|
+
{
|
495
|
+
task: {
|
496
|
+
_config: {
|
497
|
+
type: 'hash'
|
498
|
+
},
|
499
|
+
title: {
|
500
|
+
_config: {
|
501
|
+
type: 'string'
|
502
|
+
}
|
503
|
+
}
|
504
|
+
}
|
505
|
+
}.with_indifferent_access
|
506
|
+
)
|
507
|
+
end
|
508
|
+
end
|
509
|
+
|
510
|
+
context "when not nodoc" do
|
511
|
+
it "evaluates the proc in the controller's context and lists it as a nested param" do
|
512
|
+
expect(subject.params_configuration_tree).to eq(
|
513
|
+
{
|
514
|
+
task: {
|
515
|
+
_config: {
|
516
|
+
type: 'hash'
|
517
|
+
},
|
518
|
+
title: {
|
519
|
+
_config: {
|
520
|
+
type: 'string'
|
521
|
+
}
|
522
|
+
},
|
523
|
+
checklist: {
|
524
|
+
_config: {
|
525
|
+
type: 'array',
|
526
|
+
item: 'hash',
|
527
|
+
},
|
528
|
+
name: {
|
529
|
+
_config: {
|
530
|
+
type: 'string',
|
531
|
+
}
|
532
|
+
},
|
533
|
+
},
|
534
|
+
},
|
535
|
+
}.with_indifferent_access
|
536
|
+
)
|
537
|
+
end
|
205
538
|
end
|
206
539
|
end
|
207
540
|
end
|