postgres_ext 2.1.3 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +13 -22
  4. data/CHANGELOG.md +45 -0
  5. data/README.md +13 -0
  6. data/Rakefile +83 -7
  7. data/docs/querying.md +76 -6
  8. data/lib/postgres_ext/active_record/relation/predicate_builder.rb +2 -2
  9. data/lib/postgres_ext/active_record/relation/query_methods.rb +44 -11
  10. data/lib/postgres_ext/arel/predications.rb +10 -0
  11. data/lib/postgres_ext/arel/visitors/to_sql.rb +1 -1
  12. data/lib/postgres_ext/version.rb +1 -1
  13. data/postgres_ext.gemspec +4 -2
  14. data/test/arel/array_test.rb +92 -0
  15. data/{spec/arel/inet_spec.rb → test/arel/inet_test.rb} +6 -6
  16. data/{spec/queries/array_queries_spec.rb → test/queries/array_queries_test.rb} +22 -14
  17. data/test/queries/common_table_expression_test.rb +49 -0
  18. data/{spec/queries/contains_querie_spec.rb → test/queries/contains_test.rb} +5 -5
  19. data/test/queries/sanity_test.rb +32 -0
  20. data/test/queries/window_functions_test.rb +64 -0
  21. data/test/test_helper.rb +38 -0
  22. metadata +74 -150
  23. data/spec/arel/array_spec.rb +0 -77
  24. data/spec/arel/rank_spec.rb +0 -0
  25. data/spec/dummy/.gitignore +0 -15
  26. data/spec/dummy/README.rdoc +0 -261
  27. data/spec/dummy/Rakefile +0 -7
  28. data/spec/dummy/app/assets/images/rails.png +0 -0
  29. data/spec/dummy/app/assets/javascripts/application.js +0 -15
  30. data/spec/dummy/app/assets/stylesheets/application.css +0 -13
  31. data/spec/dummy/app/controllers/application_controller.rb +0 -3
  32. data/spec/dummy/app/helpers/application_helper.rb +0 -2
  33. data/spec/dummy/app/mailers/.gitkeep +0 -0
  34. data/spec/dummy/app/models/.gitkeep +0 -0
  35. data/spec/dummy/app/models/person.rb +0 -2
  36. data/spec/dummy/app/views/layouts/application.html.erb +0 -14
  37. data/spec/dummy/config.ru +0 -4
  38. data/spec/dummy/config/application.rb +0 -56
  39. data/spec/dummy/config/boot.rb +0 -6
  40. data/spec/dummy/config/database.yml.example +0 -14
  41. data/spec/dummy/config/environment.rb +0 -5
  42. data/spec/dummy/config/environments/development.rb +0 -33
  43. data/spec/dummy/config/environments/production.rb +0 -68
  44. data/spec/dummy/config/environments/test.rb +0 -33
  45. data/spec/dummy/config/initializers/backtrace_silencers.rb +0 -7
  46. data/spec/dummy/config/initializers/inflections.rb +0 -15
  47. data/spec/dummy/config/initializers/mime_types.rb +0 -5
  48. data/spec/dummy/config/initializers/secret_token.rb +0 -7
  49. data/spec/dummy/config/initializers/session_store.rb +0 -8
  50. data/spec/dummy/config/initializers/wrap_parameters.rb +0 -14
  51. data/spec/dummy/config/locales/en.yml +0 -5
  52. data/spec/dummy/config/routes.rb +0 -58
  53. data/spec/dummy/db/migrate/20120501163758_create_people.rb +0 -15
  54. data/spec/dummy/db/schema.rb +0 -31
  55. data/spec/dummy/db/seeds.rb +0 -7
  56. data/spec/dummy/lib/assets/.gitkeep +0 -0
  57. data/spec/dummy/lib/tasks/.gitkeep +0 -0
  58. data/spec/dummy/log/.gitkeep +0 -0
  59. data/spec/dummy/public/404.html +0 -26
  60. data/spec/dummy/public/422.html +0 -26
  61. data/spec/dummy/public/500.html +0 -25
  62. data/spec/dummy/public/favicon.ico +0 -0
  63. data/spec/dummy/public/index.html +0 -241
  64. data/spec/dummy/public/robots.txt +0 -5
  65. data/spec/dummy/script/rails +0 -6
  66. data/spec/dummy/spec/factories/people.rb +0 -7
  67. data/spec/dummy/test/fixtures/.gitkeep +0 -0
  68. data/spec/dummy/test/functional/.gitkeep +0 -0
  69. data/spec/dummy/test/integration/.gitkeep +0 -0
  70. data/spec/dummy/test/performance/browsing_test.rb +0 -12
  71. data/spec/dummy/test/test_helper.rb +0 -13
  72. data/spec/dummy/test/unit/.gitkeep +0 -0
  73. data/spec/dummy/vendor/assets/javascripts/.gitkeep +0 -0
  74. data/spec/dummy/vendor/assets/stylesheets/.gitkeep +0 -0
  75. data/spec/dummy/vendor/plugins/.gitkeep +0 -0
  76. data/spec/queries/common_table_expression_spec.rb +0 -36
  77. data/spec/queries/sanity_spec.rb +0 -17
  78. data/spec/queries/window_functions_spec.rb +0 -54
  79. data/spec/spec_helper.rb +0 -36
