active_record_extended_telescope 2.0.1

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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +870 -0
  3. data/lib/active_record_extended.rb +10 -0
  4. data/lib/active_record_extended/active_record.rb +25 -0
  5. data/lib/active_record_extended/active_record/relation_patch.rb +50 -0
  6. data/lib/active_record_extended/arel.rb +7 -0
  7. data/lib/active_record_extended/arel/aggregate_function_name.rb +40 -0
  8. data/lib/active_record_extended/arel/nodes.rb +49 -0
  9. data/lib/active_record_extended/arel/predications.rb +50 -0
  10. data/lib/active_record_extended/arel/sql_literal.rb +16 -0
  11. data/lib/active_record_extended/arel/visitors/postgresql_decorator.rb +122 -0
  12. data/lib/active_record_extended/patch/5_1/where_clause.rb +11 -0
  13. data/lib/active_record_extended/patch/5_2/where_clause.rb +11 -0
  14. data/lib/active_record_extended/predicate_builder/array_handler_decorator.rb +20 -0
  15. data/lib/active_record_extended/query_methods/any_of.rb +93 -0
  16. data/lib/active_record_extended/query_methods/either.rb +62 -0
  17. data/lib/active_record_extended/query_methods/inet.rb +88 -0
  18. data/lib/active_record_extended/query_methods/json.rb +329 -0
  19. data/lib/active_record_extended/query_methods/select.rb +118 -0
  20. data/lib/active_record_extended/query_methods/unionize.rb +249 -0
  21. data/lib/active_record_extended/query_methods/where_chain.rb +132 -0
  22. data/lib/active_record_extended/query_methods/window.rb +93 -0
  23. data/lib/active_record_extended/query_methods/with_cte.rb +150 -0
  24. data/lib/active_record_extended/utilities/order_by.rb +77 -0
  25. data/lib/active_record_extended/utilities/support.rb +178 -0
  26. data/lib/active_record_extended/version.rb +5 -0
  27. data/lib/active_record_extended_telescope.rb +4 -0
  28. data/spec/active_record_extended_spec.rb +7 -0
  29. data/spec/query_methods/any_of_spec.rb +131 -0
  30. data/spec/query_methods/array_query_spec.rb +64 -0
  31. data/spec/query_methods/either_spec.rb +59 -0
  32. data/spec/query_methods/hash_query_spec.rb +45 -0
  33. data/spec/query_methods/inet_query_spec.rb +112 -0
  34. data/spec/query_methods/json_spec.rb +157 -0
  35. data/spec/query_methods/select_spec.rb +115 -0
  36. data/spec/query_methods/unionize_spec.rb +165 -0
  37. data/spec/query_methods/window_spec.rb +51 -0
  38. data/spec/query_methods/with_cte_spec.rb +50 -0
  39. data/spec/spec_helper.rb +28 -0
  40. data/spec/sql_inspections/any_of_sql_spec.rb +41 -0
  41. data/spec/sql_inspections/arel/aggregate_function_name_spec.rb +41 -0
  42. data/spec/sql_inspections/arel/array_spec.rb +63 -0
  43. data/spec/sql_inspections/arel/inet_spec.rb +66 -0
  44. data/spec/sql_inspections/contains_sql_queries_spec.rb +47 -0
  45. data/spec/sql_inspections/either_sql_spec.rb +55 -0
  46. data/spec/sql_inspections/json_sql_spec.rb +82 -0
  47. data/spec/sql_inspections/unionize_sql_spec.rb +124 -0
  48. data/spec/sql_inspections/window_sql_spec.rb +98 -0
  49. data/spec/sql_inspections/with_cte_sql_spec.rb +95 -0
  50. data/spec/support/database_cleaner.rb +15 -0
  51. data/spec/support/models.rb +68 -0
  52. metadata +245 -0
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecordExtended
4
+ VERSION = "2.0.1"
5
+ end
@@ -0,0 +1,4 @@
1
+ require_relative "./active_record_extended"
2
+ module ActiveRecordExtendedTelescope
3
+ VERSION = "2.0.1"
4
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe ActiveRecordExtended do
4
+ it "has a version number" do
5
+ expect(ActiveRecordExtended::VERSION).not_to be nil
6
+ end
7
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe "Active Record Any / None of Methods" do
6
+ let!(:one) { User.create!(personal_id: 1) }
7
+ let!(:two) { User.create!(personal_id: 2) }
8
+ let!(:three) { User.create!(personal_id: 3) }
9
+
10
+ let!(:tag_one) { Tag.create!(user_id: one.id) }
11
+ let!(:tag_two) { Tag.create!(user_id: two.id) }
12
+ let!(:tag_three) { Tag.create!(user_id: three.id) }
13
+
14
+ describe "where.any_of/1" do
15
+ it "Should return queries that match any of the outlined queries" do
16
+ query = User.where.any_of({ personal_id: 1 }, { personal_id: 2 })
17
+ expect(query).to include(one, two)
18
+ expect(query).to_not include(three)
19
+ end
20
+
21
+ it "Should accept where query predicates" do
22
+ personal_one = User.where(personal_id: 1)
23
+ personal_two = User.where(personal_id: 2)
24
+ query = User.where.any_of(personal_one, personal_two)
25
+
26
+ expect(query).to include(one, two)
27
+ expect(query).to_not include(three)
28
+ end
29
+
30
+ it "Should accept query strings" do
31
+ personal_one = User.where(personal_id: 1)
32
+
33
+ query = User.where.any_of(personal_one, "personal_id > 2")
34
+ expect(query).to include(one, three)
35
+ expect(query).to_not include(two)
36
+
37
+ query = User.where.any_of(["personal_id >= ?", 2])
38
+ expect(query).to include(two, three)
39
+ expect(query).to_not include(one)
40
+ end
41
+
42
+ context "Relationship queries" do
43
+ it "Finds records that are queried from two or more has_many associations" do
44
+ user_one_tag = Tag.create!(user_id: one.id)
45
+ user_two_tag = Tag.create!(user_id: two.id)
46
+ query = Tag.where.any_of(one.hm_tags, two.hm_tags)
47
+
48
+ expect(query).to include(tag_one, tag_two, user_one_tag, user_two_tag)
49
+ expect(query).to_not include(tag_three)
50
+ end
51
+
52
+ it "Finds records that are dynamically joined" do
53
+ user_one_tag = Tag.where(users: { id: one.id }).includes(:user).references(:user)
54
+ user_two_tag = Tag.where(users: { id: two.id }).joins(:user)
55
+ query = Tag.where.any_of(user_one_tag, user_two_tag)
56
+
57
+ expect(query).to include(tag_one, tag_two)
58
+ expect(query).to_not include(tag_three)
59
+ end
60
+
61
+ it "Return matched records of a joined table on the parent level" do
62
+ query = Tag.joins(:user).where.any_of(
63
+ { users: { personal_id: 1 } },
64
+ { users: { personal_id: 3 } }
65
+ )
66
+
67
+ expect(query).to include(tag_one, tag_three)
68
+ expect(query).to_not include(tag_two)
69
+ end
70
+ end
71
+ end
72
+
73
+ describe "where.none_of/1" do
74
+ it "Should return queries that match none of the outlined queries" do
75
+ query = User.where.none_of({ personal_id: 1 }, { personal_id: 2 })
76
+ expect(query).to include(three)
77
+ expect(query).to_not include(one, two)
78
+ end
79
+
80
+ it "Should accept where query predicates" do
81
+ personal_one = User.where(personal_id: 1)
82
+ personal_two = User.where(personal_id: 2)
83
+ query = User.where.none_of(personal_one, personal_two)
84
+
85
+ expect(query).to include(three)
86
+ expect(query).to_not include(one, two)
87
+ end
88
+
89
+ it "Should accept query strings" do
90
+ personal_one = User.where(personal_id: 1)
91
+
92
+ query = User.where.none_of(personal_one, "personal_id > 2")
93
+ expect(query).to include(two)
94
+ expect(query).to_not include(one, three)
95
+
96
+ query = User.where.none_of(["personal_id >= ?", 2])
97
+ expect(query).to include(one)
98
+ expect(query).to_not include(two, three)
99
+ end
100
+
101
+ context "Relationship queries" do
102
+ it "Finds records that are queried from two or more has_many associations" do
103
+ user_one_tag = Tag.create!(user_id: one.id)
104
+ user_two_tag = Tag.create!(user_id: two.id)
105
+ query = Tag.where.none_of(one.hm_tags, two.hm_tags)
106
+
107
+ expect(query).to include(tag_three)
108
+ expect(query).to_not include(tag_one, tag_two, user_one_tag, user_two_tag)
109
+ end
110
+
111
+ it "Finds records that are dynamically joined" do
112
+ user_one_tag = Tag.where(users: { id: one.id }).includes(:user).references(:user)
113
+ user_two_tag = Tag.where(users: { id: two.id }).joins(:user)
114
+ query = Tag.where.none_of(user_one_tag, user_two_tag)
115
+
116
+ expect(query).to include(tag_three)
117
+ expect(query).to_not include(tag_one, tag_two)
118
+ end
119
+
120
+ it "Return matched records of a joined table on the parent level" do
121
+ query = Tag.joins(:user).where.none_of(
122
+ { users: { personal_id: 1 } },
123
+ { users: { personal_id: 3 } }
124
+ )
125
+
126
+ expect(query).to include(tag_two)
127
+ expect(query).to_not include(tag_one, tag_three)
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe "Active Record Array Query Methods" do
6
+ let!(:one) { User.create!(tags: [1, 2, 3], personal_id: 33) }
7
+ let!(:two) { User.create!(tags: [3, 1, 5], personal_id: 88) }
8
+ let!(:three) { User.create!(tags: [2, 8, 20], personal_id: 33) }
9
+
10
+ describe "#overlap" do
11
+ it "Should return matched records" do
12
+ query = User.where.overlap(tags: [1])
13
+ expect(query).to include(one, two)
14
+ expect(query).to_not include(three)
15
+
16
+ query = User.where.overlap(tags: [2, 3])
17
+ expect(query).to include(one, two, three)
18
+ end
19
+ end
20
+
21
+ describe "#contains" do
22
+ it "returns records that contain elements in an array" do
23
+ query = User.where.contains(tags: [1, 3])
24
+ expect(query).to include(one, two)
25
+ expect(query).to_not include(three)
26
+
27
+ query = User.where.contains(tags: [8, 2])
28
+ expect(query).to include(three)
29
+ expect(query).to_not include(one, two)
30
+ end
31
+ end
32
+
33
+ describe "#any" do
34
+ it "should return any records that match" do
35
+ query = User.where.any(tags: 3)
36
+ expect(query).to include(one, two)
37
+ expect(query).to_not include(three)
38
+ end
39
+
40
+ it "allows chaining" do
41
+ query = User.where.any(tags: 3).where(personal_id: 33)
42
+ expect(query).to include(one)
43
+ expect(query).to_not include(two, three)
44
+ end
45
+ end
46
+
47
+ describe "#all" do
48
+ let!(:contains_all) { User.create!(tags: [1], personal_id: 1) }
49
+ let!(:contains_all_two) { User.create!(tags: [1], personal_id: 2) }
50
+ let!(:contains_some) { User.create!(tags: [1, 2], personal_id: 2) }
51
+
52
+ it "should return any records that match" do
53
+ query = User.where.all(tags: 1)
54
+ expect(query).to include(contains_all, contains_all_two)
55
+ expect(query).to_not include(contains_some)
56
+ end
57
+
58
+ it "allows chaining" do
59
+ query = User.where.all(tags: 1).where(personal_id: 1)
60
+ expect(query).to include(contains_all)
61
+ expect(query).to_not include(contains_all_two, contains_some)
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe "Active Record Either Methods" do
6
+ let!(:one) { User.create! }
7
+ let!(:two) { User.create! }
8
+ let!(:three) { User.create! }
9
+ let!(:profile_l) { ProfileL.create!(user_id: one.id, likes: 100) }
10
+ let!(:profile_r) { ProfileR.create!(user_id: two.id, dislikes: 50) }
11
+
12
+ describe ".either_join/2" do
13
+ it "Should only only return records that belong to profile L or profile R" do
14
+ query = User.either_join(:profile_l, :profile_r)
15
+ expect(query).to include(one, two)
16
+ expect(query).to_not include(three)
17
+ end
18
+
19
+ context "Alias .either_joins/2" do
20
+ it "Should only only return records that belong to profile L or profile R" do
21
+ query = User.either_joins(:profile_l, :profile_r)
22
+ expect(query).to include(one, two)
23
+ expect(query).to_not include(three)
24
+ end
25
+ end
26
+ end
27
+
28
+ describe ".either_order/2" do
29
+ it "Should not exclude anyone who does not have a relationship" do
30
+ query = User.either_order(:asc, profile_l: :likes, profile_r: :dislikes)
31
+ expect(query.count).to eq(3)
32
+ expect(query[0]).to eq(two)
33
+ expect(query[1]).to eq(one)
34
+ expect(query[2]).to eq(three)
35
+ end
36
+
37
+ it "Should order users based on their likes and dislikes in ascended order" do
38
+ query = User.either_order(:asc, profile_l: :likes, profile_r: :dislikes).where(id: [one.id, two.id])
39
+ expect(query.count).to eq(2)
40
+ expect(query.first).to eq(two)
41
+ expect(query.last).to eq(one)
42
+ end
43
+
44
+ it "Should order users based on their likes and dislikes in descending order" do
45
+ query = User.either_order(:desc, profile_l: :likes, profile_r: :dislikes).where(id: [one.id, two.id])
46
+ expect(query.first).to eq(one)
47
+ expect(query.last).to eq(two)
48
+ end
49
+
50
+ context "Alias .either_order/2" do
51
+ it "Should order users based on their likes and dislikes in ascended order" do
52
+ query = User.either_orders(:asc, profile_l: :likes, profile_r: :dislikes).where(id: [one.id, two.id])
53
+ expect(query.count).to eq(2)
54
+ expect(query.first).to eq(two)
55
+ expect(query.last).to eq(one)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe "Active Record Hash Related Query Methods" do
6
+ let!(:one) { User.create!(data: { nickname: "george" }, jsonb_data: { payment: "zip" }) }
7
+ let!(:two) { User.create!(data: { nickname: "dan" }, jsonb_data: { payment: "zipper" }) }
8
+ let!(:three) { User.create!(data: { nickname: "georgey" }) }
9
+
10
+ describe "#contains" do
11
+ context "HStore Column Type" do
12
+ it "returns records that contain hash elements in joined tables" do
13
+ tag_one = Tag.create!(user_id: one.id)
14
+ tag_two = Tag.create!(user_id: two.id)
15
+
16
+ query = Tag.joins(:user).where.contains(users: { data: { nickname: "george" } })
17
+ expect(query).to include(tag_one)
18
+ expect(query).to_not include(tag_two)
19
+ end
20
+
21
+ it "returns records that contain hash value" do
22
+ query = User.where.contains(data: { nickname: "george" })
23
+ expect(query).to include(one)
24
+ expect(query).to_not include(two, three)
25
+ end
26
+ end
27
+
28
+ context "JSONB Column Type" do
29
+ it "returns records that contains a json hashed value" do
30
+ query = User.where.contains(jsonb_data: { payment: "zip" })
31
+ expect(query).to include(one)
32
+ expect(query).to_not include(two, three)
33
+ end
34
+
35
+ it "returns records that contain jsonb elements in joined tables" do
36
+ tag_one = Tag.create!(user_id: one.id)
37
+ tag_two = Tag.create!(user_id: two.id)
38
+
39
+ query = Tag.joins(:user).where.contains(users: { jsonb_data: { payment: "zip" } })
40
+ expect(query).to include(tag_one)
41
+ expect(query).to_not include(tag_two)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe "Active Record Inet Query Methods" do
6
+ before { stub_const("User", Namespaced::Record) }
7
+
8
+ describe "#inet_contained_within" do
9
+ let!(:local_1) { User.create!(ip: "127.0.0.1") }
10
+ let!(:local_44) { User.create!(ip: "127.0.0.44") }
11
+ let!(:local_99_1) { User.create!(ip: "127.0.99.1") }
12
+ let!(:local_range) { User.create!(ip: "127.0.0.1/10") }
13
+
14
+ it "Should return users who have an IP within the 127.0.0.1/24 range" do
15
+ query = User.where.inet_contained_within(ip: "127.0.0.1/24")
16
+ expect(query).to include(local_1, local_44)
17
+ expect(query).to_not include(local_99_1, local_range)
18
+ end
19
+
20
+ it "Should return all users who have an IP within the 127.0.0.1/16 range" do
21
+ query = User.where.inet_contained_within(ip: "127.0.0.1/16")
22
+ expect(query).to include(local_1, local_44, local_99_1)
23
+ expect(query).to_not include(local_range)
24
+ end
25
+ end
26
+
27
+ describe "inet_contained_within_or_equals" do
28
+ let!(:local_1) { User.create!(ip: "127.0.0.1/10") }
29
+ let!(:local_44) { User.create!(ip: "127.0.0.44/32") }
30
+ let!(:local_99_1) { User.create!(ip: "127.0.99.1") }
31
+
32
+ it "Should find records that contain a matching submask" do
33
+ query = User.where.inet_contained_within_or_equals(ip: "127.0.0.44/32")
34
+ expect(query).to include(local_44)
35
+ expect(query).to_not include(local_1, local_99_1)
36
+ end
37
+
38
+ it "Should find records that are within range of a given submask" do
39
+ query = User.where.inet_contained_within_or_equals(ip: "127.0.0.1/16")
40
+ expect(query).to include(local_44, local_99_1)
41
+ expect(query).to_not include(local_1)
42
+
43
+ query = User.where.inet_contained_within_or_equals(ip: "127.0.0.1/8")
44
+ expect(query).to include(local_1, local_44, local_99_1)
45
+ end
46
+ end
47
+
48
+ describe "#inet_contains_or_equals" do
49
+ let!(:local_1) { User.create!(ip: "127.0.0.1/10") }
50
+ let!(:local_44) { User.create!(ip: "127.0.0.44/24") }
51
+ let!(:local_99_1) { User.create!(ip: "127.0.99.1") }
52
+
53
+ it "Should find records with submask ranges that contain a given IP" do
54
+ query = User.where.inet_contains_or_equals(ip: "127.0.255.255")
55
+ expect(query).to include(local_1)
56
+ expect(query).to_not include(local_44, local_99_1)
57
+
58
+ query = User.where.inet_contains_or_equals(ip: "127.0.0.255")
59
+ expect(query).to include(local_1, local_44)
60
+ expect(query).to_not include(local_99_1)
61
+ end
62
+
63
+ it "Finds records when querying with a submasked value" do
64
+ query = User.where.inet_contains_or_equals(ip: "127.0.0.1/10")
65
+ expect(query).to include(local_1)
66
+ expect(query).to_not include(local_44, local_99_1)
67
+
68
+ query = User.where.inet_contains_or_equals(ip: "127.0.0.1/32")
69
+ expect(query).to include(local_1, local_44)
70
+ expect(query).to_not include(local_99_1)
71
+ end
72
+ end
73
+
74
+ describe "#inet_contains" do
75
+ let!(:local_1) { User.create!(ip: "127.0.0.1/10") }
76
+ let!(:local_44) { User.create!(ip: "127.0.0.44/24") }
77
+
78
+ it "Should find records that the given IP falls within" do
79
+ query = User.where.inet_contains(ip: "127.0.0.1")
80
+ expect(query).to include(local_1, local_44)
81
+ end
82
+
83
+ it "Should not find records when querying with a submasked value" do
84
+ query = User.where.inet_contains(ip: "127.0.0.0/8")
85
+ expect(query).to be_empty
86
+ end
87
+ end
88
+
89
+ describe "#inet_contains_or_is_contained_within" do
90
+ let!(:local_1) { User.create!(ip: "127.0.0.1/24") }
91
+ let!(:local_44) { User.create!(ip: "127.0.22.44/8") }
92
+ let!(:local_99_1) { User.create!(ip: "127.0.99.1") }
93
+
94
+ it "should find records where the records contain the given IP" do
95
+ query = User.where.inet_contains_or_is_contained_within(ip: "127.0.255.80")
96
+ expect(query).to include(local_44)
97
+ expect(query).to_not include(local_1, local_99_1)
98
+
99
+ query = User.where.inet_contains_or_is_contained_within(ip: "127.0.0.80")
100
+ expect(query).to include(local_1, local_44)
101
+ expect(query).to_not include(local_99_1)
102
+ end
103
+
104
+ it "Should find records that the where query contains a valid range" do
105
+ query = User.where.inet_contains_or_is_contained_within(ip: "127.0.0.80/8")
106
+ expect(query).to include(local_1, local_44, local_99_1)
107
+
108
+ query = User.where.inet_contains_or_is_contained_within(ip: "127.0.0.80/16")
109
+ expect(query).to include(local_1, local_44, local_99_1)
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "Active Record JSON methods" do
4
+ let!(:user_one) { User.create! }
5
+ let!(:user_two) { User.create! }
6
+
7
+ describe ".select_row_to_json" do
8
+ let!(:tag_one) { Tag.create!(user: user_one, tag_number: 2) }
9
+ let!(:tag_two) { Tag.create!(user: user_two, tag_number: 5) }
10
+ let(:sub_query) { Tag.select(:tag_number).where("tags.user_id = users.id") }
11
+
12
+ it "should nest a json object in the query results" do
13
+ query = User.select(:id).select_row_to_json(sub_query, as: :results).where(id: user_one.id)
14
+ expect(query.size).to eq(1)
15
+ expect(query.take.results).to be_a(Hash).and(match("tag_number" => 2))
16
+ end
17
+
18
+ # ugh wording here sucks, brain is fried.
19
+ it "accepts a block for appending additional scopes to the middle-top level" do
20
+ query = User.select(:id).select_row_to_json(sub_query, key: :tag_row, as: :results) do |scope|
21
+ scope.where("tag_row.tag_number = 5")
22
+ end
23
+
24
+ expect(query.size).to eq(2)
25
+ query.each do |result|
26
+ if result.id == user_one.id
27
+ expect(result.results).to be_blank
28
+ else
29
+ expect(result.results).to be_present.and(match("tag_number" => 5))
30
+ end
31
+ end
32
+ end
33
+
34
+ it "accepts a scope-block without arguments" do
35
+ query = User.select(:id).select_row_to_json(sub_query, key: :tag_row, as: :results) do
36
+ where("tag_row.tag_number = 5")
37
+ end
38
+
39
+ expect(query.size).to eq(2)
40
+ query.each do |result|
41
+ if result.id == user_one.id
42
+ expect(result.results).to be_blank
43
+ else
44
+ expect(result.results).to be_present.and(match("tag_number" => 5))
45
+ end
46
+ end
47
+ end
48
+
49
+ it "allows for casting results in an aggregate-able Array function" do
50
+ query = User.select(:id).select_row_to_json(sub_query, key: :tag_row, as: :results, cast_with: :array)
51
+ expect(query.take.results).to be_a(Array).and(be_present)
52
+ expect(query.take.results.first).to be_a(Hash)
53
+ end
54
+
55
+ it "raises an error if a from clause key is missing" do
56
+ expect do
57
+ User.select(:id).select_row_to_json(key: :tag_row, as: :results)
58
+ end.to raise_error(ArgumentError)
59
+ end
60
+ end
61
+
62
+ describe ".json_build_object" do
63
+ let(:sub_query) do
64
+ User.select_row_to_json(from: User.select(:id), cast_with: :array, as: :ids).where(id: user_one.id)
65
+ end
66
+
67
+ it "defaults the column alias if one is not provided" do
68
+ query = User.json_build_object(:personal, sub_query)
69
+ expect(query.size).to eq(1)
70
+ expect(query.take.results).to match(
71
+ "personal" => match("ids" => match_array([{ "id" => user_one.id }, { "id" => user_two.id }]))
72
+ )
73
+ end
74
+
75
+ it "allows for re-aliasing the default 'results' column" do
76
+ query = User.json_build_object(:personal, sub_query, as: :cool_dudes)
77
+ expect(query.take).to respond_to(:cool_dudes)
78
+ end
79
+ end
80
+
81
+ describe ".jsonb_build_object" do
82
+ let(:sub_query) { User.select(:id, :number).where(id: user_one.id) }
83
+
84
+ it "defaults the column alias if one is not provided" do
85
+ query = User.jsonb_build_object(:personal, sub_query)
86
+ expect(query.size).to eq(1)
87
+ expect(query.take.results).to be_a(Hash).and(be_present)
88
+ expect(query.take.results).to match("personal" => match("id" => user_one.id, "number" => user_one.number))
89
+ end
90
+
91
+ it "allows for re-aliasing the default 'results' column" do
92
+ query = User.jsonb_build_object(:personal, sub_query, as: :cool_dudes)
93
+ expect(query.take).to respond_to(:cool_dudes)
94
+ end
95
+
96
+ it "allows for custom value statement" do
97
+ query = User.jsonb_build_object(
98
+ :personal,
99
+ sub_query.where.not(id: user_one),
100
+ value: "COALESCE(array_agg(\"personal\"), '{}')",
101
+ as: :cool_dudes
102
+ )
103
+
104
+ expect(query.take.cool_dudes["personal"]).to be_a(Array).and(be_empty)
105
+ end
106
+
107
+ it "will raise a warning if the value doesn't include a double quoted input" do
108
+ expect do
109
+ User.jsonb_build_object(
110
+ :personal,
111
+ sub_query.where.not(id: user_one),
112
+ value: "COALESCE(array_agg(personal), '{}')",
113
+ as: :cool_dudes
114
+ )
115
+ end.to output.to_stderr
116
+ end
117
+ end
118
+
119
+ describe "Json literal builds" do
120
+ let(:original_hash) { { p: 1, b: "three", x: 3.14 } }
121
+ let(:hash_as_array_objs) { original_hash.to_a.flatten }
122
+
123
+ shared_examples_for "literal builds" do
124
+ let(:method) { raise "You are expected to over ride this!" }
125
+
126
+ it "will accept a hash arguments that will return itself" do
127
+ query = User.send(method.to_sym, original_hash)
128
+ expect(query.take.results).to be_a(Hash).and(be_present)
129
+ expect(query.take.results).to match(original_hash.stringify_keys)
130
+ end
131
+
132
+ it "will accept a standard array of key values" do
133
+ query = User.send(method.to_sym, hash_as_array_objs)
134
+ expect(query.take.results).to be_a(Hash).and(be_present)
135
+ expect(query.take.results).to match(original_hash.stringify_keys)
136
+ end
137
+
138
+ it "will accept a splatted array of key-values" do
139
+ query = User.send(method.to_sym, *hash_as_array_objs)
140
+ expect(query.take.results).to be_a(Hash).and(be_present)
141
+ expect(query.take.results).to match(original_hash.stringify_keys)
142
+ end
143
+ end
144
+
145
+ describe ".json_build_literal" do
146
+ it_behaves_like "literal builds" do
147
+ let!(:method) { :json_build_literal }
148
+ end
149
+ end
150
+
151
+ describe ".jsonb_build_literal" do
152
+ it_behaves_like "literal builds" do
153
+ let!(:method) { :jsonb_build_literal }
154
+ end
155
+ end
156
+ end
157
+ end