praxis 2.0.pre.17 → 2.0.pre.21
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/.rubocop.yml +54 -0
- data/.simplecov +3 -1
- data/.travis.yml +2 -1
- data/CHANGELOG.md +19 -0
- data/CONTRIBUTING.md +2 -79
- data/Gemfile +5 -1
- data/Guardfile +6 -4
- data/LICENSE +0 -2
- data/MAINTAINERS.md +1 -0
- data/README.md +15 -22
- data/Rakefile +4 -2
- data/bin/praxis +55 -58
- data/lib/praxis/action_definition/headers_dsl_compiler.rb +5 -6
- data/lib/praxis/action_definition.rb +65 -95
- data/lib/praxis/api_definition.rb +21 -29
- data/lib/praxis/api_general_info.rb +55 -66
- data/lib/praxis/application.rb +15 -32
- data/lib/praxis/blueprint.rb +80 -73
- data/lib/praxis/bootloader.rb +24 -33
- data/lib/praxis/bootloader_stages/environment.rb +5 -10
- data/lib/praxis/bootloader_stages/file_loader.rb +3 -6
- data/lib/praxis/bootloader_stages/plugin_config_load.rb +4 -6
- data/lib/praxis/bootloader_stages/plugin_config_prepare.rb +2 -2
- data/lib/praxis/bootloader_stages/plugin_loader.rb +3 -7
- data/lib/praxis/bootloader_stages/plugin_setup.rb +3 -3
- data/lib/praxis/bootloader_stages/routing.rb +5 -8
- data/lib/praxis/bootloader_stages/subgroup_loader.rb +2 -10
- data/lib/praxis/bootloader_stages/warn_unloaded_files.rb +15 -19
- data/lib/praxis/callbacks.rb +12 -11
- data/lib/praxis/collection.rb +11 -14
- data/lib/praxis/config.rb +17 -28
- data/lib/praxis/config_hash.rb +2 -1
- data/lib/praxis/controller.rb +7 -6
- data/lib/praxis/dispatcher.rb +34 -42
- data/lib/praxis/docs/open_api/info_object.rb +11 -8
- data/lib/praxis/docs/open_api/media_type_object.rb +18 -17
- data/lib/praxis/docs/open_api/operation_object.rb +7 -4
- data/lib/praxis/docs/open_api/parameter_object.rb +17 -14
- data/lib/praxis/docs/open_api/paths_object.rb +11 -9
- data/lib/praxis/docs/open_api/request_body_object.rb +14 -13
- data/lib/praxis/docs/open_api/response_object.rb +24 -18
- data/lib/praxis/docs/open_api/responses_object.rb +3 -1
- data/lib/praxis/docs/open_api/schema_object.rb +61 -29
- data/lib/praxis/docs/open_api/server_object.rb +5 -2
- data/lib/praxis/docs/open_api/tag_object.rb +9 -6
- data/lib/praxis/docs/open_api_generator.rb +114 -150
- data/lib/praxis/endpoint_definition.rb +60 -77
- data/lib/praxis/error_handler.rb +2 -2
- data/lib/praxis/exception.rb +2 -0
- data/lib/praxis/exceptions/config.rb +3 -1
- data/lib/praxis/exceptions/config_load.rb +2 -0
- data/lib/praxis/exceptions/config_validation.rb +3 -1
- data/lib/praxis/exceptions/invalid_configuration.rb +3 -1
- data/lib/praxis/exceptions/invalid_response.rb +3 -1
- data/lib/praxis/exceptions/invalid_trait.rb +3 -1
- data/lib/praxis/exceptions/stage_not_found.rb +3 -1
- data/lib/praxis/exceptions/validation.rb +4 -3
- data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +163 -149
- data/lib/praxis/extensions/attribute_filtering/active_record_patches/5x.rb +18 -13
- data/lib/praxis/extensions/attribute_filtering/active_record_patches/6_0.rb +13 -9
- data/lib/praxis/extensions/attribute_filtering/active_record_patches/6_1_plus.rb +14 -11
- data/lib/praxis/extensions/attribute_filtering/active_record_patches.rb +12 -9
- data/lib/praxis/extensions/attribute_filtering/filter_tree_node.rb +8 -5
- data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +89 -65
- data/lib/praxis/extensions/attribute_filtering/filters_parser.rb +68 -62
- data/lib/praxis/extensions/attribute_filtering.rb +3 -1
- data/lib/praxis/extensions/field_expansion.rb +6 -4
- data/lib/praxis/extensions/field_selection/active_record_query_selector.rb +10 -8
- data/lib/praxis/extensions/field_selection/field_selector.rb +91 -92
- data/lib/praxis/extensions/field_selection/sequel_query_selector.rb +12 -12
- data/lib/praxis/extensions/field_selection.rb +3 -1
- data/lib/praxis/extensions/pagination/active_record_pagination_handler.rb +6 -4
- data/lib/praxis/extensions/pagination/header_generator.rb +16 -11
- data/lib/praxis/extensions/pagination/ordering_params.rb +29 -28
- data/lib/praxis/extensions/pagination/pagination_handler.rb +44 -42
- data/lib/praxis/extensions/pagination/pagination_params.rb +29 -48
- data/lib/praxis/extensions/pagination/sequel_pagination_handler.rb +8 -7
- data/lib/praxis/extensions/pagination.rb +10 -15
- data/lib/praxis/extensions/rails_compat/request_methods.rb +3 -4
- data/lib/praxis/extensions/rails_compat.rb +2 -0
- data/lib/praxis/extensions/rendering.rb +12 -12
- data/lib/praxis/field_expander.rb +8 -9
- data/lib/praxis/file_group.rb +8 -12
- data/lib/praxis/finalizable.rb +1 -0
- data/lib/praxis/handlers/json.rb +5 -2
- data/lib/praxis/handlers/plain.rb +2 -1
- data/lib/praxis/handlers/www_form.rb +6 -3
- data/lib/praxis/handlers/{xml-sample.rb → xml_sample.rb} +26 -22
- data/lib/praxis/mapper/active_model_compat.rb +13 -10
- data/lib/praxis/mapper/resource.rb +196 -181
- data/lib/praxis/mapper/selector_generator.rb +106 -112
- data/lib/praxis/mapper/sequel_compat.rb +70 -67
- data/lib/praxis/media_type.rb +2 -2
- data/lib/praxis/media_type_identifier.rb +26 -22
- data/lib/praxis/middleware_app.rb +18 -15
- data/lib/praxis/multipart/parser.rb +46 -51
- data/lib/praxis/multipart/part.rb +78 -110
- data/lib/praxis/notifications.rb +2 -4
- data/lib/praxis/plugin.rb +11 -18
- data/lib/praxis/plugin_concern.rb +12 -15
- data/lib/praxis/plugins/mapper_plugin.rb +15 -13
- data/lib/praxis/plugins/pagination_plugin.rb +8 -6
- data/lib/praxis/plugins/rails_plugin.rb +33 -28
- data/lib/praxis/renderer.rb +11 -15
- data/lib/praxis/request.rb +48 -44
- data/lib/praxis/request_stages/action.rb +4 -6
- data/lib/praxis/request_stages/load_request.rb +2 -4
- data/lib/praxis/request_stages/request_stage.rb +19 -23
- data/lib/praxis/request_stages/response.rb +4 -6
- data/lib/praxis/request_stages/validate.rb +3 -5
- data/lib/praxis/request_stages/validate_params_and_headers.rb +15 -22
- data/lib/praxis/request_stages/validate_payload.rb +25 -28
- data/lib/praxis/request_superclassing.rb +3 -3
- data/lib/praxis/resource_definition.rb +1 -0
- data/lib/praxis/response.rb +24 -26
- data/lib/praxis/response_definition.rb +77 -122
- data/lib/praxis/response_template.rb +11 -15
- data/lib/praxis/responses/http.rb +23 -44
- data/lib/praxis/responses/internal_server_error.rb +18 -21
- data/lib/praxis/responses/multipart_ok.rb +4 -9
- data/lib/praxis/responses/validation_error.rb +8 -15
- data/lib/praxis/route.rb +8 -10
- data/lib/praxis/router/rack.rb +13 -7
- data/lib/praxis/router/simple.rb +10 -5
- data/lib/praxis/router.rb +27 -34
- data/lib/praxis/routing_config.rb +52 -29
- data/lib/praxis/simple_media_type.rb +5 -8
- data/lib/praxis/stage.rb +17 -25
- data/lib/praxis/tasks/api_docs.rb +17 -16
- data/lib/praxis/tasks/console.rb +3 -1
- data/lib/praxis/tasks/environment.rb +2 -0
- data/lib/praxis/tasks/routes.rb +26 -24
- data/lib/praxis/tasks.rb +3 -1
- data/lib/praxis/trait.rb +37 -46
- data/lib/praxis/types/fuzzy_hash.rb +13 -14
- data/lib/praxis/types/media_type_common.rb +11 -10
- data/lib/praxis/types/multipart_array/part_definition.rb +14 -17
- data/lib/praxis/types/multipart_array.rb +100 -115
- data/lib/praxis/validation_handler.rb +5 -3
- data/lib/praxis/version.rb +3 -1
- data/lib/praxis.rb +4 -5
- data/praxis.gemspec +22 -21
- data/spec/functional_spec.rb +44 -56
- data/spec/praxis/action_definition_spec.rb +39 -48
- data/spec/praxis/api_definition_spec.rb +45 -47
- data/spec/praxis/api_general_info_spec.rb +28 -29
- data/spec/praxis/application_spec.rb +18 -14
- data/spec/praxis/blueprint_spec.rb +33 -34
- data/spec/praxis/bootloader_spec.rb +32 -30
- data/spec/praxis/callbacks_spec.rb +37 -37
- data/spec/praxis/collection_spec.rb +18 -25
- data/spec/praxis/config_hash_spec.rb +5 -4
- data/spec/praxis/config_spec.rb +27 -26
- data/spec/praxis/controller_spec.rb +8 -9
- data/spec/praxis/endpoint_definition_spec.rb +25 -32
- data/spec/praxis/extensions/attribute_filtering/active_record_filter_query_builder_spec.rb +171 -114
- data/spec/praxis/extensions/attribute_filtering/filter_tree_node_spec.rb +22 -21
- data/spec/praxis/extensions/attribute_filtering/filtering_params_spec.rb +112 -60
- data/spec/praxis/extensions/attribute_filtering/filters_parser_spec.rb +37 -38
- data/spec/praxis/extensions/field_expansion_spec.rb +8 -10
- data/spec/praxis/extensions/field_selection/active_record_query_selector_spec.rb +14 -13
- data/spec/praxis/extensions/field_selection/field_selector_spec.rb +9 -16
- data/spec/praxis/extensions/field_selection/sequel_query_selector_spec.rb +50 -49
- data/spec/praxis/extensions/pagination/active_record_pagination_handler_spec.rb +32 -31
- data/spec/praxis/extensions/rendering_spec.rb +9 -9
- data/spec/praxis/extensions/support/spec_resources_active_model.rb +32 -49
- data/spec/praxis/extensions/support/spec_resources_sequel.rb +48 -48
- data/spec/praxis/field_expander_spec.rb +6 -5
- data/spec/praxis/file_group_spec.rb +3 -1
- data/spec/praxis/handlers/json_spec.rb +6 -5
- data/spec/praxis/mapper/resource_spec.rb +39 -29
- data/spec/praxis/mapper/selector_generator_spec.rb +80 -46
- data/spec/praxis/media_type_identifier_spec.rb +13 -10
- data/spec/praxis/media_type_spec.rb +12 -12
- data/spec/praxis/middleware_app_spec.rb +23 -22
- data/spec/praxis/multipart/parser_spec.rb +7 -9
- data/spec/praxis/notifications_spec.rb +4 -4
- data/spec/praxis/plugin_concern_spec.rb +5 -6
- data/spec/praxis/renderer_spec.rb +10 -9
- data/spec/praxis/request_spec.rb +38 -41
- data/spec/praxis/request_stages/action_spec.rb +14 -15
- data/spec/praxis/request_stages/request_stage_spec.rb +30 -41
- data/spec/praxis/request_stages/validate_spec.rb +3 -1
- data/spec/praxis/response_definition_spec.rb +79 -92
- data/spec/praxis/response_spec.rb +35 -40
- data/spec/praxis/responses/internal_server_error_spec.rb +6 -9
- data/spec/praxis/responses/validation_error_spec.rb +17 -18
- data/spec/praxis/route_spec.rb +4 -7
- data/spec/praxis/router_spec.rb +69 -79
- data/spec/praxis/routing_config_spec.rb +15 -14
- data/spec/praxis/stage_spec.rb +56 -53
- data/spec/praxis/trait_spec.rb +17 -17
- data/spec/praxis/types/fuzzy_hash_spec.rb +11 -9
- data/spec/praxis/types/multipart_array/part_definition_spec.rb +3 -2
- data/spec/praxis/types/multipart_array_spec.rb +33 -48
- data/spec/spec_app/app/concerns/authenticated.rb +5 -5
- data/spec/spec_app/app/concerns/basic_api.rb +3 -1
- data/spec/spec_app/app/concerns/log_wrapper.rb +5 -3
- data/spec/spec_app/app/controllers/base_class.rb +6 -5
- data/spec/spec_app/app/controllers/instances.rb +31 -34
- data/spec/spec_app/app/controllers/volumes.rb +6 -6
- data/spec/spec_app/app/responses/multipart.rb +1 -2
- data/spec/spec_app/app/responses/other_response.rb +2 -2
- data/spec/spec_app/config/environment.rb +19 -6
- data/spec/spec_app/config.ru +4 -3
- data/spec/spec_app/design/api.rb +13 -15
- data/spec/spec_app/design/media_types/instance.rb +6 -6
- data/spec/spec_app/design/media_types/volume.rb +2 -1
- data/spec/spec_app/design/media_types/volume_snapshot.rb +2 -1
- data/spec/spec_app/design/resources/instances.rb +11 -17
- data/spec/spec_app/design/resources/volume_snapshots.rb +4 -5
- data/spec/spec_app/design/resources/volumes.rb +4 -5
- data/spec/spec_helper.rb +11 -13
- data/spec/support/be_deep_equal_matcher.rb +5 -0
- data/spec/support/spec_authorization_plugin.rb +7 -12
- data/spec/support/spec_blueprints.rb +5 -4
- data/spec/support/spec_complex_authentication_plugin.rb +17 -34
- data/spec/support/spec_endpoint_definitions.rb +2 -3
- data/spec/support/spec_media_types.rb +28 -35
- data/spec/support/spec_resources.rb +22 -16
- data/spec/support/spec_simple_authentication_plugin.rb +5 -9
- data/tasks/loader.thor +4 -2
- data/tasks/thor/app.rb +7 -5
- data/tasks/thor/example.rb +23 -22
- data/tasks/thor/model.rb +7 -7
- data/tasks/thor/scaffold.rb +23 -23
- data/tasks/thor/templates/generator/example_app/app/v1/resources/user.rb +0 -8
- data/tasks/thor/templates/generator/scaffold/implementation/resources/item.rb +1 -2
- metadata +72 -84
- data/MAINTAINERS +0 -2
- data/TODO.md +0 -25
- data/spec/praxis/api_resource_spec.rb +0 -0
- data/spec/praxis/dispatcher_spec.rb +0 -0
- data/spec/spec_app/app/responses/bulk_response.rb +0 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# This is an example of a handler that can load and generate 'activesupport-style' xml payloads.
|
|
2
|
-
# Note that if you use your API to pass nil values for attributes as a way to unset their values,
|
|
3
|
-
# this handler will not work (as there isn't necessarily a defined "null" value in this encoding
|
|
4
|
+
# Note that if you use your API to pass nil values for attributes as a way to unset their values,
|
|
5
|
+
# this handler will not work (as there isn't necessarily a defined "null" value in this encoding
|
|
4
6
|
# (although you can probably define how to encode/decode it and use it as such)
|
|
5
7
|
# Use at your own risk
|
|
6
8
|
|
|
@@ -17,7 +19,7 @@ module Praxis
|
|
|
17
19
|
ActiveSupport::XmlMini.backend = 'Nokogiri'
|
|
18
20
|
rescue LoadError
|
|
19
21
|
raise Praxis::Exceptions::InvalidConfiguration,
|
|
20
|
-
|
|
22
|
+
'XML handler depends on builder ~> 3.2 and nokogiri ~> 1.6; please add them to your Gemfile'
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
# Parse an XML document into structured data.
|
|
@@ -46,35 +48,37 @@ module Praxis
|
|
|
46
48
|
|
|
47
49
|
case type
|
|
48
50
|
when nil
|
|
49
|
-
if (node.children.size == 1 && node.child.text?) || node.children.size
|
|
51
|
+
if (node.children.size == 1 && node.child.text?) || node.children.size.zero?
|
|
50
52
|
# leaf text node
|
|
51
|
-
|
|
53
|
+
node.content
|
|
52
54
|
else
|
|
53
55
|
# A hash
|
|
54
|
-
|
|
56
|
+
node.children.each_with_object({}) do |child, hash|
|
|
55
57
|
next unless child.element? # There might be text fragments like newlines...spaces
|
|
58
|
+
|
|
56
59
|
hash[child.name.underscore] = process(child, child.attributes['type'])
|
|
57
60
|
end
|
|
58
61
|
end
|
|
59
|
-
when
|
|
60
|
-
|
|
62
|
+
when 'array'
|
|
63
|
+
node.children.each_with_object([]) do |child, arr|
|
|
61
64
|
next unless child.element? # There might be text fragments like newlines...spaces
|
|
65
|
+
|
|
62
66
|
arr << process(child, child.attributes['type'])
|
|
63
67
|
end
|
|
64
|
-
when
|
|
65
|
-
|
|
66
|
-
when
|
|
67
|
-
|
|
68
|
-
when
|
|
69
|
-
|
|
70
|
-
when
|
|
71
|
-
|
|
72
|
-
when
|
|
73
|
-
|
|
74
|
-
when
|
|
75
|
-
|
|
76
|
-
when
|
|
77
|
-
|
|
68
|
+
when 'integer'
|
|
69
|
+
Integer(node.content)
|
|
70
|
+
when 'symbol'
|
|
71
|
+
node.content.to_sym
|
|
72
|
+
when 'decimal'
|
|
73
|
+
BigDecimal(node.content)
|
|
74
|
+
when 'float'
|
|
75
|
+
Float(node.content)
|
|
76
|
+
when 'boolean'
|
|
77
|
+
node.content != 'false'
|
|
78
|
+
when 'date'
|
|
79
|
+
Date.parse(node.content)
|
|
80
|
+
when 'dateTime'
|
|
81
|
+
DateTime.parse(node.content)
|
|
78
82
|
else
|
|
79
83
|
raise ArgumentError, "Unknown attribute type: #{type}"
|
|
80
84
|
end
|
|
@@ -28,7 +28,7 @@ module Praxis
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def _praxis_associations
|
|
31
|
-
orig =
|
|
31
|
+
orig = reflections.clone
|
|
32
32
|
|
|
33
33
|
orig.each_with_object({}) do |(k, v), hash|
|
|
34
34
|
# Assume an 'id' primary key if the system is initializing without AR connected
|
|
@@ -51,7 +51,7 @@ module Praxis
|
|
|
51
51
|
else
|
|
52
52
|
raise "Unknown association type: #{v.class.name} on #{v.klass.name} for #{v.name}"
|
|
53
53
|
end
|
|
54
|
-
# Call out any local (i.e., of this model) columns that participate in the association
|
|
54
|
+
# Call out any local (i.e., of this model) columns that participate in the association
|
|
55
55
|
info[:local_key_columns] = local_columns_used_for_the_association(info[:type], v)
|
|
56
56
|
info[:remote_key_columns] = remote_columns_used_for_the_association(info[:type], v)
|
|
57
57
|
|
|
@@ -63,8 +63,8 @@ module Praxis
|
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
def _join_foreign_key_for(assoc_reflection)
|
|
66
|
-
maj, min,
|
|
67
|
-
if maj >= 6 && min >=1
|
|
66
|
+
maj, min, = ActiveRecord.gem_version.segments
|
|
67
|
+
if maj >= 6 && min >= 1
|
|
68
68
|
assoc_reflection.join_foreign_key.to_sym
|
|
69
69
|
else
|
|
70
70
|
assoc_reflection.join_keys.foreign_key.to_sym
|
|
@@ -72,14 +72,16 @@ module Praxis
|
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
def _join_primary_key_for(assoc_reflection)
|
|
75
|
-
maj, min,
|
|
76
|
-
if maj >= 6 && min >=1
|
|
75
|
+
maj, min, = ActiveRecord.gem_version.segments
|
|
76
|
+
if maj >= 6 && min >= 1
|
|
77
77
|
assoc_reflection.join_primary_key.to_sym
|
|
78
78
|
else
|
|
79
79
|
assoc_reflection.join_keys.key.to_sym
|
|
80
80
|
end
|
|
81
81
|
end
|
|
82
|
+
|
|
82
83
|
private
|
|
84
|
+
|
|
83
85
|
def local_columns_used_for_the_association(type, assoc_reflection)
|
|
84
86
|
case type
|
|
85
87
|
when :one_to_many
|
|
@@ -92,7 +94,7 @@ module Praxis
|
|
|
92
94
|
ref = resolve_closest_through_reflection(assoc_reflection)
|
|
93
95
|
# The associated middle table will point to us by key (usually the PK, but not always)
|
|
94
96
|
[_join_foreign_key_for(ref)] # The foreign key that the last through table points to
|
|
95
|
-
else
|
|
97
|
+
else
|
|
96
98
|
raise "association type #{type} not supported"
|
|
97
99
|
end
|
|
98
100
|
end
|
|
@@ -103,16 +105,17 @@ module Praxis
|
|
|
103
105
|
case type
|
|
104
106
|
when :one_to_many, :many_to_one, :many_to_many
|
|
105
107
|
[_join_primary_key_for(assoc_reflection)]
|
|
106
|
-
else
|
|
108
|
+
else
|
|
107
109
|
raise "association type #{type} not supported"
|
|
108
110
|
end
|
|
109
111
|
end
|
|
110
|
-
|
|
112
|
+
|
|
111
113
|
# Keep following the association reflections as long as there are middle ones (i.e., through)
|
|
112
114
|
# until we come to the one next to the source
|
|
113
115
|
def resolve_closest_through_reflection(ref)
|
|
114
116
|
return ref unless ref.through_reflection?
|
|
115
|
-
|
|
117
|
+
|
|
118
|
+
resolve_closest_through_reflection(ref.through_reflection)
|
|
116
119
|
end
|
|
117
120
|
end
|
|
118
121
|
end
|
|
@@ -1,176 +1,178 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# A resource creates a data store and instantiates a list of models that it wishes to load, building up the overall set of data that it will need.
|
|
2
4
|
# Once that is complete, the data set is iterated and a resultant view is generated.
|
|
3
|
-
module Praxis
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
module Praxis
|
|
6
|
+
module Mapper
|
|
7
|
+
class Resource
|
|
8
|
+
extend Praxis::Finalizable
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
attr_accessor :record
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
@properties = {}
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
class << self
|
|
15
|
+
attr_reader :model_map, :properties
|
|
16
|
+
# Names of the memoizable things (without the @__ prefix)
|
|
17
|
+
attr_accessor :memoized_variables
|
|
18
|
+
end
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
klass.instance_eval do
|
|
23
|
-
# It is expected that each versioned set of resources
|
|
24
|
-
# will have a common Base class, and so should share
|
|
25
|
-
# a model_map
|
|
26
|
-
if self.superclass == Praxis::Mapper::Resource
|
|
27
|
-
@model_map = Hash.new
|
|
28
|
-
else
|
|
29
|
-
@model_map = self.superclass.model_map
|
|
30
|
-
end
|
|
20
|
+
# TODO: also support an attribute of sorts on the versioned resource module. ie, V1::Resources.api_version.
|
|
21
|
+
# replacing the self.superclass == Praxis::Mapper::Resource condition below.
|
|
22
|
+
def self.inherited(klass)
|
|
23
|
+
super
|
|
31
24
|
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
klass.instance_eval do
|
|
26
|
+
# It is expected that each versioned set of resources
|
|
27
|
+
# will have a common Base class, and so should share
|
|
28
|
+
# a model_map
|
|
29
|
+
@model_map = if superclass == Praxis::Mapper::Resource
|
|
30
|
+
{}
|
|
31
|
+
else
|
|
32
|
+
superclass.model_map
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
@properties = superclass.properties.clone
|
|
36
|
+
@_filters_map = {}
|
|
37
|
+
@memoized_variables = []
|
|
38
|
+
end
|
|
34
39
|
end
|
|
35
40
|
|
|
36
|
-
|
|
41
|
+
# TODO: Take symbol/string and resolve the klass (but lazily, so we don't care about load order)
|
|
42
|
+
def self.model(klass = nil)
|
|
43
|
+
if klass
|
|
44
|
+
raise "Model #{klass.name} must be compatible with Praxis. Use ActiveModelCompat or similar compatability plugin." unless klass.methods.include?(:_praxis_associations)
|
|
37
45
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
self.model_map[klass] = self
|
|
44
|
-
else
|
|
45
|
-
@model
|
|
46
|
+
@model = klass
|
|
47
|
+
model_map[klass] = self
|
|
48
|
+
else
|
|
49
|
+
@model
|
|
50
|
+
end
|
|
46
51
|
end
|
|
47
|
-
end
|
|
48
52
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
def self.property(name, dependencies: nil, through: nil)
|
|
54
|
+
properties[name] = { dependencies: dependencies, through: through }
|
|
55
|
+
end
|
|
52
56
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
def self._finalize!
|
|
58
|
+
finalize_resource_delegates
|
|
59
|
+
define_model_accessors
|
|
56
60
|
|
|
57
|
-
|
|
58
|
-
|
|
61
|
+
super
|
|
62
|
+
end
|
|
59
63
|
|
|
60
|
-
|
|
61
|
-
|
|
64
|
+
def self.finalize_resource_delegates
|
|
65
|
+
return unless @resource_delegates
|
|
62
66
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
@resource_delegates.each do |record_name, record_attributes|
|
|
68
|
+
record_attributes.each do |record_attribute|
|
|
69
|
+
define_resource_delegate(record_name, record_attribute)
|
|
70
|
+
end
|
|
66
71
|
end
|
|
67
72
|
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
73
|
|
|
71
|
-
|
|
72
|
-
|
|
74
|
+
def self.define_model_accessors
|
|
75
|
+
return if model.nil?
|
|
73
76
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
define_model_association_accessor(k,v)
|
|
77
|
+
model._praxis_associations.each do |k, v|
|
|
78
|
+
define_model_association_accessor(k, v) unless instance_methods.include? k
|
|
77
79
|
end
|
|
78
80
|
end
|
|
79
|
-
end
|
|
80
81
|
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
def self.for_record(record)
|
|
83
|
+
return record._resource if record._resource
|
|
83
84
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
if (resource_class_for_record = model_map[record.class])
|
|
86
|
+
record._resource = resource_class_for_record.new(record)
|
|
87
|
+
else
|
|
88
|
+
version = name.split('::')[0..-2].join('::')
|
|
89
|
+
resource_name = record.class.name.split('::').last
|
|
89
90
|
|
|
90
|
-
|
|
91
|
+
raise "No resource class corresponding to the model class '#{record.class}' is defined. (Did you forget to define '#{version}::#{resource_name}'?)"
|
|
92
|
+
end
|
|
91
93
|
end
|
|
92
|
-
end
|
|
93
|
-
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
95
|
+
def self.wrap(records)
|
|
96
|
+
if records.nil?
|
|
97
|
+
[]
|
|
98
|
+
elsif records.is_a?(Enumerable)
|
|
99
|
+
records.compact.map { |record| for_record(record) }
|
|
100
|
+
elsif records.respond_to?(:to_a)
|
|
101
|
+
records.to_a.compact.map { |record| for_record(record) }
|
|
102
|
+
else
|
|
103
|
+
for_record(records)
|
|
104
|
+
end
|
|
104
105
|
end
|
|
105
|
-
end
|
|
106
|
-
|
|
107
106
|
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
def self.get(condition)
|
|
108
|
+
record = model.get(condition)
|
|
110
109
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def self.all(condition={})
|
|
115
|
-
records = self.model.all(condition)
|
|
110
|
+
wrap(record)
|
|
111
|
+
end
|
|
116
112
|
|
|
117
|
-
self.
|
|
118
|
-
|
|
113
|
+
def self.all(condition = {})
|
|
114
|
+
records = model.all(condition)
|
|
119
115
|
|
|
116
|
+
wrap(records)
|
|
117
|
+
end
|
|
120
118
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
119
|
+
def self.resource_delegates
|
|
120
|
+
@resource_delegates ||= {}
|
|
121
|
+
end
|
|
124
122
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
123
|
+
def self.resource_delegate(spec)
|
|
124
|
+
spec.each do |resource_name, attributes|
|
|
125
|
+
resource_delegates[resource_name] = attributes
|
|
126
|
+
end
|
|
128
127
|
end
|
|
129
|
-
end
|
|
130
128
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
129
|
+
# Defines wrappers for model associations that return Resources
|
|
130
|
+
def self.define_model_association_accessor(name, association_spec)
|
|
131
|
+
association_model = association_spec.fetch(:model)
|
|
132
|
+
association_resource_class = model_map[association_model]
|
|
133
|
+
|
|
134
|
+
return unless association_resource_class
|
|
135
135
|
|
|
136
|
-
|
|
136
|
+
memoized_variables << name
|
|
137
137
|
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
138
138
|
def #{name}
|
|
139
|
+
return @__#{name} if instance_variable_defined?("@__#{name}")
|
|
140
|
+
|
|
139
141
|
records = record.#{name}
|
|
140
|
-
|
|
142
|
+
return nil if records.nil?
|
|
141
143
|
@__#{name} ||= #{association_resource_class}.wrap(records)
|
|
142
144
|
end
|
|
143
145
|
RUBY
|
|
144
146
|
end
|
|
145
|
-
end
|
|
146
147
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
148
|
+
def self.define_resource_delegate(resource_name, resource_attribute)
|
|
149
|
+
related_model = model._praxis_associations[resource_name][:model]
|
|
150
|
+
related_association = related_model._praxis_associations[resource_attribute]
|
|
150
151
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
152
|
+
if related_association
|
|
153
|
+
define_delegation_for_related_association(resource_name, resource_attribute, related_association)
|
|
154
|
+
else
|
|
155
|
+
define_delegation_for_related_attribute(resource_name, resource_attribute)
|
|
156
|
+
end
|
|
155
157
|
end
|
|
156
|
-
end
|
|
157
|
-
|
|
158
158
|
|
|
159
|
-
|
|
160
|
-
|
|
159
|
+
def self.define_delegation_for_related_attribute(resource_name, resource_attribute)
|
|
160
|
+
memoized_variables << resource_attribute
|
|
161
|
+
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
161
162
|
def #{resource_attribute}
|
|
162
163
|
@__#{resource_attribute} ||= if (rec = self.#{resource_name})
|
|
163
164
|
rec.#{resource_attribute}
|
|
164
165
|
end
|
|
165
166
|
end
|
|
166
|
-
|
|
167
|
-
|
|
167
|
+
RUBY
|
|
168
|
+
end
|
|
168
169
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
170
|
+
def self.define_delegation_for_related_association(resource_name, resource_attribute, related_association)
|
|
171
|
+
related_resource_class = model_map[related_association[:model]]
|
|
172
|
+
return unless related_resource_class
|
|
172
173
|
|
|
173
|
-
|
|
174
|
+
memoized_variables << resource_attribute
|
|
175
|
+
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
174
176
|
def #{resource_attribute}
|
|
175
177
|
@__#{resource_attribute} ||= if (rec = self.#{resource_name})
|
|
176
178
|
if (related = rec.#{resource_attribute})
|
|
@@ -178,88 +180,101 @@ module Praxis::Mapper
|
|
|
178
180
|
end
|
|
179
181
|
end
|
|
180
182
|
end
|
|
181
|
-
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
def self.define_accessor(name)
|
|
185
|
-
if name.to_s =~ /\?/
|
|
186
|
-
ivar_name = "is_#{name.to_s[0..-2]}"
|
|
187
|
-
else
|
|
188
|
-
ivar_name = "#{name}"
|
|
183
|
+
RUBY
|
|
189
184
|
end
|
|
190
185
|
|
|
191
|
-
|
|
186
|
+
def self.define_accessor(name)
|
|
187
|
+
ivar_name = if name.to_s =~ /\?/
|
|
188
|
+
"is_#{name.to_s[0..-2]}"
|
|
189
|
+
else
|
|
190
|
+
name.to_s
|
|
191
|
+
end
|
|
192
|
+
memoized_variables << ivar_name
|
|
193
|
+
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
192
194
|
def #{name}
|
|
193
|
-
return @__#{ivar_name} if
|
|
195
|
+
return @__#{ivar_name} if instance_variable_defined?("@__#{ivar_name}")
|
|
194
196
|
@__#{ivar_name} = record.#{name}
|
|
195
197
|
end
|
|
196
|
-
|
|
197
|
-
|
|
198
|
+
RUBY
|
|
199
|
+
end
|
|
198
200
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
201
|
+
# TODO: this shouldn't be needed if we incorporate it with the properties of the mapper...
|
|
202
|
+
# ...maybe what this means is that we can change it for a better DSL in the resource?
|
|
203
|
+
def self.filters_mapping(definition = {})
|
|
204
|
+
@_filters_map = \
|
|
205
|
+
case definition
|
|
206
|
+
when Hash
|
|
207
|
+
definition
|
|
208
|
+
when Array
|
|
209
|
+
definition.each_with_object({}) { |item, hash| hash[item.to_sym] = item }
|
|
210
|
+
else
|
|
211
|
+
raise 'Resource.filters_mapping only allows a hash or an array'
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def self.craft_filter_query(base_query, filters:)
|
|
216
|
+
if filters
|
|
217
|
+
raise "To use API filtering, you must define the mapping of api-names to resource properties (using the `filters_mapping` method in #{self})" unless @_filters_map
|
|
218
|
+
|
|
219
|
+
debug = Praxis::Application.instance.config.mapper.debug_queries
|
|
220
|
+
base_query = model._filter_query_builder_class.new(query: base_query, model: model, filters_map: @_filters_map, debug: debug).generate(filters)
|
|
210
221
|
end
|
|
211
|
-
end
|
|
212
222
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
223
|
+
base_query
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def self.craft_field_selection_query(base_query, selectors:)
|
|
227
|
+
if selectors && model._field_selector_query_builder_class
|
|
228
|
+
debug = Praxis::Application.instance.config.mapper.debug_queries
|
|
229
|
+
base_query = model._field_selector_query_builder_class.new(query: base_query, selectors: selectors, debug: debug).generate
|
|
217
230
|
end
|
|
218
|
-
|
|
219
|
-
base_query
|
|
231
|
+
|
|
232
|
+
base_query
|
|
220
233
|
end
|
|
221
|
-
|
|
222
|
-
base_query
|
|
223
|
-
end
|
|
224
234
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
235
|
+
def self.craft_pagination_query(base_query, pagination:)
|
|
236
|
+
handler_klass = model._pagination_query_builder_class
|
|
237
|
+
return base_query unless handler_klass && (pagination.paginator || pagination.order)
|
|
238
|
+
|
|
239
|
+
# Gather and save the count if required
|
|
240
|
+
pagination.total_count = handler_klass.count(base_query.dup) if pagination.paginator&.total_count
|
|
241
|
+
|
|
242
|
+
base_query = handler_klass.order(base_query, pagination.order)
|
|
243
|
+
handler_klass.paginate(base_query, pagination)
|
|
229
244
|
end
|
|
230
|
-
|
|
231
|
-
base_query
|
|
232
|
-
end
|
|
233
245
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
246
|
+
def initialize(record)
|
|
247
|
+
@record = record
|
|
248
|
+
end
|
|
237
249
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
250
|
+
def reload
|
|
251
|
+
clear_memoization
|
|
252
|
+
reload_record
|
|
241
253
|
end
|
|
242
|
-
|
|
243
|
-
base_query = handler_klass.order(base_query, pagination.order)
|
|
244
|
-
handler_klass.paginate(base_query, pagination)
|
|
245
|
-
end
|
|
246
254
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
255
|
+
def clear_memoization
|
|
256
|
+
self.class.memoized_variables.each do |name|
|
|
257
|
+
ivar = "@__#{name}"
|
|
258
|
+
remove_instance_variable(ivar) if instance_variable_defined?(ivar)
|
|
259
|
+
end
|
|
260
|
+
end
|
|
250
261
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
262
|
+
def reload_record
|
|
263
|
+
record.reload
|
|
264
|
+
end
|
|
254
265
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
self.class.define_accessor(name)
|
|
258
|
-
self.send(name)
|
|
259
|
-
else
|
|
260
|
-
super
|
|
266
|
+
def respond_to_missing?(name, *)
|
|
267
|
+
@record.respond_to?(name) || super
|
|
261
268
|
end
|
|
262
|
-
end
|
|
263
269
|
|
|
270
|
+
def method_missing(name, *args)
|
|
271
|
+
if @record.respond_to?(name)
|
|
272
|
+
self.class.define_accessor(name)
|
|
273
|
+
send(name)
|
|
274
|
+
else
|
|
275
|
+
super
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
end
|
|
264
279
|
end
|
|
265
280
|
end
|