@@ -21,5 +21,15 @@ module Arel
21
21
  def overlap(other)
22
22
  Nodes::Overlap.new self, other
23
23
  end
24
+
25
+ def any(other)
26
+ any_tags_function = Arel::Nodes::NamedFunction.new('ANY', [self])
27
+ Arel::Nodes::Equality.new(other, any_tags_function)
28
+ end
29
+
30
+ def all(other)
31
+ any_tags_function = Arel::Nodes::NamedFunction.new('ALL', [self])
32
+ Arel::Nodes::Equality.new(other, any_tags_function)
33
+ end
24
34
  end
25
35
  end
@@ -4,7 +4,7 @@ module Arel
4
4
  module Visitors
5
5
  class ToSql
6
6
  def visit_Array o, a
7
- column = a.relation.engine.columns.find { |col| col.name == a.name.to_s } if a
7
+ column = a.relation.engine.connection.columns(a.relation.name).find { |col| col.name == a.name.to_s } if a
8
8
  if column && column.respond_to?(:array) && column.array
9
9
  quoted o, a
10
10
  else
@@ -1,3 +1,3 @@
1
1
  module PostgresExt
2
- VERSION = '2.1.3'
2
+ VERSION = '2.2.0'
3
3
  end
data/postgres_ext.gemspec CHANGED
@@ -20,10 +20,12 @@ Gem::Specification.new do |gem|
20
20
  gem.add_dependency 'arel', '~> 4.0.1'
21
21
  gem.add_dependency 'pg_array_parser', '~> 0.0.9'
22
22
 
23
- gem.add_development_dependency 'rails', '~> 4.0.0'
24
- gem.add_development_dependency 'rspec-rails', '~> 2.12.0'
23
+ gem.add_development_dependency 'rake', '~> 10.1.0'
24
+ gem.add_development_dependency 'minitest'
25
+ gem.add_development_dependency 'm'
25
26
  gem.add_development_dependency 'bourne', '~> 1.3.0'
26
27
  gem.add_development_dependency 'database_cleaner'
28
+ gem.add_development_dependency 'dotenv'
27
29
  if RUBY_PLATFORM =~ /java/
28
30
  gem.add_development_dependency 'activerecord-jdbcpostgresql-adapter', '1.3.0.beta2'
29
31
  else
