active_record_extended_telescope 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
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