activerecord-spanner-adapter 2.5.0 → 2.6.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e5132b6ef0337fa324c42c5b61ff6bee8e745352c58c46afc5f583a253fc6571
4
- data.tar.gz: 699d2b993d739c78fc95fc12b30189e512517111aa9ae787d929b9d3ac828576
3
+ metadata.gz: 95f5439f841462a7fb1d4be2eac6333fb9e67ddc9038002ca15d04a6a8845f87
4
+ data.tar.gz: e713c475fc1cc4fbd056fcd4197613d79ab739fd27aa0fd22618e3871dfd0e45
5
5
  SHA512:
6
- metadata.gz: 3d97e90c71f7dad83a35fc6cfb1921c24989d88b050c88b75ff444dbc58d4c0c2a9abbde2f8197d700dca121dda5c12853e4f6cae6c27bfc0520c7facbff3338
7
- data.tar.gz: 5dbb5523a45941eff16db888d0af3c5ab7ea3e54af9e94d0f0943cef2a9b6d5e39078df1a1761e05a436910906cd19825f84f298d8e2b9043d79981dff51b4f7
6
+ metadata.gz: c3181723a9aec9fe057bf22fd454509dae392e95545b189a2177a53dd24b2e05f1e59f19d9e4e83d553843d05f86a86d037bc69cc3fb1c3614b7fd75c328afe0
7
+ data.tar.gz: 10ed060f43d6454a0f249235861787889c93e87722d70b0a89b559ce6650ad5b9ab27aa1b828b49cc43e0d22e076dc892cae42563d9f0f6b8f807b45422246cd
data/.github/CODEOWNERS CHANGED
@@ -4,4 +4,4 @@
4
4
  # For syntax help see:
5
5
  # https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax
6
6
 
7
- * @googleapis/cloud-sdk-ruby-team @olavloite @rahul2393 @ansh0l
7
+ * @googleapis/cloud-sdk-ruby-team @olavloite @rahul2393 @sakthivelmanii
@@ -1,16 +1,20 @@
1
+ name: acceptance tests on emulator
2
+
3
+ permissions:
4
+ contents: read
5
+
1
6
  on:
2
7
  push:
3
8
  branches:
4
9
  - main
5
10
  pull_request:
6
- name: acceptance tests on emulator
7
11
  jobs:
8
12
  test:
9
13
  runs-on: ubuntu-latest
10
14
 
11
15
  services:
12
16
  emulator:
13
- image: gcr.io/cloud-spanner-emulator/emulator:latest
17
+ image: gcr.io/cloud-spanner-emulator/emulator:1.5.55
14
18
  ports:
15
19
  - 9010:9010
16
20
  - 9020:9020
@@ -18,28 +22,32 @@ jobs:
18
22
  strategy:
19
23
  max-parallel: 4
20
24
  matrix:
21
- ruby: ["3.1", "3.2", "3.3", "3.4"]
25
+ ruby: ["3.2", "3.3", "3.4", "4.0"]
22
26
  ar: ["~> 7.0.0", "~> 7.1.0", "~> 7.2.0", "~> 8.0.0", "~> 8.1.0"]
23
27
  # Exclude combinations that are not supported.
24
28
  exclude:
25
- - ruby: "3.1"
26
- ar: "~> 8.0.0"
27
- - ruby: "3.1"
28
- ar: "~> 8.1.0"
29
29
  - ruby: "3.4"
30
30
  ar: "~> 7.0.0"
31
31
  - ruby: "3.4"
32
32
  ar: "~> 7.1.0"
33
33
  - ruby: "3.4"
34
34
  ar: "~> 7.2.0"
35
+ - ruby: "4.0"
36
+ ar: "~> 7.0.0"
37
+ - ruby: "4.0"
38
+ ar: "~> 7.1.0"
39
+ - ruby: "4.0"
40
+ ar: "~> 7.2.0"
35
41
  env:
36
42
  AR_VERSION: ${{ matrix.ar }}
37
43
  steps:
38
- - uses: actions/checkout@v6
44
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
45
+ with:
46
+ persist-credentials: false
39
47
  - name: Set up Ruby
40
48
  # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby
41
49
  # (see https://github.com/ruby/setup-ruby#versioning):
