active_record_extended 1.1.0 → 2.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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +87 -15
  3. data/lib/active_record_extended.rb +2 -1
  4. data/lib/active_record_extended/active_record.rb +2 -9
  5. data/lib/active_record_extended/active_record/relation_patch.rb +21 -4
  6. data/lib/active_record_extended/arel.rb +2 -0
  7. data/lib/active_record_extended/arel/aggregate_function_name.rb +40 -0
  8. data/lib/active_record_extended/arel/nodes.rb +32 -41
  9. data/lib/active_record_extended/arel/predications.rb +4 -1
  10. data/lib/active_record_extended/arel/sql_literal.rb +16 -0
  11. data/lib/active_record_extended/arel/visitors/postgresql_decorator.rb +40 -1
  12. data/lib/active_record_extended/query_methods/any_of.rb +10 -8
  13. data/lib/active_record_extended/query_methods/either.rb +1 -1
  14. data/lib/active_record_extended/query_methods/inet.rb +7 -3
  15. data/lib/active_record_extended/query_methods/json.rb +156 -50
  16. data/lib/active_record_extended/query_methods/select.rb +118 -0
  17. data/lib/active_record_extended/query_methods/unionize.rb +14 -43
  18. data/lib/active_record_extended/query_methods/where_chain.rb +14 -6
  19. data/lib/active_record_extended/query_methods/window.rb +93 -0
  20. data/lib/active_record_extended/query_methods/with_cte.rb +102 -35
  21. data/lib/active_record_extended/utilities/order_by.rb +77 -0
  22. data/lib/active_record_extended/utilities/support.rb +178 -0
  23. data/lib/active_record_extended/version.rb +1 -1
  24. data/spec/query_methods/any_of_spec.rb +40 -40
  25. data/spec/query_methods/array_query_spec.rb +14 -14
  26. data/spec/query_methods/either_spec.rb +14 -14
  27. data/spec/query_methods/hash_query_spec.rb +11 -11
  28. data/spec/query_methods/inet_query_spec.rb +33 -31
  29. data/spec/query_methods/json_spec.rb +42 -27
  30. data/spec/query_methods/select_spec.rb +115 -0
  31. data/spec/query_methods/unionize_spec.rb +56 -56
  32. data/spec/query_methods/window_spec.rb +51 -0
  33. data/spec/query_methods/with_cte_spec.rb +22 -12
  34. data/spec/spec_helper.rb +1 -1
  35. data/spec/sql_inspections/any_of_sql_spec.rb +12 -12
  36. data/spec/sql_inspections/arel/aggregate_function_name_spec.rb +41 -0
  37. data/spec/sql_inspections/arel/array_spec.rb +7 -7
  38. data/spec/sql_inspections/arel/inet_spec.rb +7 -7
  39. data/spec/sql_inspections/contains_sql_queries_spec.rb +14 -14
  40. data/spec/sql_inspections/either_sql_spec.rb +11 -11
  41. data/spec/sql_inspections/json_sql_spec.rb +44 -8
  42. data/spec/sql_inspections/unionize_sql_spec.rb +27 -27
  43. data/spec/sql_inspections/window_sql_spec.rb +98 -0
  44. data/spec/sql_inspections/with_cte_sql_spec.rb +52 -23
  45. data/spec/support/models.rb +24 -4
  46. metadata +31 -20
  47. data/lib/active_record_extended/patch/5_0/predicate_builder_decorator.rb +0 -87
  48. data/lib/active_record_extended/utilities.rb +0 -141
@@ -3,7 +3,7 @@
3
3
  require "spec_helper"
4
4
 
5
5
  RSpec.describe "Inet Column Predicates" do
6
- let(:arel_table) { Person.arel_table }
6
+ let(:arel_table) { User.arel_table }
7
7
 
8
8
  describe "#inet_contained_within" do
9
9
  it "converts Arel inet contained within statement" do
@@ -12,7 +12,7 @@ RSpec.describe "Inet Column Predicates" do
12
12
  end
13
13
 
14
14
  it "works with count" do
