brainstem 1.0.0.pre.1 → 1.0.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 +12 -0
- data/Gemfile.lock +1 -1
- data/README.md +383 -32
- data/bin/brainstem +6 -0
- data/brainstem.gemspec +2 -0
- data/docs/api_doc_generator.markdown +175 -0
- data/docs/brainstem_executable.markdown +32 -0
- data/docs/docgen.png +0 -0
- data/docs/docgen_ascii.txt +63 -0
- data/docs/executable.png +0 -0
- data/docs/executable_ascii.txt +10 -0
- data/lib/brainstem/api_docs.rb +146 -0
- data/lib/brainstem/api_docs/abstract_collection.rb +116 -0
- data/lib/brainstem/api_docs/atlas.rb +158 -0
- data/lib/brainstem/api_docs/builder.rb +167 -0
- data/lib/brainstem/api_docs/controller.rb +122 -0
- data/lib/brainstem/api_docs/controller_collection.rb +40 -0
- data/lib/brainstem/api_docs/endpoint.rb +234 -0
- data/lib/brainstem/api_docs/endpoint_collection.rb +58 -0
- data/lib/brainstem/api_docs/exceptions.rb +8 -0
- data/lib/brainstem/api_docs/formatters/abstract_formatter.rb +64 -0
- data/lib/brainstem/api_docs/formatters/markdown/controller_formatter.rb +76 -0
- data/lib/brainstem/api_docs/formatters/markdown/endpoint_collection_formatter.rb +73 -0
- data/lib/brainstem/api_docs/formatters/markdown/endpoint_formatter.rb +169 -0
- data/lib/brainstem/api_docs/formatters/markdown/helper.rb +76 -0
- data/lib/brainstem/api_docs/formatters/markdown/presenter_formatter.rb +200 -0
- data/lib/brainstem/api_docs/introspectors/abstract_introspector.rb +100 -0
- data/lib/brainstem/api_docs/introspectors/rails_introspector.rb +232 -0
- data/lib/brainstem/api_docs/presenter.rb +225 -0
- data/lib/brainstem/api_docs/presenter_collection.rb +97 -0
- data/lib/brainstem/api_docs/resolver.rb +73 -0
- data/lib/brainstem/api_docs/sinks/abstract_sink.rb +37 -0
- data/lib/brainstem/api_docs/sinks/controller_presenter_multifile_sink.rb +93 -0
- data/lib/brainstem/api_docs/sinks/stdout_sink.rb +44 -0
- data/lib/brainstem/cli.rb +146 -0
- data/lib/brainstem/cli/abstract_command.rb +97 -0
- data/lib/brainstem/cli/generate_api_docs_command.rb +169 -0
- data/lib/brainstem/concerns/controller_dsl.rb +300 -0
- data/lib/brainstem/concerns/controller_param_management.rb +30 -9
- data/lib/brainstem/concerns/formattable.rb +38 -0
- data/lib/brainstem/concerns/inheritable_configuration.rb +3 -2
- data/lib/brainstem/concerns/optional.rb +43 -0
- data/lib/brainstem/concerns/presenter_dsl.rb +76 -15
- data/lib/brainstem/controller_methods.rb +6 -3
- data/lib/brainstem/dsl/association.rb +6 -3
- data/lib/brainstem/dsl/associations_block.rb +6 -3
- data/lib/brainstem/dsl/base_block.rb +2 -4
- data/lib/brainstem/dsl/conditional.rb +7 -3
- data/lib/brainstem/dsl/conditionals_block.rb +4 -4
- data/lib/brainstem/dsl/configuration.rb +184 -8
- data/lib/brainstem/dsl/field.rb +6 -3
- data/lib/brainstem/dsl/fields_block.rb +2 -3
- data/lib/brainstem/help_text.txt +8 -0
- data/lib/brainstem/presenter.rb +27 -6
- data/lib/brainstem/presenter_validator.rb +5 -2
- data/lib/brainstem/time_classes.rb +1 -1
- data/lib/brainstem/version.rb +1 -1
- data/spec/brainstem/api_docs/abstract_collection_spec.rb +156 -0
- data/spec/brainstem/api_docs/atlas_spec.rb +353 -0
- data/spec/brainstem/api_docs/builder_spec.rb +100 -0
- data/spec/brainstem/api_docs/controller_collection_spec.rb +92 -0
- data/spec/brainstem/api_docs/controller_spec.rb +225 -0
- data/spec/brainstem/api_docs/endpoint_collection_spec.rb +144 -0
- data/spec/brainstem/api_docs/endpoint_spec.rb +346 -0
- data/spec/brainstem/api_docs/formatters/abstract_formatter_spec.rb +30 -0
- data/spec/brainstem/api_docs/formatters/markdown/controller_formatter_spec.rb +126 -0
- data/spec/brainstem/api_docs/formatters/markdown/endpoint_collection_formatter_spec.rb +85 -0
- data/spec/brainstem/api_docs/formatters/markdown/endpoint_formatter_spec.rb +261 -0
- data/spec/brainstem/api_docs/formatters/markdown/helper_spec.rb +100 -0
- data/spec/brainstem/api_docs/formatters/markdown/presenter_formatter_spec.rb +485 -0
- data/spec/brainstem/api_docs/introspectors/abstract_introspector_spec.rb +192 -0
- data/spec/brainstem/api_docs/introspectors/rails_introspector_spec.rb +170 -0
- data/spec/brainstem/api_docs/presenter_collection_spec.rb +84 -0
- data/spec/brainstem/api_docs/presenter_spec.rb +519 -0
- data/spec/brainstem/api_docs/resolver_spec.rb +72 -0
- data/spec/brainstem/api_docs/sinks/abstract_sink_spec.rb +16 -0
- data/spec/brainstem/api_docs/sinks/controller_presenter_multifile_sink_spec.rb +56 -0
- data/spec/brainstem/api_docs/sinks/stdout_sink_spec.rb +22 -0
- data/spec/brainstem/api_docs_spec.rb +58 -0
- data/spec/brainstem/cli/abstract_command_spec.rb +91 -0
- data/spec/brainstem/cli/generate_api_docs_command_spec.rb +125 -0
- data/spec/brainstem/cli_spec.rb +67 -0
- data/spec/brainstem/concerns/controller_dsl_spec.rb +471 -0
- data/spec/brainstem/concerns/controller_param_management_spec.rb +36 -16
- data/spec/brainstem/concerns/formattable_spec.rb +30 -0
- data/spec/brainstem/concerns/inheritable_configuration_spec.rb +104 -4
- data/spec/brainstem/concerns/optional_spec.rb +48 -0
- data/spec/brainstem/concerns/presenter_dsl_spec.rb +202 -31
- data/spec/brainstem/dsl/association_spec.rb +18 -2
- data/spec/brainstem/dsl/conditional_spec.rb +25 -2
- data/spec/brainstem/dsl/configuration_spec.rb +1 -1
- data/spec/brainstem/dsl/field_spec.rb +18 -2
- data/spec/brainstem/presenter_collection_spec.rb +10 -2
- data/spec/brainstem/presenter_spec.rb +32 -0
- data/spec/brainstem/presenter_validator_spec.rb +12 -7
- data/spec/dummy/rails.rb +49 -0
- data/spec/shared/atlas_taker.rb +18 -0
- data/spec/shared/formattable.rb +14 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/spec_helpers/db.rb +1 -1
- data/spec/spec_helpers/presenters.rb +20 -14
- metadata +106 -6
|
@@ -1,22 +1,43 @@
|
|
|
1
|
-
|
|
1
|
+
require 'active_support/concern'
|
|
2
|
+
require 'active_support/inflector'
|
|
3
|
+
|
|
4
|
+
# Provide `brainstem_model_name` and `brainstem_plural_model_name` in
|
|
5
|
+
# controllers for use when accessing the `params` hash.
|
|
2
6
|
|
|
3
7
|
module Brainstem
|
|
4
8
|
module Concerns
|
|
5
9
|
module ControllerParamManagement
|
|
6
10
|
extend ActiveSupport::Concern
|
|
7
11
|
|
|
8
|
-
included do
|
|
9
|
-
class_attribute :brainstem_plural_model_name, :brainstem_model_name,
|
|
10
|
-
instance_accessor: false, instance_reader: false, instance_writer: false
|
|
11
|
-
end
|
|
12
|
-
|
|
13
12
|
def brainstem_model_name
|
|
14
|
-
self.class.brainstem_model_name.to_s
|
|
13
|
+
self.class.brainstem_model_name.to_s
|
|
15
14
|
end
|
|
16
15
|
|
|
17
16
|
def brainstem_plural_model_name
|
|
18
|
-
self.class.brainstem_plural_model_name.to_s
|
|
17
|
+
self.class.brainstem_plural_model_name.to_s
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
module ClassMethods
|
|
21
|
+
def brainstem_model_name
|
|
22
|
+
@brainstem_model_name ||= controller_name.singularize
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def brainstem_plural_model_name
|
|
26
|
+
@brainstem_plural_model_name ||= self.brainstem_model_name.pluralize
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def brainstem_model_name=(name)
|
|
30
|
+
@brainstem_model_name = name
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def brainstem_plural_model_name=(name)
|
|
34
|
+
@brainstem_plural_model_name = name
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def brainstem_model_class
|
|
38
|
+
@brainstem_model_class ||= self.brainstem_model_name.classify.constantize
|
|
39
|
+
end
|
|
19
40
|
end
|
|
20
41
|
end
|
|
21
42
|
end
|
|
22
|
-
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require 'brainstem/api_docs'
|
|
2
|
+
require 'active_support/inflector/inflections'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
module Brainstem
|
|
6
|
+
module Concerns
|
|
7
|
+
module Formattable
|
|
8
|
+
attr_writer :formatters
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def valid_options
|
|
12
|
+
super | [ :formatters ]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def formatters
|
|
17
|
+
@formatters ||= ::Brainstem::ApiDocs::FORMATTERS[formatter_type]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def formatted_as(format, options = {})
|
|
22
|
+
formatters[format].call(self, options)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
#
|
|
27
|
+
# Declares the type of formatter that should be used to format an entity
|
|
28
|
+
# of this class.
|
|
29
|
+
#
|
|
30
|
+
def formatter_type
|
|
31
|
+
self.class.to_s
|
|
32
|
+
.demodulize
|
|
33
|
+
.underscore
|
|
34
|
+
.to_sym
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
require 'active_support/concern'
|
|
1
2
|
require 'brainstem/dsl/configuration'
|
|
2
3
|
|
|
3
4
|
module Brainstem
|
|
4
5
|
module Concerns
|
|
5
6
|
module InheritableConfiguration
|
|
6
7
|
extend ActiveSupport::Concern
|
|
7
|
-
|
|
8
|
+
|
|
8
9
|
module ClassMethods
|
|
9
10
|
def configuration
|
|
10
11
|
@configuration ||= begin
|
|
@@ -26,4 +27,4 @@ module Brainstem
|
|
|
26
27
|
end
|
|
27
28
|
end
|
|
28
29
|
end
|
|
29
|
-
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This module is used in lieu of an dependency on optional kwargs. Any symbol
|
|
3
|
+
# included in the array of +#valid_options+ will be whitelisted from passed
|
|
4
|
+
# options and sent to the instance on initialization.
|
|
5
|
+
#
|
|
6
|
+
# In this simple way, we can make classes accept options, whitelist which
|
|
7
|
+
# are acceptable, and set them without having to manually extend our
|
|
8
|
+
# initializer.
|
|
9
|
+
#
|
|
10
|
+
# In order to use this, your constructor must have an argument named +options+
|
|
11
|
+
# that defaults to an empty hash. Additionally, for each option you define, you
|
|
12
|
+
# should define an accessor or at least a writer.
|
|
13
|
+
#
|
|
14
|
+
# You may also implement a +#valid_options+ method. It is recommended that you
|
|
15
|
+
# make this the union of the superclass's +valid_options+ method and this
|
|
16
|
+
# class's options so that inherited options are preserved:
|
|
17
|
+
#
|
|
18
|
+
# @example
|
|
19
|
+
# def valid_options
|
|
20
|
+
# super | [ :your_options_here ]
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
module Brainstem
|
|
24
|
+
module Concerns
|
|
25
|
+
module Optional
|
|
26
|
+
|
|
27
|
+
#
|
|
28
|
+
# The options that should be extracted and sent to the class on
|
|
29
|
+
# initialization.
|
|
30
|
+
#
|
|
31
|
+
# @return [Array<Symbol>] valid options
|
|
32
|
+
#
|
|
33
|
+
def valid_options
|
|
34
|
+
[ ]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def initialize(options = {})
|
|
39
|
+
options.slice(*valid_options).each {|k, v| self.send("#{k}=", v) }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -9,6 +9,8 @@ require 'brainstem/dsl/fields_block'
|
|
|
9
9
|
require 'brainstem/dsl/associations_block'
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
require 'active_support/core_ext/array/extract_options'
|
|
13
|
+
|
|
12
14
|
module Brainstem
|
|
13
15
|
module Concerns
|
|
14
16
|
module PresenterDSL
|
|
@@ -36,6 +38,18 @@ module Brainstem
|
|
|
36
38
|
AssociationsBlock.new(configuration, &block)
|
|
37
39
|
end
|
|
38
40
|
|
|
41
|
+
def title(str, options = { nodoc: false })
|
|
42
|
+
configuration[:title] = options.merge(info: str)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def description(str, options = { nodoc: false })
|
|
46
|
+
configuration[:description] = options.merge(info: str)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def nodoc!
|
|
50
|
+
configuration[:nodoc] = true
|
|
51
|
+
end
|
|
52
|
+
|
|
39
53
|
# Declare a helper module or block whose methods will be available in dynamic fields and associations.
|
|
40
54
|
def helper(mod = nil, &block)
|
|
41
55
|
if mod
|
|
@@ -47,9 +61,14 @@ module Brainstem
|
|
|
47
61
|
end
|
|
48
62
|
end
|
|
49
63
|
|
|
64
|
+
|
|
50
65
|
# @overload default_sort_order(sort_string)
|
|
51
66
|
# Sets a default sort order.
|
|
52
|
-
# @param [String] sort_string The sort order to apply by default
|
|
67
|
+
# @param [String] sort_string The sort order to apply by default
|
|
68
|
+
# while presenting. The string must contain the name of a sort order
|
|
69
|
+
# that has explicitly been declared using {sort_order}. The string
|
|
70
|
+
# may end in +:asc+ or +:desc+ to indicate the default order's
|
|
71
|
+
# direction.
|
|
53
72
|
# @return [String] The new default sort order.
|
|
54
73
|
# @overload default_sort_order
|
|
55
74
|
# @return [String] The default sort order, or nil if one is not set.
|
|
@@ -58,31 +77,70 @@ module Brainstem
|
|
|
58
77
|
configuration[:default_sort_order]
|
|
59
78
|
end
|
|
60
79
|
|
|
61
|
-
|
|
80
|
+
|
|
81
|
+
#
|
|
82
|
+
# @overload sort_order(name, order, options)
|
|
62
83
|
# @param [Symbol] name The name of the sort order.
|
|
63
|
-
# @param [String] order The SQL string to use to sort the presented
|
|
64
|
-
#
|
|
65
|
-
# @
|
|
66
|
-
# @
|
|
67
|
-
#
|
|
84
|
+
# @param [String] order The SQL string to use to sort the presented
|
|
85
|
+
# data.
|
|
86
|
+
# @param [Hash] options
|
|
87
|
+
# @option options [String] :info Docstring for the sort order
|
|
88
|
+
# @option options [Boolean] :nodoc Whether this sort order be
|
|
89
|
+
# included in the generated documentation
|
|
90
|
+
#
|
|
91
|
+
# @overload sort_order(name, options, &block)
|
|
92
|
+
# @yieldparam scope [ActiveRecord::Relation] The scope representing
|
|
93
|
+
# the data being presented.
|
|
94
|
+
# @yieldreturn [ActiveRecord::Relation] A new scope that adds
|
|
95
|
+
# ordering requirements to the scope that was yielded.
|
|
96
|
+
#
|
|
97
|
+
# Create a named sort order, either containing a string to use as
|
|
98
|
+
# ORDER in a query, or with a block that adds an order Arel predicate
|
|
99
|
+
# to a scope.
|
|
100
|
+
#
|
|
68
101
|
# @raise [ArgumentError] if neither an order string or block is given.
|
|
69
|
-
|
|
102
|
+
#
|
|
103
|
+
def sort_order(name, *args, &block)
|
|
104
|
+
valid_options = %w(info nodoc)
|
|
105
|
+
options = args.extract_options!
|
|
106
|
+
.select { |k, v| valid_options.include?(k.to_s) }
|
|
107
|
+
order = args.first
|
|
108
|
+
|
|
70
109
|
raise ArgumentError, "A sort order must be given" unless block_given? || order
|
|
71
|
-
configuration[:sort_orders][name] = (
|
|
110
|
+
configuration[:sort_orders][name] = options.merge({
|
|
111
|
+
value: (block_given? ? block : order)
|
|
112
|
+
})
|
|
72
113
|
end
|
|
73
114
|
|
|
115
|
+
|
|
116
|
+
#
|
|
74
117
|
# @overload filter(name, options = {})
|
|
75
|
-
# @param [Symbol] name The name of the scope that may be applied as a
|
|
76
|
-
#
|
|
118
|
+
# @param [Symbol] name The name of the scope that may be applied as a
|
|
119
|
+
# filter.
|
|
120
|
+
# @option options [Object] :default If set, causes this filter to be
|
|
121
|
+
# applied to every request. If the filter accepts parameters, the
|
|
122
|
+
# value given here will be passed to the filter when it is applied.
|
|
123
|
+
# @option options [String] :info Docstring for the filter.
|
|
124
|
+
#
|
|
77
125
|
# @overload filter(name, options = {}, &block)
|
|
78
126
|
# @param [Symbol] name The filter can be requested using this name.
|
|
79
|
-
# @yieldparam scope [ActiveRecord::Relation] The scope that the
|
|
80
|
-
#
|
|
81
|
-
# @
|
|
127
|
+
# @yieldparam scope [ActiveRecord::Relation] The scope that the
|
|
128
|
+
# filter should use as a base.
|
|
129
|
+
# @yieldparam arg [Object] The argument passed when the filter was
|
|
130
|
+
# requested.
|
|
131
|
+
# @yieldreturn [ActiveRecord::Relation] A new scope that filters the
|
|
132
|
+
# scope that was yielded.
|
|
133
|
+
#
|
|
82
134
|
def filter(name, options = {}, &block)
|
|
83
|
-
|
|
135
|
+
valid_options = %w(default info include_params nodoc)
|
|
136
|
+
options.select! { |k, v| valid_options.include?(k.to_s) }
|
|
137
|
+
|
|
138
|
+
configuration[:filters][name] = options.merge({
|
|
139
|
+
value: (block_given? ? block : nil)
|
|
140
|
+
})
|
|
84
141
|
end
|
|
85
142
|
|
|
143
|
+
|
|
86
144
|
def search(&block)
|
|
87
145
|
configuration[:search] = block
|
|
88
146
|
end
|
|
@@ -104,6 +162,9 @@ module Brainstem
|
|
|
104
162
|
configuration.nest!(:filters)
|
|
105
163
|
configuration.nest!(:sort_orders)
|
|
106
164
|
configuration.nest!(:associations)
|
|
165
|
+
configuration.nonheritable!(:title)
|
|
166
|
+
configuration.nonheritable!(:description)
|
|
167
|
+
configuration.nonheritable!(:nodoc)
|
|
107
168
|
end
|
|
108
169
|
end
|
|
109
170
|
end
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
require 'brainstem/concerns/controller_param_management'
|
|
2
2
|
require 'brainstem/concerns/error_presentation'
|
|
3
|
+
require 'brainstem/concerns/controller_dsl'
|
|
3
4
|
|
|
4
5
|
module Brainstem
|
|
5
6
|
|
|
6
|
-
# ControllerMethods are intended to be included into controllers that will be
|
|
7
|
-
# The present method will pass
|
|
8
|
-
#
|
|
7
|
+
# ControllerMethods are intended to be included into controllers that will be
|
|
8
|
+
# handling requests for presented objects. The present method will pass
|
|
9
|
+
# through +params+, so that any allowed and requested includes, filters, sort
|
|
10
|
+
# orders will be applied to the presented data.
|
|
9
11
|
module ControllerMethods
|
|
10
12
|
extend ActiveSupport::Concern
|
|
11
13
|
include Concerns::ControllerParamManagement
|
|
12
14
|
include Concerns::ErrorPresentation
|
|
15
|
+
include Concerns::ControllerDSL
|
|
13
16
|
|
|
14
17
|
# Return a Ruby hash that contains models requested by the user's params and allowed
|
|
15
18
|
# by the +name+ presenter's configuration.
|
|
@@ -5,15 +5,18 @@ module Brainstem
|
|
|
5
5
|
class Association
|
|
6
6
|
include Brainstem::Concerns::Lookup
|
|
7
7
|
|
|
8
|
-
attr_reader :name, :target_class, :
|
|
8
|
+
attr_reader :name, :target_class, :options
|
|
9
9
|
|
|
10
|
-
def initialize(name, target_class,
|
|
10
|
+
def initialize(name, target_class, options)
|
|
11
11
|
@name = name.to_s
|
|
12
12
|
@target_class = target_class
|
|
13
|
-
@description = description
|
|
14
13
|
@options = options
|
|
15
14
|
end
|
|
16
15
|
|
|
16
|
+
def description
|
|
17
|
+
options[:info].presence
|
|
18
|
+
end
|
|
19
|
+
|
|
17
20
|
def method_name
|
|
18
21
|
if options[:dynamic] || options[:lookup]
|
|
19
22
|
nil
|
|
@@ -2,9 +2,12 @@ module Brainstem
|
|
|
2
2
|
module Concerns
|
|
3
3
|
module PresenterDSL
|
|
4
4
|
class AssociationsBlock < BaseBlock
|
|
5
|
-
def association(name, target_class,
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
def association(name, target_class, options = {})
|
|
6
|
+
configuration[:associations][name] = DSL::Association.new(
|
|
7
|
+
name,
|
|
8
|
+
target_class,
|
|
9
|
+
block_options.merge(format_options(options))
|
|
10
|
+
)
|
|
8
11
|
end
|
|
9
12
|
end
|
|
10
13
|
end
|
|
@@ -20,10 +20,8 @@ module Brainstem
|
|
|
20
20
|
klass.new(new_config, block_options.merge(new_options), &block)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
def
|
|
24
|
-
options
|
|
25
|
-
description = args.shift
|
|
26
|
-
[description, options]
|
|
23
|
+
def format_options(options)
|
|
24
|
+
options.symbolize_keys
|
|
27
25
|
end
|
|
28
26
|
end
|
|
29
27
|
end
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
module Brainstem
|
|
2
2
|
module DSL
|
|
3
3
|
class Conditional
|
|
4
|
-
attr_reader :name, :type, :action, :
|
|
4
|
+
attr_reader :name, :type, :action, :options
|
|
5
5
|
|
|
6
|
-
def initialize(name, type, action,
|
|
6
|
+
def initialize(name, type, action, options = {})
|
|
7
7
|
@name = name
|
|
8
8
|
@type = type
|
|
9
9
|
@action = action
|
|
10
|
-
@
|
|
10
|
+
@options = options
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def description
|
|
14
|
+
options[:info].presence
|
|
11
15
|
end
|
|
12
16
|
|
|
13
17
|
def matches?(model, helper_instance = Object.new, conditional_cache = { model: {}, request: {} })
|
|
@@ -2,12 +2,12 @@ module Brainstem
|
|
|
2
2
|
module Concerns
|
|
3
3
|
module PresenterDSL
|
|
4
4
|
class ConditionalsBlock < BaseBlock
|
|
5
|
-
def request(name, action,
|
|
6
|
-
configuration[:conditionals][name] = DSL::Conditional.new(name, :request, action,
|
|
5
|
+
def request(name, action, options = {})
|
|
6
|
+
configuration[:conditionals][name] = DSL::Conditional.new(name, :request, action, format_options(options))
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
-
def model(name, action,
|
|
10
|
-
configuration[:conditionals][name] = DSL::Conditional.new(name, :model, action,
|
|
9
|
+
def model(name, action, options = {})
|
|
10
|
+
configuration[:conditionals][name] = DSL::Conditional.new(name, :model, action, format_options(options))
|
|
11
11
|
end
|
|
12
12
|
end
|
|
13
13
|
end
|
|
@@ -1,18 +1,38 @@
|
|
|
1
1
|
require 'active_support/hash_with_indifferent_access'
|
|
2
|
+
require 'forwardable'
|
|
2
3
|
|
|
3
4
|
# A hash-like object that accepts a parent configuration object that defers to
|
|
4
5
|
# the parent in the absence of one of its own keys (thus simulating inheritance).
|
|
5
6
|
module Brainstem
|
|
6
7
|
module DSL
|
|
7
8
|
class Configuration
|
|
9
|
+
extend Forwardable
|
|
8
10
|
|
|
9
11
|
# Returns a new configuration object.
|
|
10
12
|
#
|
|
11
13
|
# @params [Object] parent_configuration The parent configuration object
|
|
12
14
|
# which the new configuration object should use as a base.
|
|
13
15
|
def initialize(parent_configuration = nil)
|
|
14
|
-
@parent_configuration
|
|
15
|
-
@storage
|
|
16
|
+
@parent_configuration = parent_configuration || ActiveSupport::HashWithIndifferentAccess.new
|
|
17
|
+
@storage = ActiveSupport::HashWithIndifferentAccess.new
|
|
18
|
+
|
|
19
|
+
#
|
|
20
|
+
# Nonheritable keys are a bit peculiar: they make the lookup for a key
|
|
21
|
+
# specified as nonheritable to return no result when it falls back to
|
|
22
|
+
# the parent configuration.
|
|
23
|
+
#
|
|
24
|
+
# These keys themselves are inheritable; in this way, a class that
|
|
25
|
+
# descends from another will keep the same behaviour as its superclass
|
|
26
|
+
# without necessarily having the same data. Or to put it another way,
|
|
27
|
+
# if you have specified that 'title' is not inheritable in a
|
|
28
|
+
# superclass's configuration, that is a property of that class, and
|
|
29
|
+
# descendent classes should behave the same way.
|
|
30
|
+
#
|
|
31
|
+
# It is also unlikely that subclasses will modify the list of
|
|
32
|
+
# nonheritable keys.
|
|
33
|
+
parent_nh_keys = parent_configuration &&
|
|
34
|
+
parent_configuration.nonheritable_keys
|
|
35
|
+
@nonheritable_keys = InheritableAppendSet.new(parent_nh_keys)
|
|
16
36
|
end
|
|
17
37
|
|
|
18
38
|
def [](key)
|
|
@@ -40,12 +60,161 @@ module Brainstem
|
|
|
40
60
|
@storage[key] ||= InheritableAppendSet.new
|
|
41
61
|
end
|
|
42
62
|
|
|
63
|
+
|
|
64
|
+
#
|
|
65
|
+
# Marks a key in the configuration as nonheritable, which means that the key:
|
|
66
|
+
#
|
|
67
|
+
# - will appear in the list of keys for this object;
|
|
68
|
+
# - will return its value when fetched from this object;
|
|
69
|
+
# - will be included in the +to_h+ output from this object;
|
|
70
|
+
# - will be included when iterating with +#each+ from this object;
|
|
71
|
+
#
|
|
72
|
+
# - will not appear in the list of keys for any child object;
|
|
73
|
+
# - will return +nil+ when fetched from any child object;
|
|
74
|
+
# - will not be included in the +#to_h+ output from any child object;
|
|
75
|
+
# - will not be included when iterating with +#each+ from any child object.
|
|
76
|
+
#
|
|
77
|
+
# @param [Symbol,String] key the key to append to the list of nonheritable
|
|
78
|
+
# keys
|
|
79
|
+
#
|
|
80
|
+
def nonheritable!(key)
|
|
81
|
+
key = key.to_s
|
|
82
|
+
self.nonheritable_keys << key unless self.nonheritable_keys.include?(key)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
attr_accessor :nonheritable_keys
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
#
|
|
90
|
+
# Returns the keys in this configuration object that are visible to child
|
|
91
|
+
# configuration objects (i.e. heritable keys).
|
|
92
|
+
#
|
|
93
|
+
# @return [Array] keys
|
|
94
|
+
#
|
|
95
|
+
def keys_visible_to_children
|
|
96
|
+
keys - nonheritable_keys.to_a
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
#
|
|
101
|
+
# Returns a hash of this object's storage, less those pairs that are
|
|
102
|
+
# not visible to children.
|
|
103
|
+
#
|
|
104
|
+
# @return [Hash] the hash, less nonheritable pairs.
|
|
105
|
+
#
|
|
106
|
+
def pairs_visible_to_children
|
|
107
|
+
to_h.select {|k, v| keys_visible_to_children.include?(k.to_s) }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
#
|
|
112
|
+
# Returns the union of all keys in this configuration plus those that are
|
|
113
|
+
# heritable in the parent.
|
|
114
|
+
#
|
|
115
|
+
# @return [Array] keys
|
|
116
|
+
#
|
|
43
117
|
def keys
|
|
44
|
-
@parent_configuration.
|
|
118
|
+
if @parent_configuration.respond_to?(:keys_visible_to_children)
|
|
119
|
+
@parent_configuration.keys_visible_to_children | @storage.keys
|
|
120
|
+
else
|
|
121
|
+
@parent_configuration.keys | @storage.keys
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
#
|
|
127
|
+
# Returns a list of nonheritable keys for the parent configuration, if
|
|
128
|
+
# the parent configuration actually keeps track of it. Otherwise returns
|
|
129
|
+
# an empty array.
|
|
130
|
+
#
|
|
131
|
+
# @return [Array<String>] the list of nonheritable keys in the
|
|
132
|
+
# parent configuration.
|
|
133
|
+
#
|
|
134
|
+
def parent_nonheritable_keys
|
|
135
|
+
if @parent_configuration.respond_to?(:nonheritable_keys)
|
|
136
|
+
@parent_configuration.nonheritable_keys
|
|
137
|
+
else
|
|
138
|
+
[]
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
#
|
|
144
|
+
# Returns whether a key is nonheritable in this configuration object's
|
|
145
|
+
# parent configuration.
|
|
146
|
+
#
|
|
147
|
+
# Is of arity -1 so it can be easily passed to methods that yield
|
|
148
|
+
# either a key, or a key/value tuple.
|
|
149
|
+
#
|
|
150
|
+
# @param [Symbol,String] key the key to check for nonheritability.
|
|
151
|
+
#
|
|
152
|
+
def key_nonheritable_in_parent?(*key)
|
|
153
|
+
parent_nonheritable_keys.include?(key.first.to_s)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
#
|
|
158
|
+
# An inversion of +key_nonheritable_in_parent+. Returns true if the
|
|
159
|
+
# key is not marked as nonheritable in the parent configuration.
|
|
160
|
+
#
|
|
161
|
+
# Is of arity -1 so it can be easily passed to methods that yield
|
|
162
|
+
# either a key, or a key/value tuple.
|
|
163
|
+
#
|
|
164
|
+
# @param [Symbol,String] key the key to check for heritability.
|
|
165
|
+
#
|
|
166
|
+
def key_inheritable_in_parent?(*key)
|
|
167
|
+
!key_nonheritable_in_parent?(key.first.to_s)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
#
|
|
172
|
+
# Returns a hash of this object's storage merged over the heritable pairs
|
|
173
|
+
# of its parent configurations.
|
|
174
|
+
#
|
|
175
|
+
# @return [Hash] the merged hash
|
|
176
|
+
#
|
|
177
|
+
def to_h
|
|
178
|
+
if @parent_configuration.respond_to?(:pairs_visible_to_children)
|
|
179
|
+
@parent_configuration.pairs_visible_to_children.merge(@storage)
|
|
180
|
+
else
|
|
181
|
+
@parent_configuration.to_h.merge(@storage)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
#
|
|
187
|
+
# Returns the value for the given key, or if it could not be found:
|
|
188
|
+
# - Raises a +KeyError+ if not passed a default or a block;
|
|
189
|
+
# - Returns the default if it is passed a default but no block;
|
|
190
|
+
# - Calls and returns the block if passed a block but no default;
|
|
191
|
+
# - Calls the block with the default and returns the block if passed a
|
|
192
|
+
# default and a block.
|
|
193
|
+
#
|
|
194
|
+
# @params [Symbol,String] key the key to look up
|
|
195
|
+
# @params [Object] default the default to return
|
|
196
|
+
# @params [Proc] block the block to call
|
|
197
|
+
#
|
|
198
|
+
# @see http://ruby-doc.org/core-2.2.1/Hash.html#method-i-fetch
|
|
199
|
+
#
|
|
200
|
+
def fetch(key, default = nil, &block)
|
|
201
|
+
val = get!(key)
|
|
202
|
+
return val if val
|
|
203
|
+
|
|
204
|
+
if default && !block_given?
|
|
205
|
+
default
|
|
206
|
+
elsif block_given?
|
|
207
|
+
default ? block.call(default) : block.call
|
|
208
|
+
else
|
|
209
|
+
raise KeyError
|
|
210
|
+
end
|
|
45
211
|
end
|
|
46
212
|
|
|
213
|
+
|
|
47
214
|
def has_key?(key)
|
|
48
|
-
@storage.has_key?(key) ||
|
|
215
|
+
@storage.has_key?(key) ||
|
|
216
|
+
(@parent_configuration.has_key?(key) &&
|
|
217
|
+
key_inheritable_in_parent?(key))
|
|
49
218
|
end
|
|
50
219
|
|
|
51
220
|
def length
|
|
@@ -58,7 +227,7 @@ module Brainstem
|
|
|
58
227
|
end
|
|
59
228
|
end
|
|
60
229
|
|
|
61
|
-
delegate :empty
|
|
230
|
+
delegate :empty? => :keys
|
|
62
231
|
|
|
63
232
|
private
|
|
64
233
|
|
|
@@ -67,14 +236,19 @@ module Brainstem
|
|
|
67
236
|
# Retrieves the value stored at key.
|
|
68
237
|
#
|
|
69
238
|
# - If +key+ is already defined, it returns that;
|
|
239
|
+
# - If +key+ in the parent is marked as nonheritable, it returns
|
|
240
|
+
# +nil+;
|
|
70
241
|
# - If +key+ in the parent is a +Configuration+, returns a new
|
|
71
242
|
# +Configuration+ with the parent set;
|
|
72
243
|
# - If +key+ in the parent is an +InheritableAppendSet+, returns a new
|
|
73
244
|
# +InheritableAppendSet+ with the parent set;
|
|
74
245
|
# - Elsewise returns the parent configuration's value for the key.
|
|
246
|
+
#
|
|
75
247
|
def get!(key)
|
|
76
248
|
@storage[key] || begin
|
|
77
|
-
if
|
|
249
|
+
if key_nonheritable_in_parent?(key)
|
|
250
|
+
nil
|
|
251
|
+
elsif @parent_configuration[key].is_a?(Configuration)
|
|
78
252
|
@storage[key] = Configuration.new(@parent_configuration[key])
|
|
79
253
|
elsif @parent_configuration[key].is_a?(InheritableAppendSet)
|
|
80
254
|
@storage[key] = InheritableAppendSet.new(@parent_configuration[key])
|
|
@@ -87,6 +261,8 @@ module Brainstem
|
|
|
87
261
|
# An Array-like object that provides `push`, `concat`, `each`, `empty?`, and `to_a` methods that act the combination
|
|
88
262
|
# of its own entries and those of a parent InheritableAppendSet, if present.
|
|
89
263
|
class InheritableAppendSet
|
|
264
|
+
extend Forwardable
|
|
265
|
+
|
|
90
266
|
def initialize(parent_array = nil)
|
|
91
267
|
@parent_array = parent_array || []
|
|
92
268
|
@storage = []
|
|
@@ -105,8 +281,8 @@ module Brainstem
|
|
|
105
281
|
@parent_array.to_a + @storage
|
|
106
282
|
end
|
|
107
283
|
|
|
108
|
-
delegate :each, :empty?,
|
|
284
|
+
delegate [:each, :empty?, :include?] => :to_a
|
|
109
285
|
end
|
|
110
286
|
end
|
|
111
287
|
end
|
|
112
|
-
end
|
|
288
|
+
end
|