42
- uses: ruby/setup-ruby@v1
50
+ uses: ruby/setup-ruby@d45b1a4e94b71acab930e56e79c6aa188764e7f9 # v1.316.0
43
51
  with:
44
52
  bundler-cache: false
45
53
  ruby-version: ${{ matrix.ruby }}
@@ -1,9 +1,13 @@
1
+ name: acceptance tests on production
2
+
3
+ permissions:
4
+ contents: read
5
+
1
6
  on:
2
7
  push:
3
8
  branches:
4
9
  - main
5
10
  pull_request:
6
- name: acceptance tests on production
7
11
  jobs:
8
12
  check-env:
9
13
  outputs:
@@ -26,20 +30,22 @@ jobs:
26
30
  matrix:
27
31
  ruby: [3.3]
28
32
  steps:
29
- - uses: actions/checkout@v6
33
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
34
+ with:
35
+ persist-credentials: false
30
36
  - name: Set up Ruby
31
37
  # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby
32
38
  # (see https://github.com/ruby/setup-ruby#versioning):
33
- uses: ruby/setup-ruby@v1
39
+ uses: ruby/setup-ruby@d45b1a4e94b71acab930e56e79c6aa188764e7f9 # v1.316.0
34
40
  with:
35
41
  bundler-cache: true
36
42
  ruby-version: ${{ matrix.ruby }}
37
43
  - name: Authenticate Google Cloud
38
- uses: google-github-actions/auth@v3
44
+ uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0
39
45
  with:
40
46
  credentials_json: ${{ secrets.GCP_SA_KEY }}
41
47
  - name: Setup GCloud
42
- uses: google-github-actions/setup-gcloud@v3
48
+ uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # v3.0.1
43
49
  with:
44
50
  project_id: ${{ secrets.GCP_PROJECT_ID }}
45
51
  - name: Install dependencies
@@ -1,37 +1,45 @@
1
+ name: ci
2
+
3
+ permissions:
4
+ contents: read
5
+
1
6
  on:
2
7
  push:
3
8
  branches:
4
9
  - main
5
10
  pull_request:
6
- name: ci
7
11
  jobs:
8
12
  test:
9
13
  runs-on: ubuntu-latest
10
14
  strategy:
11
15
  max-parallel: 4
12
16
  matrix:
13
- ruby: ["3.1", "3.2", "3.3", "3.4"]
17
+ ruby: ["3.2", "3.3", "3.4", "4.0"]
14
18
  ar: ["~> 7.0.0", "~> 7.1.0", "~> 7.2.0", "~> 8.0.0", "~> 8.1.0"]
15
19
  # Exclude combinations that are not supported.
16
20
  exclude:
17
- - ruby: "3.1"
18
- ar: "~> 8.0.0"
19
- - ruby: "3.1"
20
- ar: "~> 8.1.0"
21
21
  - ruby: "3.4"
22
22
  ar: "~> 7.0.0"
23
23
  - ruby: "3.4"
24
24
  ar: "~> 7.1.0"
25
25
  - ruby: "3.4"
26
26
  ar: "~> 7.2.0"
27
+ - ruby: "4.0"
28
+ ar: "~> 7.0.0"
29
+ - ruby: "4.0"
30
+ ar: "~> 7.1.0"
31
+ - ruby: "4.0"
32
+ ar: "~> 7.2.0"
27
33
  env:
28
34
  AR_VERSION: ${{ matrix.ar }}
29
35
  steps:
30
- - uses: actions/checkout@v6
36
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
37
+ with:
38
+ persist-credentials: false
31
39
  - name: Set up Ruby
32
40
  # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby
33
41
  # (see https://github.com/ruby/setup-ruby#versioning):
34
- uses: ruby/setup-ruby@v1
42
+ uses: ruby/setup-ruby@d45b1a4e94b71acab930e56e79c6aa188764e7f9 # v1.316.0
35
43
  with:
36
44
  bundler-cache: false
37
45
  ruby-version: ${{ matrix.ruby }}
@@ -1,16 +1,20 @@
1
+ name: nightly acceptance tests on emulator
2
+
3
+ permissions:
4
+ contents: read
5
+
1
6
  on:
2
7
  schedule:
3
8
  # 06:00 UTC
4
9
  - cron: '0 6 * * *'
5
10
  workflow_dispatch:
6
- name: nightly acceptance tests on emulator
7
11
  jobs:
8
12
  test:
9
13
  runs-on: ubuntu-latest
10
14
 
11
15
  services:
12
16
  emulator:
13
- image: gcr.io/cloud-spanner-emulator/emulator:latest
17
+ image: gcr.io/cloud-spanner-emulator/emulator:1.5.55
14
18
  ports:
15
19
  - 9010:9010
16
20
  - 9020:9020
@@ -18,26 +22,30 @@ jobs:
18
22
  strategy:
19
23
  max-parallel: 4
20
24
  matrix:
21
- ruby: ["3.1", "3.2", "3.3", "3.4"]
25
+ ruby: ["3.2", "3.3", "3.4", "4.0"]
22
26
  ar: ["~> 7.0.0", "~> 7.1.0", "~> 7.2.0", "~> 8.0.0", "~> 8.1.0"]
23
27
  # Exclude combinations that are not supported.
24
28
  exclude:
25
- - ruby: "3.1"
26
- ar: "~> 8.0.0"
27
- - ruby: "3.1"
28
- ar: "~> 8.1.0"
29
29
  - ruby: "3.4"
30
30
  ar: "~> 7.0.0"
31
31
  - ruby: "3.4"
32
32
  ar: "~> 7.1.0"
33
33
  - ruby: "3.4"
34
34
  ar: "~> 7.2.0"
35
+ - ruby: "4.0"
36
+ ar: "~> 7.0.0"
37
+ - ruby: "4.0"
38
+ ar: "~> 7.1.0"
39
+ - ruby: "4.0"
40
+ ar: "~> 7.2.0"
35
41
  env:
36
42
  AR_VERSION: ${{ matrix.ar }}
37
43
  steps:
38
- - uses: actions/checkout@v6
44
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
45
+ with:
46
+ persist-credentials: false
39
47
  - name: Set up Ruby
40
- uses: ruby/setup-ruby@v1
48
+ uses: ruby/setup-ruby@d45b1a4e94b71acab930e56e79c6aa188764e7f9 # v1.316.0
41
49
  with:
42
50
  # Disable caching as we are overriding the ActiveRecord below.
43
51
  bundler-cache: false
@@ -1,8 +1,12 @@
1
+ name: nightly acceptance tests on production
2
+
3
+ permissions:
4
+ contents: read
5
+
1
6
  on:
2
7
  schedule:
3
8
  # 02:00 UTC
4
9
  - cron: '0 2 * * *'
5
- name: nightly acceptance tests on production
6
10
  jobs:
7
11
  test:
8
12
  runs-on: ubuntu-latest
@@ -12,20 +16,22 @@ jobs:
12
16
  matrix:
13
17
  ruby: [3.3]
14
18
  steps:
15
- - uses: actions/checkout@v6
19
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
20
+ with:
21
+ persist-credentials: false
16
22
  - name: Set up Ruby
17
23
  # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby
18
24
  # (see https://github.com/ruby/setup-ruby#versioning):
19
- uses: ruby/setup-ruby@v1
25
+ uses: ruby/setup-ruby@d45b1a4e94b71acab930e56e79c6aa188764e7f9 # v1.316.0
20
26
  with:
21
27
  bundler-cache: true
22
28
  ruby-version: ${{ matrix.ruby }}
23
29
  - name: Authenticate Google Cloud
24
- uses: google-github-actions/auth@v3
30
+ uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0
25
31
  with:
26
32
  credentials_json: ${{ secrets.GCP_SA_KEY }}
27
33
  - name: Setup GCloud
28
- uses: google-github-actions/setup-gcloud@v3
34
+ uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # v3.0.1
29
35
  with:
30
36
  project_id: ${{ secrets.GCP_PROJECT_ID }}
31
37
  - name: Install dependencies
@@ -1,9 +1,13 @@
1
+ name: nightly-unit-tests
2
+
3
+ permissions:
4
+ contents: read
5
+
1
6
  on:
2
7
  schedule:
3
8
  # 05:30 UTC
4
9
  - cron: '30 5 * * *'
5
10
  workflow_dispatch:
6
- name: nightly-unit-tests
7
11
  jobs:
8
12
  test:
9
13
  runs-on: ubuntu-latest