@@ -0,0 +1,92 @@
1
+ require 'test_helper'
2
+
3
+ describe 'Array Column Predicates' do
4
+ let(:arel_table) { Person.arel_table }
5
+ describe 'Array Any' do
6
+ context 'string value' do
7
+ it 'creates any predicates' do
8
+ arel_table.where(arel_table[:tags].any('tag')).to_sql.must_match /'tag' = ANY\("people"\."tags"\)/
9
+ end
10
+ end
11
+
12
+ context 'integer value' do
13
+ it 'creates any predicates' do
14
+ arel_table.where(arel_table[:tag_ids].any(1)).to_sql.must_match /1 = ANY\("people"\."tag_ids"\)/
15
+ end
16
+ end
17
+ end
18
+
19
+ describe 'Array All' do
20
+ context 'string value' do
21
+ it 'creates all predicates' do
22
+ arel_table.where(arel_table[:tags].all('tag')).to_sql.must_match /'tag' = ALL\("people"\."tags"\)/
23
+ end
24
+ end
25
+
26
+ context 'integer value' do
27
+ it 'creates all predicates' do
28
+ arel_table.where(arel_table[:tag_ids].all(1)).to_sql.must_match /1 = ALL\("people"\."tag_ids"\)/
29
+ end
30
+ end
31
+ end
32
+
33
+ describe 'Array Overlap' do
34
+ it 'converts Arel overlap statement' do
35
+ arel_table.where(arel_table[:tags].overlap(['tag','tag 2'])).to_sql.must_match /&& '\{"tag","tag 2"\}'/
36
+ end
37
+
38
+ it 'converts Arel overlap statement' do
39
+ arel_table.where(arel_table[:tag_ids].overlap([1,2])).to_sql.must_match /&& '\{1,2\}'/
40
+ end
41
+
42
+ it 'works with count (and other predicates)' do
43
+ Person.where(arel_table[:tag_ids].overlap([1,2])).count.must_equal 0
44
+ end
45
+
46
+ it 'returns matched records' do
47
+ one = Person.create!(:tags => ['one'])
48
+ two = Person.create!(:tags => ['two'])
49
+
50
+ query = arel_table.where(arel_table[:tags].overlap(['one'])).project(Arel.sql('*'))
51
+ Person.find_by_sql(query.to_sql).must_include(one)
52
+
53
+ query = arel_table.where(arel_table[:tags].overlap(['two'])).project(Arel.sql('*'))
54
+ Person.find_by_sql(query.to_sql).must_include(two)
55
+
56
+ query = arel_table.where(arel_table[:tags].overlap(['two','one'])).project(Arel.sql('*'))
57
+ Person.find_by_sql(query.to_sql).must_include(two)
58
+ Person.find_by_sql(query.to_sql).must_include(one)
59
+ end
60
+ end
61
+
62
+ describe 'Array Contains' do
63
+ it 'converts Arel contains statement and escapes strings' do
64
+ arel_table.where(arel_table[:tags].contains(['tag','tag 2'])).to_sql.must_match /@> '\{"tag","tag 2"\}'/
65
+ end
66
+
67
+ it 'converts Arel contains statement with numbers' do
68
+ arel_table.where(arel_table[:tag_ids].contains([1,2])).to_sql.must_match /@> '\{1,2\}'/
69
+ end
70
+
71
+ it 'works with count (and other predicates)' do
72
+ Person.where(arel_table[:tag_ids].contains([1,2])).count.must_equal 0
73
+ end
74
+
75
+ it 'returns matched records' do
76
+ one = Person.create!(:tags => ['one', 'two', 'three'])
77
+ two = Person.create!(:tags => ['one', 'three'])
78
+
79
+ query = arel_table.where(arel_table[:tags].contains(['one', 'two'])).project(Arel.sql('*'))
80
+ Person.find_by_sql(query.to_sql).must_include one
81
+ Person.find_by_sql(query.to_sql).wont_include two
82
+
83
+ query = arel_table.where(arel_table[:tags].contains(['one', 'three'])).project(Arel.sql('*'))
84
+ Person.find_by_sql(query.to_sql).must_include one
85
+ Person.find_by_sql(query.to_sql).must_include two
86
+
87
+ query = arel_table.where(arel_table[:tags].contains(['two'])).project(Arel.sql('*'))
88
+ Person.find_by_sql(query.to_sql).must_include one
89
+ Person.find_by_sql(query.to_sql).wont_include two
90
+ end
91
+ end
92
+ end
@@ -1,9 +1,9 @@
1
- require 'spec_helper'
1
+ require 'test_helper'
2
2
 
3
3
  describe 'INET related AREL functions' do
4
4
  describe 'quoting IPAddr in sql statement' do
5
5
  it 'properly converts IPAddr to quoted strings when passed as an argument to a where clause' do
