activerecord-spanner-adapter 1.5.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/acceptance-tests-on-emulator.yaml +1 -1
  3. data/.github/workflows/acceptance-tests-on-production.yaml +5 -3
  4. data/.github/workflows/ci.yaml +1 -1
  5. data/.github/workflows/nightly-acceptance-tests-on-emulator.yaml +1 -1
  6. data/.github/workflows/nightly-acceptance-tests-on-production.yaml +5 -3
  7. data/.github/workflows/nightly-unit-tests.yaml +1 -1
  8. data/.github/workflows/release-please-label.yml +1 -1
  9. data/.release-please-manifest.json +1 -1
  10. data/CHANGELOG.md +14 -0
  11. data/Gemfile +5 -2
  12. data/README.md +10 -10
  13. data/acceptance/cases/interleaved_associations/has_many_associations_using_interleaved_test.rb +6 -0
  14. data/acceptance/cases/migration/change_schema_test.rb +19 -3
  15. data/acceptance/cases/migration/schema_dumper_test.rb +10 -1
  16. data/acceptance/cases/models/insert_all_test.rb +22 -0
  17. data/acceptance/cases/models/interleave_test.rb +6 -0
  18. data/acceptance/cases/tasks/database_tasks_test.rb +340 -2
  19. data/acceptance/cases/transactions/optimistic_locking_test.rb +6 -0
  20. data/acceptance/cases/transactions/read_write_transactions_test.rb +24 -0
  21. data/acceptance/models/table_with_sequence.rb +10 -0
  22. data/acceptance/schema/schema.rb +65 -19
  23. data/acceptance/test_helper.rb +1 -1
  24. data/activerecord-spanner-adapter.gemspec +1 -1
  25. data/examples/snippets/bit-reversed-sequence/README.md +103 -0
  26. data/examples/snippets/bit-reversed-sequence/Rakefile +13 -0
  27. data/examples/snippets/bit-reversed-sequence/application.rb +68 -0
  28. data/examples/snippets/bit-reversed-sequence/config/database.yml +8 -0
  29. data/examples/snippets/bit-reversed-sequence/db/migrate/01_create_tables.rb +33 -0
  30. data/examples/snippets/bit-reversed-sequence/db/schema.rb +31 -0
  31. data/examples/snippets/bit-reversed-sequence/db/seeds.rb +31 -0
  32. data/examples/snippets/bit-reversed-sequence/models/album.rb +11 -0
  33. data/examples/snippets/bit-reversed-sequence/models/singer.rb +15 -0
  34. data/examples/snippets/interleaved-tables/README.md +44 -53
  35. data/examples/snippets/interleaved-tables/Rakefile +2 -2
  36. data/examples/snippets/interleaved-tables/application.rb +2 -2
  37. data/examples/snippets/interleaved-tables/db/migrate/01_create_tables.rb +12 -18
  38. data/examples/snippets/interleaved-tables/db/schema.rb +9 -7
  39. data/examples/snippets/interleaved-tables/db/seeds.rb +1 -1
  40. data/examples/snippets/interleaved-tables/models/album.rb +3 -7
  41. data/examples/snippets/interleaved-tables/models/singer.rb +1 -1
  42. data/examples/snippets/interleaved-tables/models/track.rb +6 -7
  43. data/examples/snippets/interleaved-tables-before-7.1/README.md +167 -0
  44. data/examples/snippets/interleaved-tables-before-7.1/Rakefile +13 -0
  45. data/examples/snippets/interleaved-tables-before-7.1/application.rb +126 -0
  46. data/examples/snippets/interleaved-tables-before-7.1/config/database.yml +8 -0
  47. data/examples/snippets/interleaved-tables-before-7.1/db/migrate/01_create_tables.rb +44 -0
  48. data/examples/snippets/interleaved-tables-before-7.1/db/schema.rb +37 -0
  49. data/examples/snippets/interleaved-tables-before-7.1/db/seeds.rb +40 -0
  50. data/examples/snippets/interleaved-tables-before-7.1/models/album.rb +20 -0
  51. data/examples/snippets/interleaved-tables-before-7.1/models/singer.rb +18 -0
  52. data/examples/snippets/interleaved-tables-before-7.1/models/track.rb +28 -0
  53. data/examples/snippets/query-logs/README.md +43 -0
  54. data/examples/snippets/query-logs/Rakefile +13 -0
  55. data/examples/snippets/query-logs/application.rb +63 -0
  56. data/examples/snippets/query-logs/config/database.yml +8 -0
  57. data/examples/snippets/query-logs/db/migrate/01_create_tables.rb +21 -0
  58. data/examples/snippets/query-logs/db/schema.rb +31 -0
  59. data/examples/snippets/query-logs/db/seeds.rb +24 -0
  60. data/examples/snippets/query-logs/models/album.rb +9 -0
  61. data/examples/snippets/query-logs/models/singer.rb +9 -0
  62. data/lib/active_record/connection_adapters/spanner/column.rb +13 -0
  63. data/lib/active_record/connection_adapters/spanner/database_statements.rb +144 -35
  64. data/lib/active_record/connection_adapters/spanner/schema_cache.rb +3 -21
  65. data/lib/active_record/connection_adapters/spanner/schema_creation.rb +11 -0
  66. data/lib/active_record/connection_adapters/spanner/schema_definitions.rb +4 -0
  67. data/lib/active_record/connection_adapters/spanner/schema_statements.rb +3 -2
  68. data/lib/active_record/connection_adapters/spanner_adapter.rb +28 -9
  69. data/lib/activerecord_spanner_adapter/base.rb +58 -21
  70. data/lib/activerecord_spanner_adapter/information_schema.rb +33 -24
  71. data/lib/activerecord_spanner_adapter/primary_key.rb +1 -1
  72. data/lib/activerecord_spanner_adapter/table/column.rb +4 -9
  73. data/lib/activerecord_spanner_adapter/version.rb +1 -1
  74. data/lib/arel/visitors/spanner.rb +3 -1
  75. metadata +33 -4
