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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +81 -4
  3. data/Gemfile.lock +9 -9
  4. data/README.md +134 -37
  5. data/brainstem.gemspec +1 -1
  6. data/lib/brainstem/api_docs/endpoint.rb +40 -18
  7. data/lib/brainstem/api_docs/formatters/markdown/endpoint_formatter.rb +27 -22
  8. data/lib/brainstem/api_docs/formatters/markdown/helper.rb +9 -0
  9. data/lib/brainstem/api_docs/formatters/markdown/presenter_formatter.rb +14 -6
  10. data/lib/brainstem/api_docs/presenter.rb +3 -7
  11. data/lib/brainstem/concerns/controller_dsl.rb +138 -14
  12. data/lib/brainstem/concerns/presenter_dsl.rb +39 -6
  13. data/lib/brainstem/dsl/array_block_field.rb +25 -0
  14. data/lib/brainstem/dsl/block_field.rb +69 -0
  15. data/lib/brainstem/dsl/configuration.rb +13 -5
  16. data/lib/brainstem/dsl/field.rb +15 -1
  17. data/lib/brainstem/dsl/fields_block.rb +20 -2
  18. data/lib/brainstem/dsl/hash_block_field.rb +30 -0
  19. data/lib/brainstem/presenter.rb +10 -6
  20. data/lib/brainstem/presenter_validator.rb +20 -11
  21. data/lib/brainstem/version.rb +1 -1
  22. data/spec/brainstem/api_docs/endpoint_spec.rb +347 -14
  23. data/spec/brainstem/api_docs/formatters/markdown/endpoint_formatter_spec.rb +106 -13
  24. data/spec/brainstem/api_docs/formatters/markdown/helper_spec.rb +19 -0
  25. data/spec/brainstem/api_docs/formatters/markdown/presenter_formatter_spec.rb +150 -37
  26. data/spec/brainstem/api_docs/presenter_spec.rb +85 -18
  27. data/spec/brainstem/concerns/controller_dsl_spec.rb +615 -31
  28. data/spec/brainstem/concerns/inheritable_configuration_spec.rb +32 -9
  29. data/spec/brainstem/concerns/presenter_dsl_spec.rb +99 -25
  30. data/spec/brainstem/dsl/array_block_field_spec.rb +43 -0
  31. data/spec/brainstem/dsl/block_field_spec.rb +188 -0
  32. data/spec/brainstem/dsl/field_spec.rb +86 -20
  33. data/spec/brainstem/dsl/hash_block_field_spec.rb +166 -0
  34. data/spec/brainstem/presenter_collection_spec.rb +24 -24
  35. data/spec/brainstem/presenter_spec.rb +233 -9
  36. data/spec/brainstem/query_strategies/filter_and_search_spec.rb +1 -1
  37. data/spec/spec_helpers/presenters.rb +8 -0
  38. data/spec/spec_helpers/schema.rb +13 -0
  39. 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
- valid_options = %w(default info include_params nodoc)
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
- key = key.to_s
82
- self.nonheritable_keys << key unless self.nonheritable_keys.include?(key)
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.to_s) }
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.to_s)
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.to_s)
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
@@ -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
- descend FieldsBlock, configuration.nest!(name), &block
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
@@ -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::Field
312
- if field.conditionals_match?(model, context[:conditionals], context[:helper_instance], context[:conditional_cache]) && field.optioned?(context[:optional_fields])
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 Brianstem Field type encountered: #{field}"
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, field_or_fields|
37
- case field_or_fields
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 = field_or_fields.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
- when DSL::Configuration
44
- fields_exist(field_or_fields)
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, field_or_fields|
65
- case field_or_fields
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 field_or_fields.options[:if].present?
68
- if Array.wrap(field_or_fields.options[:if]).any? { |conditional| presenter_class.configuration[:conditionals][conditional].nil? }
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
- when DSL::Configuration
73
- conditionals_exist(field_or_fields)
81
+ else
82
+ errors.add(:fields, "'#{name}' is an unknown Brainstem field type")
74
83
  end
75
84
  end
76
85
  end
@@ -1,3 +1,3 @@
1
1
  module Brainstem
2
- VERSION = "1.1.1"
2
+ VERSION = "1.3.0"
3
3
  end
@@ -143,65 +143,398 @@ module Brainstem
143
143
  end
144
144
 
145
145
 
146
- describe "#root_param_keys" do
147
- let(:nested_param) { { title: { nodoc: nodoc, root: :sprocket } } }
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.root_param_keys).to be_empty
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.root_param_keys).to have_key(:title)
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.root_param_keys).to be_empty
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.root_param_keys).to eq({ sprocket: [ :title ] })
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(:which_param) { proc_nested_param }
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.root_param_keys).to be_empty
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
- expect(subject.root_param_keys).to eq({ widget: [ :title ] })
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