active_record_extended 2.1.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +22 -7
- data/lib/active_record_extended/arel/aggregate_function_name.rb +0 -0
- data/lib/active_record_extended/arel/nodes.rb +1 -1
- data/lib/active_record_extended/arel/predications.rb +0 -0
- data/lib/active_record_extended/arel/{sql_literal.rb → sql_literal_patch.rb} +2 -2
- data/lib/active_record_extended/arel/visitors/postgresql_decorator.rb +16 -12
- data/lib/active_record_extended/arel.rb +1 -1
- data/lib/active_record_extended/patch/array_handler_patch.rb +22 -0
- data/lib/active_record_extended/patch/relation_patch.rb +67 -0
- data/lib/active_record_extended/patch/where_clause_patch.rb +13 -0
- data/lib/active_record_extended/query_methods/any_of.rb +1 -1
- data/lib/active_record_extended/query_methods/either.rb +3 -3
- data/lib/active_record_extended/query_methods/{select.rb → foster_select.rb} +4 -4
- data/lib/active_record_extended/query_methods/inet.rb +0 -0
- data/lib/active_record_extended/query_methods/json.rb +2 -2
- data/lib/active_record_extended/query_methods/unionize.rb +5 -5
- data/lib/active_record_extended/query_methods/where_chain.rb +96 -92
- data/lib/active_record_extended/query_methods/window.rb +3 -3
- data/lib/active_record_extended/query_methods/with_cte.rb +3 -3
- data/lib/active_record_extended/utilities/order_by.rb +1 -1
- data/lib/active_record_extended/utilities/support.rb +1 -1
- data/lib/active_record_extended/version.rb +1 -1
- data/lib/active_record_extended.rb +55 -4
- metadata +21 -70
- data/lib/active_record_extended/active_record/relation_patch.rb +0 -50
- data/lib/active_record_extended/active_record.rb +0 -25
- data/lib/active_record_extended/patch/5_1/where_clause.rb +0 -11
- data/lib/active_record_extended/patch/5_2/where_clause.rb +0 -11
- data/lib/active_record_extended/predicate_builder/array_handler_decorator.rb +0 -20
- data/spec/active_record_extended_spec.rb +0 -7
- data/spec/query_methods/any_of_spec.rb +0 -131
- data/spec/query_methods/array_query_spec.rb +0 -64
- data/spec/query_methods/either_spec.rb +0 -70
- data/spec/query_methods/hash_query_spec.rb +0 -45
- data/spec/query_methods/inet_query_spec.rb +0 -112
- data/spec/query_methods/json_spec.rb +0 -157
- data/spec/query_methods/select_spec.rb +0 -115
- data/spec/query_methods/unionize_spec.rb +0 -165
- data/spec/query_methods/window_spec.rb +0 -51
- data/spec/query_methods/with_cte_spec.rb +0 -50
- data/spec/spec_helper.rb +0 -28
- data/spec/sql_inspections/any_of_sql_spec.rb +0 -41
- data/spec/sql_inspections/arel/aggregate_function_name_spec.rb +0 -41
- data/spec/sql_inspections/arel/array_spec.rb +0 -63
- data/spec/sql_inspections/arel/inet_spec.rb +0 -66
- data/spec/sql_inspections/contains_sql_queries_spec.rb +0 -47
- data/spec/sql_inspections/either_sql_spec.rb +0 -71
- data/spec/sql_inspections/json_sql_spec.rb +0 -82
- data/spec/sql_inspections/unionize_sql_spec.rb +0 -124
- data/spec/sql_inspections/window_sql_spec.rb +0 -98
- data/spec/sql_inspections/with_cte_sql_spec.rb +0 -95
- data/spec/support/database_cleaner.rb +0 -15
- data/spec/support/models.rb +0 -80
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b59ef27069d870b734f0494db168936d5536493fd1346469f7a85d9b43bc7655
|
|
4
|
+
data.tar.gz: f0e25e53250ec9d562bb75ec06e7d02deda48a9996b8a03e4fb5afac2d113abc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ffad0b652cbaac828d25ed1dbbd6e5e5479f6905bf776785090f9b76fe33fefb750fcbce7c329bbcaf077f1f5401b9817e830d89efab130feca3500bbc97c1f8
|
|
7
|
+
data.tar.gz: b0f77a0248242cc50e7f87ebd813e90c4e57bf1cfd28e35190ee93fa4ce2ae50878dd6e7f153dc017a692f13d147a9a9f8378ba9583d7705c2b71566fb20e29c
|
data/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
[](https://badge.fury.io/rb/active_record_extended)
|
|
2
|
-
[](https://github.com/GeorgeKaraszi/ActiveRecordExtended/actions/workflows/test.yml?query=branch%3Amaster+)
|
|
3
3
|
[](https://codeclimate.com/github/GeorgeKaraszi/active_record_extended/maintainability)
|
|
4
4
|
[](https://codeclimate.com/github/GeorgeKaraszi/ActiveRecordExtended/test_coverage)
|
|
5
5
|
## Index
|
|
@@ -52,12 +52,12 @@ Active Record Extended is essentially providing users with the other half of Pos
|
|
|
52
52
|
## Compatibility
|
|
53
53
|
|
|
54
54
|
This package is designed align and work with any officially supported Ruby and Rails versions.
|
|
55
|
-
- Minimum Ruby Version: 2.
|
|
56
|
-
- Minimum Rails Version: 5.
|
|
57
|
-
- Minimum Postgres Version:
|
|
58
|
-
- Latest Ruby supported: 3.
|
|
55
|
+
- Minimum Ruby Version: 2.5.x **(EOL warning!)**
|
|
56
|
+
- Minimum Rails Version: 5.2.x **(EOL warning!)**
|
|
57
|
+
- Minimum Postgres Version: 10.x **(EOL warning!)**
|
|
58
|
+
- Latest Ruby supported: 3.1.x
|
|
59
59
|
- Latest Rails supported: 7.0.x
|
|
60
|
-
- Postgres:
|
|
60
|
+
- Postgres: 10-current(14) (probably works with most older versions to a certain point)
|
|
61
61
|
|
|
62
62
|
## Installation
|
|
63
63
|
|
|
@@ -412,12 +412,27 @@ If any or all of your json sub-queries include a CTE, read the [Subquery CTE Got
|
|
|
412
412
|
#### Row To JSON
|
|
413
413
|
[Postgres 'ROW_TO_JSON' function](https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSON-CREATION-TABLE)
|
|
414
414
|
|
|
415
|
-
The implementation of the`.
|
|
415
|
+
The implementation of the`.select_row_to_json/2` method is designed to be used with sub-queries. As a means for taking complex
|
|
416
416
|
query logic and transform them into a single or multiple json responses. These responses are required to be assigned
|
|
417
417
|
to an aliased column on the parent(callee) level.
|
|
418
418
|
|
|
419
419
|
While quite the mouthful of an explanation. The implementation of combining unrelated or semi-related queries is quite smooth(imo).
|
|
420
420
|
|
|
421
|
+
**Arguments:**
|
|
422
|
+
- `from` [String, Arel, or ActiveRecord::Relation]: A subquery that can be nested into a `ROW_TO_JSON` clause
|
|
423
|
+
|
|
424
|
+
**Options:**
|
|
425
|
+
- `as` [Symbol or String] (default="results"): What the column will be aliased to
|
|
426
|
+
- `key` [Symbol or String] (default=[random letter]): Internal query alias name.
|
|
427
|
+
* This is useful if you would like to add additional mid-level predicate clauses
|
|
428
|
+
- `cast_with` [Symbol or Array\<Symbol>]:
|
|
429
|
+
* `:to_jsonb`
|
|
430
|
+
* `:array`
|
|
431
|
+
* `:array_agg`
|
|
432
|
+
* `:distinct` (auto applies `:array_agg` & `:to_jsonb`)
|
|
433
|
+
- `order_by` [Symbol or Hash]: Applies an ordering operation (similar to ActiveRecord #order)
|
|
434
|
+
* **Note**: this option will be ignored if you need to order a DISTINCT Aggregated Array.
|
|
435
|
+
|
|
421
436
|
```ruby
|
|
422
437
|
physical_cat = Category.create!(name: "Physical")
|
|
423
438
|
products = 3.times.map { Product.create! }
|
|
File without changes
|
|
@@ -5,7 +5,7 @@ require "arel/nodes/function"
|
|
|
5
5
|
|
|
6
6
|
module Arel
|
|
7
7
|
module Nodes
|
|
8
|
-
|
|
8
|
+
unless ActiveRecordExtended::AR_VERSION_GTE_6_1
|
|
9
9
|
["Contains", "Overlaps"].each { |binary_node_name| const_set(binary_node_name, Class.new(::Arel::Nodes::Binary)) }
|
|
10
10
|
end
|
|
11
11
|
|
|
File without changes
|
|
@@ -5,7 +5,7 @@ require "arel/nodes/sql_literal"
|
|
|
5
5
|
# CTE alias fix for Rails 6.1
|
|
6
6
|
module Arel
|
|
7
7
|
module Nodes
|
|
8
|
-
module
|
|
8
|
+
module SqlLiteralPatch
|
|
9
9
|
def name
|
|
10
10
|
self
|
|
11
11
|
end
|
|
@@ -13,4 +13,4 @@ module Arel
|
|
|
13
13
|
end
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
Arel::Nodes::SqlLiteral.prepend(Arel::Nodes::
|
|
16
|
+
Arel::Nodes::SqlLiteral.prepend(Arel::Nodes::SqlLiteralPatch)
|
|
@@ -9,21 +9,25 @@ module ActiveRecordExtended
|
|
|
9
9
|
|
|
10
10
|
# rubocop:disable Naming/MethodName
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
unless ActiveRecordExtended::AR_VERSION_GTE_6_1
|
|
13
|
+
def visit_Arel_Nodes_Overlaps(object, collector)
|
|
14
|
+
infix_value object, collector, " && "
|
|
15
|
+
end
|
|
14
16
|
end
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
unless ActiveRecordExtended::AR_VERSION_GTE_6_1
|
|
19
|
+
def visit_Arel_Nodes_Contains(object, collector)
|
|
20
|
+
left_column = object.left.relation.name.classify.constantize.columns.detect do |col|
|
|
21
|
+
matchable_column?(col, object)
|
|
22
|
+
end
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
if [:hstore, :jsonb].include?(left_column&.type)
|
|
25
|
+
visit_Arel_Nodes_ContainsHStore(object, collector)
|
|
26
|
+
elsif left_column.try(:array)
|
|
27
|
+
visit_Arel_Nodes_ContainsArray(object, collector)
|
|
28
|
+
else
|
|
29
|
+
visit_Arel_Nodes_Inet_Contains(object, collector)
|
|
30
|
+
end
|
|
27
31
|
end
|
|
28
32
|
end
|
|
29
33
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "active_record_extended/arel/nodes"
|
|
4
|
-
require "active_record_extended/arel/
|
|
4
|
+
require "active_record_extended/arel/sql_literal_patch"
|
|
5
5
|
require "active_record_extended/arel/aggregate_function_name"
|
|
6
6
|
require "active_record_extended/arel/predications"
|
|
7
7
|
require "active_record_extended/arel/visitors/postgresql_decorator"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_record/relation/predicate_builder"
|
|
4
|
+
require "active_record/relation/predicate_builder/array_handler"
|
|
5
|
+
|
|
6
|
+
module ActiveRecordExtended
|
|
7
|
+
module Patch
|
|
8
|
+
module ArrayHandlerPatch
|
|
9
|
+
def call(attribute, value)
|
|
10
|
+
cache = ActiveRecord::Base.connection.schema_cache
|
|
11
|
+
if cache.data_source_exists?(attribute.relation.name)
|
|
12
|
+
column = cache.columns(attribute.relation.name).detect { |col| col.name.to_s == attribute.name.to_s }
|
|
13
|
+
return attribute.eq(value) if column.try(:array)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
super(attribute, value)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
ActiveRecord::PredicateBuilder::ArrayHandler.prepend(ActiveRecordExtended::Patch::ArrayHandlerPatch)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecordExtended
|
|
4
|
+
module Patch
|
|
5
|
+
module RelationPatch
|
|
6
|
+
module QueryDelegation
|
|
7
|
+
AR_EX_QUERY_METHODS = (
|
|
8
|
+
[
|
|
9
|
+
:with, :define_window, :select_window, :foster_select,
|
|
10
|
+
:either_join, :either_joins, :either_order, :either_orders
|
|
11
|
+
] +
|
|
12
|
+
ActiveRecordExtended::QueryMethods::Unionize::UNIONIZE_METHODS +
|
|
13
|
+
ActiveRecordExtended::QueryMethods::Json::JSON_QUERY_METHODS
|
|
14
|
+
).freeze
|
|
15
|
+
|
|
16
|
+
delegate(*AR_EX_QUERY_METHODS, to: :all)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
module Merger
|
|
20
|
+
def merge
|
|
21
|
+
merge_ctes!
|
|
22
|
+
merge_union!
|
|
23
|
+
merge_windows!
|
|
24
|
+
super
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def merge_union!
|
|
28
|
+
return if other.unionize_storage.empty?
|
|
29
|
+
|
|
30
|
+
relation.union_values += other.union_values
|
|
31
|
+
relation.union_operations += other.union_operations
|
|
32
|
+
relation.union_ordering_values += other.union_ordering_values
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def merge_windows!
|
|
36
|
+
return unless other.window_values?
|
|
37
|
+
|
|
38
|
+
relation.window_values |= other.window_values
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def merge_ctes!
|
|
42
|
+
return unless other.with_values?
|
|
43
|
+
|
|
44
|
+
if other.recursive_value? && !relation.recursive_value?
|
|
45
|
+
relation.with!.recursive(other.cte)
|
|
46
|
+
else
|
|
47
|
+
relation.with!(other.cte)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
module ArelBuildPatch
|
|
53
|
+
def build_arel(*aliases)
|
|
54
|
+
super.tap do |arel|
|
|
55
|
+
build_windows(arel) if window_values?
|
|
56
|
+
build_unions(arel) if union_values?
|
|
57
|
+
build_with(arel) if with_values?
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
ActiveRecord::Relation.prepend(ActiveRecordExtended::Patch::RelationPatch::ArelBuildPatch)
|
|
66
|
+
ActiveRecord::Relation::Merger.prepend(ActiveRecordExtended::Patch::RelationPatch::Merger)
|
|
67
|
+
ActiveRecord::Base.extend(ActiveRecordExtended::Patch::RelationPatch::QueryDelegation)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecordExtended
|
|
4
|
+
module Patch
|
|
5
|
+
module WhereClausePatch
|
|
6
|
+
def modified_predicates(&block)
|
|
7
|
+
ActiveRecord::Relation::WhereClause.new(predicates.map(&block))
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
ActiveRecord::Relation::WhereClause.prepend(ActiveRecordExtended::Patch::WhereClausePatch)
|
|
@@ -29,7 +29,7 @@ module ActiveRecordExtended
|
|
|
29
29
|
|
|
30
30
|
def hash_map_queries(queries)
|
|
31
31
|
if queries.size == 1 && queries.first.is_a?(Hash)
|
|
32
|
-
queries.first.each_pair.map { |attr, predicate|
|
|
32
|
+
queries.first.each_pair.map { |attr, predicate| { attr => predicate } }
|
|
33
33
|
else
|
|
34
34
|
queries
|
|
35
35
|
end
|
|
@@ -26,7 +26,7 @@ module ActiveRecordExtended
|
|
|
26
26
|
private
|
|
27
27
|
|
|
28
28
|
def xor_field_sql(options)
|
|
29
|
-
XOR_FIELD_SQL %
|
|
29
|
+
XOR_FIELD_SQL % xor_field_options(options).to_h
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def sort_order_sql(dir)
|
|
@@ -35,7 +35,7 @@ module ActiveRecordExtended
|
|
|
35
35
|
|
|
36
36
|
def xor_field_options(options)
|
|
37
37
|
str_args = options.flatten.take(XOR_FIELD_KEYS.size).map(&:to_s)
|
|
38
|
-
|
|
38
|
+
XOR_FIELD_KEYS.zip(str_args).to_h
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def map_columns_to_tables(associations_and_columns)
|
|
@@ -60,4 +60,4 @@ module ActiveRecordExtended
|
|
|
60
60
|
end
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
-
ActiveRecord::
|
|
63
|
+
ActiveRecord::Relation.prepend(ActiveRecordExtended::QueryMethods::Either)
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module ActiveRecordExtended
|
|
4
4
|
module QueryMethods
|
|
5
|
-
module
|
|
5
|
+
module FosterSelect
|
|
6
6
|
class SelectHelper
|
|
7
|
-
include
|
|
8
|
-
include
|
|
7
|
+
include ActiveRecordExtended::Utilities::Support
|
|
8
|
+
include ActiveRecordExtended::Utilities::OrderBy
|
|
9
9
|
|
|
10
10
|
AGGREGATE_ONE_LINERS = /^(exists|sum|max|min|avg|count|jsonb?_agg|(bit|bool)_(and|or)|xmlagg|array_agg)$/.freeze
|
|
11
11
|
|
|
@@ -115,4 +115,4 @@ module ActiveRecordExtended
|
|
|
115
115
|
end
|
|
116
116
|
end
|
|
117
117
|
|
|
118
|
-
ActiveRecord::Relation.prepend(ActiveRecordExtended::QueryMethods::
|
|
118
|
+
ActiveRecord::Relation.prepend(ActiveRecordExtended::QueryMethods::FosterSelect)
|
|
File without changes
|
|
@@ -12,8 +12,8 @@ module ActiveRecordExtended
|
|
|
12
12
|
].freeze
|
|
13
13
|
|
|
14
14
|
class JsonChain
|
|
15
|
-
include
|
|
16
|
-
include
|
|
15
|
+
include ActiveRecordExtended::Utilities::Support
|
|
16
|
+
include ActiveRecordExtended::Utilities::OrderBy
|
|
17
17
|
|
|
18
18
|
DEFAULT_ALIAS = '"results"'
|
|
19
19
|
TO_JSONB_OPTIONS = [:array_agg, :distinct, :to_jsonb].to_set.freeze
|
|
@@ -7,8 +7,8 @@ module ActiveRecordExtended
|
|
|
7
7
|
UNIONIZE_METHODS = [:union, :union_all, :union_except, :union_intersect].freeze
|
|
8
8
|
|
|
9
9
|
class UnionChain
|
|
10
|
-
include
|
|
11
|
-
include
|
|
10
|
+
include ActiveRecordExtended::Utilities::Support
|
|
11
|
+
include ActiveRecordExtended::Utilities::OrderBy
|
|
12
12
|
|
|
13
13
|
def initialize(scope)
|
|
14
14
|
@scope = scope
|
|
@@ -107,7 +107,7 @@ module ActiveRecordExtended
|
|
|
107
107
|
end
|
|
108
108
|
|
|
109
109
|
def union(opts = :chain, *args)
|
|
110
|
-
return UnionChain.new(spawn) if
|
|
110
|
+
return UnionChain.new(spawn) if opts == :chain
|
|
111
111
|
|
|
112
112
|
opts.nil? ? self : spawn.union!(opts, *args, chain_method: __callee__)
|
|
113
113
|
end
|
|
@@ -121,7 +121,7 @@ module ActiveRecordExtended
|
|
|
121
121
|
def union!(opts = :chain, *args, chain_method: :union)
|
|
122
122
|
union_chain = UnionChain.new(self)
|
|
123
123
|
chain_method ||= :union
|
|
124
|
-
return union_chain if
|
|
124
|
+
return union_chain if opts == :chain
|
|
125
125
|
|
|
126
126
|
union_chain.public_send(chain_method, *([opts] + args))
|
|
127
127
|
end
|
|
@@ -178,7 +178,7 @@ module ActiveRecordExtended
|
|
|
178
178
|
def build_union_nodes!(raise_error = true)
|
|
179
179
|
unionize_error_or_warn!(raise_error)
|
|
180
180
|
union_values.each_with_index.reduce(nil) do |union_node, (relation_node, index)|
|
|
181
|
-
next resolve_relation_node(relation_node) if union_node.nil?
|
|
181
|
+
next resolve_relation_node(relation_node) if union_node.nil? # rubocop:disable Lint/UnmodifiedReduceAccumulator
|
|
182
182
|
|
|
183
183
|
operation = union_operations.fetch(index - 1, :union)
|
|
184
184
|
left = union_node
|
|
@@ -1,117 +1,121 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module ActiveRecordExtended
|
|
4
|
-
module
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
alias overlap overlaps
|
|
4
|
+
module QueryMethods
|
|
5
|
+
module WhereChain
|
|
6
|
+
# Finds Records that have an array column that contain any a set of values
|
|
7
|
+
# User.where.overlap(tags: [1,2])
|
|
8
|
+
# # SELECT * FROM users WHERE tags && {1,2}
|
|
9
|
+
def overlaps(opts, *rest)
|
|
10
|
+
substitute_comparisons(opts, rest, Arel::Nodes::Overlaps, "overlap")
|
|
11
|
+
end
|
|
12
|
+
alias overlap overlaps
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
# Finds Records that contain an element in an array column
|
|
15
|
+
# User.where.any(tags: 3)
|
|
16
|
+
# # SELECT user.* FROM user WHERE 3 = ANY(user.tags)
|
|
17
|
+
def any(opts, *rest)
|
|
18
|
+
equality_to_function("ANY", opts, rest)
|
|
19
|
+
end
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
# Finds Records that contain a single matchable array element
|
|
22
|
+
# User.where.all(tags: 3)
|
|
23
|
+
# # SELECT user.* FROM user WHERE 3 = ALL(user.tags)
|
|
24
|
+
def all(opts, *rest)
|
|
25
|
+
equality_to_function("ALL", opts, rest)
|
|
26
|
+
end
|
|
28
27
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
28
|
+
# Finds Records that contains a nested set elements
|
|
29
|
+
#
|
|
30
|
+
# Array Column Type:
|
|
31
|
+
# User.where.contains(tags: [1, 3])
|
|
32
|
+
# # SELECT user.* FROM user WHERE user.tags @> {1,3}
|
|
33
|
+
#
|
|
34
|
+
# HStore Column Type:
|
|
35
|
+
# User.where.contains(data: { nickname: 'chainer' })
|
|
36
|
+
# # SELECT user.* FROM user WHERE user.data @> 'nickname' => 'chainer'
|
|
37
|
+
#
|
|
38
|
+
# JSONB Column Type:
|
|
39
|
+
# User.where.contains(data: { nickname: 'chainer' })
|
|
40
|
+
# # SELECT user.* FROM user WHERE user.data @> {'nickname': 'chainer'}
|
|
41
|
+
#
|
|
42
|
+
# This can also be used along side joined tables
|
|
43
|
+
#
|
|
44
|
+
# JSONB Column Type Example:
|
|
45
|
+
# Tag.joins(:user).where.contains(user: { data: { nickname: 'chainer' } })
|
|
46
|
+
# # SELECT tags.* FROM tags INNER JOIN user on user.id = tags.user_id WHERE user.data @> { nickname: 'chainer' }
|
|
47
|
+
#
|
|
48
|
+
def contains(opts, *rest)
|
|
49
|
+
if ActiveRecordExtended::AR_VERSION_GTE_6_1
|
|
50
|
+
return substitute_comparisons(opts, rest, Arel::Nodes::Contains, "contains")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
build_where_chain(opts, rest) do |arel|
|
|
54
|
+
case arel
|
|
55
|
+
when Arel::Nodes::In, Arel::Nodes::Equality
|
|
56
|
+
column = left_column(arel) || column_from_association(arel)
|
|
57
|
+
|
|
58
|
+
if [:hstore, :jsonb].include?(column.type)
|
|
59
|
+
Arel::Nodes::ContainsHStore.new(arel.left, arel.right)
|
|
60
|
+
elsif column.try(:array)
|
|
61
|
+
Arel::Nodes::ContainsArray.new(arel.left, arel.right)
|
|
62
|
+
else
|
|
63
|
+
raise ArgumentError.new("Invalid argument for .where.contains(), got #{arel.class}")
|
|
64
|
+
end
|
|
59
65
|
else
|
|
60
66
|
raise ArgumentError.new("Invalid argument for .where.contains(), got #{arel.class}")
|
|
61
67
|
end
|
|
62
|
-
else
|
|
63
|
-
raise ArgumentError.new("Invalid argument for .where.contains(), got #{arel.class}")
|
|
64
68
|
end
|
|
65
69
|
end
|
|
66
|
-
end
|
|
67
70
|
|
|
68
|
-
|
|
71
|
+
private
|
|
69
72
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
def matchable_column?(col, arel)
|
|
74
|
+
col.name == arel.left.name.to_s || col.name == arel.left.relation.name.to_s
|
|
75
|
+
end
|
|
73
76
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
def column_from_association(arel)
|
|
78
|
+
assoc = assoc_from_related_table(arel)
|
|
79
|
+
assoc.klass.columns.detect { |col| matchable_column?(col, arel) } if assoc
|
|
80
|
+
end
|
|
78
81
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
def assoc_from_related_table(arel)
|
|
83
|
+
@scope.klass.reflect_on_association(arel.left.relation.name.to_sym) ||
|
|
84
|
+
@scope.klass.reflect_on_association(arel.left.relation.name.singularize.to_sym)
|
|
85
|
+
end
|
|
83
86
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
def left_column(arel)
|
|
88
|
+
@scope.klass.columns_hash[arel.left.name] || @scope.klass.columns_hash[arel.left.relation.name]
|
|
89
|
+
end
|
|
87
90
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
91
|
+
def equality_to_function(function_name, opts, rest)
|
|
92
|
+
build_where_chain(opts, rest) do |arel|
|
|
93
|
+
case arel
|
|
94
|
+
when Arel::Nodes::Equality
|
|
95
|
+
Arel::Nodes::Equality.new(arel.right, Arel::Nodes::NamedFunction.new(function_name, [arel.left]))
|
|
96
|
+
else
|
|
97
|
+
raise ArgumentError.new("Invalid argument for .where.#{function_name.downcase}(), got #{arel.class}")
|
|
98
|
+
end
|
|
95
99
|
end
|
|
96
100
|
end
|
|
97
|
-
end
|
|
98
101
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
def substitute_comparisons(opts, rest, arel_node_class, method)
|
|
103
|
+
build_where_chain(opts, rest) do |arel|
|
|
104
|
+
case arel
|
|
105
|
+
when Arel::Nodes::In, Arel::Nodes::Equality
|
|
106
|
+
arel_node_class.new(arel.left, arel.right)
|
|
107
|
+
else
|
|
108
|
+
raise ArgumentError.new("Invalid argument for .where.#{method}(), got #{arel.class}")
|
|
109
|
+
end
|
|
106
110
|
end
|
|
107
111
|
end
|
|
108
|
-
end
|
|
109
112
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
def build_where_clause_for(scope, opts, rest)
|
|
114
|
+
if ActiveRecordExtended::AR_VERSION_GTE_6_1
|
|
115
|
+
scope.send(:build_where_clause, opts, rest)
|
|
116
|
+
else
|
|
117
|
+
scope.send(:where_clause_factory).build(opts, rest)
|
|
118
|
+
end
|
|
115
119
|
end
|
|
116
120
|
end
|
|
117
121
|
end
|
|
@@ -120,7 +124,7 @@ end
|
|
|
120
124
|
module ActiveRecord
|
|
121
125
|
module QueryMethods
|
|
122
126
|
class WhereChain
|
|
123
|
-
prepend ActiveRecordExtended::WhereChain
|
|
127
|
+
prepend ActiveRecordExtended::QueryMethods::WhereChain
|
|
124
128
|
|
|
125
129
|
def build_where_chain(opts, rest, &block)
|
|
126
130
|
where_clause = build_where_clause_for(@scope, opts, rest)
|
|
@@ -4,8 +4,8 @@ module ActiveRecordExtended
|
|
|
4
4
|
module QueryMethods
|
|
5
5
|
module Window
|
|
6
6
|
class DefineWindowChain
|
|
7
|
-
include
|
|
8
|
-
include
|
|
7
|
+
include ActiveRecordExtended::Utilities::Support
|
|
8
|
+
include ActiveRecordExtended::Utilities::OrderBy
|
|
9
9
|
|
|
10
10
|
def initialize(scope, window_name)
|
|
11
11
|
@scope = scope
|
|
@@ -24,7 +24,7 @@ module ActiveRecordExtended
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
class WindowSelectBuilder
|
|
27
|
-
include
|
|
27
|
+
include ActiveRecordExtended::Utilities::Support
|
|
28
28
|
|
|
29
29
|
def initialize(window_function, args, window_name)
|
|
30
30
|
@window_function = window_function
|
|
@@ -4,7 +4,7 @@ module ActiveRecordExtended
|
|
|
4
4
|
module QueryMethods
|
|
5
5
|
module WithCTE
|
|
6
6
|
class WithCTE
|
|
7
|
-
include
|
|
7
|
+
include ActiveRecordExtended::Utilities::Support
|
|
8
8
|
include Enumerable
|
|
9
9
|
extend Forwardable
|
|
10
10
|
|
|
@@ -113,14 +113,14 @@ module ActiveRecordExtended
|
|
|
113
113
|
|
|
114
114
|
# @param [Hash, WithCTE] opts
|
|
115
115
|
def with(opts = :chain, *rest)
|
|
116
|
-
return WithChain.new(spawn) if
|
|
116
|
+
return WithChain.new(spawn) if opts == :chain
|
|
117
117
|
|
|
118
118
|
opts.blank? ? self : spawn.with!(opts, *rest)
|
|
119
119
|
end
|
|
120
120
|
|
|
121
121
|
# @param [Hash, WithCTE] opts
|
|
122
122
|
def with!(opts = :chain, *_rest)
|
|
123
|
-
return WithChain.new(self) if
|
|
123
|
+
return WithChain.new(self) if opts == :chain
|
|
124
124
|
|
|
125
125
|
tap do |scope|
|
|
126
126
|
scope.cte ||= WithCTE.new(self)
|
|
@@ -49,7 +49,7 @@ module ActiveRecordExtended
|
|
|
49
49
|
obj.each_pair do |o_key, o_value|
|
|
50
50
|
new_hash["#{tbl_or_col}.#{o_key}"] = o_value
|
|
51
51
|
end
|
|
52
|
-
elsif
|
|
52
|
+
elsif ActiveRecord::QueryMethods::VALID_DIRECTIONS.include?(obj)
|
|
53
53
|
new_hash[tbl_or_col] = obj
|
|
54
54
|
elsif obj.nil?
|
|
55
55
|
new_hash[tbl_or_col.to_s] = :asc
|
|
@@ -113,7 +113,7 @@ module ActiveRecordExtended
|
|
|
113
113
|
case value.to_s
|
|
114
114
|
# Ignore keys that contain double quotes or a Arel.star (*)[all columns]
|
|
115
115
|
# or if a table has already been explicitly declared (ex: users.id)
|
|
116
|
-
when "*", /((^".+"$)|(^[[:alpha:]]+\.[[:alnum:]]+))/
|
|
116
|
+
when "*", /((^".+"$)|(^[[:alpha:]]+\.[[:alnum:]]+)|\(.+\))/
|
|
117
117
|
value
|
|
118
118
|
else
|
|
119
119
|
PG::Connection.quote_ident(value.to_s)
|