tarantool-rails 0.1.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 +7 -0
- data/CHANGELOG.md +14 -0
- data/LICENSE +21 -0
- data/README.md +217 -0
- data/lib/generators/tarantool/install/install_generator.rb +20 -0
- data/lib/generators/tarantool/install/templates/tarantool.yml +24 -0
- data/lib/generators/tarantool/install/templates/tarantool_schema.rb +8 -0
- data/lib/generators/tarantool/model/model_generator.rb +77 -0
- data/lib/generators/tarantool/model/templates/model.rb.tt +9 -0
- data/lib/tarantool/rails/configuration.rb +83 -0
- data/lib/tarantool/rails/controller_runtime.rb +48 -0
- data/lib/tarantool/rails/errors.rb +7 -0
- data/lib/tarantool/rails/log_subscriber.rb +92 -0
- data/lib/tarantool/rails/railtie.rb +35 -0
- data/lib/tarantool/rails/tasks/schema.rake +24 -0
- data/lib/tarantool/rails/version.rb +7 -0
- data/lib/tarantool/rails.rb +102 -0
- metadata +128 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 824fd7e958e18a4576a55a510ae6713557bb7d0e6a36ce202f18d72785eb18ef
|
|
4
|
+
data.tar.gz: e33adb1ed56190a0d11f32c1994a71f2e247c2548e30bb594344b581794f03ba
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: cd86cbe614985b58a745dfa2807df813e8b1eb95f7ea0a044f26f0a539577c15dac3cbeffa33bcec5774c31583a9a324a7706fbbb72ba16392a4c7e46088bd2f
|
|
7
|
+
data.tar.gz: 5afa1af094609b0ed6f6637b7a1aa08dbe6afcf8b9ae3cda57031ad0f4a3565bb23ff204e37c5e9f779366cc916fdcb83025f8dec3de43501eda8b9ae5f90451
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (2026-10-17)
|
|
4
|
+
|
|
5
|
+
First release.
|
|
6
|
+
|
|
7
|
+
- `config/tarantool.yml` per environment through `config_for`; `Tarantool::Rails.config`, `pool` (one per
|
|
8
|
+
process), `storage`, `schema`, `admin_client`, `disconnect!`.
|
|
9
|
+
- Models use the configured storage through `Tarantool::Model.default_storage`, built on first use.
|
|
10
|
+
- `Tarantool::Rails::LogSubscriber`: ActiveRecord-style request and lifecycle lines; `ControllerRuntime` adds
|
|
11
|
+
`Tarantool: 1.2ms` to "Completed" lines.
|
|
12
|
+
- Rake tasks `tarantool:schema:plan`, `apply` and `check`.
|
|
13
|
+
- Generators `tarantool:install` and `tarantool:model`.
|
|
14
|
+
- Depends on `railties >= 7.1, < 9`, `tarantool-client ~> 0.2`, `tarantool-model ~> 0.3`, `tarantool-schema ~> 0.1`.
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Starlexxx
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# tarantool-rails
|
|
2
|
+
|
|
3
|
+
Rails integration for [tarantool-client](../tarantool-client), [tarantool-model](../tarantool-model) and
|
|
4
|
+
[tarantool-schema](../tarantool-schema). One gem gives an application `config/tarantool.yml` per environment,
|
|
5
|
+
one connection pool per process, models on that pool without an initializer, ActiveRecord-style request lines
|
|
6
|
+
in the Rails log with a `Tarantool: 1.2ms` share in "Completed" lines, `tarantool:schema:*` rake tasks and
|
|
7
|
+
generators for the configuration and for models.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Requirements: Ruby >= 3.2, Rails 7.1+, Tarantool 2.11+ or 3.x.
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
gem 'tarantool-rails'
|
|
15
|
+
gem 'tarantool-crud' # only for storage: crud
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
rails g tarantool:install # config/tarantool.yml and config/tarantool_schema.rb
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## config/tarantool.yml
|
|
23
|
+
|
|
24
|
+
Read with `config_for`, so ERB, a `shared` section and the environment sections work as in `database.yml`.
|
|
25
|
+
|
|
26
|
+
```yaml
|
|
27
|
+
default: &default
|
|
28
|
+
user: app
|
|
29
|
+
password: <%= ENV["TARANTOOL_PASSWORD"] %>
|
|
30
|
+
connect_timeout: 3
|
|
31
|
+
request_timeout: 10
|
|
32
|
+
|
|
33
|
+
development:
|
|
34
|
+
<<: *default
|
|
35
|
+
addresses: [tcp://localhost:3301]
|
|
36
|
+
admin_uri: tcp://app:secret@localhost:3301
|
|
37
|
+
|
|
38
|
+
test:
|
|
39
|
+
<<: *default
|
|
40
|
+
addresses: [tcp://localhost:3301]
|
|
41
|
+
|
|
42
|
+
production:
|
|
43
|
+
<<: *default
|
|
44
|
+
addresses:
|
|
45
|
+
- tcp://db1:3301
|
|
46
|
+
- tcp://db2:3301
|
|
47
|
+
read: { mode: prefer_ro }
|
|
48
|
+
write: { mode: rw }
|
|
49
|
+
admin_uri: <%= ENV["TARANTOOL_ADMIN_URI"] %>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
| Key | |
|
|
53
|
+
|---|---|
|
|
54
|
+
| `addresses` | required, Array of URIs; `address` with a single String is accepted too |
|
|
55
|
+
| `user`, `password`, `connect_timeout`, `request_timeout`, `reconnect`, `reconnect_attempts`, `reconnect_delay`, `reconnect_max_delay`, `max_frame_size` | passed to `Tarantool::Client::Pool.new` as they are |
|
|
56
|
+
| `read`, `write` | options for `Storage::Box`: `mode` (`prefer_ro`, `rw`, ...) and `timeout` |
|
|
57
|
+
| `storage` | `box` (default) or `crud` |
|
|
58
|
+
| `crud` | for `storage: crud`: `primary_index` (default `primary`), `read`, `write` and `timeout_margin` for `Tarantool::Crud::Client.new` |
|
|
59
|
+
| `admin_uri` | connection for the schema rake tasks, a user that may create spaces |
|
|
60
|
+
|
|
61
|
+
Any other key raises `ArgumentError` at boot. A missing file is not an error at boot, so the install generator
|
|
62
|
+
can run; the first model operation then raises `Tarantool::Rails::Error`.
|
|
63
|
+
|
|
64
|
+
## What the Railtie does
|
|
65
|
+
|
|
66
|
+
- `Tarantool::Rails.config` holds the environment section as a `Configuration` (its `inspect` hides the
|
|
67
|
+
password).
|
|
68
|
+
- `Tarantool::Rails.pool` is one `Tarantool::Client::Pool` per process, built on first use with
|
|
69
|
+
`instrumenter: ActiveSupport::Notifications`. After `fork` the pool reopens its connections on its own.
|
|
70
|
+
- `Tarantool::Rails.storage` is a `Storage::Box` (or `Storage::Crud`) over the pool, and
|
|
71
|
+
`Tarantool::Model.default_storage` is set so every model without a storage of its own uses it. Nothing
|
|
72
|
+
connects until a model is used.
|
|
73
|
+
- `Tarantool::Rails::LogSubscriber` writes a line per request and `ControllerRuntime` adds the Tarantool time
|
|
74
|
+
to "Completed" lines; turn both off with `config.tarantool.log = false`.
|
|
75
|
+
- `Tarantool::Rails.schema` is the `Tarantool::Schema::Definition` from `config/tarantool_schema.rb`
|
|
76
|
+
(`config.tarantool.schema_path` changes the file).
|
|
77
|
+
- `Tarantool::Rails.disconnect!` closes the pool; the next use builds a new one.
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
class User
|
|
81
|
+
include Tarantool::Model
|
|
82
|
+
|
|
83
|
+
attribute :id, :integer
|
|
84
|
+
attribute :email, :string
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
User.create!(email: 'a@b.c') # through Tarantool::Rails.storage
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Log
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
Tarantool insert users (0.31ms) db1:3301
|
|
94
|
+
Tarantool select users by email (0.18ms) db2:3301
|
|
95
|
+
Tarantool call user.touch (0.42ms) db1:3301
|
|
96
|
+
Tarantool execute (0.57ms) SELECT COUNT(*) FROM "users" db1:3301
|
|
97
|
+
Tarantool update users (0.26ms) db1:3301 Tarantool::ServerError(3)
|
|
98
|
+
Completed 200 OK in 4ms (Views: 1.9ms | Tarantool: 0.5ms)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Request lines are debug, colored like ActiveRecord's when `colorize_logging` is on. Keys, tuples and
|
|
102
|
+
arguments never appear: the client does not put them in the event payload. A failed request shows the error
|
|
103
|
+
class and code, not the message. Lifecycle events are logged too: `disconnect` at warn, `reconnect_failed` at
|
|
104
|
+
error, `reconnect`, `fork` and `role_change` at info.
|
|
105
|
+
|
|
106
|
+
## Schema
|
|
107
|
+
|
|
108
|
+
`config/tarantool_schema.rb` assigns the definition:
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
Tarantool::Rails.schema = Tarantool::Schema::Definition.new do
|
|
112
|
+
ensure_space :users,
|
|
113
|
+
format: [{ name: :id, type: :unsigned }, { name: :email, type: :string }],
|
|
114
|
+
indexes: [{ name: :primary, parts: [:id], sequence: true }, { name: :email, parts: [:email] }]
|
|
115
|
+
end
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
| Task | |
|
|
119
|
+
|---|---|
|
|
120
|
+
| `rails tarantool:schema:plan` | prints what `apply` would create; drift raises `MismatchError` with the diff |
|
|
121
|
+
| `rails tarantool:schema:apply` | creates the missing spaces and indexes and prints them; never alters or drops |
|
|
122
|
+
| `rails tarantool:schema:check` | for CI: exits 1 and prints `pending: ...` when anything would be created, 0 when live equals declared |
|
|
123
|
+
|
|
124
|
+
The tasks connect with `admin_uri`; see the tarantool-schema README for the privileges that user needs.
|
|
125
|
+
|
|
126
|
+
## Generators
|
|
127
|
+
|
|
128
|
+
```sh
|
|
129
|
+
rails g tarantool:model User email:string age:unsigned? token:uuid
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
writes `app/models/user.rb`
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
class User
|
|
136
|
+
include Tarantool::Model
|
|
137
|
+
|
|
138
|
+
attribute :id, :integer
|
|
139
|
+
attribute :email, :string
|
|
140
|
+
attribute :age, :integer
|
|
141
|
+
attribute :token, Tarantool::Model::Types::UUID.new
|
|
142
|
+
end
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
and adds the space to `config/tarantool_schema.rb` (skip with `--skip-schema`):
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
ensure_space :users,
|
|
149
|
+
format: [
|
|
150
|
+
{ name: :id, type: :unsigned },
|
|
151
|
+
{ name: :email, type: :string },
|
|
152
|
+
{ name: :age, type: :unsigned, is_nullable: true },
|
|
153
|
+
{ name: :token, type: :uuid }
|
|
154
|
+
],
|
|
155
|
+
indexes: [
|
|
156
|
+
{ name: :primary, parts: [:id], sequence: true }
|
|
157
|
+
]
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
`id` is added first as `unsigned` with a sequence unless you list it yourself. A `?` suffix makes the field
|
|
161
|
+
nullable. Types: `integer`, `unsigned`, `string`, `boolean`, `decimal`, `datetime`, `uuid`, `interval`,
|
|
162
|
+
`binary`. `Admin::User` gets the space `admin_users`, the plural tarantool-model uses.
|
|
163
|
+
|
|
164
|
+
## Testing
|
|
165
|
+
|
|
166
|
+
Point the default storage at tarantool-model's in-memory storage, built from the same declaration the deploy
|
|
167
|
+
applies. No connection is made in the test environment unless something reads `Tarantool::Rails.pool`.
|
|
168
|
+
|
|
169
|
+
```ruby
|
|
170
|
+
# spec/rails_helper.rb
|
|
171
|
+
require 'tarantool/model/testing'
|
|
172
|
+
|
|
173
|
+
RSpec.configure do |config|
|
|
174
|
+
config.before do
|
|
175
|
+
Tarantool::Model.default_storage = Tarantool::Model::Testing::MemoryStorage.from_definition(Tarantool::Rails.schema)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## storage: crud
|
|
181
|
+
|
|
182
|
+
```yaml
|
|
183
|
+
production:
|
|
184
|
+
addresses: [tcp://router1:3301, tcp://router2:3301]
|
|
185
|
+
storage: crud
|
|
186
|
+
crud:
|
|
187
|
+
primary_index: primary
|
|
188
|
+
read: { mode: read, prefer_replica: true }
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Models then go through `Tarantool::Model::Storage::Crud` over `Tarantool::Crud::Client`; the `tarantool-crud`
|
|
192
|
+
gem must be in the Gemfile.
|
|
193
|
+
|
|
194
|
+
## Public API
|
|
195
|
+
|
|
196
|
+
`Tarantool::Rails` (`config`, `pool`, `storage`, `schema`, `admin_client`, `disconnect!`),
|
|
197
|
+
`Tarantool::Rails::Configuration`, `Tarantool::Rails::LogSubscriber`, `Tarantool::Rails::Error`
|
|
198
|
+
(descends from `Tarantool::Error`), the rake tasks and the generators.
|
|
199
|
+
|
|
200
|
+
Everything else, including anything tagged `@api private` in its source comment, is an implementation detail
|
|
201
|
+
and may change in any release.
|
|
202
|
+
|
|
203
|
+
## Development
|
|
204
|
+
|
|
205
|
+
```sh
|
|
206
|
+
podman compose -f tarantool-client/docker-compose.yml up -d --wait tarantool-2.11 tarantool-3
|
|
207
|
+
# or: docker compose -f tarantool-client/docker-compose.yml up -d --wait tarantool-2.11 tarantool-3
|
|
208
|
+
|
|
209
|
+
cd tarantool-rails
|
|
210
|
+
bundle exec rspec # unit
|
|
211
|
+
INTEGRATION=1 TARANTOOL_URI=tcp://app:secret@localhost:3301 bundle exec rspec # 2.11
|
|
212
|
+
INTEGRATION=1 TARANTOOL_URI=tcp://app:secret@localhost:3302 bundle exec rspec # 3.x
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## License
|
|
216
|
+
|
|
217
|
+
MIT, see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/generators'
|
|
4
|
+
|
|
5
|
+
module Tarantool
|
|
6
|
+
module Generators
|
|
7
|
+
# rails g tarantool:install
|
|
8
|
+
class InstallGenerator < ::Rails::Generators::Base
|
|
9
|
+
source_root File.expand_path('templates', __dir__)
|
|
10
|
+
|
|
11
|
+
def create_configuration
|
|
12
|
+
copy_file 'tarantool.yml', 'config/tarantool.yml'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def create_schema
|
|
16
|
+
copy_file 'tarantool_schema.rb', 'config/tarantool_schema.rb'
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Every key is documented in the tarantool-rails README. The file goes through ERB like database.yml.
|
|
2
|
+
default: &default
|
|
3
|
+
user: app
|
|
4
|
+
password: <%= ENV["TARANTOOL_PASSWORD"] %>
|
|
5
|
+
connect_timeout: 3
|
|
6
|
+
request_timeout: 10
|
|
7
|
+
|
|
8
|
+
development:
|
|
9
|
+
<<: *default
|
|
10
|
+
addresses: [tcp://localhost:3301]
|
|
11
|
+
admin_uri: <%= ENV.fetch("TARANTOOL_ADMIN_URI", "tcp://app:secret@localhost:3301") %>
|
|
12
|
+
|
|
13
|
+
test:
|
|
14
|
+
<<: *default
|
|
15
|
+
addresses: [tcp://localhost:3301]
|
|
16
|
+
|
|
17
|
+
production:
|
|
18
|
+
<<: *default
|
|
19
|
+
addresses:
|
|
20
|
+
- tcp://db1:3301
|
|
21
|
+
- tcp://db2:3301
|
|
22
|
+
read: { mode: prefer_ro }
|
|
23
|
+
write: { mode: rw }
|
|
24
|
+
admin_uri: <%= ENV["TARANTOOL_ADMIN_URI"] %>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Applied by `rails tarantool:schema:apply`; `rails g tarantool:model` adds spaces here.
|
|
4
|
+
Tarantool::Rails.schema = Tarantool::Schema::Definition.new do
|
|
5
|
+
# ensure_space :users,
|
|
6
|
+
# format: [{ name: :id, type: :unsigned }, { name: :email, type: :string }],
|
|
7
|
+
# indexes: [{ name: :primary, parts: [:id], sequence: true }, { name: :email, parts: [:email] }]
|
|
8
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/generators'
|
|
4
|
+
require 'rails/generators/named_base'
|
|
5
|
+
|
|
6
|
+
module Tarantool
|
|
7
|
+
module Generators
|
|
8
|
+
# rails g tarantool:model User email:string age:unsigned?
|
|
9
|
+
class ModelGenerator < ::Rails::Generators::NamedBase
|
|
10
|
+
Field = Data.define(:name, :attribute_type, :field_type, :nullable) do
|
|
11
|
+
def declaration
|
|
12
|
+
"{ name: :#{name}, type: :#{field_type}#{', is_nullable: true' if nullable} }"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
TYPES = {
|
|
17
|
+
'integer' => [':integer', 'integer'], 'unsigned' => [':integer', 'unsigned'], 'string' => [':string', 'string'],
|
|
18
|
+
'boolean' => [':boolean', 'boolean'], 'decimal' => [':decimal', 'decimal'],
|
|
19
|
+
'datetime' => [':datetime', 'datetime'], 'uuid' => ['Tarantool::Model::Types::UUID.new', 'uuid'],
|
|
20
|
+
'interval' => ['Tarantool::Model::Types::Interval.new', 'interval'],
|
|
21
|
+
'binary' => ['Tarantool::Model::Types::Binary.new', 'varbinary']
|
|
22
|
+
}.freeze
|
|
23
|
+
ID = Field.new(name: 'id', attribute_type: ':integer', field_type: 'unsigned', nullable: false)
|
|
24
|
+
SCHEMA_PATH = 'config/tarantool_schema.rb'
|
|
25
|
+
|
|
26
|
+
source_root File.expand_path('templates', __dir__)
|
|
27
|
+
|
|
28
|
+
argument :fields, type: :array, default: [], banner: 'name:type name:type?'
|
|
29
|
+
class_option :skip_schema, type: :boolean, default: false, desc: "Do not add the space to #{SCHEMA_PATH}"
|
|
30
|
+
|
|
31
|
+
# Runs before any file is written so an unknown type leaves nothing behind.
|
|
32
|
+
def parse_fields
|
|
33
|
+
model_fields
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def create_model_file
|
|
37
|
+
template 'model.rb.tt', File.join('app/models', class_path, "#{file_name}.rb")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def add_space_to_schema
|
|
41
|
+
return if options[:skip_schema] || !File.exist?(File.join(destination_root, SCHEMA_PATH))
|
|
42
|
+
|
|
43
|
+
inject_into_file SCHEMA_PATH, space_declaration, before: /^end\s*\z/
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def model_fields
|
|
49
|
+
@model_fields ||= begin
|
|
50
|
+
declared = fields.map { |field| parse(field) }
|
|
51
|
+
declared.any? { |field| field.name == 'id' } ? declared : [ID, *declared]
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def parse(field)
|
|
56
|
+
name, type = field.split(':', 2)
|
|
57
|
+
raise Thor::Error, "#{field}: expected name:type" if type.nil? || type.empty?
|
|
58
|
+
|
|
59
|
+
nullable = type.end_with?('?')
|
|
60
|
+
attribute_type, field_type = TYPES[type.delete_suffix('?')]
|
|
61
|
+
raise Thor::Error, "unknown type #{type} for #{name}; use one of #{TYPES.keys.join(', ')}" unless attribute_type
|
|
62
|
+
|
|
63
|
+
Field.new(name:, attribute_type:, field_type:, nullable:)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def space_declaration
|
|
67
|
+
sequence = model_fields.first.equal?(ID) ? ', sequence: true' : ''
|
|
68
|
+
lines = ["ensure_space :#{plural_table_name},", ' format: [',
|
|
69
|
+
model_fields.map { |field| " #{field.declaration}" }.join(",\n"),
|
|
70
|
+
' ],', ' indexes: [',
|
|
71
|
+
" { name: :primary, parts: [:#{model_fields.first.name}]#{sequence} }", ' ]']
|
|
72
|
+
|
|
73
|
+
"\n#{lines.join("\n").gsub(/^/, ' ')}\n"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tarantool
|
|
4
|
+
module Rails
|
|
5
|
+
# One environment section of config/tarantool.yml, validated and split by consumer: addresses and client
|
|
6
|
+
# options go to the Pool, read/write to Storage::Box, crud to the crud client, admin_uri to the rake tasks.
|
|
7
|
+
Configuration = Data.define(:addresses, :client_options, :storage, :read, :write, :crud, :admin_uri) do
|
|
8
|
+
def inspect
|
|
9
|
+
fields = to_h.map { |key, value| "#{key}=#{filtered(key, value).inspect}" }.join(', ')
|
|
10
|
+
|
|
11
|
+
"#<data #{self.class.name} #{fields}>"
|
|
12
|
+
end
|
|
13
|
+
alias_method :to_s, :inspect
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def filtered(key, value)
|
|
18
|
+
case key
|
|
19
|
+
when :client_options then value.key?(:password) ? value.merge(password: '[FILTERED]') : value
|
|
20
|
+
when :admin_uri then value&.sub(%r{:[^:@/]*@}, ':[FILTERED]@')
|
|
21
|
+
else value
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Builds a Configuration from the Hash config_for returns.
|
|
27
|
+
class Configuration
|
|
28
|
+
CLIENT_KEYS = %i[user password connect_timeout request_timeout reconnect reconnect_attempts reconnect_delay
|
|
29
|
+
reconnect_max_delay max_frame_size].freeze
|
|
30
|
+
KEYS = (CLIENT_KEYS + %i[addresses address read write storage crud admin_uri]).freeze
|
|
31
|
+
CRUD_KEYS = %i[primary_index read write timeout_margin].freeze
|
|
32
|
+
STORAGES = %i[box crud].freeze
|
|
33
|
+
|
|
34
|
+
class << self
|
|
35
|
+
def from_hash(hash)
|
|
36
|
+
hash = checked(hash.to_h.transform_keys(&:to_sym))
|
|
37
|
+
|
|
38
|
+
new(addresses: addresses(hash), client_options: hash.slice(*CLIENT_KEYS), storage: storage(hash[:storage]),
|
|
39
|
+
read: options(hash[:read]), write: options(hash[:write]), crud: crud(hash[:crud]),
|
|
40
|
+
admin_uri: hash[:admin_uri])
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def checked(hash)
|
|
46
|
+
unknown = hash.keys - KEYS
|
|
47
|
+
raise ArgumentError, "unknown key #{unknown.first} in config/tarantool.yml" if unknown.any?
|
|
48
|
+
|
|
49
|
+
hash
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def addresses(hash)
|
|
53
|
+
addresses = Array(hash[:addresses] || hash[:address])
|
|
54
|
+
raise ArgumentError, 'addresses is required in config/tarantool.yml' if addresses.empty?
|
|
55
|
+
|
|
56
|
+
addresses
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def storage(value)
|
|
60
|
+
storage = (value || :box).to_sym
|
|
61
|
+
raise ArgumentError, "storage must be box or crud, got #{value.inspect}" unless STORAGES.include?(storage)
|
|
62
|
+
|
|
63
|
+
storage
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def options(hash)
|
|
67
|
+
options = (hash || {}).to_h.transform_keys(&:to_sym)
|
|
68
|
+
options[:mode] = options[:mode].to_sym if options.key?(:mode)
|
|
69
|
+
|
|
70
|
+
options
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def crud(hash)
|
|
74
|
+
crud = { primary_index: :primary }.merge((hash || {}).to_h.transform_keys(&:to_sym))
|
|
75
|
+
unknown = crud.keys - CRUD_KEYS
|
|
76
|
+
raise ArgumentError, "unknown key #{unknown.first} in crud" if unknown.any?
|
|
77
|
+
|
|
78
|
+
crud.merge(primary_index: crud[:primary_index].to_sym)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support/core_ext/module/attr_internal'
|
|
4
|
+
|
|
5
|
+
module Tarantool
|
|
6
|
+
module Rails
|
|
7
|
+
# Adds the time spent in Tarantool requests to the "Completed" line of a controller action and takes it out
|
|
8
|
+
# of the Views share, as ActiveRecord does with db_runtime.
|
|
9
|
+
module ControllerRuntime
|
|
10
|
+
extend ActiveSupport::Concern
|
|
11
|
+
|
|
12
|
+
# @api private
|
|
13
|
+
module ClassMethods
|
|
14
|
+
def log_process_action(payload)
|
|
15
|
+
messages = super
|
|
16
|
+
runtime = payload[:tarantool_runtime]
|
|
17
|
+
messages << format('Tarantool: %.1fms', runtime.to_f) if runtime
|
|
18
|
+
|
|
19
|
+
messages
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
attr_internal :tarantool_runtime
|
|
26
|
+
|
|
27
|
+
def process_action(...)
|
|
28
|
+
LogSubscriber.reset_runtime
|
|
29
|
+
|
|
30
|
+
super
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def cleanup_view_runtime
|
|
34
|
+
self.tarantool_runtime = tarantool_runtime.to_f + LogSubscriber.reset_runtime
|
|
35
|
+
runtime = super
|
|
36
|
+
during_render = LogSubscriber.reset_runtime
|
|
37
|
+
self.tarantool_runtime += during_render
|
|
38
|
+
|
|
39
|
+
runtime - during_render
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def append_info_to_payload(payload)
|
|
43
|
+
super
|
|
44
|
+
payload[:tarantool_runtime] = tarantool_runtime.to_f + LogSubscriber.reset_runtime
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tarantool
|
|
4
|
+
module Rails
|
|
5
|
+
# ActiveRecord-style lines for the client's events, and the time spent in requests per thread so the
|
|
6
|
+
# controller log can show a Tarantool share next to Views.
|
|
7
|
+
class LogSubscriber < ActiveSupport::LogSubscriber
|
|
8
|
+
COLORS = {
|
|
9
|
+
select: MAGENTA, insert: GREEN, replace: GREEN, upsert: GREEN, update: YELLOW, delete: RED,
|
|
10
|
+
begin: CYAN, commit: CYAN, rollback: CYAN
|
|
11
|
+
}.freeze
|
|
12
|
+
PRIMARY = [0, 'primary', :primary].freeze
|
|
13
|
+
RUNTIME = :tarantool_rails_runtime
|
|
14
|
+
private_constant :COLORS, :PRIMARY, :RUNTIME
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
def runtime
|
|
18
|
+
ActiveSupport::IsolatedExecutionState[RUNTIME] ||= 0.0
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def runtime=(value)
|
|
22
|
+
ActiveSupport::IsolatedExecutionState[RUNTIME] = value
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def reset_runtime
|
|
26
|
+
runtime.tap { self.runtime = 0.0 }
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def request(event)
|
|
31
|
+
self.class.runtime += event.duration
|
|
32
|
+
return unless logger.debug?
|
|
33
|
+
|
|
34
|
+
payload = event.payload
|
|
35
|
+
name = color(request_name(payload), COLORS.fetch(payload[:request], WHITE), bold: true)
|
|
36
|
+
|
|
37
|
+
debug [name, format('(%.2fms)', event.duration), *request_details(payload)].join(' ')
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def disconnect(event)
|
|
41
|
+
warn "Tarantool disconnected from #{event.payload[:address]}: #{event.payload[:reason]}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def reconnect(event)
|
|
45
|
+
info "Tarantool reconnected to #{event.payload[:address]} after #{attempts(event.payload[:attempts])}"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def reconnect_failed(event)
|
|
49
|
+
error "Tarantool gave up reconnecting to #{event.payload[:address]}: #{event.payload[:exception].join(': ')}"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def fork(event)
|
|
53
|
+
info "Tarantool reopened #{event.payload[:address]} after fork"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def role_change(event)
|
|
57
|
+
info "Tarantool #{event.payload[:address]} is now #{event.payload[:read_only] ? 'read-only' : 'writable'}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def request_name(payload)
|
|
63
|
+
parts = ['Tarantool', payload[:request], payload[:space], payload[:function]]
|
|
64
|
+
parts << "by #{payload[:index]}" if payload.key?(:index) && !PRIMARY.include?(payload[:index])
|
|
65
|
+
parts << "##{payload[:statement_id]}" if payload.key?(:statement_id)
|
|
66
|
+
|
|
67
|
+
parts.compact.join(' ')
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def request_details(payload)
|
|
71
|
+
details = [payload[:sql], payload[:address]]
|
|
72
|
+
details << request_error(payload) if payload.key?(:exception)
|
|
73
|
+
details << 'schema_reloaded' if payload[:schema_reloaded]
|
|
74
|
+
|
|
75
|
+
details.compact
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# The message may quote data (a duplicate key repeats the tuple); the line keeps the class and code.
|
|
79
|
+
def request_error(payload)
|
|
80
|
+
exception = payload[:exception_object]
|
|
81
|
+
klass = exception ? exception.class.name : payload[:exception].first
|
|
82
|
+
code = exception.respond_to?(:code) ? exception.code : nil
|
|
83
|
+
|
|
84
|
+
code ? "#{klass}(#{code})" : klass
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def attempts(count)
|
|
88
|
+
count == 1 ? '1 attempt' : "#{count} attempts"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tarantool
|
|
4
|
+
module Rails
|
|
5
|
+
# Wires configuration, the default model storage, logging and rake tasks into the application.
|
|
6
|
+
class Railtie < ::Rails::Railtie
|
|
7
|
+
config.tarantool = ActiveSupport::OrderedOptions.new
|
|
8
|
+
config.tarantool.log = true
|
|
9
|
+
|
|
10
|
+
# A missing config/tarantool.yml is not an error at boot: the install generator has to be able to run.
|
|
11
|
+
initializer 'tarantool.configuration' do |app|
|
|
12
|
+
Tarantool::Rails.schema_path = app.config.tarantool.schema_path || app.root.join('config/tarantool_schema.rb')
|
|
13
|
+
next unless app.root.join('config/tarantool.yml').exist?
|
|
14
|
+
|
|
15
|
+
section = app.config_for(:tarantool)
|
|
16
|
+
raise Error, "config/tarantool.yml has no #{::Rails.env} section" unless section
|
|
17
|
+
|
|
18
|
+
Tarantool::Rails.config = Configuration.from_hash(section)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
initializer 'tarantool.model' do
|
|
22
|
+
Tarantool::Rails.use_default_storage!
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
initializer 'tarantool.log' do |app|
|
|
26
|
+
next unless app.config.tarantool.log
|
|
27
|
+
|
|
28
|
+
LogSubscriber.attach_to :tarantool
|
|
29
|
+
ActiveSupport.on_load(:action_controller) { include ControllerRuntime }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
rake_tasks { load File.expand_path('tasks/schema.rake', __dir__) }
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :tarantool do
|
|
4
|
+
namespace :schema do
|
|
5
|
+
desc 'Print what tarantool:schema:apply would create; fail on drift'
|
|
6
|
+
task plan: :environment do
|
|
7
|
+
Tarantool::Rails.admin_client { |client| puts Tarantool::Rails.schema.plan(client) }
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
desc 'Create missing Tarantool spaces and indexes; fail on drift'
|
|
11
|
+
task apply: :environment do
|
|
12
|
+
Tarantool::Rails.admin_client { |client| puts Tarantool::Rails.schema.apply(client) }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
desc 'Fail unless the live schema equals the declaration, for CI'
|
|
16
|
+
task check: :environment do
|
|
17
|
+
pending = Tarantool::Rails.admin_client { |client| Tarantool::Rails.schema.plan(client) }
|
|
18
|
+
next if pending.empty?
|
|
19
|
+
|
|
20
|
+
pending.each { |line| puts "pending: #{line}" }
|
|
21
|
+
exit 1
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'monitor'
|
|
4
|
+
require 'tarantool/client'
|
|
5
|
+
require 'tarantool/model'
|
|
6
|
+
require 'tarantool/schema'
|
|
7
|
+
|
|
8
|
+
require_relative 'rails/version'
|
|
9
|
+
require_relative 'rails/errors'
|
|
10
|
+
require_relative 'rails/configuration'
|
|
11
|
+
require_relative 'rails/log_subscriber'
|
|
12
|
+
require_relative 'rails/controller_runtime'
|
|
13
|
+
require_relative 'rails/railtie' if defined?(Rails::Railtie)
|
|
14
|
+
|
|
15
|
+
module Tarantool
|
|
16
|
+
# Rails integration: configuration from config/tarantool.yml, one Pool per process, models on it, log lines,
|
|
17
|
+
# schema rake tasks and generators.
|
|
18
|
+
module Rails
|
|
19
|
+
LOCK = Monitor.new
|
|
20
|
+
private_constant :LOCK
|
|
21
|
+
|
|
22
|
+
class << self
|
|
23
|
+
attr_accessor :config, :schema_path
|
|
24
|
+
attr_writer :schema
|
|
25
|
+
|
|
26
|
+
def pool
|
|
27
|
+
LOCK.synchronize { @pool ||= build_pool }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def storage
|
|
31
|
+
LOCK.synchronize { @storage ||= build_storage }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def schema
|
|
35
|
+
LOCK.synchronize { @schema ||= load_schema }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def admin_client
|
|
39
|
+
uri = configured.admin_uri
|
|
40
|
+
raise Error, 'admin_uri is not set in config/tarantool.yml' unless uri
|
|
41
|
+
|
|
42
|
+
client = Tarantool::Client.new(uri)
|
|
43
|
+
|
|
44
|
+
yield client
|
|
45
|
+
ensure
|
|
46
|
+
client&.close
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Models without a storage of their own use the configured one, built on first use.
|
|
50
|
+
def use_default_storage!
|
|
51
|
+
@owns_default_storage = true
|
|
52
|
+
Model.default_storage = -> { storage }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def disconnect!
|
|
56
|
+
LOCK.synchronize do
|
|
57
|
+
@pool&.close
|
|
58
|
+
@pool = nil
|
|
59
|
+
@storage = nil
|
|
60
|
+
use_default_storage! if @owns_default_storage
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def configured
|
|
67
|
+
config || raise(Error, 'Tarantool::Rails.config is not set: config/tarantool.yml is missing ' \
|
|
68
|
+
'(rails g tarantool:install) or the Railtie is not loaded')
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def build_pool
|
|
72
|
+
Tarantool::Client::Pool.new(configured.addresses, **configured.client_options,
|
|
73
|
+
instrumenter: ActiveSupport::Notifications)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def build_storage
|
|
77
|
+
configured.storage == :box ? build_box_storage : build_crud_storage
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def build_box_storage
|
|
81
|
+
Model::Storage::Box.new(pool, read: configured.read, write: configured.write)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def build_crud_storage
|
|
85
|
+
require 'tarantool/crud'
|
|
86
|
+
options = configured.crud
|
|
87
|
+
crud = Tarantool::Crud::Client.new(pool, **options.slice(:read, :write, :timeout_margin))
|
|
88
|
+
|
|
89
|
+
Model::Storage::Crud.new(crud, primary_index: options[:primary_index])
|
|
90
|
+
rescue LoadError
|
|
91
|
+
raise Error, 'storage: crud needs the tarantool-crud gem; add it to the Gemfile'
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def load_schema
|
|
95
|
+
path = schema_path.to_s
|
|
96
|
+
load(path)
|
|
97
|
+
|
|
98
|
+
@schema || raise(Error, "#{path} must assign Tarantool::Rails.schema")
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: tarantool-rails
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Starlexxx
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-10-17 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: railties
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '7.1'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '9'
|
|
23
|
+
type: :runtime
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
requirements:
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '7.1'
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '9'
|
|
33
|
+
- !ruby/object:Gem::Dependency
|
|
34
|
+
name: tarantool-client
|
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0.2'
|
|
40
|
+
type: :runtime
|
|
41
|
+
prerelease: false
|
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0.2'
|
|
47
|
+
- !ruby/object:Gem::Dependency
|
|
48
|
+
name: tarantool-model
|
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0.3'
|
|
54
|
+
type: :runtime
|
|
55
|
+
prerelease: false
|
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0.3'
|
|
61
|
+
- !ruby/object:Gem::Dependency
|
|
62
|
+
name: tarantool-schema
|
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0.1'
|
|
68
|
+
type: :runtime
|
|
69
|
+
prerelease: false
|
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0.1'
|
|
75
|
+
description: config/tarantool.yml per environment, one connection pool per process,
|
|
76
|
+
models on it without an initializer, ActiveRecord-style log lines, schema rake tasks
|
|
77
|
+
and generators.
|
|
78
|
+
email:
|
|
79
|
+
- apavlovskiy088@gmail.com
|
|
80
|
+
executables: []
|
|
81
|
+
extensions: []
|
|
82
|
+
extra_rdoc_files: []
|
|
83
|
+
files:
|
|
84
|
+
- CHANGELOG.md
|
|
85
|
+
- LICENSE
|
|
86
|
+
- README.md
|
|
87
|
+
- lib/generators/tarantool/install/install_generator.rb
|
|
88
|
+
- lib/generators/tarantool/install/templates/tarantool.yml
|
|
89
|
+
- lib/generators/tarantool/install/templates/tarantool_schema.rb
|
|
90
|
+
- lib/generators/tarantool/model/model_generator.rb
|
|
91
|
+
- lib/generators/tarantool/model/templates/model.rb.tt
|
|
92
|
+
- lib/tarantool/rails.rb
|
|
93
|
+
- lib/tarantool/rails/configuration.rb
|
|
94
|
+
- lib/tarantool/rails/controller_runtime.rb
|
|
95
|
+
- lib/tarantool/rails/errors.rb
|
|
96
|
+
- lib/tarantool/rails/log_subscriber.rb
|
|
97
|
+
- lib/tarantool/rails/railtie.rb
|
|
98
|
+
- lib/tarantool/rails/tasks/schema.rake
|
|
99
|
+
- lib/tarantool/rails/version.rb
|
|
100
|
+
homepage: https://github.com/Starlexxx/tarantool-ruby
|
|
101
|
+
licenses:
|
|
102
|
+
- MIT
|
|
103
|
+
metadata:
|
|
104
|
+
source_code_uri: https://github.com/Starlexxx/tarantool-ruby/tree/master/tarantool-rails
|
|
105
|
+
changelog_uri: https://github.com/Starlexxx/tarantool-ruby/blob/master/tarantool-rails/CHANGELOG.md
|
|
106
|
+
bug_tracker_uri: https://github.com/Starlexxx/tarantool-ruby/issues
|
|
107
|
+
homepage_uri: https://github.com/Starlexxx/tarantool-ruby
|
|
108
|
+
rubygems_mfa_required: 'true'
|
|
109
|
+
post_install_message:
|
|
110
|
+
rdoc_options: []
|
|
111
|
+
require_paths:
|
|
112
|
+
- lib
|
|
113
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - ">="
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '3.2'
|
|
118
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
119
|
+
requirements:
|
|
120
|
+
- - ">="
|
|
121
|
+
- !ruby/object:Gem::Version
|
|
122
|
+
version: '0'
|
|
123
|
+
requirements: []
|
|
124
|
+
rubygems_version: 3.5.22
|
|
125
|
+
signing_key:
|
|
126
|
+
specification_version: 4
|
|
127
|
+
summary: Rails integration for tarantool-client, tarantool-model and tarantool-schema
|
|
128
|
+
test_files: []
|