@@ -11,26 +15,30 @@ jobs:
11
15
  max-parallel: 4
12
16
  matrix:
13
17
  # Run acceptance tests all supported combinations of Ruby and ActiveRecord.
14
- ruby: ["3.1", "3.2", "3.3", "3.4"]
18
+ ruby: ["3.2", "3.3", "3.4", "4.0"]
15
19
  ar: ["~> 7.0.0", "~> 7.1.0", "~> 7.2.0", "~> 8.0.0", "~> 8.1.0"]
16
20
  # Exclude combinations that are not supported.
17
21
  exclude:
18
- - ruby: "3.1"
19
- ar: "~> 8.0.0"
20
- - ruby: "3.1"
21
- ar: "~> 8.1.0"
22
22
  - ruby: "3.4"
23
23
  ar: "~> 7.0.0"
24
24
  - ruby: "3.4"
25
25
  ar: "~> 7.1.0"
26
26
  - ruby: "3.4"
27
27
  ar: "~> 7.2.0"
28
+ - ruby: "4.0"
29
+ ar: "~> 7.0.0"
30
+ - ruby: "4.0"
31
+ ar: "~> 7.1.0"
32
+ - ruby: "4.0"
33
+ ar: "~> 7.2.0"
28
34
  env:
29
35
  AR_VERSION: ${{ matrix.ar }}
30
36
  steps:
31
- - uses: actions/checkout@v6
37
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
38
+ with:
39
+ persist-credentials: false
32
40
  - name: Set up Ruby
33
- uses: ruby/setup-ruby@v1
41
+ uses: ruby/setup-ruby@d45b1a4e94b71acab930e56e79c6aa188764e7f9 # v1.316.0
34
42
  with:
35
43
  # Disable caching as we are overriding the ActiveRecord below.
36
44
  bundler-cache: false
@@ -1,5 +1,8 @@
1
1
  name: rubocop
2
2
 
3
+ permissions:
4
+ contents: read
5
+
3
6
  on:
4
7
  pull_request:
5
8
  types: [opened, synchronize]
@@ -12,13 +15,15 @@ jobs:
12
15
  timeout-minutes: 10
13
16
 
14
17
  steps:
15
- - uses: actions/checkout@v6
18
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
19
+ with:
20
+ persist-credentials: false
16
21
  - name: setup ruby
17
- uses: ruby/setup-ruby@v1
22
+ uses: ruby/setup-ruby@d45b1a4e94b71acab930e56e79c6aa188764e7f9 # v1.316.0
18
23
  with:
19
- ruby-version: '3.4.8'
24
+ ruby-version: '4.0.5'
20
25
  - name: cache gems
21
- uses: actions/cache@v5
26
+ uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
22
27
  with:
23
28
  path: vendor/bundle
24
29
  key: ${{ runner.os }}-rubocop-${{ hashFiles('**/Gemfile.lock') }}
@@ -1,6 +1,10 @@
1
+ name: samples
2
+
3
+ permissions:
4
+ contents: read
5
+
1
6
  on:
2
7
  pull_request:
3
- name: samples
4
8
  jobs:
5
9
  test:
6
10
  runs-on: ubuntu-latest
@@ -8,24 +12,30 @@ jobs:
8
12
  strategy:
9
13
  max-parallel: 4
10
14
  matrix:
11
- ruby: ["3.1", "3.2", "3.3", "3.4"]
15
+ ruby: ["3.2", "3.3", "3.4", "4.0"]
12
16
  ar: ["~> 7.0.0", "~> 7.1.0", "~> 7.2.0", "~> 8.0.0"]
13
17
  # Exclude combinations that are not supported.
14
18
  exclude:
15
- - ruby: "3.1"
16
- ar: "~> 8.0.0"
17
19
  - ruby: "3.4"
18
20
  ar: "~> 7.0.0"
19
21
  - ruby: "3.4"
20
22
  ar: "~> 7.1.0"
21
23
  - ruby: "3.4"
22
24
  ar: "~> 7.2.0"
25
+ - ruby: "4.0"
26
+ ar: "~> 7.0.0"
27
+ - ruby: "4.0"
28
+ ar: "~> 7.1.0"
29
+ - ruby: "4.0"
30
+ ar: "~> 7.2.0"
23
31
  env:
24
32
  AR_VERSION: ${{ matrix.ar }}
