praxis 2.0.pre.17 → 2.0.pre.21
Sign up to get free protection for your applications and to get access to all the features.
- 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
|