active_record_extended 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +12 -18
- data/lib/active_record_extended.rb +2 -1
- data/lib/active_record_extended/active_record.rb +2 -0
- data/lib/active_record_extended/active_record/relation_patch.rb +1 -1
- data/lib/active_record_extended/arel.rb +1 -0
- data/lib/active_record_extended/arel/aggregate_function_name.rb +40 -0
- data/lib/active_record_extended/arel/nodes.rb +31 -41
- data/lib/active_record_extended/arel/predications.rb +4 -1
- data/lib/active_record_extended/arel/visitors/postgresql_decorator.rb +40 -1
- data/lib/active_record_extended/patch/5_0/predicate_builder_decorator.rb +4 -4
- data/lib/active_record_extended/patch/5_0/regex_match.rb +10 -0
- data/lib/active_record_extended/query_methods/any_of.rb +5 -4
- data/lib/active_record_extended/query_methods/inet.rb +1 -1
- data/lib/active_record_extended/query_methods/json.rb +152 -43
- data/lib/active_record_extended/query_methods/select.rb +117 -0
- data/lib/active_record_extended/query_methods/unionize.rb +4 -39
- data/lib/active_record_extended/query_methods/with_cte.rb +3 -3
- data/lib/active_record_extended/utilities/order_by.rb +96 -0
- data/lib/active_record_extended/utilities/support.rb +175 -0
- data/lib/active_record_extended/version.rb +1 -1
- data/spec/query_methods/any_of_spec.rb +40 -40
- data/spec/query_methods/array_query_spec.rb +14 -14
- data/spec/query_methods/either_spec.rb +14 -14
- data/spec/query_methods/hash_query_spec.rb +11 -11
- data/spec/query_methods/inet_query_spec.rb +33 -31
- data/spec/query_methods/json_spec.rb +40 -25
- data/spec/query_methods/select_spec.rb +115 -0
- data/spec/query_methods/unionize_spec.rb +54 -54
- data/spec/query_methods/with_cte_spec.rb +12 -12
- data/spec/sql_inspections/any_of_sql_spec.rb +11 -11
- data/spec/sql_inspections/arel/aggregate_function_name_spec.rb +41 -0
- data/spec/sql_inspections/arel/array_spec.rb +7 -7
- data/spec/sql_inspections/arel/inet_spec.rb +7 -7
- data/spec/sql_inspections/contains_sql_queries_spec.rb +14 -14
- data/spec/sql_inspections/either_sql_spec.rb +11 -11
- data/spec/sql_inspections/json_sql_spec.rb +38 -8
- data/spec/sql_inspections/unionize_sql_spec.rb +27 -27
- data/spec/sql_inspections/with_cte_sql_spec.rb +22 -22
- data/spec/support/models.rb +18 -4
- metadata +11 -3
- data/lib/active_record_extended/utilities.rb +0 -141
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b524c24c37e0b6f7449d70e5030b893119b98dec6646b905c3a307c9a671c7f1
|
4
|
+
data.tar.gz: 18cb235fba334ebfaa752b92ad024117f686176b0e9107af6333e317a333c70f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 78539255c169c26a1d6bab3968a2eed038d8391d532f97d55dabf7a7c071cddf4eee1424586021cd7b790db4abf042cf0617ac69ede670caeb9cde55a23125a4
|
7
|
+
data.tar.gz: 798ea355e5ebe213735db1552e7328d05c855c45800c941d4c5400a812b6d71f169ff52a48c0b9e6bf678d2dcf83ac1c7308a172b120caf9f83a240b0025e599
|
data/README.md
CHANGED
@@ -46,7 +46,6 @@ The only problem is that this has created a wild west of environments of sorts.
|
|
46
46
|
|
47
47
|
Active Record Extended is essentially providing users with the other half of Postgreses querying abilities. Due to Rails/ActiveRecord/Arel being designed to be DB agnostic, there are a lot of left out features; Either by choice or the simple lack of supporting API's for other databases. However some features are not exactly PG explicit. Some are just helper methods to express an idea much more easily.
|
48
48
|
|
49
|
-
|
50
49
|
## Compatibility
|
51
50
|
|
52
51
|
This package is designed align and work with any officially supported Ruby and Rails versions.
|
@@ -56,6 +55,18 @@ This package is designed align and work with any officially supported Ruby and R
|
|
56
55
|
- Latest Rails supported: 6.0.x
|
57
56
|
- Postgres: 9.6-current(11) (probably works with most older versions to a certain point)
|
58
57
|
|
58
|
+
## Installation
|
59
|
+
|
60
|
+
Add this line to your application's Gemfile:
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
gem 'active_record_extended'
|
64
|
+
```
|
65
|
+
|
66
|
+
And then execute:
|
67
|
+
|
68
|
+
$ bundle
|
69
|
+
|
59
70
|
## Usage
|
60
71
|
|
61
72
|
### Predicate Query Methods
|
@@ -776,23 +787,6 @@ SELECT "people".*
|
|
776
787
|
) ) ORDER BY personal_id DESC, id DESC) people
|
777
788
|
```
|
778
789
|
|
779
|
-
|
780
|
-
## Installation
|
781
|
-
|
782
|
-
Add this line to your application's Gemfile:
|
783
|
-
|
784
|
-
```ruby
|
785
|
-
gem 'active_record_extended'
|
786
|
-
```
|
787
|
-
|
788
|
-
And then execute:
|
789
|
-
|
790
|
-
$ bundle
|
791
|
-
|
792
|
-
Or install it yourself as:
|
793
|
-
|
794
|
-
$ gem install active_record_extended
|
795
|
-
|
796
790
|
## License
|
797
791
|
|
798
792
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -1,7 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_record_extended/version"
|
4
|
-
require "active_record_extended/utilities"
|
4
|
+
require "active_record_extended/utilities/support"
|
5
|
+
require "active_record_extended/utilities/order_by"
|
5
6
|
require "active_record_extended/active_record"
|
6
7
|
require "active_record_extended/arel"
|
7
8
|
|
@@ -21,9 +21,11 @@ require "active_record_extended/query_methods/any_of"
|
|
21
21
|
require "active_record_extended/query_methods/either"
|
22
22
|
require "active_record_extended/query_methods/inet"
|
23
23
|
require "active_record_extended/query_methods/json"
|
24
|
+
require "active_record_extended/query_methods/select"
|
24
25
|
|
25
26
|
if ActiveRecord::VERSION::MAJOR == 5 && ActiveRecord::VERSION::MINOR <= 1
|
26
27
|
if ActiveRecord::VERSION::MINOR.zero?
|
28
|
+
require "active_record_extended/patch/5_0/regex_match"
|
27
29
|
require "active_record_extended/patch/5_0/predicate_builder_decorator"
|
28
30
|
end
|
29
31
|
require "active_record_extended/patch/5_1/where_clause"
|
@@ -6,7 +6,7 @@ require "active_record_extended/query_methods/json"
|
|
6
6
|
module ActiveRecordExtended
|
7
7
|
module RelationPatch
|
8
8
|
module QueryDelegation
|
9
|
-
delegate :with, to: :all
|
9
|
+
delegate :with, :foster_select, to: :all
|
10
10
|
delegate(*::ActiveRecordExtended::QueryMethods::Unionize::UNIONIZE_METHODS, to: :all)
|
11
11
|
delegate(*::ActiveRecordExtended::QueryMethods::Json::JSON_QUERY_METHODS, to: :all)
|
12
12
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Arel
|
4
|
+
module Nodes
|
5
|
+
class AggregateFunctionName < ::Arel::Nodes::Node
|
6
|
+
include Arel::Predications
|
7
|
+
include Arel::WindowPredications
|
8
|
+
attr_accessor :name, :expressions, :distinct, :alias, :orderings
|
9
|
+
|
10
|
+
def initialize(name, expr, distinct = false)
|
11
|
+
super()
|
12
|
+
@name = name.to_s.upcase
|
13
|
+
@expressions = expr
|
14
|
+
@distinct = distinct
|
15
|
+
end
|
16
|
+
|
17
|
+
def order_by(expr)
|
18
|
+
@orderings = expr
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def as(aliaz)
|
23
|
+
self.alias = SqlLiteral.new(aliaz)
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def hash
|
28
|
+
[@name, @expressions, @distinct, @alias, @orderings].hash
|
29
|
+
end
|
30
|
+
|
31
|
+
def eql?(other)
|
32
|
+
self.class == other.class &&
|
33
|
+
expressions == other.expressions &&
|
34
|
+
orderings == other.orderings &&
|
35
|
+
distinct == other.distinct
|
36
|
+
end
|
37
|
+
alias == eql?
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -1,56 +1,46 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "arel/nodes/binary"
|
4
|
+
require "arel/nodes/function"
|
4
5
|
|
5
6
|
module Arel
|
6
7
|
module Nodes
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
8
|
+
%w[
|
9
|
+
Overlap
|
10
|
+
Contains
|
11
|
+
ContainsHStore
|
12
|
+
ContainsArray
|
13
|
+
ContainedInArray
|
14
|
+
].each { |binary_node_name| const_set(binary_node_name, Class.new(::Arel::Nodes::Binary)) }
|
15
|
+
|
16
|
+
%w[
|
17
|
+
RowToJson
|
18
|
+
JsonBuildObject
|
19
|
+
JsonbBuildObject
|
20
|
+
ToJson
|
21
|
+
ToJsonb
|
22
|
+
Array
|
23
|
+
ArrayAgg
|
24
|
+
].each do |function_node_name|
|
25
|
+
func_klass = Class.new(::Arel::Nodes::Function) do
|
26
|
+
def initialize(*args)
|
27
|
+
super
|
28
|
+
return if @expressions.is_a?(::Array)
|
29
|
+
@expressions = @expressions.is_a?(::Arel::Node) ? [@expressions] : [::Arel.sql(@expressions)]
|
28
30
|
end
|
29
31
|
end
|
30
|
-
end
|
31
32
|
|
32
|
-
|
33
|
-
def initialize(*args)
|
34
|
-
super
|
35
|
-
@expressions = Array(@expressions)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
class JsonbBuildObject < JsonBuildObject
|
33
|
+
const_set(function_node_name, func_klass)
|
40
34
|
end
|
41
35
|
|
42
36
|
module Inet
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
end
|
51
|
-
|
52
|
-
class ContainsOrContainedWithin < Arel::Nodes::Binary
|
53
|
-
end
|
37
|
+
%w[
|
38
|
+
Contains
|
39
|
+
ContainsEquals
|
40
|
+
ContainedWithin
|
41
|
+
ContainedWithinEquals
|
42
|
+
ContainsOrContainedWithin
|
43
|
+
].each { |binary_node_name| const_set(binary_node_name, Class.new(::Arel::Nodes::Binary)) }
|
54
44
|
end
|
55
45
|
end
|
56
46
|
end
|
@@ -21,12 +21,15 @@ module Arel
|
|
21
21
|
def contains(other)
|
22
22
|
Nodes::Contains.new self, Nodes.build_quoted(other, self)
|
23
23
|
end
|
24
|
-
alias inet_contains contains
|
25
24
|
|
26
25
|
def contained_in_array(other)
|
27
26
|
Nodes::ContainedInArray.new self, Nodes.build_quoted(other, self)
|
28
27
|
end
|
29
28
|
|
29
|
+
def inet_contains(other)
|
30
|
+
Nodes::Inet::Contains.new self, Nodes.build_quoted(other, self)
|
31
|
+
end
|
32
|
+
|
30
33
|
def inet_contains_or_is_contained_within(other)
|
31
34
|
Nodes::Inet::ContainsOrContainedWithin.new self, Nodes.build_quoted(other, self)
|
32
35
|
end
|
@@ -23,7 +23,7 @@ module ActiveRecordExtended
|
|
23
23
|
elsif left_column.try(:array)
|
24
24
|
visit_Arel_Nodes_ContainsArray(object, collector)
|
25
25
|
else
|
26
|
-
|
26
|
+
visit_Arel_Nodes_Inet_Contains(object, collector)
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
@@ -59,6 +59,45 @@ module ActiveRecordExtended
|
|
59
59
|
aggregate "JSONB_BUILD_OBJECT", object, collector
|
60
60
|
end
|
61
61
|
|
62
|
+
def visit_Arel_Nodes_ToJson(object, collector)
|
63
|
+
aggregate "TO_JSON", object, collector
|
64
|
+
end
|
65
|
+
|
66
|
+
def visit_Arel_Nodes_ToJsonb(object, collector)
|
67
|
+
aggregate "TO_JSONB", object, collector
|
68
|
+
end
|
69
|
+
|
70
|
+
def visit_Arel_Nodes_Array(object, collector)
|
71
|
+
aggregate "ARRAY", object, collector
|
72
|
+
end
|
73
|
+
|
74
|
+
def visit_Arel_Nodes_ArrayAgg(object, collector)
|
75
|
+
aggregate "ARRAY_AGG", object, collector
|
76
|
+
end
|
77
|
+
|
78
|
+
def visit_Arel_Nodes_AggregateFunctionName(object, collector)
|
79
|
+
collector << "#{object.name}("
|
80
|
+
collector << "DISTINCT " if object.distinct
|
81
|
+
collector = inject_join(object.expressions, collector, ", ")
|
82
|
+
|
83
|
+
if object.orderings
|
84
|
+
collector << " ORDER BY "
|
85
|
+
collector = inject_join(object.orderings, collector, ", ")
|
86
|
+
end
|
87
|
+
collector << ")"
|
88
|
+
|
89
|
+
if object.alias
|
90
|
+
collector << " AS "
|
91
|
+
visit object.alias, collector
|
92
|
+
else
|
93
|
+
collector
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def visit_Arel_Nodes_Inet_Contains(object, collector)
|
98
|
+
infix_value object, collector, " >> "
|
99
|
+
end
|
100
|
+
|
62
101
|
def visit_Arel_Nodes_Inet_ContainedWithinEquals(object, collector)
|
63
102
|
infix_value object, collector, " <<= "
|
64
103
|
end
|
@@ -6,20 +6,20 @@
|
|
6
6
|
#
|
7
7
|
# Without joins
|
8
8
|
# Before:
|
9
|
-
#
|
9
|
+
# User.where.contains(data: { nickname: "george" })
|
10
10
|
# #=> "SELECT \"people\".* FROM \"people\" WHERE (\"data\".\"nickname\" @> 'george')"
|
11
11
|
#
|
12
12
|
# After:
|
13
|
-
#
|
13
|
+
# User.where.contains(data: { nickname: "george" })
|
14
14
|
# #=> "SELECT \"people\".* FROM \"people\" WHERE (\"people\".\"data\" @> '\"nickname\"=>\"george\"')"
|
15
15
|
#
|
16
16
|
# With Joins
|
17
17
|
# Before:
|
18
|
-
# Tag.joins(:
|
18
|
+
# Tag.joins(:user).where.contains(people: { data: { nickname: "george" } })
|
19
19
|
# #=> NoMethodError: undefined method `type' for nil:NilClass
|
20
20
|
#
|
21
21
|
# After:
|
22
|
-
# Tag.joins(:
|
22
|
+
# Tag.joins(:user).where.contains(people: { data: { nickname: "george" } })
|
23
23
|
# #=> "SELECT \"tags\".* FROM \"tags\" INNER JOIN \"people\" ON \"people\".\"id\" = \"tags\".\"person_id\"
|
24
24
|
# WHERE (\"people\".\"data\" @> '\"nickname\"=>\"george\"')"
|
25
25
|
#
|
@@ -28,7 +28,7 @@ module ActiveRecordExtended
|
|
28
28
|
private
|
29
29
|
|
30
30
|
def hash_map_queries(queries)
|
31
|
-
if queries.
|
31
|
+
if queries.size == 1 && queries.first.is_a?(Hash)
|
32
32
|
queries.first.each_pair.map { |attr, predicate| Hash[attr, predicate] }
|
33
33
|
else
|
34
34
|
queries
|
@@ -38,9 +38,10 @@ module ActiveRecordExtended
|
|
38
38
|
def build_query(queries)
|
39
39
|
query_map = construct_query_mappings(queries)
|
40
40
|
query = yield(query_map[:arel_query], query_map[:binds])
|
41
|
-
query
|
42
|
-
|
43
|
-
|
41
|
+
query
|
42
|
+
.joins(query_map[:joins].to_a)
|
43
|
+
.includes(query_map[:includes].to_a)
|
44
|
+
.references(query_map[:references].to_a)
|
44
45
|
end
|
45
46
|
|
46
47
|
def construct_query_mappings(queries) # rubocop:disable Metrics/AbcSize
|
@@ -57,7 +57,7 @@ module ActiveRecordExtended
|
|
57
57
|
# #=> "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"ip\" >> '127.0.0.255/32'"
|
58
58
|
#
|
59
59
|
def inet_contains(opts, *rest)
|
60
|
-
substitute_comparisons(opts, rest, Arel::Nodes::Contains, "inet_contains")
|
60
|
+
substitute_comparisons(opts, rest, Arel::Nodes::Inet::Contains, "inet_contains")
|
61
61
|
end
|
62
62
|
|
63
63
|
# This method is a combination of `inet_contains` and `inet_contained_within`
|
@@ -12,35 +12,39 @@ module ActiveRecordExtended
|
|
12
12
|
].freeze
|
13
13
|
|
14
14
|
class JsonChain
|
15
|
-
include ::ActiveRecordExtended::Utilities
|
16
|
-
|
15
|
+
include ::ActiveRecordExtended::Utilities::Support
|
16
|
+
include ::ActiveRecordExtended::Utilities::OrderBy
|
17
|
+
|
18
|
+
DEFAULT_ALIAS = '"results"'
|
19
|
+
TO_JSONB_OPTIONS = [:array_agg, :distinct, :to_jsonb].to_set.freeze
|
20
|
+
ARRAY_OPTIONS = [:array, true].freeze
|
17
21
|
|
18
22
|
def initialize(scope)
|
19
23
|
@scope = scope
|
20
24
|
end
|
21
25
|
|
22
26
|
def row_to_json!(**args, &block)
|
23
|
-
options = json_object_options(args
|
27
|
+
options = json_object_options(args, except: [:values, :value])
|
24
28
|
build_row_to_json(**options, &block)
|
25
29
|
end
|
26
30
|
|
27
31
|
def json_build_object!(*args)
|
28
|
-
options = json_object_options(args
|
32
|
+
options = json_object_options(args, except: [:values, :cast_with, :order_by])
|
29
33
|
build_json_object(Arel::Nodes::JsonBuildObject, **options)
|
30
34
|
end
|
31
35
|
|
32
36
|
def jsonb_build_object!(*args)
|
33
|
-
options = json_object_options(args
|
37
|
+
options = json_object_options(args, except: [:values, :cast_with, :order_by])
|
34
38
|
build_json_object(Arel::Nodes::JsonbBuildObject, **options)
|
35
39
|
end
|
36
40
|
|
37
41
|
def json_build_literal!(*args)
|
38
|
-
options = json_object_options(args
|
42
|
+
options = json_object_options(args, only: [:values, :col_alias])
|
39
43
|
build_json_literal(Arel::Nodes::JsonBuildObject, **options)
|
40
44
|
end
|
41
45
|
|
42
46
|
def jsonb_build_literal!(*args)
|
43
|
-
options = json_object_options(args
|
47
|
+
options = json_object_options(args, only: [:values, :col_alias])
|
44
48
|
build_json_literal(Arel::Nodes::JsonbBuildObject, **options)
|
45
49
|
end
|
46
50
|
|
@@ -60,42 +64,83 @@ module ActiveRecordExtended
|
|
60
64
|
col_value = to_arel_sql(value.presence || tbl_alias)
|
61
65
|
json_build_object = arel_klass.new(to_sql_array(col_key, col_value))
|
62
66
|
|
63
|
-
|
64
|
-
unless col_value.index(/".+"/)
|
67
|
+
unless /".+"/.match?(col_value)
|
65
68
|
warn("`#{col_value}`: the `value` argument should contain a double quoted key reference for safety")
|
66
69
|
end
|
67
70
|
|
68
71
|
@scope.select(nested_alias_escape(json_build_object, col_alias)).from(nested_alias_escape(from, tbl_alias))
|
69
72
|
end
|
70
73
|
|
71
|
-
def build_row_to_json(from:,
|
72
|
-
|
74
|
+
def build_row_to_json(from:, **options, &block)
|
75
|
+
key = options[:key]
|
76
|
+
row_to_json = ::Arel::Nodes::RowToJson.new(double_quote(key))
|
77
|
+
row_to_json = ::Arel::Nodes::ToJsonb.new(row_to_json) if options.dig(:cast_with, :to_jsonb)
|
78
|
+
|
73
79
|
dummy_table = from_clause_constructor(from, key).select(row_to_json)
|
74
|
-
dummy_table =
|
80
|
+
dummy_table = dummy_table.instance_eval(&block) if block_given?
|
81
|
+
return dummy_table if options[:col_alias].blank?
|
82
|
+
|
83
|
+
query = wrap_row_to_json(dummy_table, options)
|
84
|
+
@scope.select(query)
|
85
|
+
end
|
75
86
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
87
|
+
def wrap_row_to_json(dummy_table, options)
|
88
|
+
cast_opts = options[:cast_with]
|
89
|
+
col_alias = options[:col_alias]
|
90
|
+
order_by = options[:order_by]
|
91
|
+
|
92
|
+
if cast_opts[:array_agg] || cast_opts[:distinct]
|
93
|
+
wrap_with_agg_array(dummy_table, col_alias, order_by: order_by, distinct: cast_opts[:distinct])
|
94
|
+
elsif cast_opts[:array]
|
95
|
+
wrap_with_array(dummy_table, col_alias, order_by: order_by)
|
80
96
|
else
|
81
|
-
|
97
|
+
nested_alias_escape(dummy_table, col_alias)
|
82
98
|
end
|
83
99
|
end
|
84
100
|
|
85
|
-
|
86
|
-
|
101
|
+
# TODO: [V2 release] Drop support for option :cast_as_array in favor of a more versatile :cast_with option
|
102
|
+
def json_object_options(args, except: [], only: []) # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
|
103
|
+
options = {}
|
104
|
+
lean_opts = lambda do |key, &block|
|
105
|
+
if only.present?
|
106
|
+
options[key] ||= block.call if only.include?(key)
|
107
|
+
elsif !except.include?(key)
|
108
|
+
options[key] ||= block.call
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
flatten_safely(args) do |arg|
|
87
113
|
next if arg.nil?
|
88
114
|
|
89
115
|
if arg.is_a?(Hash)
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
116
|
+
lean_opts.call(:key) { arg.delete(:key) || key_generator }
|
117
|
+
lean_opts.call(:value) { arg.delete(:value).presence }
|
118
|
+
lean_opts.call(:col_alias) { arg.delete(:as) }
|
119
|
+
lean_opts.call(:order_by) { order_by_expression(arg.delete(:order_by)) }
|
120
|
+
lean_opts.call(:from) { arg.delete(:from).tap(&method(:pipe_cte_with!)) }
|
121
|
+
lean_opts.call(:cast_with) { casting_options(arg.delete(:cast_with) || arg.delete(:cast_as_array)) }
|
95
122
|
end
|
96
123
|
|
97
|
-
|
98
|
-
|
124
|
+
unless except.include?(:values)
|
125
|
+
options[:values] ||= []
|
126
|
+
options[:values] << (arg.respond_to?(:to_a) ? arg.to_a : arg)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
options.tap(&:compact!)
|
131
|
+
end
|
132
|
+
|
133
|
+
def casting_options(cast_with)
|
134
|
+
return {} if cast_with.blank?
|
135
|
+
|
136
|
+
skip_convert = [Symbol, TrueClass, FalseClass]
|
137
|
+
Array(cast_with).compact.each_with_object({}) do |arg, options|
|
138
|
+
arg = arg.to_sym unless skip_convert.include?(arg.class)
|
139
|
+
options[:to_jsonb] |= TO_JSONB_OPTIONS.include?(arg)
|
140
|
+
options[:array] |= ARRAY_OPTIONS.include?(arg)
|
141
|
+
options[:array_agg] |= arg == :array_agg
|
142
|
+
options[:distinct] |= arg == :distinct
|
143
|
+
end
|
99
144
|
end
|
100
145
|
end
|
101
146
|
|
@@ -111,8 +156,21 @@ module ActiveRecordExtended
|
|
111
156
|
# - This is useful if you would like to add additional mid-level clauses (see mid-level scope example)
|
112
157
|
#
|
113
158
|
# - cast_as_array [boolean] (default=false): Determines if the query should be nested inside an Array() function
|
159
|
+
# * Will be deprecated in V2.0 in favor of `cast_with` argument
|
114
160
|
#
|
115
|
-
#
|
161
|
+
# - cast_with [Symbol or Array of symbols]: Actions to transform your query
|
162
|
+
# * :to_jsonb
|
163
|
+
# * :array
|
164
|
+
# * :array_agg (including just :array with this option will favor :array_agg)
|
165
|
+
# * :distinct (auto applies :array_agg & :to_jsonb)
|
166
|
+
#
|
167
|
+
# - order_by [Symbol or hash]: Applies an ordering operation (similar to ActiveRecord #order)
|
168
|
+
# - NOTE: this option will be ignored if you need to order a DISTINCT Aggregated Array,
|
169
|
+
# since postgres will thrown an error.
|
170
|
+
#
|
171
|
+
#
|
172
|
+
#
|
173
|
+
# Examples:
|
116
174
|
# subquery = Group.select(:name, :category_id).where("user_id = users.id")
|
117
175
|
# User.select(:name, email).select_row_to_json(subquery, as: :users_groups, cast_as_array: true)
|
118
176
|
# #=> [<#User name:.., email:.., users_groups: [{ name: .., category_id: .. }, ..]]
|
@@ -123,8 +181,74 @@ module ActiveRecordExtended
|
|
123
181
|
# User.select_row_to_json(subquery, key: :group, cast_as_array: true) do |scope|
|
124
182
|
# scope.where(group: { name: "Nerd Core" })
|
125
183
|
# end
|
184
|
+
# #=> ```sql
|
185
|
+
# SELECT ARRAY(
|
186
|
+
# SELECT ROW_TO_JSON("group")
|
187
|
+
# FROM(SELECT name, category_id FROM groups) AS group
|
188
|
+
# WHERE group.name = 'Nerd Core'
|
189
|
+
# )
|
190
|
+
# ```
|
191
|
+
#
|
192
|
+
#
|
193
|
+
# - Array of JSONB objects
|
194
|
+
#
|
195
|
+
# subquery = Group.select(:name, :category_id)
|
196
|
+
# User.select_row_to_json(subquery, key: :group, cast_with: [:array, :to_jsonb]) do |scope|
|
197
|
+
# scope.where(group: { name: "Nerd Core" })
|
198
|
+
# end
|
199
|
+
# #=> ```sql
|
200
|
+
# SELECT ARRAY(
|
201
|
+
# SELECT TO_JSONB(ROW_TO_JSON("group"))
|
202
|
+
# FROM(SELECT name, category_id FROM groups) AS group
|
203
|
+
# WHERE group.name = 'Nerd Core'
|
204
|
+
# )
|
205
|
+
# ```
|
206
|
+
#
|
207
|
+
# - Distinct Aggregated Array
|
208
|
+
#
|
209
|
+
# subquery = Group.select(:name, :category_id)
|
210
|
+
# User.select_row_to_json(subquery, key: :group, cast_with: [:array_agg, :distinct]) do |scope|
|
211
|
+
# scope.where(group: { name: "Nerd Core" })
|
212
|
+
# end
|
213
|
+
# #=> ```sql
|
214
|
+
# SELECT ARRAY_AGG(DISTINCT (
|
215
|
+
# SELECT TO_JSONB(ROW_TO_JSON("group"))
|
216
|
+
# FROM(SELECT name, category_id FROM groups) AS group
|
217
|
+
# WHERE group.name = 'Nerd Core'
|
218
|
+
# ))
|
219
|
+
# ```
|
220
|
+
#
|
221
|
+
# - Ordering a Non-aggregated Array
|
222
|
+
#
|
223
|
+
# subquery = Group.select(:name, :category_id)
|
224
|
+
# User.select_row_to_json(subquery, key: :group, cast_with: :array, order_by: { group: { name: :desc } })
|
225
|
+
# #=> ```sql
|
226
|
+
# SELECT ARRAY(
|
227
|
+
# SELECT ROW_TO_JSON("group")
|
228
|
+
# FROM(SELECT name, category_id FROM groups) AS group
|
229
|
+
# ORDER BY group.name DESC
|
230
|
+
# )
|
231
|
+
# ```
|
232
|
+
#
|
233
|
+
# - Ordering an Aggregated Array
|
234
|
+
#
|
235
|
+
# Subquery = Group.select(:name, :category_id)
|
236
|
+
# User
|
237
|
+
# .joins(:people_groups)
|
238
|
+
# .select_row_to_json(
|
239
|
+
# subquery,
|
240
|
+
# key: :group,
|
241
|
+
# cast_with: :array_agg,
|
242
|
+
# order_by: { people_groups: :category_id }
|
243
|
+
# )
|
244
|
+
# #=> ```sql
|
245
|
+
# SELECT ARRAY_AGG((
|
246
|
+
# SELECT ROW_TO_JSON("group")
|
247
|
+
# FROM(SELECT name, category_id FROM groups) AS group
|
248
|
+
# ORDER BY group.name DESC
|
249
|
+
# ) ORDER BY people_groups.category_id ASC)
|
250
|
+
# ```
|
126
251
|
#
|
127
|
-
|
128
252
|
def select_row_to_json(from = nil, **options, &block)
|
129
253
|
from.is_a?(Hash) ? options.merge!(from) : options.reverse_merge!(from: from)
|
130
254
|
options.compact!
|
@@ -163,21 +287,6 @@ module ActiveRecordExtended
|
|
163
287
|
# .take
|
164
288
|
# .results["gang_members"] #=> "BANG!"
|
165
289
|
#
|
166
|
-
#
|
167
|
-
# - Adding mid-level scopes
|
168
|
-
#
|
169
|
-
# subquery = Group.select(:name, :category_id)
|
170
|
-
# User.select_row_to_json(subquery, key: :group, cast_as_array: true) do |scope|
|
171
|
-
# scope.where(group: { name: "Nerd Core" })
|
172
|
-
# end #=> ```sql
|
173
|
-
# SELECT ARRAY(
|
174
|
-
# SELECT ROW_TO_JSON("group")
|
175
|
-
# FROM(SELECT name, category_id FROM groups) AS group
|
176
|
-
# WHERE group.name = 'Nerd Core'
|
177
|
-
# )
|
178
|
-
# ```
|
179
|
-
#
|
180
|
-
|
181
290
|
def json_build_object(key, from, **options)
|
182
291
|
options[:key] = key
|
183
292
|
options[:from] = from
|