@@ -0,0 +1,68 @@
1
+ # Copyright 2023 Google LLC
2
+ #
3
+ # Use of this source code is governed by an MIT-style
4
+ # license that can be found in the LICENSE file or at
5
+ # https://opensource.org/licenses/MIT.
6
+
7
+ require "io/console"
8
+ require_relative "../config/environment"
9
+ require_relative "models/singer"
10
+ require_relative "models/album"
11
+
12
+ class Application
13
+ def self.run
14
+ # Create a new singer.
15
+ singer = create_new_singer
16
+
17
+ # Create a new album.
18
+ album = create_new_album singer
19
+
20
+ # Verify that the album exists.
21
+ find_album singer.singerid, album.albumid
22
+
23
+ # List all singers, albums and tracks.
24
+ list_singers_albums
25
+
26
+ puts ""
27
+ puts "Press any key to end the application"
28
+ STDIN.getch
29
+ end
30
+
31
+ def self.find_album singerid, albumid
32
+ album = Album.find [singerid, albumid]
33
+ puts "Found album: #{album.title}"
34
+ end
35
+
36
+ def self.list_singers_albums
37
+ puts ""
38
+ puts "Listing all singers with corresponding albums"
39
+ Singer.all.order("last_name, first_name").each do |singer|
40
+ puts "#{singer.first_name} #{singer.last_name} has #{singer.albums.count} albums:"
41
+ singer.albums.order("title").each do |album|
42
+ puts " #{album.title}"
43
+ end
44
+ end
45
+ end
46
+
47
+ def self.create_new_singer
48
+ # Create a new singer. The singerid is generated by the bit-reversed sequence in the database and returned.
49
+ puts ""
50
+ singer = Singer.create first_name: "Melissa", last_name: "Garcia"
51
+ puts "Created a new singer '#{singer.first_name} #{singer.last_name}' with id #{singer.singerid}"
52
+
53
+ singer
54
+ end
55
+
56
+ def self.create_new_album singer
57
+ # Create a new album.
58
+ puts ""
59
+ puts "Creating a new album for #{singer.first_name} #{singer.last_name}"
60
+ # The albumid is not generated by a sequence in the database.
61
+ album = singer.albums.build albumid: 1, title: "New Title"
62
+ album.save!
63
+
64
+ album
65
+ end
66
+ end
67
+
68
+ Application.run
@@ -0,0 +1,8 @@
1
+ development:
2
+ adapter: spanner
3
+ emulator_host: localhost:9010
4
+ project: test-project
5
+ instance: test-instance
6
+ database: testdb
7
+ pool: 5
8
+ timeout: 5000
@@ -0,0 +1,33 @@
1
+ # Copyright 2023 Google LLC
2
+ #
3
+ # Use of this source code is governed by an MIT-style
4
+ # license that can be found in the LICENSE file or at
5
+ # https://opensource.org/licenses/MIT.
6
+
7
+ class CreateTables < ActiveRecord::Migration[7.1]
8
+ def change
9
+ # Execute the entire migration as one DDL batch.
10
+ connection.ddl_batch do
11
+ # TODO: Uncomment when bit-reversed sequences are supported in the emulator.
12
+ # connection.execute "create sequence singer_sequence OPTIONS (sequence_kind = 'bit_reversed_positive')"
13
+
14
+ # Explicitly define the primary key.
15
+ create_table :singers, id: false, primary_key: :singerid do |t|
16
+ # TODO: Uncomment when bit-reversed sequences are supported in the emulator.
17
+ # t.integer :singerid, primary_key: true, null: false,
18
+ # default: -> { "GET_NEXT_SEQUENCE_VALUE(SEQUENCE singer_sequence)" }
19
+ t.integer :singerid, primary_key: true, null: false, default: -> { "FARM_FINGERPRINT(GENERATE_UUID())" }
20
+ t.string :first_name
21
+ t.string :last_name
22
+ end
23
+
24
+ create_table :albums, primary_key: [:singerid, :albumid], id: false do |t|
25
+ # Interleave the `albums` table in the parent table `singers`.
26
+ t.interleave_in :singers
27
+ t.integer :singerid, null: false
28
+ t.integer :albumid, null: false
29
+ t.string :title
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,31 @@
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
+ # This file is the source Rails uses to define your schema when running `bin/rails
6
+ # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
7
+ # be faster and is potentially less error prone than running all of your
8
+ # migrations from scratch. Old migrations may fail to apply correctly if those
9
+ # migrations use external dependencies or application code.
10
+ #
11
+ # It's strongly recommended that you check this file into your version control system.
12
+
13
+ ActiveRecord::Schema[7.1].define(version: 1) do
14
+ connection.start_batch_ddl
15
+
16
+ create_table "albums", primary_key: ["singerid", "albumid"], force: :cascade do |t|
17
+ t.integer "singerid", limit: 8, null: false
18
+ t.integer "albumid", limit: 8, null: false
19
+ t.string "title"
20
+ end
21
+
22
+ create_table "singers", primary_key: "singerid", default: -> { "FARM_FINGERPRINT(GENERATE_UUID())" }, force: :cascade do |t|
23
+ t.string "first_name"
24
+ t.string "last_name"
25
+ end
26
+
27
+ connection.run_batch
28
+ rescue
29
+ abort_batch
30
+ raise
31
+ end
@@ -0,0 +1,31 @@
1
+ # Copyright 2023 Google LLC
2
+ #
3
+ # Use of this source code is governed by an MIT-style
4
+ # license that can be found in the LICENSE file or at
5
+ # https://opensource.org/licenses/MIT.
6
+
7
+ require_relative "../../config/environment.rb"
8
+ require_relative "../models/singer"
9
+ require_relative "../models/album"
10
+
11
+ first_names = %w[Pete Alice John Ethel Trudy Naomi Wendy Ruben Thomas Elly]
12
+ last_names = %w[Wendelson Allison Peterson Johnson Henderson Ericsson Aronson Tennet Courtou]
13
+
14
+ adjectives = %w[daily happy blue generous cooked bad open]
15
+ nouns = %w[windows potatoes bank street tree glass bottle]
16
+
17
+ # Note: We do not use mutations to insert these rows, because letting the database generate the primary key means that
18
+ # we rely on a `THEN RETURN id` clause in the insert statement. This is only supported for DML statements, and not for
19
+ # mutations.
20
+ ActiveRecord::Base.transaction do
21
+ singers = []
22
+ 5.times do
23
+ singers << Singer.create(first_name: first_names.sample, last_name: last_names.sample)
24
+ end
25
+
26
+ albums = []
27
+ 20.times do
28
+ singer = singers.sample
29
+ albums << Album.create(title: "#{adjectives.sample} #{nouns.sample}", singer: singer)
30
+ end
31
+ end
@@ -0,0 +1,11 @@
1
+ # Copyright 2023 Google LLC
2
+ #
3
+ # Use of this source code is governed by an MIT-style
4
+ # license that can be found in the LICENSE file or at
5
+ # https://opensource.org/licenses/MIT.
6
+
7
+ class Album < ActiveRecord::Base
8
+ # `albums` is defined as INTERLEAVE IN PARENT `singers`.
9
+ # The primary key of `singers` is `singerid`.
10
+ belongs_to :singer, foreign_key: :singerid
11
+ end
@@ -0,0 +1,15 @@
1
+ # Copyright 2023 Google LLC
2
+ #
3
+ # Use of this source code is governed by an MIT-style
4
+ # license that can be found in the LICENSE file or at
5
+ # https://opensource.org/licenses/MIT.
6
+
7
+ class Singer < ActiveRecord::Base
8
+ # Set the sequence name so the ActiveRecord provider knows that it should let the database generate the primary key
9
+ # value and return it using a `THEN RETURN id` clause.
10
+ self.sequence_name = :singer_sequence
11
+
12
+ # `albums` is defined as INTERLEAVE IN PARENT `singers`.
13
+ # The primary key of `albums` is (`singerid`, `albumid`).
14
+ has_many :albums, foreign_key: :singerid
15
+ end
@@ -1,9 +1,10 @@
1
1
  # Sample - Interleaved Tables
