torque-postgresql 1.1.8 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/lib/torque/postgresql.rb +0 -2
  3. data/lib/torque/postgresql/adapter.rb +0 -1
  4. data/lib/torque/postgresql/adapter/database_statements.rb +4 -15
  5. data/lib/torque/postgresql/adapter/schema_creation.rb +13 -23
  6. data/lib/torque/postgresql/adapter/schema_definitions.rb +7 -21
  7. data/lib/torque/postgresql/adapter/schema_dumper.rb +71 -11
  8. data/lib/torque/postgresql/adapter/schema_statements.rb +2 -12
  9. data/lib/torque/postgresql/associations.rb +0 -3
  10. data/lib/torque/postgresql/associations/association.rb +0 -4
  11. data/lib/torque/postgresql/associations/association_scope.rb +18 -60
  12. data/lib/torque/postgresql/associations/belongs_to_many_association.rb +12 -15
  13. data/lib/torque/postgresql/associations/preloader.rb +0 -32
  14. data/lib/torque/postgresql/associations/preloader/association.rb +13 -10
  15. data/lib/torque/postgresql/autosave_association.rb +4 -4
  16. data/lib/torque/postgresql/auxiliary_statement.rb +1 -13
  17. data/lib/torque/postgresql/coder.rb +1 -2
  18. data/lib/torque/postgresql/config.rb +0 -6
  19. data/lib/torque/postgresql/inheritance.rb +13 -17
  20. data/lib/torque/postgresql/reflection/abstract_reflection.rb +19 -25
  21. data/lib/torque/postgresql/reflection/belongs_to_many_reflection.rb +4 -38
  22. data/lib/torque/postgresql/relation.rb +11 -16
  23. data/lib/torque/postgresql/relation/auxiliary_statement.rb +2 -8
  24. data/lib/torque/postgresql/relation/distinct_on.rb +1 -1
  25. data/lib/torque/postgresql/version.rb +1 -1
  26. data/spec/en.yml +19 -0
  27. data/spec/factories/authors.rb +6 -0
  28. data/spec/factories/comments.rb +13 -0
  29. data/spec/factories/posts.rb +6 -0
  30. data/spec/factories/tags.rb +5 -0
  31. data/spec/factories/texts.rb +5 -0
  32. data/spec/factories/users.rb +6 -0
  33. data/spec/factories/videos.rb +5 -0
  34. data/spec/mocks/cache_query.rb +16 -0
  35. data/spec/mocks/create_table.rb +35 -0
  36. data/spec/models/activity.rb +3 -0
  37. data/spec/models/activity_book.rb +4 -0
  38. data/spec/models/activity_post.rb +7 -0
  39. data/spec/models/activity_post/sample.rb +4 -0
  40. data/spec/models/author.rb +4 -0
  41. data/spec/models/author_journalist.rb +4 -0
  42. data/spec/models/comment.rb +3 -0
  43. data/spec/models/course.rb +2 -0
  44. data/spec/models/geometry.rb +2 -0
  45. data/spec/models/guest_comment.rb +4 -0
  46. data/spec/models/post.rb +6 -0
  47. data/spec/models/tag.rb +2 -0
  48. data/spec/models/text.rb +2 -0
  49. data/spec/models/time_keeper.rb +2 -0
  50. data/spec/models/user.rb +8 -0
  51. data/spec/models/video.rb +2 -0
  52. data/spec/schema.rb +141 -0
  53. data/spec/spec_helper.rb +59 -0
  54. data/spec/tests/arel_spec.rb +72 -0
  55. data/spec/tests/auxiliary_statement_spec.rb +593 -0
  56. data/spec/tests/belongs_to_many_spec.rb +240 -0
  57. data/spec/tests/coder_spec.rb +367 -0
  58. data/spec/tests/collector_spec.rb +59 -0
  59. data/spec/tests/distinct_on_spec.rb +65 -0
  60. data/spec/tests/enum_set_spec.rb +306 -0
  61. data/spec/tests/enum_spec.rb +621 -0
  62. data/spec/tests/geometric_builder_spec.rb +221 -0
  63. data/spec/tests/has_many_spec.rb +390 -0
  64. data/spec/tests/interval_spec.rb +167 -0
  65. data/spec/tests/lazy_spec.rb +24 -0
  66. data/spec/tests/period_spec.rb +954 -0
  67. data/spec/tests/quoting_spec.rb +24 -0
  68. data/spec/tests/range_spec.rb +36 -0
  69. data/spec/tests/relation_spec.rb +57 -0
  70. data/spec/tests/table_inheritance_spec.rb +403 -0
  71. metadata +103 -15
  72. data/lib/torque/postgresql/associations/join_dependency/join_association.rb +0 -15
  73. data/lib/torque/postgresql/schema_dumper.rb +0 -101