6
- Person.where(:ip => IPAddr.new('127.0.0.1')).to_sql.should include("'127.0.0.1/32'")
6
+ Person.where(:ip => IPAddr.new('127.0.0.1')).to_sql.must_include("'127.0.0.1/32'")
7
7
  end
8
8
  end
9
9
 
@@ -11,7 +11,7 @@ describe 'INET related AREL functions' do
11
11
  it 'converts Arel contained_within statements to <<' do
12
12
  arel_table = Person.arel_table
13
13
 
14
- arel_table.where(arel_table[:ip].contained_within(IPAddr.new('127.0.0.1/24'))).to_sql.should match /<< '127.0.0.0\/24'/
14
+ arel_table.where(arel_table[:ip].contained_within(IPAddr.new('127.0.0.1/24'))).to_sql.must_match /<< '127.0.0.0\/24'/
15
15
  end
16
16
  end
17
17
 
@@ -19,14 +19,14 @@ describe 'INET related AREL functions' do
19
19
  it 'converts Arel contained_within_or_equals statements to <<=' do
20
20
  arel_table = Person.arel_table
21
21
 
22
- arel_table.where(arel_table[:ip].contained_within_or_equals(IPAddr.new('127.0.0.1/24'))).to_sql.should match /<<= '127.0.0.0\/24'/
22
+ arel_table.where(arel_table[:ip].contained_within_or_equals(IPAddr.new('127.0.0.1/24'))).to_sql.must_match /<<= '127.0.0.0\/24'/
23
23
  end
24
24
  end
25
25
  describe 'contains (>>) operator' do
26
26
  it 'converts Arel contains statements to >>' do
27
27
  arel_table = Person.arel_table
28
28
 
29
- arel_table.where(arel_table[:ip].contains(IPAddr.new('127.0.0.1/24'))).to_sql.should match />> '127.0.0.0\/24'/
29
+ arel_table.where(arel_table[:ip].contains(IPAddr.new('127.0.0.1/24'))).to_sql.must_match />> '127.0.0.0\/24'/
30
30
  end
31
31
  end
32
32
 
@@ -34,7 +34,7 @@ describe 'INET related AREL functions' do
34
34
  it 'converts Arel contains_or_equals statements to >>=' do
35
35
  arel_table = Person.arel_table
36
36
 
37
- arel_table.where(arel_table[:ip].contains_or_equals(IPAddr.new('127.0.0.1/24'))).to_sql.should match />>= '127.0.0.0\/24'/
37
+ arel_table.where(arel_table[:ip].contains_or_equals(IPAddr.new('127.0.0.1/24'))).to_sql.must_match />>= '127.0.0.0\/24'/
38
38
  end
39
39
  end
40
40
  end
@@ -1,4 +1,4 @@
1
- require 'spec_helper'
1
+ require 'test_helper'
2
2
 
3
3
  describe 'Array queries' do
