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