15
- expect(Person.where(arel_table[:ip].inet_contained_within(IPAddr.new("127.0.0.1"))).count).to eq(0)
15
+ expect(User.where(arel_table[:ip].inet_contained_within(IPAddr.new("127.0.0.1"))).count).to eq(0)
16
16
  end
17
17
  end
18
18
 
@@ -23,7 +23,7 @@ RSpec.describe "Inet Column Predicates" do
23
23
  end
24
24
 
25
25
  it "works with count" do
26
- expect(Person.where(arel_table[:ip].inet_contained_within_or_equals(IPAddr.new("127.0.0.1"))).count).to eq(0)
26
+ expect(User.where(arel_table[:ip].inet_contained_within_or_equals(IPAddr.new("127.0.0.1"))).count).to eq(0)
27
27
  end
28
28
  end
29
29
 
@@ -34,7 +34,7 @@ RSpec.describe "Inet Column Predicates" do
34
34
  end
35
35
 
36
36
  it "works with count" do
37
- expect(Person.where(arel_table[:ip].inet_contains_or_equals(IPAddr.new("127.0.0.1"))).count).to eq(0)
37
+ expect(User.where(arel_table[:ip].inet_contains_or_equals(IPAddr.new("127.0.0.1"))).count).to eq(0)
38
38
  end
39
39
  end
40
40
 
@@ -45,7 +45,7 @@ RSpec.describe "Inet Column Predicates" do
45
45
  end
46
46
 
47
47
  it "works with count" do
48
- expect(Person.where(arel_table[:ip].inet_contains("127.0.0.1")).count).to eq(0)
48
+ expect(User.where(arel_table[:ip].inet_contains("127.0.0.1")).count).to eq(0)
49
49
  end
50
50
  end
51
51
 
@@ -59,8 +59,8 @@ RSpec.describe "Inet Column Predicates" do
59
59
  end
60
60
 
61
61
  it "works with count" do
62
- expect(Person.where(arel_table[:ip].inet_contains_or_is_contained_within("127.0.0.1")).count).to eq(0)
63
- expect(Person.where(arel_table[:ip].inet_contains_or_is_contained_within(IPAddr.new("127.0.0.1"))).count).to eq(0)
62
+ expect(User.where(arel_table[:ip].inet_contains_or_is_contained_within("127.0.0.1")).count).to eq(0)
63
+ expect(User.where(arel_table[:ip].inet_contains_or_is_contained_within(IPAddr.new("127.0.0.1"))).count).to eq(0)
64
64
  end
65
65
  end
66
66
  end
@@ -3,44 +3,44 @@
3
3
  require "spec_helper"
4
4
 
5
5
  RSpec.describe "Contains SQL Queries" do