2
2
 
3
- This example shows how to use interleaved tables with the Spanner ActiveRecord adapter.
4
- Interleaved tables use composite primary keys. This is not natively supported by ActiveRecord.
5
- It is therefore necessary to use the `composite_primary_keys` (https://github.com/composite-primary-keys/composite_primary_keys)
6
- gem to enable the use of interleaved tables.
3
+ __NOTE__: This example requires Rails 7.1 or later.
4
+
5
+ This example shows how to use interleaved tables with the Spanner ActiveRecord adapter in Rails 7.1 and later.
6
+ Interleaved tables use composite primary keys. This is only supported by Rails 7.1 and later. For older versions,
7
+ you need to use the third-party gem `composite_primary_key` (https://github.com/composite-primary-keys/composite_primary_keys).
7
8
 
8
9
  See https://cloud.google.com/spanner/docs/schema-and-data-model#creating-interleaved-tables for more information
9
10
  on interleaved tables if you are not familiar with this concept.
@@ -13,8 +14,6 @@ You can create interleaved tables using migrations in ActiveRecord by using the
13
14
  methods that are defined on `TableDefinition`:
14
15
  * `interleave_in`: Specifies which parent table a child table should be interleaved in and optionally whether
15
16
  deletes of a parent record should automatically cascade delete all child records.
16
- * `parent_key`: Creates a column that is a reference to (a part of) the primary key of the parent table. Each child
17
- table must include all the primary key columns of the parent table as a `parent_key`.
18
17
 
19
18
  Cloud Spanner requires a child table to include the exact same primary key columns as the parent table in addition to
20
19
  the primary key column(s) of the child table. This means that the default `id` primary key column of ActiveRecord is
@@ -32,62 +31,55 @@ CREATE TABLE singers (
32
31
  ) PRIMARY KEY (singerid);
33
32
 
34
33
  CREATE TABLE albums (
35
- albumid INT64 NOT NULL,
36
34
  singerid INT64 NOT NULL,
35
+ albumid INT64 NOT NULL,
37
36
  title STRING(MAX)
38
37
  ) PRIMARY KEY (singerid, albumid), INTERLEAVE IN PARENT singers;
39
38
 
40
39
  CREATE TABLE tracks (
41
- trackid INT64 NOT NULL,
42
40
  singerid INT64 NOT NULL,
43
41
  albumid INT64 NOT NULL,
42
+ trackid INT64 NOT NULL,
44
43
  title STRING(MAX),
45
44
  duration NUMERIC
46
45
  ) PRIMARY KEY (singerid, albumid, trackid), INTERLEAVE IN PARENT albums ON DELETE CASCADE;
47
46
  ```
48
47
 
49
- This schema can be created in ActiveRecord as follows:
48
+ This schema can be created in ActiveRecord 7.1 and later as follows:
50
49
 
51
50
  ```ruby
52
- create_table :singers, id: false do |t|
53
- # Explicitly define the primary key with a custom name to prevent all primary key columns from being named `id`.
54
- t.primary_key :singerid
55
- t.string :first_name
56
- t.string :last_name
57
- end
51
+ # Execute the entire migration as one DDL batch.
52
+ connection.ddl_batch do
53
+ # Explicitly define the primary key.
54
+ create_table :singers, id: false, primary_key: :singerid do |t|
55
+ t.integer :singerid
56
+ t.string :first_name
57
+ t.string :last_name
58
+ end
58
59
 
59
- create_table :albums, id: false do |t|
60
- # Interleave the `albums` table in the parent table `singers`.
61
- t.interleave_in :singers
62
- t.primary_key :albumid
63
- # `singerid` is defined as a `parent_key` which makes it a part of the primary key in the table definition, but
64
- # it is not presented to ActiveRecord as part of the primary key, to prevent ActiveRecord from considering this
65
- # to be an entity with a composite primary key (which is not supported by ActiveRecord).
66
- t.parent_key :singerid
67
- t.string :title
68
- end
60
+ create_table :albums, primary_key: [:singerid, :albumid], id: false do |t|
61
+ # Interleave the `albums` table in the parent table `singers`.
62
+ t.interleave_in :singers
63
+ t.integer :singerid
64
+ t.integer :albumid
65
+ t.string :title
66
+ end
69
67
 
70
- create_table :tracks, id: false do |t|
71
- # Interleave the `tracks` table in the parent table `albums` and cascade delete all tracks that belong to an
72
- # album when an album is deleted.
73
- t.interleave_in :albums, :cascade
74
- # Add `trackid` as the primary key in the table definition. Add the other key parts as
75
- # a `parent_key`.
76
- t.primary_key :trackid
77
- # `singerid` and `albumid` form the parent key of `tracks`. These are part of the primary key definition in the
78
- # database, but are presented as parent keys to ActiveRecord.
79
- t.parent_key :singerid
80
- t.parent_key :albumid
81
- t.string :title
82
- t.numeric :duration
68
+ create_table :tracks, primary_key: [:singerid, :albumid, :trackid], id: false do |t|
69
+ # Interleave the `tracks` table in the parent table `albums` and cascade delete all tracks that belong to an
70
+ # album when an album is deleted.
71
+ t.interleave_in :albums, :cascade
72
+ t.integer :singerid
73
+ t.integer :albumid
74
+ t.integer :trackid
75
+ t.string :title
76
+ t.numeric :duration
77
+ end
83
78
  end
84
79
  ```
85
80
 
86
81
  ## Models for Interleaved Tables
87
- The model definition for an interleaved table (a child table) must use the `primary_keys=col1, col2, ...`
88
- function from the `composite_primary_keys` gem.
89
-
90
- An interleaved table parent/child relationship must be modelled as a `belongs_to`/`has_many` association in
82
+ An interleaved table parent/child relationship can be modelled as a `belongs_to`/`has_many` association in
91
83
  ActiveRecord. As the columns that are used to reference a parent record use a custom column name, it is required to also
92
84
  include the custom column name(s) in the `belongs_to` and `has_many` definitions.
93
85
 
@@ -108,7 +100,7 @@ class Singer < ActiveRecord::Base
108
100
  has_many :albums, foreign_key: :singerid
109
101
 
110
102
  # `tracks` is defined as INTERLEAVE IN PARENT `albums`.
111
- # The primary key of `tracks` is (`singerid`, `albumid`, `trackid`).
103
+ # The primary key of `tracks` is [`singerid`, `albumid`, `trackid`].
112
104
  # The `singerid` column can be used to associate tracks with a singer without the need to go through albums.
113
105
  # Note also that the inclusion of `singerid` as a column in `tracks` is required in order to make `tracks` a child
114
106
  # table of `albums` which has primary key (`singerid`, `albumid`).
@@ -116,24 +108,21 @@ class Singer < ActiveRecord::Base
116
108
  end
117
109
 
118
110
  class Album < ActiveRecord::Base
119
- # Use the `composite_primary_key` gem to create a composite primary key definition for the model.
120
- self.primary_keys = :singerid, :albumid
121
-
122
111
  # `albums` is defined as INTERLEAVE IN PARENT `singers`.
123
112
  # The primary key of `singers` is `singerid`.
124
113
  belongs_to :singer, foreign_key: :singerid
125
114
 
126
115
  # `tracks` is defined as INTERLEAVE IN PARENT `albums`.
127
116
  # The primary key of `albums` is (`singerid`, `albumid`).
128
- has_many :tracks, foreign_key: [:singerid, :albumid]
117
+ # Rails 7.1 requires using query_constraints to define a composite foreign key.
118
+ has_many :tracks, query_constraints: [:singerid, :albumid]
129
119
  end
130
120
 
131
121
  class Track < ActiveRecord::Base
132
- # Use the `composite_primary_key` gem to create a composite primary key definition for the model.
133
- self.primary_keys = :singerid, :albumid, :trackid
134
-
135
- # `tracks` is defined as INTERLEAVE IN PARENT `albums`. The primary key of `albums` is (`singerid`, `albumid`).
136
- belongs_to :album, foreign_key: [:singerid, :albumid]
122
+ # `tracks` is defined as INTERLEAVE IN PARENT `albums`.
123
+ # The primary key of `albums` is (`singerid`, `albumid`).
124
+ # Rails 7.1 requires a composite primary key in a belongs_to relationship to be specified as query_constraints.
125
+ belongs_to :album, query_constraints: [:singerid, :albumid]
137
126
 
138
127
  # `tracks` also has a `singerid` column that can be used to associate a Track with a Singer.
139
128
  belongs_to :singer, foreign_key: :singerid
@@ -157,8 +146,10 @@ end
157
146
  The sample will automatically start a Spanner Emulator in a docker container and execute the sample
158
147
  against that emulator. The emulator will automatically be stopped when the application finishes.
159
148
 
160
- Run the application with the command
149
+ Run the application with the following commands:
161
150
 
162
151
  ```bash
152
+ export AR_VERSION="~> 7.1.2"
153
+ bundle install
163
154
  bundle exec rake run
164
155
  ```
@@ -1,4 +1,4 @@
1
- # Copyright 2022 Google LLC
1
+ # Copyright 2023 Google LLC
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -7,7 +7,7 @@
7
7
  require_relative "../config/environment"
8
8
  require "sinatra/activerecord/rake"
9
9
 
10
- desc "Sample showing how to work with interleaved tables in ActiveRecord."
10
+ desc "Sample showing how to work with interleaved tables in ActiveRecord 7.1 and later."
11
11
  task :run do
12
12
  Dir.chdir("..") { sh "bundle exec rake run[interleaved-tables]" }
13
13
  end
@@ -1,4 +1,4 @@
1
- # Copyright 2022 Google LLC
1
+ # Copyright 2023 Google LLC
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -49,7 +49,7 @@ class Application
49
49
  puts "Found album: #{album.title}"
50
50
  end
51
51
 
52
- def self.list_singers_albums_tracks
52
+ def self.list_singers_albums
53
53
  puts ""
54
54
  puts "Listing all singers with corresponding albums and tracks"
55
55
  Singer.all.order("last_name, first_name").each do |singer|
@@ -1,41 +1,35 @@
1
- # Copyright 2022 Google LLC
1
+ # Copyright 2023 Google LLC
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
5
5
  # https://opensource.org/licenses/MIT.
6
6
 
7
- class CreateTables < ActiveRecord::Migration[6.0]
7
+ class CreateTables < ActiveRecord::Migration[7.1]
8
8
  def change
9
9
  # Execute the entire migration as one DDL batch.
10
10
  connection.ddl_batch do
11
- create_table :singers, id: false do |t|
12
- # Explicitly define the primary key with a custom name to prevent all primary key columns from being named `id`.
13
- t.primary_key :singerid
11
+ # Explicitly define the primary key.
12
+ create_table :singers, id: false, primary_key: :singerid do |t|
13
+ t.integer :singerid
14
14
  t.string :first_name
15
15
  t.string :last_name
16
16
  end
17
17
 
18
- create_table :albums, id: false do |t|
18
+ create_table :albums, primary_key: [:singerid, :albumid], id: false do |t|
19
19
  # Interleave the `albums` table in the parent table `singers`.
20
20
  t.interleave_in :singers
21
- t.primary_key :albumid
22
- # `singerid` is defined as a `parent_key` which makes it a part of the primary key in the table definition, but
23
- # it is not presented to ActiveRecord as part of the primary key, to prevent ActiveRecord from considering this
24
- # to be an entity with a composite primary key (which is not supported by ActiveRecord).
25
- t.parent_key :singerid
21
+ t.integer :singerid
22
+ t.integer :albumid
26
23
  t.string :title
27
24
  end
28
25
 
29
- create_table :tracks, id: false do |t|
26
+ create_table :tracks, primary_key: [:singerid, :albumid, :trackid], id: false do |t|
30
27
  # Interleave the `tracks` table in the parent table `albums` and cascade delete all tracks that belong to an
31
28
  # album when an album is deleted.
32
29
  t.interleave_in :albums, :cascade
33
- # `trackid` is considered the only primary key column by ActiveRecord.
34
- t.primary_key :trackid
35
- # `singerid` and `albumid` form the parent key of `tracks`. These are part of the primary key definition in the
36
- # database, but are presented as parent keys to ActiveRecord.
37
- t.parent_key :singerid
38
- t.parent_key :albumid
30
+ t.integer :singerid
31
+ t.integer :albumid
32
+ t.integer :trackid
39
33
  t.string :title
40
34
  t.numeric :duration
41
35
  end
@@ -10,22 +10,24 @@
10
10
  #
11
11
  # It's strongly recommended that you check this file into your version control system.
12
12
 
13
- ActiveRecord::Schema.define(version: 1) do
13
+ ActiveRecord::Schema[7.1].define(version: 1) do
14
14
  connection.start_batch_ddl
15
15
 
16
- create_table "albums", primary_key: "albumid", id: { limit: 8 }, force: :cascade do |t|
17
- t.integer "singerid", limit: 8, null: false
16
+ create_table "albums", primary_key: ["singerid", "albumid"], force: :cascade do |t|
17
+ t.integer "singerid", limit: 8
18
+ t.integer "albumid", limit: 8
18
19
  t.string "title"
19
20
  end
20
21
 
21
- create_table "singers", primary_key: "singerid", id: { limit: 8 }, force: :cascade do |t|
22
+ create_table "singers", primary_key: "singerid", force: :cascade do |t|
22
23
  t.string "first_name"
23
24
  t.string "last_name"
24
25
  end
25
26
 
26
- create_table "tracks", primary_key: "trackid", id: { limit: 8 }, force: :cascade do |t|
27
- t.integer "singerid", limit: 8, null: false
28
- t.integer "albumid", limit: 8, null: false
27
+ create_table "tracks", primary_key: ["singerid", "albumid", "trackid"], force: :cascade do |t|
28
+ t.integer "singerid", limit: 8
29
+ t.integer "albumid", limit: 8
30
+ t.integer "trackid", limit: 8
29
31
  t.string "title"
30
32
  t.decimal "duration"
31
33
  end
@@ -1,4 +1,4 @@
1
- # Copyright 2022 Google LLC
1
+ # Copyright 2023 Google LLC
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,20 +1,16 @@
1
- # Copyright 2022 Google LLC
1
+ # Copyright 2023 Google LLC
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
5
5
  # https://opensource.org/licenses/MIT.
6
6
 
7
- require "composite_primary_keys"
8
-
9
7
  class Album < ActiveRecord::Base
10
- # Use the `composite_primary_key` gem to create a composite primary key definition for the model.
11
- self.primary_keys = :singerid, :albumid
12
-
13
8
  # `albums` is defined as INTERLEAVE IN PARENT `singers`.
14
9
  # The primary key of `singers` is `singerid`.
15
10
  belongs_to :singer, foreign_key: :singerid
16
11
 
17
12
  # `tracks` is defined as INTERLEAVE IN PARENT `albums`.
18
13
  # The primary key of `albums` is (`singerid`, `albumid`).
19
- has_many :tracks, foreign_key: [:singerid, :albumid]
14
+ # Rails 7.1 requires using query_constraints to define a composite foreign key.
15
+ has_many :tracks, query_constraints: [:singerid, :albumid]
20
16
  end
@@ -1,4 +1,4 @@
1
- # Copyright 2022 Google LLC
1
+ # Copyright 2023 Google LLC
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
@@ -1,17 +1,16 @@
1
- # Copyright 2022 Google LLC
1
+ # Copyright 2023 Google LLC
2
2
  #
3
3
  # Use of this source code is governed by an MIT-style
4
4
  # license that can be found in the LICENSE file or at
5
5
  # https://opensource.org/licenses/MIT.
6
6
 
7
7
  class Track < ActiveRecord::Base
8
- # Use the `composite_primary_key` gem to create a composite primary key definition for the model.
9
- self.primary_keys = :singerid, :albumid, :trackid
8
+ # `tracks` is defined as INTERLEAVE IN PARENT `albums`.
9
+ # The primary key of `albums` is (`singerid`, `albumid`).
10
+ # Rails 7.1 requires a composite primary key in a belongs_to relationship to be specified as query_constraints.
11
+ belongs_to :album, query_constraints: [:singerid, :albumid]
10
12
 
11
- # `tracks` is defined as INTERLEAVE IN PARENT `albums`. The primary key of `albums` is ()`singerid`, `albumid`).
12
- belongs_to :album, foreign_key: [:singerid, :albumid]
13
-
14
- # `tracks` also has a `singerid` column should be used to associate a Track with a Singer.
13
+ # `tracks` also has a `singerid` column that can be used to associate a Track with a Singer.
15
14
  belongs_to :singer, foreign_key: :singerid
16
15
 
17
16
  # Override the default initialize method to automatically set the singer attribute when an album is given.