@@ -0,0 +1,6 @@
1
+ FactoryBot.define do
2
+ factory :author do
3
+ name { Faker::Name.name }
4
+ specialty { Enum::Specialties.values.sample }
5
+ end
6
+ end
@@ -0,0 +1,13 @@
1
+ FactoryBot.define do
2
+ factory :comment do
3
+ content { Faker::Lorem.paragraph }
4
+
5
+ factory :comment_recursive do
6
+ comment_id { Comment.order('RANDOM()').first.id }
7
+ end
8
+
9
+ trait :random_user do
10
+ user_id { User.order('RANDOM()').first.id }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,6 @@
1
+ FactoryBot.define do
2
+ factory :post do
3
+ title { Faker::Lorem.sentence }
4
+ content { Faker::Lorem.paragraph }
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ FactoryBot.define do
2
+ factory :tag do
3
+ name { Faker::Lorem.sentence }
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ FactoryBot.define do
2
+ factory :text do
3
+ content { Faker::Lorem.sentence }
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ FactoryBot.define do
2
+ factory :user do
3
+ name { Faker::Name.name }
4
+ role { 'visitor' }
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ FactoryBot.define do
2
+ factory :video do
3
+ title { Faker::Lorem.sentence }
4
+ end
5
+ end
@@ -0,0 +1,16 @@
1
+ module Mocks
2
+ module CacheQuery
3
+ def get_last_executed_query(&block)
4
+ conn = ActiveRecord::Base.connection
5
+ conn.instance_variable_set(:@query_cache_enabled, true)
6
+
7
+ block.call
8
+ result = conn.query_cache.keys.first
9
+
10
+ conn.instance_variable_set(:@query_cache_enabled, false)
11
+ conn.instance_variable_get(:@query_cache).delete(result)
12
+
13
+ result
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,35 @@
1
+ module Mocks
2
+ module CreateTable
3
+ def mock_create_table
4
+ path = ActiveRecord::Base.connection.method(:create_table).super_method.source_location[0]
5
+
6
+ before :all do
7
+ ActiveRecord::ConnectionAdapters::SchemaStatements.send(:define_method, :create_table) do |table_name, **options, &block|
8
+ td = create_table_definition(table_name, **options)
9
+
10
+ # Does things as the same as schema statements
11
+ if options[:id] != false && !options[:as]
12
+ pk = options.fetch(:primary_key) do
13
+ ActiveRecord::Base.get_primary_key table_name.to_s.singularize
14
+ end
15
+
16
+ if pk.is_a?(Array)
17
+ td.primary_keys pk
18
+ else
19
+ td.primary_key pk, options.fetch(:id, :primary_key), **options
20
+ end
21
+ end
22
+
23
+ block.call(td) if block.present?
24
+
25
+ # Now generate the SQL and return it
26
+ schema_creation.accept td
27
+ end
28
+ end
29
+
30
+ after :all do
31
+ load path
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,3 @@
1
+ class Activity < ActiveRecord::Base
2
+ belongs_to :author
3
+ end
@@ -0,0 +1,4 @@
1
+ require_relative 'activity'
2
+
3
+ class ActivityBook < Activity
4
+ end
@@ -0,0 +1,7 @@
1
+ require_relative 'activity'
2
+
3
+ class ActivityPost < Activity
4
+ belongs_to :post
5
+ end
6
+
7
+ require_relative 'activity_post/sample'
@@ -0,0 +1,4 @@
1
+ class ActivityPost < Activity
2
+ class Sample < ActivityPost
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ class Author < ActiveRecord::Base
2
+ has_many :activities, -> { cast_records }
3
+ has_many :posts
4
+ end
@@ -0,0 +1,4 @@
1
+ require_relative 'author'
2
+
3
+ class AuthorJournalist < Author
4
+ end
@@ -0,0 +1,3 @@
1
+ class Comment < ActiveRecord::Base
2
+ belongs_to :user
3
+ end
@@ -0,0 +1,2 @@
1
+ class Course < ActiveRecord::Base
2
+ end
@@ -0,0 +1,2 @@
1
+ class Geometry < ActiveRecord::Base
2
+ end
@@ -0,0 +1,4 @@
1
+ require_relative 'comment'
2
+
3
+ class GuestComment < Comment
4
+ end
@@ -0,0 +1,6 @@
1
+ class Post < ActiveRecord::Base
2
+ belongs_to :author
3
+ belongs_to :activity
4
+
5
+ scope :test_scope, -> { where('1=1') }
6
+ end
@@ -0,0 +1,2 @@
1
+ class Tag < ActiveRecord::Base
2
+ end
@@ -0,0 +1,2 @@
1
+ class Text < ActiveRecord::Base
2
+ end
@@ -0,0 +1,2 @@
1
+ class TimeKeeper < ActiveRecord::Base
2
+ end
@@ -0,0 +1,8 @@
1
+ class User < ActiveRecord::Base
2
+ has_many :comments
3
+
4
+ auxiliary_statement :last_comment do |cte|
5
+ cte.query Comment.distinct_on(:user_id).order(:user_id, id: :desc)
6
+ cte.attributes id: :comment_id, content: :comment_content
7
+ end
8
+ end
@@ -0,0 +1,2 @@
1
+ class Video < ActiveRecord::Base
2
+ end
data/spec/schema.rb ADDED
@@ -0,0 +1,141 @@
1
+ # This file is auto-generated from the current state of the database. Instead
2
+ # of editing this file, please use the migrations feature of Active Record to
3
+ # incrementally modify your database, and then regenerate this schema definition.
4
+ #
5
+ # Note that this schema.rb definition is the authoritative source for your
6
+ # database schema. If you need to create the application database on another
7
+ # system, you should be using db:schema:load, not running all the migrations
8
+ # from scratch. The latter is a flawed and unsustainable approach (the more migrations
9
+ # you'll amass, the slower it'll run and the greater likelihood for issues).
10
+ #
11
+ # It's strongly recommended that you check this file into your version control system.
12
+
13
+ begin
14
+ version = 61
15
+
16
+ raise SystemExit if ActiveRecord::Migrator.current_version == version
17
+ ActiveRecord::Schema.define(version: version) do
18
+ self.verbose = false
19
+
20
+ # These are extensions that must be enabled in order to support this database
21
+ enable_extension "plpgsql"
22
+
23
+ # These are user-defined types used on this database
24
+ create_enum "content_status", ["created", "draft", "published", "archived"], force: :cascade
25
+ create_enum "specialties", ["books", "movies", "plays"], force: :cascade
26
+ create_enum "roles", ["visitor", "assistant", "manager", "admin"], force: :cascade
27
+ create_enum "conflicts", ["valid", "invalid", "untrusted"], force: :cascade
28
+ create_enum "types", ["A", "B", "C", "D"], force: :cascade
29
+
30
+ create_table "geometries", force: :cascade do |t|
31
+ t.point "point"
32
+ t.line "line"
33
+ t.lseg "lseg"
34
+ t.box "box"
35
+ t.path "closed_path"
36
+ t.path "open_path"
37
+ t.polygon "polygon"
38
+ t.circle "circle"
39
+ end
40
+
41
+ create_table "time_keepers", force: :cascade do |t|
42
+ t.daterange "available"
43
+ t.tsrange "period"
44
+ t.tstzrange "tzperiod"
45
+ t.interval "th"
46
+ end
47
+
48
+ create_table "tags", force: :cascade do |t|
49
+ t.string "name"
50
+ end
51
+
52
+ create_table "videos", force: :cascade do |t|
53
+ t.bigint "tag_ids", array: true
54
+ t.string "title"
55
+ t.string "url"
56
+ t.enum "type", subtype: :types
57
+ t.enum "conflicts", subtype: :conflicts, array: true
58
+ t.datetime "created_at", null: false
59
+ t.datetime "updated_at", null: false
60
+ end
61
+
62
+ create_table "authors", force: :cascade do |t|
63
+ t.string "name"
64
+ t.string "type"
65
+ t.enum "specialty", subtype: :specialties
66
+ end
67
+
68
+ create_table "texts", force: :cascade do |t|
69
+ t.integer "user_id"
70
+ t.string "content"
71
+ t.enum "conflict", subtype: :conflicts
72
+ end
73
+
74
+ create_table "comments", force: :cascade do |t|
75
+ t.integer "user_id", null: false
76
+ t.integer "comment_id"
77
+ t.text "content", null: false
78
+ t.string "kind"
79
+ t.index ["user_id"], name: "index_comments_on_user_id", using: :btree
80
+ t.index ["comment_id"], name: "index_comments_on_comment_id", using: :btree
81
+ end
82
+
83
+ create_table "courses", force: :cascade do |t|
84
+ t.string "title", null: false
85
+ t.interval "duration"
86
+ t.enum "types", subtype: :types, array: true, default: [:A, :B]
87
+ t.datetime "created_at", null: false
88
+ t.datetime "updated_at", null: false
89
+ end
90
+
91
+ create_table "images", force: :cascade, id: false do |t|
92
+ t.string "file"
93
+ end
94
+
95
+ create_table "posts", force: :cascade do |t|
96
+ t.integer "author_id"
97
+ t.integer "activity_id"
98
+ t.string "title"
99
+ t.text "content"
100
+ t.enum "status", subtype: :content_status
101
+ t.index ["author_id"], name: "index_posts_on_author_id", using: :btree
102
+ end
103
+
104
+ create_table "users", force: :cascade do |t|
105
+ t.string "name", null: false
106
+ t.enum "role", subtype: :roles, default: :visitor
107
+ t.datetime "created_at", null: false
108
+ t.datetime "updated_at", null: false
109
+ end
110
+
111
+ create_table "activities", force: :cascade do |t|
112
+ t.integer "author_id"
113
+ t.string "title"
114
+ t.boolean "active"
115
+ t.enum "kind", subtype: :types
116
+ t.datetime "created_at", null: false
117
+ t.datetime "updated_at", null: false
118
+ end
119
+
120
+ create_table "activity_books", force: :cascade, inherits: :activities do |t|
121
+ t.text "description"
122
+ t.string "url"
123
+ t.boolean "activated"
124
+ end
125
+
126
+ create_table "activity_posts", force: :cascade, inherits: [:activities, :images] do |t|
127
+ t.integer "post_id"
128
+ t.string "url"
129
+ t.integer "activated"
130
+ end
131
+
132
+ create_table "activity_post_samples", force: :cascade, inherits: :activity_posts
133
+
134
+ # create_table "activity_blanks", force: :cascade, inherits: :activities
135
+
136
+ # create_table "activity_images", force: :cascade, inherits: [:activities, :images]
137
+
138
+ add_foreign_key "posts", "authors"
139
+ end
140
+ rescue SystemExit
141
+ end
@@ -0,0 +1,59 @@
1
+ require 'torque-postgresql'
2
+ require 'database_cleaner'
3
+ require 'factory_bot'
4
+ require 'dotenv'
5
+ require 'faker'
6
+ require 'rspec'
7
+ require 'byebug'
8
+
9
+ Dotenv.load
10
+
11
+ ActiveRecord::Base.establish_connection(ENV['DATABASE_URL'])
12
+ cache = ActiveRecord::Base.connection.schema_cache
13
+
14
+ cleaner = ->() do
15
+ cache.instance_variable_set(:@inheritance_loaded, false)
16
+ cache.instance_variable_set(:@inheritance_dependencies, {})
17
+ cache.instance_variable_set(:@inheritance_associations, {})
18
+ end
19
+
20
+ load File.join('schema.rb')
21
+ Dir.glob(File.join('spec', '{models,factories,mocks}', '*.rb')) do |file|
22
+ require file[5..-4]
23
+ end
24
+
25
+ cleaner.call
26
+ I18n.load_path << Pathname.pwd.join('spec', 'en.yml')
27
+ RSpec.configure do |config|
28
+ config.extend Mocks::CreateTable
29
+ config.include Mocks::CacheQuery
30
+
31
+ config.formatter = :documentation
32
+ config.color = true
33
+ config.tty = true
34
+
35
+ # Handles acton before rspec initialize
36
+ config.before(:suite) do
37
+ DatabaseCleaner.clean_with(:truncation)
38
+ end
39
+
40
+ config.before(:each) do
41
+ DatabaseCleaner.strategy = :transaction
42
+ end
43
+
44
+ config.before(:each, js: true) do
45
+ DatabaseCleaner.strategy = :truncation
46
+ end
47
+
48
+ config.before(:each) do
49
+ DatabaseCleaner.start
50
+ end
51
+
52
+ config.after(:each) do
53
+ DatabaseCleaner.clean
54
+ end
55
+
56
+ config.before(:each) do
57
+ cleaner.call
58
+ end
59
+ end
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe 'Arel' do
4
+ context 'on inflix operation' do
5
+ let(:list) { Torque::PostgreSQL::Arel::INFLIX_OPERATION }
6
+ let(:collector) { ::Arel::Collectors::SQLString }
7
+ let(:attribute) { ::Arel::Table.new('a')['sample'] }
8
+ let(:conn) { ActiveRecord::Base.connection }
9
+ let(:visitor) { ::Arel::Visitors::PostgreSQL.new(conn) }
10
+
11
+ [
12
+ [:overlaps, [1, 2], "ARRAY[1, 2]"],
13
+ [:contains, [3, 4], "ARRAY[3, 4]"],
14
+ [:contained_by, [5, 6], "ARRAY[5, 6]"],
15
+ [:has_key, ::Arel.sql("'a'"), "'a'"],
16
+ [:has_all_keys, ['b', 'c'], "ARRAY['b', 'c']"],
17
+ [:has_any_keys, ['d', 'e'], "ARRAY['d', 'e']"],
18
+
19
+ [:strictly_left, ::Arel.sql('numrange(1, 2)'), 'numrange(1, 2)'],
20
+ [:strictly_right, ::Arel.sql('numrange(3, 4)'), 'numrange(3, 4)'],
21
+ [:doesnt_right_extend, ::Arel.sql('numrange(5, 6)'), 'numrange(5, 6)'],
22
+ [:doesnt_left_extend, ::Arel.sql('numrange(7, 8)'), 'numrange(7, 8)'],
23
+ [:adjacent_to, ::Arel.sql('numrange(9, 0)'), 'numrange(9, 0)'],
24
+ ].each do |(operator, value, quoted_value)|
25
+ klass_name = operator.to_s.camelize
26
+
27
+ context "##{operator}" do
28
+ let(:instance) { attribute.public_send(operator, value) }
29
+
30
+ context 'for attribute' do
31
+ let(:klass) { ::Arel::Nodes.const_get(klass_name) }
32
+
33
+ it "returns a new #{klass_name}" do
34
+ expect(instance).to be_a(klass)
35
+ end
36
+ end
37
+
38
+ context 'for visitor' do
39
+ let(:result) { visitor.accept(instance, collector.new).value }
40
+
41
+ it 'returns a formatted operation' do
42
+ expect(result).to be_eql("\"a\".\"sample\" #{list[klass_name]} #{quoted_value}")
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ context 'on cast' do
50
+ it 'provides an array method' do
51
+ sample1 = ::Arel.array(1, 2, 3, 4)
52
+ sample2 = ::Arel.array([1, 2, 3, 4])
53
+ sample3 = ::Arel.array(1, 2, 3, 4, cast: 'bigint')
54
+ sample4 = ::Arel.array([1, 2, 3, 4], [5, 6, 7, 8], cast: 'integer')
55
+
56
+ expect(sample1.to_sql).to be_eql('ARRAY[1, 2, 3, 4]')
57
+ expect(sample2.to_sql).to be_eql('ARRAY[1, 2, 3, 4]')
58
+ expect(sample3.to_sql).to be_eql('ARRAY[1, 2, 3, 4]::bigint[]')
59
+ expect(sample4.to_sql).to be_eql('ARRAY[ARRAY[1, 2, 3, 4], ARRAY[5, 6, 7, 8]]::integer[]')
60
+ end
61
+
62
+ it 'provides a cast method' do
63
+ attribute = ::Arel::Table.new('a')['sample']
64
+ quoted = ::Arel::Nodes::build_quoted([1])
65
+ casted = ::Arel::Nodes::build_quoted(1, attribute)
66
+
67
+ expect(attribute.cast('text').to_sql).to be_eql('"a"."sample"::text')
68
+ expect(quoted.cast('bigint', true).to_sql).to be_eql('ARRAY[1]::bigint[]')
69
+ expect(casted.cast('string').to_sql).to be_eql("1::string")
70
+ end
71
+ end
72
+ end