torque-postgresql 1.1.8 → 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 (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