6
- let(:contains_array_regex) { /\"people\"\.\"tag_ids\" @> '\{1,2\}'/ }
7
- let(:contains_hstore_regex) { /\"people\"\.\"data\" @> '\"nickname\"=>"Dan"'/ }
8
- let(:contains_jsonb_regex) { /\"people\"\.\"jsonb_data\" @> '\{"nickname\":\"Dan"}'/ }
9
- let(:contained_in_array_regex) { /\"people\"\.\"tag_ids\" <@ '\{1,2\}'/ }
10
- let(:contained_in_hstore_regex) { /\"people\"\.\"data\" <@ '\"nickname\"=>"Dan"'/ }
11
- let(:contained_in_jsonb_regex) { /\"people\"\.\"jsonb_data\" <@ '\{"nickname\":\"Dan"}'/ }
12
- let(:contains_equals_regex) { /\"people\"\.\"ip\" >>= '127.0.0.1'/ }
13
- let(:equality_regex) { /\"people\"\.\"tags\" = '\{"?working"?\}'/ }
6
+ let(:contains_array_regex) { /"users"\."tag_ids" @> '\{1,2\}'/ }
7
+ let(:contains_hstore_regex) { /"users"\."data" @> '"nickname"=>"Dan"'/ }
8
+ let(:contains_jsonb_regex) { /"users"\."jsonb_data" @> '\{"nickname":"Dan"}'/ }
9
+ let(:contained_in_array_regex) { /"users"\."tag_ids" <@ '\{1,2\}'/ }
10
+ let(:contained_in_hstore_regex) { /"users"\."data" <@ '"nickname"=>"Dan"'/ }
11
+ let(:contained_in_jsonb_regex) { /"users"\."jsonb_data" <@ '\{"nickname":"Dan"}'/ }
12
+ let(:contains_equals_regex) { /"users"\."ip" >>= '127.0.0.1'/ }
13
+ let(:equality_regex) { /"users"\."tags" = '\{"?working"?\}'/ }
14
14
 
15
15
  describe ".where.contains(:column => value)" do
16
16
  it "generates the appropriate where clause for array columns" do
17
- query = Person.where.contains(tag_ids: [1, 2]).to_sql
17
+ query = User.where.contains(tag_ids: [1, 2]).to_sql
18
18
  expect(query).to match_regex(contains_array_regex)
19
19
  end
20
20
 
21
21
  it "generates the appropriate where clause for hstore columns" do
22
- query = Person.where.contains(data: { nickname: "Dan" }).to_sql
22
+ query = User.where.contains(data: { nickname: "Dan" }).to_sql
23
23
  expect(query).to match_regex(contains_hstore_regex)
24
24
  end
25
25
 
26
26
  it "generates the appropriate where clause for jsonb columns" do
27
- query = Person.where.contains(jsonb_data: { nickname: "Dan" }).to_sql
27
+ query = User.where.contains(jsonb_data: { nickname: "Dan" }).to_sql
28
28
  expect(query).to match_regex(contains_jsonb_regex)
29
29
  end
30
30
 
31
31
  it "generates the appropriate where clause for hstore columns on joins" do
32
- query = Tag.joins(:person).where.contains(people: { data: { nickname: "Dan" } }).to_sql
32
+ query = Tag.joins(:user).where.contains(users: { data: { nickname: "Dan" } }).to_sql
33
33
  expect(query).to match_regex(contains_hstore_regex)
34
34
  end
35
35
 
36
36
  it "allows chaining" do
37
- query = Person.where.contains(tag_ids: [1, 2]).where(tags: ["working"]).to_sql
37
+ query = User.where.contains(tag_ids: [1, 2]).where(tags: ["working"]).to_sql
38
38
  expect(query).to match_regex(contains_array_regex)
39
39
  expect(query).to match_regex(equality_regex)
40
40
  end
41
41
 
42
42
  it "generates the appropriate where clause for array columns on joins" do
43
- query = Tag.joins(:person).where.contains(people: { tag_ids: [1, 2] }).to_sql
43
+ query = Tag.joins(:user).where.contains(users: { tag_ids: [1, 2] }).to_sql
44
44
  expect(query).to match_regex(contains_array_regex)
45
45
  end
46
46
  end
@@ -3,14 +3,14 @@
3
3
  require "spec_helper"
4
4
 
5
5
  RSpec.describe "Either Methods SQL Queries" do
6
- let(:contains_array_regex) { /\"people\"\.\"tag_ids\" @> '\{1,2\}'/ }
7
- let(:profile_l_outer_join) { /LEFT OUTER JOIN \"profile_ls\" ON \"profile_ls\".\"person_id\" = \"people\".\"id\"/ }
8
- let(:profile_r_outer_join) { /LEFT OUTER JOIN \"profile_rs\" ON \"profile_rs\".\"person_id\" = \"people\".\"id\"/ }
6
+ let(:contains_array_regex) { /"users"\."tag_ids" @> '\{1,2\}'/ }
7
+ let(:profile_l_outer_join) { /LEFT OUTER JOIN "profile_ls" ON "profile_ls"."user_id" = "users"."id"/ }
8
+ let(:profile_r_outer_join) { /LEFT OUTER JOIN "profile_rs" ON "profile_rs"."user_id" = "users"."id"/ }
9
9
  let(:where_join_case) do
10
- "WHERE ((CASE WHEN profile_ls.person_id IS NULL"\
11
- " THEN profile_rs.person_id"\
12
- " ELSE profile_ls.person_id END) "\
13
- "= people.id)"
10
+ "WHERE ((CASE WHEN profile_ls.user_id IS NULL"\
11
+ " THEN profile_rs.user_id"\
12
+ " ELSE profile_ls.user_id END) "\
13
+ "= users.id)"
14
14
  end
15
15
 
16
16
  let(:order_case) do
@@ -22,20 +22,20 @@ RSpec.describe "Either Methods SQL Queries" do
22
22
 
23
23
  describe ".either_join/2" do
24
24
  it "Should contain outer joins on the provided relationships" do
25
- query = Person.either_join(:profile_l, :profile_r).to_sql
25
+ query = User.either_join(:profile_l, :profile_r).to_sql
26
26
  expect(query).to match_regex(profile_l_outer_join)
27
27
  expect(query).to match_regex(profile_r_outer_join)
28
28
  end
29
29
 
30
30
  it "Should contain a case statement that will conditionally alternative between tables" do
31
- query = Person.either_join(:profile_l, :profile_r).to_sql
31
+ query = User.either_join(:profile_l, :profile_r).to_sql
32
32
  expect(query).to include(where_join_case)
33
33
  end
34
34
  end
35
35
 
36
36
  describe ".either_order/2" do
37
- let(:ascended_order) { Person.either_order(:asc, profile_l: :likes, profile_r: :dislikes).to_sql }
38
- let(:descended_order) { Person.either_order(:desc, profile_l: :likes, profile_r: :dislikes).to_sql }
37
+ let(:ascended_order) { User.either_order(:asc, profile_l: :likes, profile_r: :dislikes).to_sql }
38
+ let(:descended_order) { User.either_order(:desc, profile_l: :likes, profile_r: :dislikes).to_sql }
39
39
 
40
40
  it "Should contain outer joins on the provided relationships" do
41
41
  expect(ascended_order).to match_regex(profile_l_outer_join)
@@ -4,39 +4,75 @@ require "spec_helper"
4
4
 
5
5
  RSpec.describe "JSON Methods SQL Queries" do
6
6
  let(:single_with) { /^WITH .all_others. AS(?!.*WITH \w?)/mi }
7
- let(:override_with) { /^WITH .all_others. AS \(.+WHERE .people.\..id. = 10\)/mi }
7
+ let(:override_with) { /^WITH .all_others. AS \(.+WHERE .users.\..id. = 10\)/mi }
8
8
 
9
9
  describe ".select_row_to_json" do
10
10
  context "when a subquery contains a CTE table" do
11
- let(:cte_person) { Person.with(all_others: Person.where.not(id: 1)).where(id: 2) }
11
+ let(:cte_person) { User.with(all_others: User.where.not(id: 1)).where(id: 2) }
12
12
 
13
13
  it "should push the CTE to the callee's level" do
14
- query = Person.select_row_to_json(cte_person, as: :results).to_sql
14
+ query = User.select_row_to_json(cte_person, as: :results).to_sql
15
15
  expect(query).to match_regex(single_with)
16
16
  end
17
17
 
18
18
  it "should favor the parents CTE table if names collide" do
19
- query = Person.with(all_others: Person.where(id: 10))
19
+ query = User.with(all_others: User.where(id: 10))
20
20
  query = query.select_row_to_json(cte_person, as: :results).to_sql
21
21
 
22
22
  expect(query).to match_regex(single_with)
23
23
  expect(query).to match_regex(override_with)
24
24
  end
25
25
  end
26
+
27
+ context "When adding cast_with: option" do
28
+ it "should wrap the row_to_json expression with to_jsonb" do
29
+ query = User.select_row_to_json(User.where(id: 10), cast_with: :to_jsonb, key: :convert_this, as: :results).to_sql
30
+ expect(query).to match_regex(/SELECT \(SELECT TO_JSONB\(ROW_TO_JSON\("convert_this"\)\) FROM \(.+\).+\) AS "results"/)
31
+ end
32
+
33
+ it "should cast object to an array" do
34
+ query = User.select_row_to_json(User.where(id: 10), cast_with: :array, key: :convert_this, as: :results).to_sql
35
+ expect(query).to match_regex(/SELECT \(ARRAY\(SELECT ROW_TO_JSON\("convert_this"\) FROM \(.+\).+\)\) AS "results"/)
36
+ end
37
+
38
+ it "should cast object to an aggregated array" do
39
+ query = User.select_row_to_json(User.where(id: 10), cast_with: :array_agg, key: :convert_this, as: :results).to_sql
40
+ expect(query).to match_regex(/SELECT \(ARRAY_AGG\(\(SELECT TO_JSONB\(ROW_TO_JSON\("convert_this"\)\) FROM \(.+\).+\)\)\) AS "results"/)
41
+ end
42
+
43
+ context "When multiple cast_with options are used" do
44
+ it "should cast query with to_jsonb and as an Array" do
45
+ query = User.select_row_to_json(User.where(id: 10), cast_with: [:to_jsonb, :array], key: :convert_this, as: :results).to_sql
46
+ expect(query).to match_regex(/SELECT \(ARRAY\(SELECT TO_JSONB\(ROW_TO_JSON\("convert_this"\)\) FROM \(.+\).+\)\) AS "results"/)
47
+ end
48
+
49
+ it "should cast query as a distinct Aggregated Array" do
50
+ query = User.select_row_to_json(User.where(id: 10), cast_with: [:array_agg, :distinct], key: :convert_this, as: :results).to_sql
51
+ expect(query).to match_regex(/SELECT \(ARRAY_AGG\(DISTINCT \(SELECT TO_JSONB\(ROW_TO_JSON\("convert_this"\)\) FROM \(.+\).+\)\)\) AS "results"/)
52
+ end
53
+ end
54
+ end
55
+
56
+ context "when the subquery is a STI record type" do
57
+ it "should not append sti 'type IN(..)' where clauses to the nested query" do
58
+ query = User.select_row_to_json(AdminSti.where(id: 10), cast_with: :array, key: :convert_this, as: :results).to_sql
59
+ expect(query).to match_regex(/SELECT \(ARRAY\(SELECT ROW_TO_JSON\("convert_this"\) FROM \(.*\) convert_this\)\) AS .+/)
60
+ end
61
+ end
26
62
  end
27
63
 
28
64
  describe ".json_build_object" do
29
65
  context "when a subquery contains a CTE table" do
30
- let(:cte_person) { Person.with(all_others: Person.where.not(id: 1)).where(id: 2) }
66
+ let(:cte_person) { User.with(all_others: User.where.not(id: 1)).where(id: 2) }
31
67
 
32
68
  it "should push the CTE to the callee's level" do
33
- query = Person.json_build_object(:peoples, cte_person).to_sql
69
+ query = User.json_build_object(:userss, cte_person).to_sql
34
70
  expect(query).to match_regex(single_with)
35
71
  end
36
72
 
37
73
  it "should favor the parents CTE table if names collide" do
38
- query = Person.with(all_others: Person.where(id: 10))
39
- query = query.json_build_object(:peoples, cte_person).to_sql
74
+ query = User.with(all_others: User.where(id: 10))
75
+ query = query.json_build_object(:users, cte_person).to_sql
40
76
 
41
77
  expect(query).to match_regex(single_with)
42
78
  expect(query).to match_regex(override_with)
@@ -3,27 +3,27 @@
3
3
  require "spec_helper"
4
4
 
5
5
  RSpec.describe "Union SQL Queries" do
6
- let(:person) { Person.where(id: 1) }
7
- let(:other_person) { Person.where("id = 2") }
6
+ let(:user) { User.where(id: 1) }
7
+ let(:other_user) { User.where("id = 2") }
8
8
 
9
9
  shared_examples_for "unions" do
10
- it { is_expected.to eq("( (#{person.to_sql}) #{described_union} (#{other_person.to_sql}) )") }
10
+ it { is_expected.to eq("( (#{user.to_sql}) #{described_union} (#{other_user.to_sql}) )") }
11
11
  end
12
12
 
13
13
  shared_examples_for "piping nest CTE tables" do
14
- let(:cte_person) { Person.with(all_others: Person.where.not(id: 1)).where(id: 2) }
14
+ let(:cte_user) { User.with(all_others: User.where.not(id: 1)).where(id: 2) }
15
15
  let(:method) { raise "Required to override this method!" }
16
16
  let(:single_with) { /^WITH .all_others. AS(?!.*WITH \w?)/mi }
17
- let(:override_with) { /^WITH .all_others. AS \(.+WHERE .people.\..id. = 10\)/mi }
17
+ let(:override_with) { /^WITH .all_others. AS \(.+WHERE .users.\..id. = 10\)/mi }
18
18
 
19
19
  it "should push the CTE to the callee's level" do
20
- query = Person.send(method.to_sym, cte_person, other_person).to_sql
20
+ query = User.send(method.to_sym, cte_user, other_user).to_sql
21
21
  expect(query).to match_regex(single_with)
22
22
  end
23
23
 
24
24
  it "should favor the parents CTE table if names collide" do
25
- query = Person.with(all_others: Person.where(id: 10))
26
- query = query.send(method.to_sym, cte_person, other_person).to_sql
25
+ query = User.with(all_others: User.where(id: 10))
26
+ query = query.send(method.to_sym, cte_user, other_user).to_sql
27
27
 
28
28
  expect(query).to match_regex(single_with)
29
29
  expect(query).to match_regex(override_with)
@@ -32,7 +32,7 @@ RSpec.describe "Union SQL Queries" do
32
32
 
33
33
  describe ".union" do
34
34
  let!(:described_union) { "UNION" }
35
- subject(:described_method) { Person.union(person, other_person).to_union_sql }
35
+ subject(:described_method) { User.union(user, other_user).to_union_sql }
36
36
  it_behaves_like "unions"
37
37
  it_behaves_like "piping nest CTE tables" do
38
38
  let!(:method) { :union }
@@ -41,7 +41,7 @@ RSpec.describe "Union SQL Queries" do
41
41
 
42
42
  describe ".union.all" do
43
43
  let!(:described_union) { "UNION ALL" }
44
- subject(:described_method) { Person.union.all(person, other_person).to_union_sql }
44
+ subject(:described_method) { User.union.all(user, other_user).to_union_sql }
45
45
  it_behaves_like "unions"
46
46
  it_behaves_like "piping nest CTE tables" do
47
47
  let!(:method) { :union_all }
@@ -50,7 +50,7 @@ RSpec.describe "Union SQL Queries" do
50
50
 
51
51
  describe ".union.except" do
52
52
  let!(:described_union) { "EXCEPT" }
53
- subject(:described_method) { Person.union.except(person, other_person).to_union_sql }
53
+ subject(:described_method) { User.union.except(user, other_user).to_union_sql }
54
54
  it_behaves_like "unions"
55
55
  it_behaves_like "piping nest CTE tables" do
56
56
  let!(:method) { :union_except }
@@ -59,7 +59,7 @@ RSpec.describe "Union SQL Queries" do
59
59
 
60
60
  describe "union.intersect" do
61
61
  let!(:described_union) { "INTERSECT" }
62
- subject(:described_method) { Person.union.intersect(person, other_person).to_union_sql }
62
+ subject(:described_method) { User.union.intersect(user, other_user).to_union_sql }
63
63
  it_behaves_like "unions"
64
64
  it_behaves_like "piping nest CTE tables" do
65
65
  let!(:method) { :union_intersect }
@@ -69,28 +69,28 @@ RSpec.describe "Union SQL Queries" do
69
69
  describe "union.as" do
70
70
  context "when a union.as has been called" do
71
71
  subject(:described_method) do
72
- Person.select("happy_people.id").union(person, other_person).union.as(:happy_people).to_sql
72
+ User.select("happy_users.id").union(user, other_user).union.as(:happy_users).to_sql
73
73
  end
74
74
 
75
- it "should alias the union from clause to 'happy_people'" do
76
- expect(described_method).to match_regex(/FROM \(+.+\) UNION \(.+\)+ happy_people$/)
77
- expect(described_method).to match_regex(/^SELECT happy_people\.id FROM.+happy_people$/)
75
+ it "should alias the union from clause to 'happy_users'" do
76
+ expect(described_method).to match_regex(/FROM \(+.+\) UNION \(.+\)+ happy_users$/)
77
+ expect(described_method).to match_regex(/^SELECT (happy_users\.id|"happy_users"\."id") FROM.+happy_users$/)
78
78
  end
79
79
  end
80
80
 
81
81
  context "when user.as hasn't been called" do
82
- subject(:described_method) { Person.select(:id).union(person, other_person).to_sql }
82
+ subject(:described_method) { User.select(:id).union(user, other_user).to_sql }
83
83
 
84
84
  it "should retain the actual class calling table name as the union alias" do
85
- expect(described_method).to match_regex(/FROM \(+.+\) UNION \(.+\)+ people$/)
86
- expect(described_method).to match_regex(/^SELECT \"people\"\.\"id\" FROM.+people$/)
85
+ expect(described_method).to match_regex(/FROM \(+.+\) UNION \(.+\)+ users$/)
86
+ expect(described_method).to match_regex(/^SELECT "users"\."id" FROM.+users$/)
87
87
  end
88
88
  end
89
89
  end
90
90
 
91
91
  describe "union.order" do
92
92
  context "when rendering with .to_union_sql" do
93
- subject(:described_method) { Person.union(person, other_person).union.order(:id, name: :desc).to_union_sql }
93
+ subject(:described_method) { User.union(user, other_user).union.order(:id, name: :desc).to_union_sql }
94
94
 
95
95
  it "Should append an 'ORDER BY' to the end of the union statements" do
96
96
  expect(described_method).to match_regex(/^\(+.+\) UNION \(.+\) \) ORDER BY id, name DESC$/)
@@ -98,10 +98,10 @@ RSpec.describe "Union SQL Queries" do
98
98
  end
99
99
 
100
100
  context "when rendering with .to_sql" do
101
- subject(:described_method) { Person.union(person, other_person).union.order(:id, name: :desc).to_sql }
101
+ subject(:described_method) { User.union(user, other_user).union.order(:id, name: :desc).to_sql }
102
102
 
103
103
  it "Should append an 'ORDER BY' to the end of the union statements" do
104
- expect(described_method).to match_regex(/FROM \(+.+\) UNION \(.+\) \) ORDER BY id, name DESC\) people$/)
104
+ expect(described_method).to match_regex(/FROM \(+.+\) UNION \(.+\) \) ORDER BY id, name DESC\) users$/)
105
105
  end
106
106
  end
107
107
 
@@ -109,11 +109,11 @@ RSpec.describe "Union SQL Queries" do
109
109
  let(:query_regex) { /(?<=\)\s(ORDER BY)) id/ }
110
110
 
111
111
  it "should only append an order by to the very end of a union statements" do
112
- query = Person.union.order(id: :asc, tags: :desc)
113
- .union(person.order(id: :asc, tags: :desc))
114
- .union(person.order(:id, :tags))
115
- .union(other_person.order(id: :desc, tags: :desc))
116
- .to_union_sql
112
+ query = User.union.order(id: :asc, tags: :desc)
113
+ .union(user.order(id: :asc, tags: :desc))
114
+ .union(user.order(:id, :tags))
115
+ .union(other_user.order(id: :desc, tags: :desc))
116
+ .to_union_sql
117
117
 
118
118
  index = query.index(query_regex)
119
119
  expect(index).to be_truthy
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe "Active Record WINDOW Query inspection" do
6
+ describe "#define_window" do
7
+ context "when there is a single defined window" do
8
+ it "should only contain a single defined window statement at the bottom" do
9
+ query = Tag.define_window(:w_test).partition_by(:user_id).to_sql
10
+ expect(query).to eq('SELECT "tags".* FROM "tags" WINDOW w_test AS (PARTITION BY user_id)')
11
+ end
12
+
13
+ it "should return a single defined window with a defined ORDER BY" do
14
+ query = Tag.define_window(:w_test).partition_by(:user_id, order_by: { tags: { user_id: :desc } }).to_sql
15
+ expect(query).to end_with("WINDOW w_test AS (PARTITION BY user_id ORDER BY tags.user_id DESC)")
16
+ end
17
+
18
+ it "should place the window function below the WHERE and GROUP BY statements" do
19
+ query = Tag.define_window(:w_test).partition_by(:user_id).where(id: 1).group(:user_id).to_sql
20
+ expect(query).to eq('SELECT "tags".* FROM "tags" WHERE "tags"."id" = 1 GROUP BY "tags"."user_id" WINDOW w_test AS (PARTITION BY user_id)')
21
+ end
22
+ end
23
+
24
+ context "when there are multiple defined windows" do
25
+ it "should only contain a single defined window statement at the bottom" do
26
+ query =
27
+ Tag
28
+ .define_window(:test).partition_by(:user_id)
29
+ .define_window(:other_window).partition_by(:id)
30
+ .to_sql
31
+
32
+ expect(query).to end_with("WINDOW test AS (PARTITION BY user_id), other_window AS (PARTITION BY id)")
33
+ end
34
+
35
+ it "should contain each windows order by statements" do
36
+ query =
37
+ Tag
38
+ .define_window(:test).partition_by(:user_id, order_by: :id)
39
+ .define_window(:other_window).partition_by(:id, order_by: { tags: :user_id })
40
+ .to_sql
41
+
42
+ expect(query).to end_with("WINDOW test AS (PARTITION BY user_id ORDER BY id), other_window AS (PARTITION BY id ORDER BY tags.user_id ASC)")
43
+ end
44
+ end
45
+ end
46
+
47
+ describe "#window_select" do
48
+ let(:query_base) { Tag.define_window(:w).partition_by(:user_id, order_by: :id) }
49
+ let(:expected_end) { "WINDOW w AS (PARTITION BY user_id ORDER BY id)" }
50
+
51
+ context "when using no argument window methods" do
52
+ [:row_to_number, :rank, :dense_rank, :percent_rank, :cume_dist].each do |window_function|
53
+ context "#{window_function.to_s.upcase}()" do
54
+ let(:expected_function) { "#{window_function.to_s.upcase}()" }
55
+ let(:query) do
56
+ query_base.select_window(window_function, over: :w, as: :window_response).to_sql
57
+ end
58
+
59
+ it "appends the function to the select query" do
60
+ expected_start = "SELECT (#{expected_function} OVER w) AS \"window_response\""
61
+ expect(query).to start_with(expected_start).and(end_with(expected_end))
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ context "when using an argument based window method" do
68
+ let(:argument_list) { ["a", 1, :sauce] }
69
+
70
+ { ntile: 1, lag: 2, lead: 3, first_value: 1, last_value: 1, nth_value: 2 }.each_pair do |window_function, arg_count|
71
+ context "#{window_function.to_s.upcase}/#{arg_count}" do
72
+ let(:arguments) { argument_list.first(arg_count) }
73
+ let(:expected_function) { "#{window_function.to_s.upcase}(#{arguments.join(', ')})" }
74
+ let(:query) do
75
+ query_base.select_window(window_function, *arguments, over: :w, as: :window_response).to_sql
76
+ end
77
+
78
+ it "appends the function to the select query" do
79
+ expected_start = "SELECT (#{expected_function} OVER w) AS \"window_response\""
80
+ expect(query).to start_with(expected_start).and(end_with(expected_end))
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ context "when not providing a partition by value" do
87
+ it "should construct a window function" do
88
+ query =
89
+ Tag
90
+ .define_window(:no_args).partition_by(order_by: { tag_number: :desc })
91
+ .select_window(:row_number, over: :no_args, as: :my_row)
92
+ .to_sql
93
+
94
+ expect(query).to eq("SELECT (ROW_NUMBER() OVER no_args) AS \"my_row\" FROM \"tags\" WINDOW no_args AS (ORDER BY tag_number DESC)")
95
+ end
96
+ end
97
+ end
98
+ end