4
4
  let(:equality_regex) { %r{\"people\"\.\"tags\" = '\{\"working\"\}'} }
@@ -10,63 +10,71 @@ describe 'Array queries' do
10
10
  describe '.where(:array_column => [])' do
11
11
  it 'returns an array string instead of IN ()' do
12
12
  query = Person.where(:tags => ['working']).to_sql
13
- query.should match equality_regex
13
+ query.must_match equality_regex
14
+ end
15
+ end
16
+
17
+ describe '.where(joins: { array_column: [] })' do
18
+ it 'returns an array string instead of IN ()' do
19
+ skip
20
+ query = Person.joins(:hm_tags).where(tags: { categories: ['working'] }).to_sql
21
+ query.must_match equality_regex
14
22
  end
15
23
  end
16
24
 
17
25
  describe '.where.overlap(:column => value)' do
18
26
  it 'generates the appropriate where clause' do
19
27
  query = Person.where.overlap(:tag_ids => [1,2])
20
- query.to_sql.should match overlap_regex
28
+ query.to_sql.must_match overlap_regex
21
29
  end
22
30
 
23
31
  it 'allows chaining' do
24
32
  query = Person.where.overlap(:tag_ids => [1,2]).where(:tags => ['working']).to_sql
25
33
 
26
- query.should match overlap_regex
27
- query.should match equality_regex
34
+ query.must_match overlap_regex
35
+ query.must_match equality_regex
28
36
  end
29
37
  end
30
38
 
31
39
  describe '.where.contains(:column => value)' do
32
40
  it 'generates the appropriate where clause' do
33
41
  query = Person.where.contains(:tag_ids => [1,2])
34
- query.to_sql.should match contains_regex
42
+ query.to_sql.must_match contains_regex
35
43
  end
36
44
 
37
45
  it 'allows chaining' do
38
46
  query = Person.where.contains(:tag_ids => [1,2]).where(:tags => ['working']).to_sql
39
47
 
40
- query.should match contains_regex
41
- query.should match equality_regex
48
+ query.must_match contains_regex
49
+ query.must_match equality_regex
42
50
  end
43
51
  end
44
52
 
45
53
  describe '.where.any(:column => value)' do
46
54
  it 'generates the appropriate where clause' do
47
55
  query = Person.where.any(:tag_ids => 2)
48
- query.to_sql.should match any_regex
56
+ query.to_sql.must_match any_regex
49
57
  end
50
58
 
51
59
  it 'allows chaining' do
52
60
  query = Person.where.any(:tag_ids => 2).where(:tags => ['working']).to_sql
53
61
 
54
- query.should match any_regex
55
- query.should match equality_regex
62
+ query.must_match any_regex
63
+ query.must_match equality_regex
56
64
  end
57
65
  end
58
66
 
59
67
  describe '.where.all(:column => value)' do
60
68
  it 'generates the appropriate where clause' do
61
69
  query = Person.where.all(:tag_ids => 2)
62
- query.to_sql.should match all_regex
70
+ query.to_sql.must_match all_regex
63
71
  end
64
72
 
65
73
  it 'allows chaining' do
66
74
  query = Person.where.all(:tag_ids => 2).where(:tags => ['working']).to_sql
67
75
 
68
- query.should match all_regex
69
- query.should match equality_regex
76
+ query.must_match all_regex
77
+ query.must_match equality_regex
70
78
  end
71
79
  end
72
80
  end
@@ -0,0 +1,49 @@
1
+ require 'test_helper'
2
+
3
+ describe 'Common Table Expression queries' do
4
+ describe '.with(common_table_expression_hash)' do
5
+ it 'generates an expression with the CTE' do
6
+ query = Person.with(lucky_number_seven: Person.where(lucky_number: 7)).joins('JOIN lucky_number_seven ON lucky_number_seven.id = people.id')
7
+ query.to_sql.must_equal 'WITH lucky_number_seven AS (SELECT "people".* FROM "people" WHERE "people"."lucky_number" = 7) SELECT "people".* FROM "people" JOIN lucky_number_seven ON lucky_number_seven.id = people.id'
8
+ end
9
+
10
+ it 'generates an expression with multiple CTEs' do
11
+ query = Person.with(lucky_number_seven: Person.where(lucky_number: 7), lucky_number_three: Person.where(lucky_number: 3)).joins('JOIN lucky_number_seven ON lucky_number_seven.id = people.id').joins('JOIN lucky_number_three ON lucky_number_three.id = people.id')
12
+ query.to_sql.must_equal 'WITH lucky_number_seven AS (SELECT "people".* FROM "people" WHERE "people"."lucky_number" = 7), lucky_number_three AS (SELECT "people".* FROM "people" WHERE "people"."lucky_number" = 3) SELECT "people".* FROM "people" JOIN lucky_number_seven ON lucky_number_seven.id = people.id JOIN lucky_number_three ON lucky_number_three.id = people.id'
13
+ end
14
+
15
+ it 'generates an expression with multiple with calls' do
16
+ query = Person.with(lucky_number_seven: Person.where(lucky_number: 7)).with(lucky_number_three: Person.where(lucky_number: 3)).joins('JOIN lucky_number_seven ON lucky_number_seven.id = people.id').joins('JOIN lucky_number_three ON lucky_number_three.id = people.id')
17
+ query.to_sql.must_equal 'WITH lucky_number_seven AS (SELECT "people".* FROM "people" WHERE "people"."lucky_number" = 7), lucky_number_three AS (SELECT "people".* FROM "people" WHERE "people"."lucky_number" = 3) SELECT "people".* FROM "people" JOIN lucky_number_seven ON lucky_number_seven.id = people.id JOIN lucky_number_three ON lucky_number_three.id = people.id'
18
+ end
19
+
20
+ it 'generates an expression with recursive' do
21
+ query = Person.with.recursive(lucky_number_seven: Person.where(lucky_number: 7)).joins('JOIN lucky_number_seven ON lucky_number_seven.id = people.id')
22
+ query.to_sql.must_equal 'WITH RECURSIVE lucky_number_seven AS (SELECT "people".* FROM "people" WHERE "people"."lucky_number" = 7) SELECT "people".* FROM "people" JOIN lucky_number_seven ON lucky_number_seven.id = people.id'
23
+ end
24
+
25
+ it 'accepts Arel::SelectMangers' do
26
+ arel_table = Arel::Table.new 'test'
27
+ arel_manager = arel_table.project arel_table[:foo]
28
+
29
+ query = Person.with(testing: arel_manager)
30
+ query.to_sql.must_equal 'WITH testing AS (SELECT "test"."foo" FROM "test") SELECT "people".* FROM "people"'
31
+ end
32
+ end
33
+
34
+ describe '.from_cte(common_table_expression_hash)' do
35
+ it 'generates an expression with the CTE as the main table' do
36
+ query = Person.from_cte('lucky_number_seven', Person.where(lucky_number: 7)).where(id: 5)
37
+ query.to_sql.must_equal 'WITH lucky_number_seven AS (SELECT "people".* FROM "people" WHERE "people"."lucky_number" = 7) SELECT "lucky_number_seven".* FROM "lucky_number_seven" WHERE "lucky_number_seven"."id" = 5'
38
+ end
39
+
40
+ it 'returns instances of the model' do
41
+ 3.times { Person.create! lucky_number: 7 }
42
+ 3.times { Person.create! lucky_number: 3 }
43
+ people = Person.from_cte('lucky_number_seven', Person.where(lucky_number: 7))
44
+
45
+ people.count.must_equal 3
46
+ people.first.lucky_number.must_equal 7
47
+ end
48
+ end
49
+ end
@@ -1,4 +1,4 @@
1
- require 'spec_helper'
1
+ require 'test_helper'
2
2
 
3
3
  describe 'Contains queries' do
4
4
  let(:contained_within_regex) { %r{\"people\"\.\"ip\" << '127.0.0.1/24'} }
@@ -9,28 +9,28 @@ describe 'Contains queries' do
9
9
  describe '.where.contained_within(:column, value)' do
10
10
  it 'generates the appropriate where clause' do
11
11
  query = Person.where.contained_within(:ip => '127.0.0.1/24')
12
- query.to_sql.should match contained_within_regex
12
+ query.to_sql.must_match contained_within_regex
13
13
  end
14
14
  end
15
15
 
16
16
  describe '.where.contained_within_or_equals(:column, value)' do
17
17
  it 'generates the appropriate where clause' do
18
18
  query = Person.where.contained_within_or_equals(:ip => '127.0.0.1/24')
19
- query.to_sql.should match contained_within_equals_regex
19
+ query.to_sql.must_match contained_within_equals_regex
20
20
  end
21
21
  end
22
22
 
23
23
  describe '.where.contains(:column, value)' do
24
24
  it 'generates the appropriate where clause' do
25
25
  query = Person.where.contains(:ip => '127.0.0.1')
26
- query.to_sql.should match contains_regex
26
+ query.to_sql.must_match contains_regex
27
27
  end
28
28
  end
29
29
 
30
30
  describe '.where.contained_within_or_equals(:column, value)' do
31
31
  it 'generates the appropriate where clause' do
32
32
  query = Person.where.contains_or_equals(:ip => '127.0.0.1')
33
- query.to_sql.should match contains_equals_regex
33
+ query.to_sql.must_match contains_equals_regex
34
34
  end
35
35
  end
36
36
  end
@@ -0,0 +1,32 @@
1
+ require 'test_helper'
2
+
3
+ describe 'Ensure that we don\'t stomp on Active Record\'s queries' do
4
+ describe '.where' do
5
+ it 'generates IN clauses for non array columns' do
6
+ query = Person.where(:id => [1,2,3]).to_sql
7
+
8
+ query.must_match /IN \(1, 2, 3\)/
9
+ end
10
+
11
+ it 'generates IN clauses for non array columns' do
12
+ query = Person.where(:id => []).to_sql
13
+
14
+ query.must_match /WHERE 1=0/
15
+ end
16
+ end
17
+
18
+ describe '.where(joins: { column: [] })' do
19
+ it 'generates IN clause for non array columns' do
20
+ query = Person.joins(:hm_tags).where(tags: { id: ['working'] }).to_sql
21
+ query.must_match /WHERE "tags"\."id" IN \('working'\)/
22
+ end
23
+ end
24
+
25
+ describe '#habtm_association_ids=' do
26
+ it 'properly deletes records through HABTM association method' do
27
+ person = Person.create(habtm_tag_ids: [Tag.create.id])
28
+ person.update_attributes(habtm_tag_ids: [])
29
+ person.habtm_tag_ids.must_be_empty
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,64 @@
1
+ require 'test_helper'
2
+
3
+ describe 'Window functions' do
4
+ describe 'ranked' do
5
+ it 'uses the order when no order is passed to ranked' do
6
+ query = Person.order(lucky_number: :desc).ranked
7
+ query.to_sql.must_equal 'SELECT "people".*, rank() OVER (ORDER BY "people"."lucky_number" DESC) FROM "people" ORDER BY "people"."lucky_number" DESC'
8
+ end
9
+
10
+ it 'uses the order when no order is passed to ranked, swapped calls' do
11
+ query = Person.ranked.order(lucky_number: :desc)
12
+ query.to_sql.must_equal 'SELECT "people".*, rank() OVER (ORDER BY "people"."lucky_number" DESC) FROM "people" ORDER BY "people"."lucky_number" DESC'
13
+ end
14
+
15
+ it 'uses the rank value when there is an order passed to it' do
16
+ query = Person.ranked(lucky_number: :desc)
17
+ query.to_sql.must_equal 'SELECT "people".*, rank() OVER (ORDER BY "people"."lucky_number" DESC) FROM "people"'
18
+ end
19
+
20
+ it 'uses the rank value when a symbol passed to it' do
21
+ query = Person.ranked(:lucky_number)
22
+ query.to_sql.must_equal 'SELECT "people".*, rank() OVER (ORDER BY "people"."lucky_number" ASC) FROM "people"'
23
+ end
24
+
25
+ it 'uses the rank value when a string passed to it' do
26
+ query = Person.ranked('ORDER BY lucky_number DESC')
27
+ query.to_sql.must_equal 'SELECT "people".*, rank() OVER (ORDER BY lucky_number DESC) FROM "people"'
28
+ end
29
+
30
+ it 'combines the order and rank' do
31
+ query = Person.ranked(lucky_number: :desc).order(id: :asc)
32
+ query.to_sql.must_equal 'SELECT "people".*, rank() OVER (ORDER BY "people"."lucky_number" DESC) FROM "people" ORDER BY "people"."id" ASC'
33
+ end
34
+
35
+ it 'executes the query with the rank' do
36
+ Person.create!
37
+ Person.create!
38
+
39
+ ranked_people = Person.ranked(:id)
40
+ ranked_people[0].rank.must_equal 1
41
+ ranked_people[1].rank.must_equal 2
42
+ end
43
+
44
+ it 'does not apply the rank when performing a count' do
45
+ query = Person.ranked(lucky_number: :desc).count
46
+ query.must_equal 0
47
+
48
+ Person.create!
49
+
50
+ query = Person.ranked(lucky_number: :desc).count
51
+ query.must_equal 1
52
+ end
53
+
54
+ it 'does not apply the rank when checking presence' do
55
+ query = Person.ranked(lucky_number: :desc).present?
56
+ query.must_equal false
57
+
58
+ Person.create!
59
+
60
+ query = Person.ranked(lucky_number: :desc).present?
61
+ query.must_equal true
62
+ end
63
+ end
64
+ end