25
33
  steps:
26
- - uses: actions/checkout@v6
34
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
35
+ with:
36
+ persist-credentials: false
27
37
  - name: Set up Ruby
28
- uses: ruby/setup-ruby@v1
38
+ uses: ruby/setup-ruby@d45b1a4e94b71acab930e56e79c6aa188764e7f9 # v1.316.0
29
39
  with:
30
40
  bundler-cache: false
31
41
  ruby-version: ${{ matrix.ruby }}
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "2.5.0"
2
+ ".": "2.6.0"
3
3
  }
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ### 2.6.0 (2026-07-09)
4
+
5
+ #### Features
6
+
7
+ * Ruby 3.2 minimum and Ruby 4.0 support ([#397](https://github.com/googleapis/ruby-spanner-activerecord/issues/397))
8
+
9
+ ### 2.5.1 (2026-06-30)
10
+
11
+ #### Bug Fixes
12
+
13
+ * use DateTime instead of Time for time data type ([#395](https://github.com/googleapis/ruby-spanner-activerecord/issues/395))
14
+
3
15
  ### 2.5.0 (2026-02-11)
4
16
 
5
17
  #### Features
data/Gemfile CHANGED
@@ -10,7 +10,9 @@ gem "minitest", "~> 5.27.0"
10
10
  gem "minitest-rg", "~> 5.4.0"
11
11
  gem "pry", "~> 0.14.2"
12
12
  gem "pry-byebug", "~> 3.11.0"
13
+ gem "readline"
13
14
  gem "mutex_m"
15
+ gem "irb"
14
16
  # Add sqlite3 for testing for compatibility with other adapters.
15
17
  gem 'sqlite3'
16
18
 
@@ -22,3 +24,8 @@ end
22
24
  # Required for samples
23
25
  gem "docker-api"
24
26
  gem "sinatra-activerecord"
27
+
28
+ # Force google-protobuf to compile from source to avoid ABI issues in CI
29
+ if ENV["CI"]
30
+ gem "google-protobuf", force_ruby_platform: true
31
+ end
data/README.md CHANGED
@@ -11,9 +11,9 @@ for Spanner PostgreSQL-dialect databases.__
11
11
 
12
12
  This project provides a Spanner adapter for ActiveRecord. It supports the following versions:
13
13
 
14
- - ActiveRecord 7.0.x with Ruby 3.1 and higher.
15
- - ActiveRecord 7.1.x with Ruby 3.1 and higher.
16
- - ActiveRecord 7.2.x with Ruby 3.1 and higher.
14
+ - ActiveRecord 7.0.x with Ruby 3.2 and higher.
15
+ - ActiveRecord 7.1.x with Ruby 3.2 and higher.
16
+ - ActiveRecord 7.2.x with Ruby 3.2 and higher.
17
17
  - ActiveRecord 8.0.x with Ruby 3.2 and higher.
18
18
  - ActiveRecord 8.1.x with Ruby 3.2 and higher.
19
19
 
@@ -300,7 +300,7 @@ module ActiveRecord
300
300
 
301
301
  column = connection.columns(:testings).find { |c| c.name == "foo" }
302
302
 
303
- assert_equal :time, column.type
303
+ assert_equal :datetime, column.type
304
304
  assert_equal "TIMESTAMP", column.sql_type
305
305
  end
306
306
 
@@ -76,7 +76,7 @@ module ActiveRecord
76
76
  connection = pool_or_connection
77
77
  schema = StringIO.new
78
78
  ActiveRecord::SchemaDumper.dump connection, schema
79
- assert schema.string.include?("t.time \"last_updated\", allow_commit_timestamp: true"), schema.string
79
+ assert_match(/t\.(datetime|timestamp) "last_updated",.*allow_commit_timestamp: true/, schema.string, schema.string)
80
80
  end
81
81
 
82
82
  def test_dump_schema_contains_virtual_column
@@ -45,7 +45,7 @@ module ActiveRecord
45
45
  end
46
46
 
47
47
  def test_date_time_string_value_with_subsecond_precision
48
- expected_time = ::Time.local 2017, 07, 4, 14, 19, 10, 897761
48
+ expected_time = ::Time.utc 2017, 7, 4, 14, 19, 10, 897761
49
49
 
50
50
  string_value = "2017-07-04 14:19:10.897761"
51
51
 
@@ -53,6 +53,7 @@ module ActiveRecord
53
53
  assert_equal expected_time, record.start_time.utc
54
54
 
55
55
  record.save!
56
+ record.reload
56
57
  assert_equal expected_time, record.start_time.utc
57
58
 
58
59
  assert_equal record, TestTypeModel.find_by(start_time: string_value)
@@ -60,12 +61,13 @@ module ActiveRecord
60
61
 
61
62
  def test_date_time_with_string_value_with_non_iso_format
62
63
  string_value = "04/07/2017 2:19pm"
63
- expected_time = ::Time.local 2017, 07, 4, 14, 19
64
+ expected_time = ::Time.utc 2017, 7, 4, 14, 19
64
65
 
65
66
  record = TestTypeModel.new start_time: string_value
66
67
  assert_equal expected_time, record.start_time
67
68
 
68
69
  record.save!
70
+ record.reload
69
71
  assert_equal expected_time, record.start_time.utc
70
72
 
71
73
  assert_equal record, TestTypeModel.find_by(start_time: string_value)
@@ -82,6 +84,124 @@ module ActiveRecord
82
84
 
83
85
  assert_equal expected_time, record.start_time
84
86
  end
87
+
88
+ def test_multiparameter_datetime
89
+ expected_time = ::Time.utc 2020, 12, 25, 10, 30, 0
90
+ record = TestTypeModel.new start_time: { 1 => 2020, 2 => 12, 3 => 25, 4 => 10, 5 => 30 }
91
+
92
+ assert_equal expected_time, record.start_time
93
+
94
+ record.save!
95
+ record.reload
96
+
97
+ assert_equal expected_time, record.start_time
98
+ end
99
+
100
+ def test_date_time_string_value_with_timezone_aware_attributes
101
+ old_zone = ::Time.zone
102
+ ::Time.zone = "UTC"
103
+ TestTypeModel.time_zone_aware_attributes = true
104
+ TestTypeModel.reset_column_information
105
+
106
+ string_value = "2017-07-04 14:19:10.897761"
107
+ expected_time = ::Time.zone.local 2017, 7, 4, 14, 19, 10, 897761
108
+
109
+ record = TestTypeModel.new start_time: string_value
110
+ assert_equal expected_time, record.start_time
111
+
112
+ record.save!
113
+ record.reload
114
+ assert_equal expected_time, record.start_time
115
+
116
+ assert_equal record, TestTypeModel.find_by(start_time: string_value)
117
+ ensure
118
+ TestTypeModel.time_zone_aware_attributes = false
119
+ TestTypeModel.reset_column_information
120
+ ::Time.zone = old_zone
121
+ end
122
+
123
+ def test_string_value_with_paris_timezone
124
+ old_zone = ::Time.zone
125
+ ::Time.zone = "Paris" # Paris is UTC+2 in July (DST)
126
+ TestTypeModel.time_zone_aware_attributes = true
127
+ TestTypeModel.reset_column_information
128
+
129
+ begin
130
+ string_value = "2017-07-04 14:19:10.897761123"
131
+ # 14:19:10 in Paris (DST) is 12:19:10 UTC
132
+ # Subseconds: 897761123 nanoseconds = 897761.123 microseconds
133
+ expected_time = ::Time.zone.local(2017, 7, 4, 14, 19, 10, Rational(897761123, 1000))
134
+
135
+ record = TestTypeModel.new start_time: string_value
136
+ assert_equal expected_time, record.start_time
137
+ assert_equal 897761123, record.start_time.nsec
138
+ assert_instance_of ActiveSupport::TimeWithZone, record.start_time
139
+ assert_equal "Paris", record.start_time.time_zone.name
140
+
141
+ record.save!
142
+
143
+ # Verify directly in the database using the raw Spanner client (bypasses ActiveRecord)
144
+ raw_client = Google::Cloud::Spanner.new(
145
+ project: connector_config["project"],
146
+ emulator_host: connector_config["emulator_host"]
147
+ )
148
+ db_client = raw_client.client(
149
+ connector_config["instance"],
150
+ connector_config["database"]
151
+ )
152
+
153
+ results = db_client.execute "SELECT start_time FROM test_types WHERE id = #{record.id}"
154
+ raw_value = results.rows.first[:start_time]
155
+
156
+ # Spanner client returns TIMESTAMP as a UTC Time object
157
+ expected_raw_time = ::Time.utc(2017, 7, 4, 12, 19, 10, Rational(897761123, 1000))
158
+ assert_equal expected_raw_time, raw_value
159
+ assert_equal 897761123, raw_value.nsec
160
+ assert_equal "UTC", raw_value.zone
161
+
162
+ record.reload
163
+
164
+ assert_equal expected_time, record.start_time
165
+ assert_equal 897761123, record.start_time.nsec
166
+ assert_instance_of ActiveSupport::TimeWithZone, record.start_time
167
+ assert_equal "Paris", record.start_time.time_zone.name
168
+ ensure
169
+ TestTypeModel.time_zone_aware_attributes = false
170
+ TestTypeModel.reset_column_information
171
+ ::Time.zone = old_zone
172
+ end
173
+ end
174
+
175
+ def test_string_value_with_utc_and_local_timezones
176
+ # 1. With 'Z' (UTC)
177
+ string_value_utc = "2017-07-04 14:19:10.897761123Z"
178
+ expected_time_utc = ::Time.utc 2017, 7, 4, 14, 19, 10, Rational(897761123, 1000)
179
+ record = TestTypeModel.new start_time: string_value_utc
180
+ assert_equal expected_time_utc, record.start_time
181
+ assert_equal 897761123, record.start_time.nsec
182
+
183
+ # 2. With Offset (+02:00)
184
+ string_value_offset = "2017-07-04 14:19:10.897761123+02:00"
185
+ # 14:19:10+02:00 is 12:19:10 UTC
186
+ expected_time_offset = ::Time.utc 2017, 7, 4, 12, 19, 10, Rational(897761123, 1000)
187
+ record = TestTypeModel.new start_time: string_value_offset
188
+ assert_equal expected_time_offset, record.start_time
189
+ assert_equal 897761123, record.start_time.nsec
190
+
191
+ # 3. Without offset, but with ActiveRecord.default_timezone = :local
192
+ old_default_timezone = ActiveRecord.default_timezone
193
+ ActiveRecord.default_timezone = :local
194
+ begin
195
+ string_value_no_offset = "2017-07-04 14:19:10.897761123"
196
+ # Should be interpreted as local time
197
+ expected_time_local = ::Time.local 2017, 7, 4, 14, 19, 10, Rational(897761123, 1000)
198
+ record = TestTypeModel.new start_time: string_value_no_offset
199
+ assert_equal expected_time_local, record.start_time
200
+ assert_equal 897761123, record.start_time.nsec
201
+ ensure
202
+ ActiveRecord.default_timezone = old_default_timezone
203
+ end
204
+ end
85
205
  end
86
206
  end
87
- end
207
+ end
@@ -22,7 +22,7 @@ Gem::Specification.new do |spec|
22
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename f }
23
23
  spec.require_paths = ["lib"]
24
24
 
25
- spec.required_ruby_version = ">= 3.1"
25
+ spec.required_ruby_version = ">= 3.2"
26
26
 
27
27
  spec.add_dependency "google-cloud-spanner", "~> 2.25"
28
28
  spec.add_dependency "google-cloud-spanner-v1", "~> 1.7"
@@ -30,7 +30,7 @@ Gem::Specification.new do |spec|
30
30
 
31
31
  spec.add_development_dependency "autotest-suffix", "~> 1.1"
32
32
  spec.add_development_dependency "bundler", [">= 2.0", "< 5.0"]
33
- spec.add_development_dependency "google-style", "~> 1.31.0"
33
+ spec.add_development_dependency "google-style", "~> 1.32.0"
34
34
  spec.add_development_dependency "minitest", "~> 5.10"
35
35
  spec.add_development_dependency "minitest-autotest", "~> 1.0"
36
36
  spec.add_development_dependency "minitest-focus", "~> 1.1"
@@ -6,6 +6,7 @@
6
6
 
7
7
  require_relative "config/environment"
8
8
  require "docker"
9
+ require "google/cloud/spanner"
9
10
 
10
11
  def fetch_samples
11
12
  Dir.entries(".").select do |entry|
@@ -50,6 +51,25 @@ task :run, [:sample] do |_t, args|
50
51
  run_sample sample
51
52
  end
52
53
 
54
+ def wait_for_emulator
55
+ puts "Waiting for Spanner emulator to be ready..."
56
+ spanner = Google::Cloud::Spanner.new project: "test-project", emulator_host: "127.0.0.1:9010"
57
+ retries = 20
58
+ begin
59
+ # Make a cheap API call to verify the emulator is actually responding
60
+ spanner.instances
61
+ puts "Spanner emulator is ready."
62
+ rescue StandardError => e
63
+ if retries > 0
64
+ retries -= 1
65
+ sleep 0.5
66
+ retry
67
+ else
68
+ puts "Timed out waiting for Spanner emulator. Last error: #{e.message}"
69
+ end
70
+ end
71
+ end
72
+
53
73
  def run_sample sample
54
74
  puts "Running #{sample}"
55
75
  puts "Downloading Spanner emulator image..."
@@ -68,6 +88,7 @@ def run_sample sample
68
88
  begin
69
89
  puts "Starting Spanner emulator..."
70
90
  container.start!
91
+ wait_for_emulator
71
92
  Dir.chdir sample do
72
93
  sh "ruby ../bin/create_emulator_instance.rb"
73
94
  sh "rake db:migrate"
@@ -9,7 +9,24 @@
9
9
  module ActiveRecord
10
10
  module Type
11
11
  module Spanner
12
- class Time < ActiveRecord::Type::Time
12
+ class Time < ActiveRecord::Type::DateTime
13
+ def cast_value value
14
+ if value.is_a? ::String
15
+ return if value.empty?
16
+ begin
17
+ if ActiveRecord.default_timezone == :utc
18
+ ::DateTime.parse(value).to_time.getutc
19
+ else
20
+ ::Time.parse(value).getlocal
21
+ end
22
+ rescue StandardError
23
+ super
24
+ end
25
+ else
26
+ super
27
+ end
28
+ end
29
+
13
30
  def serialize_with_isolation_level value, isolation_level
14
31
  if value == :commit_timestamp
15
32
  return "PENDING_COMMIT_TIMESTAMP()" if isolation_level == :dml
@@ -24,18 +41,14 @@ module ActiveRecord
24
41
  val.acts_like?(:time) ? val.utc.rfc3339(9) : val
25
42
  end
26
43
 
27
- def user_input_in_time_zone value
28
- return value.in_time_zone if value.is_a? ::Time
29
- super value
44
+ def value_from_multiparameter_assignment values
45
+ defaults = { 1 => 2000, 2 => 1, 3 => 1 }
46
+ super defaults.merge(values)
30
47
  end
31
48
 
32
49
  private
33
50
 
34
- def cast_value value
35
- if value.is_a? ::String
36
- value = value.empty? ? nil : ::Time.parse(value)
37
- end
38
-
51
+ def apply_seconds_precision value
39
52
  value
40
53
  end
41
54
  end
@@ -5,5 +5,5 @@
5
5
  # https://opensource.org/licenses/MIT.
6
6
 
7
7
  module ActiveRecordSpannerAdapter
8
- VERSION = "2.5.0".freeze
8
+ VERSION = "2.6.0".freeze
9
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-spanner-adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.5.0
4
+ version: 2.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Google LLC
@@ -97,14 +97,14 @@ dependencies:
97
97
  requirements:
98
98
  - - "~>"
99
99
  - !ruby/object:Gem::Version
100
- version: 1.31.0
100
+ version: 1.32.0
101
101
  type: :development
102
102
  prerelease: false
103
103
  version_requirements: !ruby/object:Gem::Requirement
104
104
  requirements:
105
105
  - - "~>"
106
106
  - !ruby/object:Gem::Version
107
- version: 1.31.0
107
+ version: 1.32.0
108
108
  - !ruby/object:Gem::Dependency
109
109
  name: minitest
110
110
  requirement: !ruby/object:Gem::Requirement
@@ -597,7 +597,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
597
597
  requirements:
598
598
  - - ">="
599
599
  - !ruby/object:Gem::Version
600
- version: '3.1'
600
+ version: '3.2'
601
601
  required_rubygems_version: !ruby/object:Gem::Requirement
602
602
  requirements:
603
603
  - - ">="