praxis 2.0.pre.8 → 2.0.pre.13
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/.gitignore +2 -0
- data/.ruby-version +1 -1
- data/.travis.yml +1 -3
- data/CHANGELOG.md +33 -0
- data/TODO.md +1 -4
- data/bin/praxis +67 -12
- data/lib/praxis.rb +10 -3
- data/lib/praxis/action_definition.rb +15 -13
- data/lib/praxis/action_definition/headers_dsl_compiler.rb +0 -7
- data/lib/praxis/api_general_info.rb +1 -1
- data/lib/praxis/application.rb +6 -2
- data/lib/praxis/blueprint.rb +357 -0
- data/lib/praxis/bootloader.rb +9 -3
- data/lib/praxis/bootloader_stages/environment.rb +16 -13
- data/lib/praxis/collection.rb +1 -11
- data/lib/praxis/config_hash.rb +44 -0
- data/lib/praxis/docs/{openapi → open_api}/info_object.rb +18 -10
- data/lib/praxis/docs/{openapi → open_api}/media_type_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/operation_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/parameter_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/paths_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/request_body_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/response_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/responses_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/schema_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/server_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/tag_object.rb +0 -0
- data/lib/praxis/docs/open_api_generator.rb +91 -6
- data/lib/praxis/endpoint_definition.rb +273 -0
- data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +182 -58
- data/lib/praxis/extensions/attribute_filtering/filter_tree_node.rb +3 -2
- data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +47 -56
- data/lib/praxis/extensions/attribute_filtering/filters_parser.rb +153 -0
- data/lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb +20 -8
- data/lib/praxis/extensions/field_expansion.rb +3 -36
- data/lib/praxis/extensions/pagination.rb +5 -32
- data/lib/praxis/extensions/pagination/ordering_params.rb +1 -1
- data/lib/praxis/extensions/pagination/pagination_params.rb +6 -4
- data/lib/praxis/field_expander.rb +90 -0
- data/lib/praxis/finalizable.rb +34 -0
- data/lib/praxis/mapper/active_model_compat.rb +4 -0
- data/lib/praxis/mapper/resource.rb +18 -2
- data/lib/praxis/mapper/selector_generator.rb +2 -1
- data/lib/praxis/mapper/sequel_compat.rb +7 -0
- data/lib/praxis/media_type.rb +3 -68
- data/lib/praxis/plugin_concern.rb +1 -1
- data/lib/praxis/plugins/mapper_plugin.rb +24 -15
- data/lib/praxis/plugins/pagination_plugin.rb +34 -4
- data/lib/praxis/renderer.rb +88 -0
- data/lib/praxis/request.rb +1 -1
- data/lib/praxis/resource_definition.rb +2 -311
- data/lib/praxis/response_definition.rb +2 -10
- data/lib/praxis/response_template.rb +3 -3
- data/lib/praxis/router.rb +2 -2
- data/lib/praxis/routing_config.rb +1 -1
- data/lib/praxis/tasks/api_docs.rb +17 -64
- data/lib/praxis/tasks/routes.rb +1 -1
- data/lib/praxis/types/media_type_common.rb +1 -11
- data/lib/praxis/version.rb +1 -1
- data/praxis.gemspec +0 -1
- data/spec/functional_spec.rb +5 -9
- data/spec/praxis/action_definition_spec.rb +12 -20
- data/spec/praxis/blueprint_spec.rb +373 -0
- data/spec/praxis/bootloader_spec.rb +10 -2
- data/spec/praxis/collection_spec.rb +0 -13
- data/spec/praxis/config_hash_spec.rb +64 -0
- data/spec/praxis/{resource_definition_spec.rb → endpoint_definition_spec.rb} +37 -64
- data/spec/praxis/extensions/attribute_filtering/active_record_filter_query_builder_spec.rb +249 -168
- data/spec/praxis/extensions/attribute_filtering/filter_tree_node_spec.rb +25 -6
- data/spec/praxis/extensions/attribute_filtering/filtering_params_spec.rb +190 -8
- data/spec/praxis/extensions/attribute_filtering/filters_parser_spec.rb +140 -0
- data/spec/praxis/extensions/field_expansion_spec.rb +5 -24
- data/spec/praxis/extensions/field_selection/active_record_query_selector_spec.rb +1 -1
- data/spec/praxis/extensions/field_selection/sequel_query_selector_spec.rb +1 -1
- data/spec/praxis/extensions/support/spec_resources_active_model.rb +1 -1
- data/spec/praxis/field_expander_spec.rb +149 -0
- data/spec/praxis/mapper/selector_generator_spec.rb +1 -1
- data/spec/praxis/media_type_identifier_spec.rb +5 -4
- data/spec/praxis/media_type_spec.rb +4 -93
- data/spec/praxis/renderer_spec.rb +188 -0
- data/spec/praxis/response_definition_spec.rb +0 -31
- data/spec/praxis/response_spec.rb +1 -1
- data/spec/praxis/router_spec.rb +8 -8
- data/spec/praxis/routing_config_spec.rb +3 -3
- data/spec/spec_app/app/controllers/instances.rb +13 -7
- data/spec/spec_app/design/media_types/instance.rb +1 -19
- data/spec/spec_app/design/media_types/volume.rb +1 -1
- data/spec/spec_app/design/media_types/volume_snapshot.rb +2 -14
- data/spec/spec_app/design/resources/instances.rb +5 -8
- data/spec/spec_app/design/resources/volume_snapshots.rb +1 -1
- data/spec/spec_app/design/resources/volumes.rb +1 -1
- data/spec/support/spec_authorization_plugin.rb +1 -1
- data/spec/support/spec_blueprints.rb +72 -0
- data/spec/support/{spec_resource_definitions.rb → spec_endpoint_definitions.rb} +2 -2
- data/spec/support/spec_media_types.rb +6 -26
- data/tasks/thor/app.rb +8 -34
- data/tasks/thor/example.rb +51 -285
- data/tasks/thor/model.rb +40 -0
- data/tasks/thor/scaffold.rb +117 -0
- data/tasks/thor/templates/generator/empty_app/.gitignore +0 -1
- data/tasks/thor/templates/generator/empty_app/Gemfile +7 -23
- data/tasks/thor/templates/generator/empty_app/README.md +1 -1
- data/tasks/thor/templates/generator/empty_app/Rakefile +4 -13
- data/tasks/thor/templates/generator/empty_app/{design/response_templates → app/v1/resources}/.empty_directory +0 -0
- data/tasks/thor/templates/generator/empty_app/{design/response_templates → app/v1/resources}/.gitkeep +0 -0
- data/tasks/thor/templates/generator/empty_app/config/environment.rb +26 -17
- data/tasks/thor/templates/generator/empty_app/{design/v1/resources → config/initializers}/.empty_directory +0 -0
- data/tasks/thor/templates/generator/empty_app/{design/v1/resources → config/initializers}/.gitkeep +0 -0
- data/tasks/thor/templates/generator/empty_app/design/v1/endpoints/.empty_directory +0 -0
- data/tasks/thor/templates/generator/empty_app/design/v1/endpoints/.gitkeep +0 -0
- data/tasks/thor/templates/generator/empty_app/docs/.empty_directory +0 -0
- data/tasks/thor/templates/generator/empty_app/docs/.gitkeep +0 -0
- data/tasks/thor/templates/generator/empty_app/spec/spec_helper.rb +14 -9
- data/tasks/thor/templates/generator/example_app/.gitignore +1 -0
- data/tasks/thor/templates/generator/example_app/Gemfile +19 -0
- data/tasks/thor/templates/generator/example_app/Rakefile +61 -0
- data/tasks/thor/templates/generator/example_app/app/models/user.rb +6 -0
- data/tasks/thor/templates/generator/example_app/app/v1/concerns/controller_base.rb +24 -0
- data/tasks/thor/templates/generator/example_app/app/v1/controllers/users.rb +17 -0
- data/tasks/thor/templates/generator/example_app/app/v1/resources/base.rb +11 -0
- data/tasks/thor/templates/generator/example_app/app/v1/resources/user.rb +25 -0
- data/tasks/thor/templates/generator/example_app/config.ru +30 -0
- data/tasks/thor/templates/generator/example_app/config/environment.rb +41 -0
- data/tasks/thor/templates/generator/example_app/db/migrate/20201010101010_create_users_table.rb +12 -0
- data/tasks/thor/templates/generator/example_app/db/seeds.rb +6 -0
- data/tasks/thor/templates/generator/example_app/design/api.rb +18 -0
- data/tasks/thor/templates/generator/example_app/design/v1/endpoints/users.rb +37 -0
- data/tasks/thor/templates/generator/example_app/design/v1/media_types/user.rb +21 -0
- data/tasks/thor/templates/generator/example_app/spec/helpers/database_helper.rb +20 -0
- data/tasks/thor/templates/generator/example_app/spec/spec_helper.rb +42 -0
- data/tasks/thor/templates/generator/example_app/spec/v1/controllers/users_spec.rb +37 -0
- data/tasks/thor/templates/generator/scaffold/design/endpoints/collection.rb +98 -0
- data/tasks/thor/templates/generator/scaffold/design/media_types/item.rb +18 -0
- data/tasks/thor/templates/generator/scaffold/implementation/controllers/collection.rb +77 -0
- data/tasks/thor/templates/generator/scaffold/implementation/resources/base.rb +11 -0
- data/tasks/thor/templates/generator/scaffold/implementation/resources/item.rb +45 -0
- data/tasks/thor/templates/generator/scaffold/models/active_record.rb +6 -0
- data/tasks/thor/templates/generator/scaffold/models/sequel.rb +6 -0
- metadata +64 -136
- data/lib/api_browser/.bowerrc +0 -3
- data/lib/api_browser/.editorconfig +0 -21
- data/lib/api_browser/Gruntfile.js +0 -581
- data/lib/api_browser/app/index.html +0 -59
- data/lib/api_browser/app/js/app.js +0 -48
- data/lib/api_browser/app/js/controllers/action.js +0 -47
- data/lib/api_browser/app/js/controllers/controller.js +0 -10
- data/lib/api_browser/app/js/controllers/menu.js +0 -93
- data/lib/api_browser/app/js/controllers/trait.js +0 -10
- data/lib/api_browser/app/js/controllers/type.js +0 -24
- data/lib/api_browser/app/js/directives/attribute_description.js +0 -56
- data/lib/api_browser/app/js/directives/attribute_table.js +0 -28
- data/lib/api_browser/app/js/directives/conditional_requirements.js +0 -13
- data/lib/api_browser/app/js/directives/fixed_if_fits.js +0 -38
- data/lib/api_browser/app/js/directives/highlight.js +0 -14
- data/lib/api_browser/app/js/directives/menu_item.js +0 -59
- data/lib/api_browser/app/js/directives/no_container.js +0 -8
- data/lib/api_browser/app/js/directives/readable_list.js +0 -87
- data/lib/api_browser/app/js/directives/request_examples.js +0 -31
- data/lib/api_browser/app/js/directives/type_placeholder.js +0 -30
- data/lib/api_browser/app/js/directives/url.js +0 -15
- data/lib/api_browser/app/js/factories/Configuration.js +0 -12
- data/lib/api_browser/app/js/factories/Documentation.js +0 -61
- data/lib/api_browser/app/js/factories/Example.js +0 -51
- data/lib/api_browser/app/js/factories/PageInfo.js +0 -9
- data/lib/api_browser/app/js/factories/normalize_attributes.js +0 -20
- data/lib/api_browser/app/js/factories/prepare_template.js +0 -15
- data/lib/api_browser/app/js/factories/template_for.js +0 -128
- data/lib/api_browser/app/js/filters/attribute_name.js +0 -10
- data/lib/api_browser/app/js/filters/friendly_json.js +0 -5
- data/lib/api_browser/app/js/filters/has_requirement.js +0 -14
- data/lib/api_browser/app/js/filters/header_info.js +0 -9
- data/lib/api_browser/app/js/filters/is_empty.js +0 -8
- data/lib/api_browser/app/js/filters/markdown.js +0 -6
- data/lib/api_browser/app/js/filters/resource_name.js +0 -5
- data/lib/api_browser/app/js/filters/tag_requirement.js +0 -13
- data/lib/api_browser/app/sass/modules/_body.scss +0 -40
- data/lib/api_browser/app/sass/modules/_cloke.scss +0 -8
- data/lib/api_browser/app/sass/modules/_header.scss +0 -10
- data/lib/api_browser/app/sass/modules/_nav.scss +0 -7
- data/lib/api_browser/app/sass/modules/_sidebar.scss +0 -134
- data/lib/api_browser/app/sass/modules/_switch.scss +0 -55
- data/lib/api_browser/app/sass/modules/_table.scss +0 -13
- data/lib/api_browser/app/sass/praxis.scss +0 -70
- data/lib/api_browser/app/sass/variables/_bootstrap-variables.scss +0 -774
- data/lib/api_browser/app/views/action.html +0 -97
- data/lib/api_browser/app/views/builtin/field-selector.html +0 -24
- data/lib/api_browser/app/views/controller.html +0 -55
- data/lib/api_browser/app/views/directives/attribute_description.html +0 -2
- data/lib/api_browser/app/views/directives/attribute_description/default.html +0 -2
- data/lib/api_browser/app/views/directives/attribute_description/example.html +0 -13
- data/lib/api_browser/app/views/directives/attribute_description/headers.html +0 -8
- data/lib/api_browser/app/views/directives/attribute_description/member_options.html +0 -4
- data/lib/api_browser/app/views/directives/attribute_description/values.html +0 -14
- data/lib/api_browser/app/views/directives/attribute_table.html +0 -17
- data/lib/api_browser/app/views/directives/menu_item.html +0 -8
- data/lib/api_browser/app/views/directives/url.html +0 -3
- data/lib/api_browser/app/views/examples/general.html +0 -26
- data/lib/api_browser/app/views/home.html +0 -5
- data/lib/api_browser/app/views/layout.html +0 -8
- data/lib/api_browser/app/views/menu.html +0 -42
- data/lib/api_browser/app/views/navbar.html +0 -9
- data/lib/api_browser/app/views/trait.html +0 -13
- data/lib/api_browser/app/views/type.html +0 -6
- data/lib/api_browser/app/views/type/details.html +0 -33
- data/lib/api_browser/app/views/types/embedded/array.html +0 -2
- data/lib/api_browser/app/views/types/embedded/default.html +0 -12
- data/lib/api_browser/app/views/types/embedded/field-selector.html +0 -13
- data/lib/api_browser/app/views/types/embedded/links.html +0 -11
- data/lib/api_browser/app/views/types/embedded/requirements.html +0 -6
- data/lib/api_browser/app/views/types/embedded/single_req.html +0 -9
- data/lib/api_browser/app/views/types/embedded/struct.html +0 -14
- data/lib/api_browser/app/views/types/label/link.html +0 -1
- data/lib/api_browser/app/views/types/label/primitive.html +0 -1
- data/lib/api_browser/app/views/types/label/primitive_collection.html +0 -1
- data/lib/api_browser/app/views/types/label/type.html +0 -1
- data/lib/api_browser/app/views/types/label/type_collection.html +0 -1
- data/lib/api_browser/app/views/types/main/array.html +0 -22
- data/lib/api_browser/app/views/types/main/default.html +0 -23
- data/lib/api_browser/app/views/types/main/hash.html +0 -23
- data/lib/api_browser/app/views/types/standalone/array.html +0 -3
- data/lib/api_browser/app/views/types/standalone/default.html +0 -18
- data/lib/api_browser/app/views/types/standalone/struct.html +0 -2
- data/lib/api_browser/bower_template.json +0 -41
- data/lib/api_browser/package-lock.json +0 -7110
- data/lib/api_browser/package.json +0 -43
- data/lib/praxis/docs/generator.rb +0 -243
- data/lib/praxis/docs/link_builder.rb +0 -30
- data/lib/praxis/links.rb +0 -135
- data/lib/praxis/types/multipart.rb +0 -109
- data/spec/api_browser/directives/type_placeholder_spec.js +0 -134
- data/spec/api_browser/factories/configuration_spec.js +0 -32
- data/spec/api_browser/factories/documentation_spec.js +0 -100
- data/spec/api_browser/factories/normalize_attributes_spec.js +0 -92
- data/spec/api_browser/factories/template_for_spec.js +0 -67
- data/spec/api_browser/filters/attribute_name_spec.js +0 -23
- data/spec/praxis/types/multipart_spec.rb +0 -112
- data/tasks/thor/templates/generator/empty_app/.rspec +0 -1
- data/tasks/thor/templates/generator/empty_app/Guardfile +0 -3
- data/tasks/thor/templates/generator/empty_app/config/rainbows.rb +0 -57
- data/tasks/thor/templates/generator/empty_app/docs/app.js +0 -1
- data/tasks/thor/templates/generator/empty_app/docs/styles.scss +0 -3
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Praxis
|
|
3
|
+
module Finalizable
|
|
4
|
+
def self.extended(klass)
|
|
5
|
+
klass.module_eval do
|
|
6
|
+
@finalizable = Set.new
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def inherited(base)
|
|
11
|
+
@finalizable << base
|
|
12
|
+
base.instance_variable_set(:@finalizable, @finalizable)
|
|
13
|
+
base.instance_variable_set(:@finalized, false)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def finalizable
|
|
17
|
+
@finalizable
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def finalized?
|
|
21
|
+
@finalized
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def _finalize!
|
|
25
|
+
@finalized = true
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def finalize!
|
|
29
|
+
finalizable.reject(&:finalized?).each(&:_finalize!)
|
|
30
|
+
|
|
31
|
+
finalize! unless finalizable.all?(&:finalized?)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -23,6 +23,10 @@ module Praxis
|
|
|
23
23
|
Praxis::Extensions::FieldSelection::ActiveRecordQuerySelector
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
+
def _pagination_query_builder_class
|
|
27
|
+
Praxis::Extensions::Pagination::ActiveRecordPaginationHandler
|
|
28
|
+
end
|
|
29
|
+
|
|
26
30
|
def _praxis_associations
|
|
27
31
|
orig = self.reflections.clone
|
|
28
32
|
|
|
@@ -30,6 +30,7 @@ module Praxis::Mapper
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
@properties = self.superclass.properties.clone
|
|
33
|
+
@_filters_map = {}
|
|
33
34
|
end
|
|
34
35
|
|
|
35
36
|
end
|
|
@@ -197,7 +198,7 @@ module Praxis::Mapper
|
|
|
197
198
|
|
|
198
199
|
# TODO: this shouldn't be needed if we incorporate it with the properties of the mapper...
|
|
199
200
|
# ...maybe what this means is that we can change it for a better DSL in the resource?
|
|
200
|
-
def self.filters_mapping(definition)
|
|
201
|
+
def self.filters_mapping(definition={})
|
|
201
202
|
@_filters_map = \
|
|
202
203
|
case definition
|
|
203
204
|
when Hash
|
|
@@ -211,7 +212,9 @@ module Praxis::Mapper
|
|
|
211
212
|
|
|
212
213
|
def self.craft_filter_query(base_query, filters:) # rubocop:disable Metrics/AbcSize
|
|
213
214
|
if filters
|
|
214
|
-
|
|
215
|
+
unless @_filters_map
|
|
216
|
+
raise "To use API filtering, you must define the mapping of api-names to resource properties (using the `filters_mapping` method in #{self})"
|
|
217
|
+
end
|
|
215
218
|
debug = Praxis::Application.instance.config.mapper.debug_queries
|
|
216
219
|
base_query = model._filter_query_builder_class.new(query: base_query, model: model, filters_map: @_filters_map, debug: debug).generate(filters)
|
|
217
220
|
end
|
|
@@ -228,6 +231,19 @@ module Praxis::Mapper
|
|
|
228
231
|
base_query
|
|
229
232
|
end
|
|
230
233
|
|
|
234
|
+
def self.craft_pagination_query(base_query, pagination: ) # rubocop:disable Metrics/AbcSize
|
|
235
|
+
handler_klass = model._pagination_query_builder_class
|
|
236
|
+
return base_query unless (handler_klass && (pagination.paginator || pagination.order))
|
|
237
|
+
|
|
238
|
+
# Gather and save the count if required
|
|
239
|
+
if pagination.paginator&.total_count
|
|
240
|
+
pagination.total_count = handler_klass.count(base_query.dup)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
base_query = handler_klass.order(base_query, pagination.order)
|
|
244
|
+
handler_klass.paginate(base_query, pagination)
|
|
245
|
+
end
|
|
246
|
+
|
|
231
247
|
def initialize(record)
|
|
232
248
|
@record = record
|
|
233
249
|
end
|
|
@@ -48,7 +48,7 @@ module Praxis::Mapper
|
|
|
48
48
|
new_fields_as_hash = association[:remote_key_columns].each_with_object({}) do|name, hash|
|
|
49
49
|
hash[name] = true
|
|
50
50
|
end
|
|
51
|
-
fields.merge
|
|
51
|
+
fields = fields.merge(new_fields_as_hash)
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
node.add(fields) unless fields == true
|
|
@@ -141,6 +141,7 @@ module Praxis::Mapper
|
|
|
141
141
|
def add(resource, fields)
|
|
142
142
|
@root = SelectorGeneratorNode.new(resource)
|
|
143
143
|
@root.add(fields)
|
|
144
|
+
self
|
|
144
145
|
end
|
|
145
146
|
|
|
146
147
|
def selectors
|
|
@@ -7,6 +7,9 @@ module Praxis::Mapper
|
|
|
7
7
|
|
|
8
8
|
included do
|
|
9
9
|
attr_accessor :_resource
|
|
10
|
+
class <<self
|
|
11
|
+
alias_method :find_by, :find # Easy way to be method compatible with AR
|
|
12
|
+
end
|
|
10
13
|
end
|
|
11
14
|
|
|
12
15
|
module ClassMethods
|
|
@@ -19,6 +22,10 @@ module Praxis::Mapper
|
|
|
19
22
|
Praxis::Extensions::FieldSelection::SequelQuerySelector
|
|
20
23
|
end
|
|
21
24
|
|
|
25
|
+
def _pagination_query_builder_class
|
|
26
|
+
Praxis::Extensions::Pagination::SequelPaginationHandler
|
|
27
|
+
end
|
|
28
|
+
|
|
22
29
|
def _praxis_associations
|
|
23
30
|
orig = self.association_reflections.clone
|
|
24
31
|
orig.each do |k,v|
|
data/lib/praxis/media_type.rb
CHANGED
|
@@ -34,8 +34,9 @@ module Praxis
|
|
|
34
34
|
# description: 'The factory in which this widget was produced'
|
|
35
35
|
# end
|
|
36
36
|
#
|
|
37
|
-
# # All resources
|
|
38
|
-
#
|
|
37
|
+
# # All resources will be assigned a default_fieldset (with only non-blueprint attributes)
|
|
38
|
+
# # But one can explicitly tailor that by using the `default_fieldset` DSL
|
|
39
|
+
# default_fieldset do
|
|
39
40
|
# attribute :id
|
|
40
41
|
# attribute :color
|
|
41
42
|
# attribute :material
|
|
@@ -44,72 +45,6 @@ module Praxis
|
|
|
44
45
|
class MediaType < Praxis::Blueprint
|
|
45
46
|
|
|
46
47
|
include Types::MediaTypeCommon
|
|
47
|
-
|
|
48
|
-
class FieldResolver
|
|
49
|
-
def self.resolve(type,fields)
|
|
50
|
-
self.new.resolve(type,fields)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
attr_reader :history
|
|
54
|
-
|
|
55
|
-
def initialize
|
|
56
|
-
@history = Hash.new do |hash,key|
|
|
57
|
-
hash[key] = Hash.new
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def resolve(type,fields)
|
|
62
|
-
history_key = fields
|
|
63
|
-
history_type = type
|
|
64
|
-
if fields.kind_of?(Array)
|
|
65
|
-
loop do
|
|
66
|
-
type = type.member_attribute.type
|
|
67
|
-
fields = fields.first
|
|
68
|
-
break unless fields.kind_of?(Array)
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
return true if fields == true
|
|
73
|
-
|
|
74
|
-
if history[history_type].include? history_key
|
|
75
|
-
return history[history_type][history_key]
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
result = history[history_type][history_key] = {}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
fields.each do |name, sub_fields|
|
|
82
|
-
|
|
83
|
-
new_type = type.attributes[name].type
|
|
84
|
-
result[name] = resolve(new_type, sub_fields)
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
result
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
# perform a deep recursive *in place* merge
|
|
91
|
-
# form all values in +source+ onto +target+
|
|
92
|
-
#
|
|
93
|
-
# note: can not use ActiveSupport's Hash#deep_merge! because it does not
|
|
94
|
-
# properly do a recursive `deep_merge!`, but instead does `deep_merge`,
|
|
95
|
-
# which destroys the self-referential behavior of field hashes.
|
|
96
|
-
#
|
|
97
|
-
# note: unlike Hash#merge, doesn't take a block.
|
|
98
|
-
def deep_merge(target, source)
|
|
99
|
-
source.each do |current_key, source_value|
|
|
100
|
-
target_value = target[current_key]
|
|
101
|
-
|
|
102
|
-
target[current_key] = if target_value.is_a?(Hash) && source_value.is_a?(Hash)
|
|
103
|
-
deep_merge(target_value, source_value)
|
|
104
|
-
else
|
|
105
|
-
source_value
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
target
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
end
|
|
112
|
-
|
|
113
48
|
end
|
|
114
49
|
|
|
115
50
|
end
|
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
require 'singleton'
|
|
2
|
-
require 'praxis/extensions/attribute_filtering'
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
require 'praxis/extensions/field_selection'
|
|
4
|
+
|
|
5
|
+
module Praxis
|
|
5
6
|
module Plugins
|
|
6
7
|
module MapperPlugin
|
|
7
8
|
include Praxis::PluginConcern
|
|
8
9
|
|
|
10
|
+
# The Mapper plugin is an overarching set of things to include in your application
|
|
11
|
+
# when you want to use the rendring, field_selection, filtering (and potentially pagination) extensions
|
|
12
|
+
# To use the plugin, set it up like any other plugin by registering to the bootloader.
|
|
13
|
+
# Typically you'd do that in environment.rb, inside the `Praxis::Application.configure do |application|` block, by:
|
|
14
|
+
# application.bootloader.use Praxis::Plugins::MapperPlugin
|
|
15
|
+
#
|
|
16
|
+
# The plugin accepts only 1 configuration option thus far, which you can set inside the same block as:
|
|
17
|
+
# application.config.mapper.debug_queries = true
|
|
18
|
+
# when debug_queries is set to true, the system will output information about the expanded fields
|
|
19
|
+
# and associations that the system ihas calculated necessary to pull from the DB, based on the requested
|
|
20
|
+
# API fields, API filters and `property` dependencies defined in the domain models (i.e., resources)
|
|
9
21
|
class Plugin < Praxis::Plugin
|
|
10
22
|
include Singleton
|
|
11
23
|
|
|
@@ -29,36 +41,33 @@ module Praxis
|
|
|
29
41
|
extend ActiveSupport::Concern
|
|
30
42
|
|
|
31
43
|
included do
|
|
44
|
+
include Praxis::Extensions::Rendering
|
|
32
45
|
include Praxis::Extensions::FieldExpansion
|
|
33
46
|
end
|
|
34
47
|
|
|
35
|
-
def set_selectors
|
|
36
|
-
return unless self.media_type.respond_to?(:domain_model) &&
|
|
37
|
-
self.media_type.domain_model < Praxis::Mapper::Resource
|
|
38
|
-
|
|
39
|
-
resolved = Praxis::MediaType::FieldResolver.resolve(self.media_type, self.expanded_fields)
|
|
40
|
-
selector_generator.add(self.media_type.domain_model, resolved)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
48
|
def build_query(base_query) # rubocop:disable Metrics/AbcSize
|
|
44
49
|
domain_model = self.media_type&.domain_model
|
|
45
50
|
raise "No domain model defined for #{self.name}. Cannot use the attribute filtering helpers without it" unless domain_model
|
|
46
51
|
|
|
47
52
|
filters = request.params.filters if request.params&.respond_to?(:filters)
|
|
53
|
+
# Handle filters
|
|
48
54
|
base_query = domain_model.craft_filter_query( base_query , filters: filters )
|
|
49
|
-
|
|
55
|
+
# Handle field and nested field selection
|
|
50
56
|
base_query = domain_model.craft_field_selection_query(base_query, selectors: selector_generator.selectors)
|
|
57
|
+
# handle pagination and ordering if the pagination extention is included
|
|
58
|
+
base_query = domain_model.craft_pagination_query(base_query, pagination: _pagination) if self.respond_to?(:_pagination)
|
|
51
59
|
|
|
52
|
-
# TODO: handle pagination and ordering
|
|
53
60
|
base_query
|
|
54
61
|
end
|
|
55
62
|
|
|
56
63
|
def selector_generator
|
|
57
|
-
|
|
58
|
-
|
|
64
|
+
return unless self.media_type.respond_to?(:domain_model) &&
|
|
65
|
+
self.media_type.domain_model < Praxis::Mapper::Resource
|
|
59
66
|
|
|
67
|
+
@selector_generator ||= \
|
|
68
|
+
Praxis::Mapper::SelectorGenerator.new.add(self.media_type.domain_model, self.expanded_fields)
|
|
69
|
+
end
|
|
60
70
|
end
|
|
61
|
-
|
|
62
71
|
end
|
|
63
72
|
end
|
|
64
73
|
end
|
|
@@ -1,14 +1,45 @@
|
|
|
1
1
|
require 'singleton'
|
|
2
2
|
require 'praxis/extensions/pagination'
|
|
3
3
|
|
|
4
|
-
#
|
|
4
|
+
# The PaginationPlugin can be configured to take advantage of adding pagination and sorting to
|
|
5
|
+
# your DB queries.
|
|
6
|
+
# When combined with the MapperPlugin, there is no extra configuration that needs to be done for
|
|
7
|
+
# the system to appropriately identify the pagination and order parameters in the API, and translate
|
|
8
|
+
# that in to the appropriate queries to fetch.
|
|
9
|
+
#
|
|
10
|
+
# To use this plugin without the MapperPlugin (probably a rare case), one can apply the appropriate
|
|
11
|
+
# clauses onto a query, by directly calling (in the controller) the `craft_pagination_query` method
|
|
12
|
+
# of the domain_model associated to the controller's mediatype.
|
|
13
|
+
# For example, here's how you can manually use this extension in a fictitious users index action:
|
|
14
|
+
# def index
|
|
15
|
+
# base_query = User.all # Start by not excluding any user
|
|
16
|
+
# domain_model = self.media_type.domain_model
|
|
17
|
+
# objs = domain_model.craft_pagination_query(base_query, pagination: _pagination)
|
|
18
|
+
# display(objs)
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# This plugin accepts configuration about the default behavior of pagination.
|
|
22
|
+
# Any of these configs can individually be overidden when defining each Pagination/Order parameters
|
|
23
|
+
# in any of the Endpoint actions.
|
|
24
|
+
#
|
|
5
25
|
# Example configuration for this plugin
|
|
6
26
|
# Praxis::Application.configure do |application|
|
|
7
27
|
# application.bootloader.use Praxis::Plugins::PaginationPlugin, {
|
|
28
|
+
# # The maximum number of results that a paginated response will ever allow
|
|
8
29
|
# max_items: 500, # Unlimited by default,
|
|
30
|
+
# # The default page size to use when no `items` is specified
|
|
9
31
|
# default_page_size: 100,
|
|
10
|
-
#
|
|
11
|
-
#
|
|
32
|
+
# # Disallows the use of the page type pagination mode when true (i.e., using 'page=' parameter)
|
|
33
|
+
# disallow_paging_by_default: true, # Default false
|
|
34
|
+
# # Disallows the use of the cursor type pagination mode when true (i.e., using 'by=' or 'from=' parameter)
|
|
35
|
+
# disallow_cursor_by_default: true, # Default false
|
|
36
|
+
# # The default mode params to use
|
|
37
|
+
# paging_default_mode: {by: :uuid}, # Default {by: :uid}
|
|
38
|
+
# # Weather or not to enforce that all requested sort fields are part of the media_type attributes
|
|
39
|
+
# # when false (not enforced) only the first field would be checked
|
|
40
|
+
# sorting: {
|
|
41
|
+
# enforce_all_fields: false # Default true
|
|
42
|
+
# }
|
|
12
43
|
# end
|
|
13
44
|
# end
|
|
14
45
|
#
|
|
@@ -39,7 +70,6 @@ module Praxis
|
|
|
39
70
|
attribute :paging_default_mode, Hash, default: Praxis::Types::PaginationParams.paging_default_mode
|
|
40
71
|
attribute :disallow_paging_by_default, Attributor::Boolean, default: Praxis::Types::PaginationParams.disallow_paging_by_default
|
|
41
72
|
attribute :disallow_cursor_by_default, Attributor::Boolean, default: Praxis::Types::PaginationParams.disallow_cursor_by_default
|
|
42
|
-
attribute :disallow_cursor_by_default, Attributor::Boolean, default: Praxis::Types::PaginationParams.disallow_cursor_by_default
|
|
43
73
|
attribute :sorting do
|
|
44
74
|
attribute :enforce_all_fields, Attributor::Boolean, default: Praxis::Types::OrderingParams.enforce_all_fields
|
|
45
75
|
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Praxis
|
|
4
|
+
class Renderer
|
|
5
|
+
attr_reader :include_nil
|
|
6
|
+
attr_reader :cache
|
|
7
|
+
|
|
8
|
+
class CircularRenderingError < StandardError
|
|
9
|
+
attr_reader :object
|
|
10
|
+
attr_reader :context
|
|
11
|
+
|
|
12
|
+
def initialize(object, context)
|
|
13
|
+
@object = object
|
|
14
|
+
@context = context
|
|
15
|
+
|
|
16
|
+
first = Attributor.humanize_context(context[0..10])
|
|
17
|
+
last = Attributor.humanize_context(context[-5..-1])
|
|
18
|
+
pretty_context = "#{first}...#{last}"
|
|
19
|
+
super("SystemStackError in rendering #{object.class} with context: #{pretty_context}")
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def initialize(include_nil: false)
|
|
24
|
+
@cache = Hash.new do |hash, key|
|
|
25
|
+
hash[key] = {}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
@include_nil = include_nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Renders an object using a given list of fields.
|
|
32
|
+
#
|
|
33
|
+
# @param [Object] object the object to render
|
|
34
|
+
# @param [Hash] fields the correct set of fields, as from FieldExpander
|
|
35
|
+
def render(object, fields, context: Attributor::DEFAULT_ROOT_CONTEXT)
|
|
36
|
+
if object.is_a? Praxis::Blueprint
|
|
37
|
+
@cache[object._cache_key][fields] ||= _render(object, fields, context: context)
|
|
38
|
+
else
|
|
39
|
+
if object.class < Attributor::Collection
|
|
40
|
+
object.each_with_index.collect do |sub_object, i|
|
|
41
|
+
sub_context = context + ["at(#{i})"]
|
|
42
|
+
render(sub_object, fields, context: sub_context)
|
|
43
|
+
end
|
|
44
|
+
else
|
|
45
|
+
_render(object, fields, context: context)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
rescue SystemStackError
|
|
49
|
+
raise CircularRenderingError.new(object, context)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def _render(object, fields, context: Attributor::DEFAULT_ROOT_CONTEXT)
|
|
53
|
+
if fields == true
|
|
54
|
+
return case object
|
|
55
|
+
when Attributor::Dumpable
|
|
56
|
+
object.dump
|
|
57
|
+
else
|
|
58
|
+
object
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
fields.each_with_object({}) do |(key, subfields), hash|
|
|
63
|
+
begin
|
|
64
|
+
value = object._get_attr(key)
|
|
65
|
+
rescue => e
|
|
66
|
+
raise Attributor::DumpError.new(context: context, name: key, type: object.class, original_exception: e)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
if value.nil?
|
|
70
|
+
hash[key] = nil if self.include_nil
|
|
71
|
+
next
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
if subfields == true
|
|
75
|
+
hash[key] = case value
|
|
76
|
+
when Attributor::Dumpable
|
|
77
|
+
value.dump
|
|
78
|
+
else
|
|
79
|
+
value
|
|
80
|
+
end
|
|
81
|
+
else
|
|
82
|
+
new_context = context + [key]
|
|
83
|
+
hash[key] = render(value, subfields, context: new_context)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|