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,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Praxis
|
|
4
4
|
module Extensions
|
|
@@ -10,13 +10,14 @@ module Praxis
|
|
|
10
10
|
# This is necessary as (the latest AR code):
|
|
11
11
|
# * does not carry over "references" in joins if they are not SqlLiterals
|
|
12
12
|
# * but, at the same time, it indexes the references using the .to_sym value (which is really expected to be the normal string, without quotes)
|
|
13
|
-
# If we pass a normal SqlLiteral, instead of our wrapper, without quoting the table, the current AR code will never quote it to form the
|
|
13
|
+
# If we pass a normal SqlLiteral, instead of our wrapper, without quoting the table, the current AR code will never quote it to form the
|
|
14
14
|
# SQL string, as it's already a literal...so our "/" type separators as names won't work without quoting.
|
|
15
15
|
class QuasiSqlLiteral < Arel::Nodes::SqlLiteral
|
|
16
16
|
def initialize(quoted:, symbolized:)
|
|
17
17
|
@symbolized = symbolized
|
|
18
18
|
super(quoted)
|
|
19
19
|
end
|
|
20
|
+
|
|
20
21
|
def to_sym
|
|
21
22
|
@symbolized
|
|
22
23
|
end
|
|
@@ -27,67 +28,61 @@ module Praxis
|
|
|
27
28
|
attr_reader :model, :filters_map
|
|
28
29
|
|
|
29
30
|
# Base query to build upon
|
|
30
|
-
def initialize(query
|
|
31
|
-
#
|
|
31
|
+
def initialize(query:, model:, filters_map:, debug: false)
|
|
32
|
+
# NOTE: Do not make the initial_query an attr reader to make sure we don't count/leak on modifying it. Easier to mostly use class methods
|
|
32
33
|
@initial_query = query
|
|
33
34
|
@model = model
|
|
34
35
|
@filters_map = filters_map
|
|
35
|
-
@logger = debug ? Logger.new(
|
|
36
|
+
@logger = debug ? Logger.new($stdout) : nil
|
|
36
37
|
@active_record_version_maj = ActiveRecord.gem_version.segments[0]
|
|
37
38
|
end
|
|
38
|
-
|
|
39
|
+
|
|
39
40
|
def debug_query(msg, query)
|
|
40
|
-
@logger
|
|
41
|
+
@logger&.info(msg + query.to_sql)
|
|
41
42
|
end
|
|
42
43
|
|
|
43
44
|
def generate(filters)
|
|
44
45
|
# Resolve the names and values first, based on filters_map
|
|
45
46
|
root_node = _convert_to_treenode(filters)
|
|
46
47
|
crafted = craft_filter_query(root_node, for_model: @model)
|
|
47
|
-
debug_query(
|
|
48
|
+
debug_query('SQL due to filters: ', crafted.all)
|
|
48
49
|
crafted
|
|
49
50
|
end
|
|
50
51
|
|
|
51
52
|
def craft_filter_query(nodetree, for_model:)
|
|
52
53
|
result = _compute_joins_and_conditions_data(nodetree, model: for_model, parent_reflection: nil)
|
|
53
54
|
return @initial_query if result[:conditions].empty?
|
|
54
|
-
|
|
55
|
+
|
|
55
56
|
# Find the root group (usually an AND group) but can be an OR group, or nil if there's only 1 condition
|
|
56
57
|
root_parent_group = result[:conditions].first[:node_object].parent_group || result[:conditions].first[:node_object]
|
|
57
|
-
|
|
58
|
-
root_parent_group = root_parent_group.parent_group
|
|
59
|
-
end
|
|
58
|
+
root_parent_group = root_parent_group.parent_group until root_parent_group.parent_group.nil?
|
|
60
59
|
|
|
61
60
|
# Process the joins
|
|
62
61
|
query_with_joins = result[:associations_hash].empty? ? @initial_query : @initial_query.left_outer_joins(result[:associations_hash])
|
|
63
62
|
|
|
64
63
|
# Proc to apply a single condition
|
|
65
|
-
apply_single_condition =
|
|
64
|
+
apply_single_condition = proc do |condition, associated_query|
|
|
66
65
|
colo = condition[:model].columns_hash[condition[:name].to_s]
|
|
67
66
|
column_prefix = condition[:column_prefix]
|
|
68
67
|
association_key_column = \
|
|
69
|
-
if ref = condition[:parent_reflection]
|
|
68
|
+
if (ref = condition[:parent_reflection])
|
|
70
69
|
# get the target model of the association(where the assoc pk is)
|
|
71
|
-
target_model =
|
|
72
|
-
target_model.columns_hash[
|
|
73
|
-
else
|
|
74
|
-
nil
|
|
70
|
+
target_model = ref.klass
|
|
71
|
+
target_model.columns_hash[ref.association_primary_key]
|
|
75
72
|
end
|
|
76
73
|
|
|
77
74
|
# Mark where clause referencing the appropriate alias IF it's not the root table, as there is no association to reference
|
|
78
|
-
# If we added root table as a reference, we better make sure it is not quoted, as it actually makes AR to see it as an
|
|
75
|
+
# If we added root table as a reference, we better make sure it is not quoted, as it actually makes AR to see it as an
|
|
79
76
|
# unmatched reference and eager loads the whole association (it means eager load ALL the things). Not good.
|
|
80
|
-
unless for_model.table_name == column_prefix
|
|
81
|
-
associated_query = associated_query.references(build_reference_value(column_prefix, query: associated_query))
|
|
82
|
-
end
|
|
77
|
+
associated_query = associated_query.references(build_reference_value(column_prefix, query: associated_query)) unless for_model.table_name == column_prefix
|
|
83
78
|
self.class.add_clause(
|
|
84
|
-
query: associated_query,
|
|
85
|
-
column_prefix: column_prefix,
|
|
86
|
-
column_object: colo,
|
|
87
|
-
op: condition[:op],
|
|
79
|
+
query: associated_query,
|
|
80
|
+
column_prefix: column_prefix,
|
|
81
|
+
column_object: colo,
|
|
82
|
+
op: condition[:op],
|
|
88
83
|
value: condition[:value],
|
|
89
84
|
fuzzy: condition[:fuzzy],
|
|
90
|
-
association_key_column: association_key_column
|
|
85
|
+
association_key_column: association_key_column
|
|
91
86
|
)
|
|
92
87
|
end
|
|
93
88
|
|
|
@@ -97,7 +92,7 @@ module Praxis
|
|
|
97
92
|
if root_parent_group.is_a?(FilteringParams::Condition)
|
|
98
93
|
# A Single condition it is easy to handle
|
|
99
94
|
apply_single_condition.call(result[:conditions].first, query_with_joins)
|
|
100
|
-
elsif root_parent_group.items.all?{|i| i.is_a?(FilteringParams::Condition)}
|
|
95
|
+
elsif root_parent_group.items.all? { |i| i.is_a?(FilteringParams::Condition) }
|
|
101
96
|
# Only 1 top level root, with only with simple condition items
|
|
102
97
|
if root_parent_group.type == :and
|
|
103
98
|
result[:conditions].reverse.inject(query_with_joins) do |accum, condition|
|
|
@@ -113,115 +108,40 @@ module Praxis
|
|
|
113
108
|
end
|
|
114
109
|
end
|
|
115
110
|
else
|
|
116
|
-
raise
|
|
111
|
+
raise 'Mixing AND and OR conditions is not supported for ActiveRecord <6.'
|
|
117
112
|
end
|
|
118
113
|
else # ActiveRecord 6+
|
|
119
114
|
# Process the conditions in a depth-first order, and return the resulting query
|
|
120
115
|
_depth_first_traversal(
|
|
121
|
-
root_query: query_with_joins,
|
|
122
|
-
root_node: root_parent_group,
|
|
123
|
-
conditions: result[:conditions],
|
|
116
|
+
root_query: query_with_joins,
|
|
117
|
+
root_node: root_parent_group,
|
|
118
|
+
conditions: result[:conditions],
|
|
124
119
|
&apply_single_condition
|
|
125
120
|
)
|
|
126
121
|
end
|
|
127
122
|
end
|
|
128
123
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
#
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
else
|
|
142
|
-
first_query, *rest_queries = root_node.items.map do |child|
|
|
143
|
-
_depth_first_traversal(root_query: root_query, root_node: child, conditions: conditions, &block)
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
rest_queries.each.inject(first_query) do |q, a_query|
|
|
147
|
-
root_node.type == :and ? q.and(a_query) : q.or(a_query)
|
|
148
|
-
end
|
|
149
|
-
end
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
def _mapped_filter(name)
|
|
153
|
-
target = @filters_map[name]
|
|
154
|
-
unless target
|
|
155
|
-
filter_name = name.to_s
|
|
156
|
-
if (@model.attribute_names + @model.reflections.keys).include?(filter_name)
|
|
157
|
-
# Cache it in the filters mapping (to avoid later lookups), and return it.
|
|
158
|
-
@filters_map[name] = name
|
|
159
|
-
target = name
|
|
160
|
-
end
|
|
161
|
-
end
|
|
162
|
-
return target
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
# Resolve and convert from filters, to a more manageable and param-type-independent structure
|
|
166
|
-
def _convert_to_treenode(filters)
|
|
167
|
-
# Resolve the names and values first, based on filters_map
|
|
168
|
-
resolved_array = []
|
|
169
|
-
filters.parsed_array.each do |filter|
|
|
170
|
-
mapped_value = _mapped_filter(filter[:name])
|
|
171
|
-
unless mapped_value
|
|
172
|
-
msg = "Filtering by #{filter[:name]} is not allowed. No implementation mapping defined for it has been found \
|
|
173
|
-
and there is not a model attribute with this name either.\n" \
|
|
174
|
-
"Please add a mapping for #{filter[:name]} in the `filters_mapping` method of the appropriate Resource class"
|
|
175
|
-
raise msg
|
|
176
|
-
end
|
|
177
|
-
bindings_array = \
|
|
178
|
-
if mapped_value.is_a?(Proc)
|
|
179
|
-
result = mapped_value.call(filter)
|
|
180
|
-
# Result could be an array of hashes (each hash has name/op/value to identify a condition)
|
|
181
|
-
result_from_proc = result.is_a?(Array) ? result : [result]
|
|
182
|
-
# Make sure we tack on the node object associated with the filter
|
|
183
|
-
result_from_proc.map{|hash| hash.merge(node_object: filter[:node_object])}
|
|
184
|
-
else
|
|
185
|
-
# For non-procs there's only 1 filter and 1 value (we're just overriding the mapped value)
|
|
186
|
-
[filter.merge( name: mapped_value)]
|
|
187
|
-
end
|
|
188
|
-
resolved_array = resolved_array + bindings_array
|
|
189
|
-
end
|
|
190
|
-
FilterTreeNode.new(resolved_array, path: [ALIAS_TABLE_PREFIX])
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
# Calculate join tree and conditions array for the nodetree object and its children
|
|
194
|
-
def _compute_joins_and_conditions_data(nodetree, model:, parent_reflection:)
|
|
195
|
-
h = {}
|
|
196
|
-
conditions = []
|
|
197
|
-
nodetree.children.each do |name, child|
|
|
198
|
-
child_reflection = model.reflections[name.to_s]
|
|
199
|
-
result = _compute_joins_and_conditions_data(child, model: child_reflection.klass, parent_reflection: child_reflection)
|
|
200
|
-
h[name] = result[:associations_hash]
|
|
201
|
-
|
|
202
|
-
conditions += result[:conditions]
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
column_prefix = nodetree.path == [ALIAS_TABLE_PREFIX] ? model.table_name : nodetree.path.join(REFERENCES_STRING_SEPARATOR)
|
|
206
|
-
nodetree.conditions.each do |condition|
|
|
207
|
-
# If it's a final ! or !! operation on an association from the parent, it means we need to add a condition
|
|
208
|
-
# on the existence (or lack of) of the whole associated table
|
|
209
|
-
ref = model.reflections[condition[:name].to_s]
|
|
210
|
-
if ref && ['!','!!'].include?(condition[:op])
|
|
211
|
-
cp = (nodetree.path + [condition[:name].to_s]).join(REFERENCES_STRING_SEPARATOR)
|
|
212
|
-
conditions += [condition.merge(column_prefix: cp, model: model, parent_reflection: ref)]
|
|
213
|
-
h[condition[:name]] = {}
|
|
214
|
-
else
|
|
215
|
-
# Save the parent reflection where the condition applies as well (used later to get assoc keys)
|
|
216
|
-
conditions += [condition.merge(column_prefix: column_prefix, model: model, parent_reflection: parent_reflection)]
|
|
124
|
+
# not in filters....checks if it's a valid path
|
|
125
|
+
# array of strings
|
|
126
|
+
def self.valid_path?(model, path)
|
|
127
|
+
first_component, *rest = path
|
|
128
|
+
if model.attribute_names.include?(first_component)
|
|
129
|
+
true
|
|
130
|
+
elsif model.reflections.keys.include?(first_component)
|
|
131
|
+
if rest.empty?
|
|
132
|
+
true # Allow associations as a leaf too (as they can have the ! and !! operator)
|
|
133
|
+
else # Follow the association
|
|
134
|
+
nested_model = model.reflections[first_component].klass
|
|
135
|
+
valid_path?(nested_model, rest)
|
|
217
136
|
end
|
|
218
|
-
|
|
137
|
+
else
|
|
138
|
+
false
|
|
219
139
|
end
|
|
220
|
-
{associations_hash: h, conditions: conditions}
|
|
221
140
|
end
|
|
222
141
|
|
|
223
|
-
|
|
224
|
-
|
|
142
|
+
# rubocop:disable Metrics/ParameterLists,Naming/MethodParameterName
|
|
143
|
+
def self.add_clause(query:, column_prefix:, column_object:, op:, value:, fuzzy:, association_key_column:)
|
|
144
|
+
likeval = get_like_value(value, fuzzy)
|
|
225
145
|
|
|
226
146
|
association_op = nil
|
|
227
147
|
case op
|
|
@@ -236,7 +156,7 @@ module Praxis
|
|
|
236
156
|
end
|
|
237
157
|
|
|
238
158
|
if association_op
|
|
239
|
-
neg = association_op == :not_null
|
|
159
|
+
neg = association_op == :not_null
|
|
240
160
|
qr = quote_right_part(query: query, value: nil, column_object: association_key_column, negative: neg)
|
|
241
161
|
return query.where("#{quote_column_path(query: query, prefix: column_prefix, column_object: association_key_column)} #{qr}")
|
|
242
162
|
end
|
|
@@ -278,11 +198,14 @@ module Praxis
|
|
|
278
198
|
raise "Unsupported Operator!!! #{op}"
|
|
279
199
|
end
|
|
280
200
|
end
|
|
201
|
+
# rubocop:enable Metrics/ParameterLists,Naming/MethodParameterName
|
|
281
202
|
|
|
203
|
+
# rubocop:disable Naming/MethodParameterName
|
|
282
204
|
def self.add_safe_where(query:, tab:, col:, op:, value:)
|
|
283
|
-
quoted_value = query.connection.quote_default_expression(value,col)
|
|
284
|
-
query.where("#{
|
|
205
|
+
quoted_value = query.connection.quote_default_expression(value, col)
|
|
206
|
+
query.where("#{quote_column_path(query: query, prefix: tab, column_object: col)} #{op} #{quoted_value}")
|
|
285
207
|
end
|
|
208
|
+
# rubocop:enable Naming/MethodParameterName
|
|
286
209
|
|
|
287
210
|
def self.quote_column_path(query:, prefix:, column_object:)
|
|
288
211
|
c = query.connection
|
|
@@ -299,44 +222,135 @@ module Praxis
|
|
|
299
222
|
conn = query.connection
|
|
300
223
|
if value.nil?
|
|
301
224
|
no = negative ? ' NOT' : ''
|
|
302
|
-
"IS#{no} #{conn.quote_default_expression(value,column_object)}"
|
|
225
|
+
"IS#{no} #{conn.quote_default_expression(value, column_object)}"
|
|
303
226
|
elsif value.is_a?(Array)
|
|
304
227
|
no = negative ? 'NOT ' : ''
|
|
305
|
-
list = value.map{|v| conn.quote_default_expression(v,column_object)}
|
|
228
|
+
list = value.map { |v| conn.quote_default_expression(v, column_object) }
|
|
306
229
|
"#{no}IN (#{list.join(',')})"
|
|
307
|
-
elsif value
|
|
308
|
-
raise
|
|
230
|
+
elsif value.is_a?(Range)
|
|
231
|
+
raise 'TODO!'
|
|
309
232
|
else
|
|
310
233
|
op = negative ? '<>' : '='
|
|
311
|
-
"#{op} #{conn.quote_default_expression(value,column_object)}"
|
|
234
|
+
"#{op} #{conn.quote_default_expression(value, column_object)}"
|
|
312
235
|
end
|
|
313
236
|
end
|
|
314
237
|
|
|
315
238
|
# Returns nil if the value was not a fuzzzy pattern
|
|
316
|
-
def self.get_like_value(value,fuzzy)
|
|
239
|
+
def self.get_like_value(value, fuzzy)
|
|
317
240
|
is_fuzzy = fuzzy.is_a?(Array) ? !fuzzy.compact.empty? : fuzzy
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
241
|
+
return unless is_fuzzy
|
|
242
|
+
|
|
243
|
+
raise MultiMatchWithFuzzyNotAllowedByAdapter unless value.is_a?(String)
|
|
244
|
+
|
|
245
|
+
case fuzzy
|
|
246
|
+
when :start_end
|
|
247
|
+
"%#{value}%"
|
|
248
|
+
when :start
|
|
249
|
+
"%#{value}"
|
|
250
|
+
when :end
|
|
251
|
+
"#{value}%"
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
private
|
|
256
|
+
|
|
257
|
+
def _depth_first_traversal(root_query:, root_node:, conditions:, &block)
|
|
258
|
+
# Save the associated query for non-leaves
|
|
259
|
+
root_node.associated_query = root_query if root_node.is_a?(FilteringParams::ConditionGroup)
|
|
260
|
+
|
|
261
|
+
if root_node.is_a?(FilteringParams::Condition)
|
|
262
|
+
matching_condition = conditions.find { |cond| cond[:node_object] == root_node }
|
|
263
|
+
|
|
264
|
+
# The simplified case of a single top level condition (without a wrapping group)
|
|
265
|
+
# will need to pass the root query itself
|
|
266
|
+
associated_query = root_node.parent_group ? root_node.parent_group.associated_query : root_query
|
|
267
|
+
yield matching_condition, associated_query
|
|
268
|
+
else
|
|
269
|
+
first_query, *rest_queries = root_node.items.map do |child|
|
|
270
|
+
_depth_first_traversal(root_query: root_query, root_node: child, conditions: conditions, &block)
|
|
321
271
|
end
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
272
|
+
|
|
273
|
+
rest_queries.each.inject(first_query) do |q, a_query|
|
|
274
|
+
root_node.type == :and ? q.and(a_query) : q.or(a_query)
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def _mapped_filter(name)
|
|
280
|
+
target = @filters_map[name]
|
|
281
|
+
unless target
|
|
282
|
+
path = name.to_s.split('.')
|
|
283
|
+
if self.class.valid_path?(@model, path)
|
|
284
|
+
# Cache it in the filters mapping (to avoid later lookups), and return it.
|
|
285
|
+
@filters_map[name] = name
|
|
286
|
+
target = name
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
target
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# Resolve and convert from filters, to a more manageable and param-type-independent structure
|
|
293
|
+
def _convert_to_treenode(filters)
|
|
294
|
+
# Resolve the names and values first, based on filters_map
|
|
295
|
+
resolved_array = []
|
|
296
|
+
filters.parsed_array.each do |filter|
|
|
297
|
+
mapped_value = _mapped_filter(filter[:name])
|
|
298
|
+
unless mapped_value
|
|
299
|
+
msg = "Filtering by #{filter[:name]} is not allowed. No implementation mapping defined for it has been found \
|
|
300
|
+
and there is not a model attribute with this name either.\n" \
|
|
301
|
+
"Please add a mapping for #{filter[:name]} in the `filters_mapping` method of the appropriate Resource class"
|
|
302
|
+
raise msg
|
|
303
|
+
end
|
|
304
|
+
bindings_array = \
|
|
305
|
+
if mapped_value.is_a?(Proc)
|
|
306
|
+
result = mapped_value.call(filter)
|
|
307
|
+
# Result could be an array of hashes (each hash has name/op/value to identify a condition)
|
|
308
|
+
result_from_proc = result.is_a?(Array) ? result : [result]
|
|
309
|
+
# Make sure we tack on the node object associated with the filter
|
|
310
|
+
result_from_proc.map { |hash| hash.merge(node_object: filter[:node_object]) }
|
|
311
|
+
else
|
|
312
|
+
# For non-procs there's only 1 filter and 1 value (we're just overriding the mapped value)
|
|
313
|
+
[filter.merge(name: mapped_value)]
|
|
314
|
+
end
|
|
315
|
+
resolved_array += bindings_array
|
|
316
|
+
end
|
|
317
|
+
FilterTreeNode.new(resolved_array, path: [ALIAS_TABLE_PREFIX])
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# Calculate join tree and conditions array for the nodetree object and its children
|
|
321
|
+
def _compute_joins_and_conditions_data(nodetree, model:, parent_reflection:)
|
|
322
|
+
h = {}
|
|
323
|
+
conditions = []
|
|
324
|
+
nodetree.children.each do |name, child|
|
|
325
|
+
child_reflection = model.reflections[name.to_s]
|
|
326
|
+
result = _compute_joins_and_conditions_data(child, model: child_reflection.klass, parent_reflection: child_reflection)
|
|
327
|
+
h[name] = result[:associations_hash]
|
|
328
|
+
|
|
329
|
+
conditions += result[:conditions]
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
column_prefix = nodetree.path == [ALIAS_TABLE_PREFIX] ? model.table_name : nodetree.path.join(REFERENCES_STRING_SEPARATOR)
|
|
333
|
+
nodetree.conditions.each do |condition|
|
|
334
|
+
# If it's a final ! or !! operation on an association from the parent, it means we need to add a condition
|
|
335
|
+
# on the existence (or lack of) of the whole associated table
|
|
336
|
+
ref = model.reflections[condition[:name].to_s]
|
|
337
|
+
if ref && ['!', '!!'].include?(condition[:op])
|
|
338
|
+
cp = (nodetree.path + [condition[:name].to_s]).join(REFERENCES_STRING_SEPARATOR)
|
|
339
|
+
conditions += [condition.merge(column_prefix: cp, model: model, parent_reflection: ref)]
|
|
340
|
+
h[condition[:name]] = {}
|
|
341
|
+
else
|
|
342
|
+
# Save the parent reflection where the condition applies as well (used later to get assoc keys)
|
|
343
|
+
conditions += [condition.merge(column_prefix: column_prefix, model: model, parent_reflection: parent_reflection)]
|
|
329
344
|
end
|
|
330
|
-
else
|
|
331
|
-
nil
|
|
332
345
|
end
|
|
346
|
+
{ associations_hash: h, conditions: conditions }
|
|
333
347
|
end
|
|
334
348
|
|
|
335
349
|
# The value that we need to stick in the references method is different in the latest Rails
|
|
336
|
-
maj, min,
|
|
337
|
-
if maj == 5 || (maj == 6 && min
|
|
350
|
+
maj, min, = ActiveRecord.gem_version.segments
|
|
351
|
+
if maj == 5 || (maj == 6 && min.zero?)
|
|
338
352
|
# In AR 6 (and 6.0) the references are simple strings
|
|
339
|
-
def build_reference_value(column_prefix,
|
|
353
|
+
def build_reference_value(column_prefix, **_args)
|
|
340
354
|
column_prefix
|
|
341
355
|
end
|
|
342
356
|
else
|
|
@@ -350,4 +364,4 @@ module Praxis
|
|
|
350
364
|
end
|
|
351
365
|
end
|
|
352
366
|
end
|
|
353
|
-
end
|
|
367
|
+
end
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rubocop:disable all
|
|
3
|
+
|
|
1
4
|
require 'active_record'
|
|
2
5
|
|
|
3
6
|
module ActiveRecord
|
|
@@ -5,7 +8,7 @@ module ActiveRecord
|
|
|
5
8
|
class Relation
|
|
6
9
|
def construct_join_dependency
|
|
7
10
|
including = eager_load_values + includes_values
|
|
8
|
-
|
|
11
|
+
# Praxis: inject references into the join dependency
|
|
9
12
|
ActiveRecord::Associations::JoinDependency.new(
|
|
10
13
|
klass, table, including, references: references_values
|
|
11
14
|
)
|
|
@@ -34,18 +37,20 @@ module ActiveRecord
|
|
|
34
37
|
|
|
35
38
|
alias_tracker.aliases
|
|
36
39
|
end
|
|
37
|
-
|
|
38
40
|
end
|
|
41
|
+
|
|
39
42
|
module Associations
|
|
40
43
|
class JoinDependency
|
|
41
44
|
attr_accessor :references
|
|
45
|
+
|
|
42
46
|
private
|
|
43
|
-
|
|
47
|
+
|
|
48
|
+
def initialize(base, table, associations, references:)
|
|
44
49
|
tree = self.class.make_tree associations
|
|
45
50
|
@references = references # Save the references values into the instance (to use during build)
|
|
46
51
|
@join_root = JoinBase.new(base, table, build(tree, base))
|
|
47
52
|
end
|
|
48
|
-
|
|
53
|
+
|
|
49
54
|
# Praxis: table aliases for is shared for 5x and 6.0
|
|
50
55
|
def table_aliases_for(parent, node)
|
|
51
56
|
node.reflection.chain.map do |reflection|
|
|
@@ -57,8 +62,8 @@ module ActiveRecord
|
|
|
57
62
|
)
|
|
58
63
|
# through tables do not need a special alias_path alias (as they shouldn't really referenced by the client)
|
|
59
64
|
if is_root_reflection && node.alias_path
|
|
60
|
-
table = table.left if table.is_a?(Arel::Nodes::TableAlias) #un-alias it if necessary
|
|
61
|
-
table = table.alias(node.alias_path.join('/'))
|
|
65
|
+
table = table.left if table.is_a?(Arel::Nodes::TableAlias) # un-alias it if necessary
|
|
66
|
+
table = table.alias(node.alias_path.join('/'))
|
|
62
67
|
end
|
|
63
68
|
table
|
|
64
69
|
end
|
|
@@ -71,20 +76,20 @@ module ActiveRecord
|
|
|
71
76
|
reflection.check_validity!
|
|
72
77
|
reflection.check_eager_loadable!
|
|
73
78
|
|
|
74
|
-
if reflection.polymorphic?
|
|
75
|
-
|
|
76
|
-
end
|
|
79
|
+
raise EagerLoadPolymorphicError, reflection if reflection.polymorphic?
|
|
80
|
+
|
|
77
81
|
# Praxis: set an alias_path in the JoinAssociation if its path matches a requested reference
|
|
78
|
-
child_path =
|
|
82
|
+
child_path = path && !path.empty? ? path + [name] : nil
|
|
79
83
|
association = JoinAssociation.new(reflection, build(right, reflection.klass, path: child_path))
|
|
80
84
|
association.alias_path = child_path if references.include?(child_path.join('/'))
|
|
81
|
-
association
|
|
85
|
+
association
|
|
82
86
|
end
|
|
83
87
|
end
|
|
84
|
-
|
|
85
88
|
end
|
|
89
|
+
|
|
86
90
|
class ActiveRecord::Associations::JoinDependency::JoinAssociation
|
|
87
91
|
attr_accessor :alias_path
|
|
88
92
|
end
|
|
89
93
|
end
|
|
90
|
-
end
|
|
94
|
+
end
|
|
95
|
+
# rubocop:enable all
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rubocop:disable all
|
|
3
|
+
|
|
1
4
|
# FOR AR < 6.1
|
|
2
5
|
module ActiveRecord
|
|
3
6
|
PRAXIS_JOIN_ALIAS_PREFIX = Praxis::Extensions::AttributeFiltering::ALIAS_TABLE_PREFIX
|
|
@@ -15,6 +18,7 @@ module ActiveRecord
|
|
|
15
18
|
attr_accessor :references
|
|
16
19
|
|
|
17
20
|
private
|
|
21
|
+
|
|
18
22
|
def initialize(base, table, associations, join_type, references: nil)
|
|
19
23
|
tree = self.class.make_tree associations
|
|
20
24
|
@references = references # Save the references values into the instance (to use during build)
|
|
@@ -35,8 +39,8 @@ module ActiveRecord
|
|
|
35
39
|
)
|
|
36
40
|
# through tables do not need a special alias_path alias (as they shouldn't really referenced by the client)
|
|
37
41
|
if is_root_reflection && node.alias_path
|
|
38
|
-
table = table.left if table.is_a?(Arel::Nodes::TableAlias) #un-alias it if necessary
|
|
39
|
-
table = table.alias(node.alias_path.join('/'))
|
|
42
|
+
table = table.left if table.is_a?(Arel::Nodes::TableAlias) # un-alias it if necessary
|
|
43
|
+
table = table.alias(node.alias_path.join('/'))
|
|
40
44
|
end
|
|
41
45
|
table
|
|
42
46
|
end
|
|
@@ -49,20 +53,20 @@ module ActiveRecord
|
|
|
49
53
|
reflection.check_validity!
|
|
50
54
|
reflection.check_eager_loadable!
|
|
51
55
|
|
|
52
|
-
if reflection.polymorphic?
|
|
53
|
-
|
|
54
|
-
end
|
|
56
|
+
raise EagerLoadPolymorphicError, reflection if reflection.polymorphic?
|
|
57
|
+
|
|
55
58
|
# Praxis: set an alias_path in the JoinAssociation if its path matches a requested reference
|
|
56
|
-
child_path =
|
|
59
|
+
child_path = path && !path.empty? ? path + [name] : nil
|
|
57
60
|
association = JoinAssociation.new(reflection, build(right, reflection.klass, path: child_path))
|
|
58
61
|
association.alias_path = child_path if references.include?(child_path.join('/'))
|
|
59
|
-
association
|
|
62
|
+
association
|
|
60
63
|
end
|
|
61
64
|
end
|
|
62
|
-
|
|
63
65
|
end
|
|
66
|
+
|
|
64
67
|
class ActiveRecord::Associations::JoinDependency::JoinAssociation
|
|
65
68
|
attr_accessor :alias_path
|
|
66
69
|
end
|
|
67
70
|
end
|
|
68
|
-
end
|
|
71
|
+
end
|
|
72
|
+
# rubocop:enable all
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rubocop:disable all
|
|
3
|
+
|
|
1
4
|
# FOR AR >= 6.1
|
|
2
5
|
module ActiveRecord
|
|
3
6
|
PRAXIS_JOIN_ALIAS_PREFIX = Praxis::Extensions::AttributeFiltering::ALIAS_TABLE_PREFIX
|
|
4
7
|
module Associations
|
|
5
8
|
class JoinDependency
|
|
6
|
-
|
|
7
9
|
private
|
|
10
|
+
|
|
8
11
|
def make_constraints(parent, child, join_type)
|
|
9
12
|
foreign_table = parent.table
|
|
10
13
|
foreign_klass = parent.base_klass
|
|
@@ -19,7 +22,7 @@ module ActiveRecord
|
|
|
19
22
|
|
|
20
23
|
table_name = @references[reflection.name.to_sym]
|
|
21
24
|
# Praxis: set an alias_path in the JoinAssociation if its path matches a requested reference
|
|
22
|
-
table_name
|
|
25
|
+
table_name ||= @references[child&.alias_path.join('/').to_sym]
|
|
23
26
|
|
|
24
27
|
table = alias_tracker.aliased_table_for(reflection.klass.arel_table, table_name) do
|
|
25
28
|
name = reflection.alias_candidate(parent.table_name)
|
|
@@ -38,21 +41,21 @@ module ActiveRecord
|
|
|
38
41
|
reflection.check_validity!
|
|
39
42
|
reflection.check_eager_loadable!
|
|
40
43
|
|
|
41
|
-
if reflection.polymorphic?
|
|
42
|
-
|
|
43
|
-
end
|
|
44
|
+
raise EagerLoadPolymorphicError, reflection if reflection.polymorphic?
|
|
45
|
+
|
|
44
46
|
# Praxis: set an alias_path in the JoinAssociation if its path matches a requested reference
|
|
45
|
-
child_path =
|
|
47
|
+
child_path = path && !path.empty? ? path + [name] : nil
|
|
46
48
|
association = JoinAssociation.new(reflection, build(right, reflection.klass, path: child_path))
|
|
47
|
-
#association.alias_path = child_path if references.include?(child_path.join('/'))
|
|
49
|
+
# association.alias_path = child_path if references.include?(child_path.join('/'))
|
|
48
50
|
association.alias_path = child_path # ??? should be the line above no?
|
|
49
|
-
association
|
|
51
|
+
association
|
|
50
52
|
end
|
|
51
|
-
end
|
|
53
|
+
end
|
|
52
54
|
end
|
|
53
|
-
|
|
55
|
+
|
|
54
56
|
class ActiveRecord::Associations::JoinDependency::JoinAssociation
|
|
55
57
|
attr_accessor :alias_path
|
|
56
58
|
end
|
|
57
59
|
end
|
|
58
|
-
end
|
|
60
|
+
end
|
|
61
|
+
# rubocop:enable all
|
|
@@ -1,15 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'active_record'
|
|
2
4
|
|
|
3
|
-
maj, min,
|
|
5
|
+
maj, min, = ActiveRecord.gem_version.segments
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
case maj
|
|
8
|
+
when 5
|
|
9
|
+
require_relative 'active_record_patches/5x'
|
|
10
|
+
when 6
|
|
11
|
+
if min.zero?
|
|
12
|
+
require_relative 'active_record_patches/6_0'
|
|
10
13
|
else
|
|
11
|
-
require_relative 'active_record_patches/6_1_plus
|
|
14
|
+
require_relative 'active_record_patches/6_1_plus'
|
|
12
15
|
end
|
|
13
16
|
else
|
|
14
|
-
raise
|
|
15
|
-
end
|
|
17
|
+
raise 'Filtering only supported for ActiveRecord >= 5 && <= 6'
|
|
18
|
+
end
|