gritz-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 +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +59 -0
- data/lib/generators/gritz/controller/controller_generator.rb +32 -0
- data/lib/generators/gritz/controller/templates/controller.rb.tt +11 -0
- data/lib/generators/gritz/install/install_generator.rb +25 -0
- data/lib/generators/gritz/install/templates/gritz +7 -0
- data/lib/generators/gritz/install/templates/gritz.rb +9 -0
- data/lib/generators/gritz/install/templates/initializer.rb +5 -0
- data/lib/gritz/rails/executor.rb +18 -0
- data/lib/gritz/rails/railtie.rb +14 -0
- data/lib/gritz/rails/version.rb +7 -0
- data/lib/gritz/rails.rb +73 -0
- metadata +93 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: cdb05adc6d6f492115eb0e7d9de033bd3e4509a65f60db732377e254b7f28d5f
|
|
4
|
+
data.tar.gz: 927579595d4645896cac9dfe6f8514ccd2e52abf94ad10f1cb77c07158dff833
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: dac749d3fc8f50ac2716e3de2557f5fd62b9cb61cd62c5fdfaa0a37dbaa76d690279a4de3cf8cd0baf53128878e08cbe7228e940317f7ab2b1c8a896f08ec6f1
|
|
7
|
+
data.tar.gz: 02d442a42de49ba6a6c0a27587e74b380f510c6baebb28b9ab6c79318774cb75cd1f878f6af4c08d856d8923cd0b9081b5b2f7e31ae7ef34e721bbb8d832a21f
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ydah
|
|
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
|
|
13
|
+
all 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
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Gritz Rails
|
|
2
|
+
|
|
3
|
+
Rails integration for [Gritz](https://github.com/gritzrpc/gritz): RPC controller loading, generators, complete-RPC execution and single-process development reloading. Requires CRuby 3.3+, Rails 8.0 or 8.1, and Gritz 0.5.0. Runtime dependencies are `gritz-core` and `railties`; applications choose their transport separately.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
gem "gritz", "~> 0.5.0"
|
|
9
|
+
gem "gritz-rails", "~> 0.1.0"
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The first `gritz-rails` release is prepared for the project owner. Until it is published, use `gem "gritz-rails", git: "https://github.com/gritzrpc/gritz-rails.git", branch: "main"`.
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
bin/rails generate gritz:install
|
|
16
|
+
# Generate protobufs into lib/protos, then generate a bound controller:
|
|
17
|
+
bin/rails generate gritz:controller Greeter SayHello --service Helloworld::Greeter::Service
|
|
18
|
+
bin/gritz routes
|
|
19
|
+
bin/gritz check
|
|
20
|
+
bin/gritz start
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Implement each generated action using `request.message` or `request.each_message`, and `stream.write` for response streams. The controller generator accepts RPC names and `--service` names; it adds the controller to `config/gritz.rb`. Set `strict_routes true` after all actions are implemented.
|
|
24
|
+
|
|
25
|
+
## Rails lifecycle
|
|
26
|
+
|
|
27
|
+
Generated `config/gritz.rb` loads the application through `rails_app`, selects four workers in production and zero elsewhere, and registers controllers. Additional settings come after `rails_app`, including explicit `reflection false` or `true`. Rails development enables Reflection by default; other environments keep the core's disabled default.
|
|
28
|
+
|
|
29
|
+
Every complete application RPC runs inside the Rails executor. Development uses the Rails reloader and resolves controller constants after reload. Active Record connections return to their pools, query caches finish and CurrentAttributes reset on successful and failed calls. Application threads created inside a handler must use Rails' own executor wrapping. See the [Rails execution guide](https://guides.rubyonrails.org/threading_and_code_execution.html).
|
|
30
|
+
|
|
31
|
+
Application code is eager loaded before fork. In prefork mode, all Active Record pools disconnect after eager loading, before Ruby warmup, and again before each fork. Ruby's Rails fork tracking remains active. Development reloading requires `workers 0`; changing protobuf definitions, bound services or registered routes requires a server restart. Production code changes use Gritz's `USR2` fresh-interpreter replacement.
|
|
32
|
+
|
|
33
|
+
`app/rpc` participates in Rails autoloading and eager loading. Generated protobuf files in `lib/protos` are excluded from both Zeitwerk loaders and required explicitly by the generated initializer. Keep generated files outside normal autoload directories.
|
|
34
|
+
|
|
35
|
+
For an already initialized application, use `Gritz::Rails.install(config, application: Rails.application)` once before starting the server. Match Active Record pool capacity to RPC concurrency with `RAILS_MAX_THREADS` or your database configuration.
|
|
36
|
+
|
|
37
|
+
## Examples and migration
|
|
38
|
+
|
|
39
|
+
The [Rails catalog sample](examples/rails_app) serves SQLite-backed unary and streaming RPCs with four production workers. It includes the [memory validation](docs/reports/T5-07-rails-memory.md).
|
|
40
|
+
|
|
41
|
+
Existing Gruf applications can use the optional `Gritz::Compat::Gruf` controller and interceptor adapter in `gritz-core`. See the [migration guide](https://github.com/gritzrpc/gritz-core/blob/main/docs/guides/migrating-from-gruf.md).
|
|
42
|
+
|
|
43
|
+
## Development
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
bundle install
|
|
47
|
+
COVERAGE=1 bundle exec rake
|
|
48
|
+
bundle exec rubocop
|
|
49
|
+
bundle exec bundler-audit check --update
|
|
50
|
+
bundle exec rake build
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
CI tests Ruby 3.3, 3.4 and 4.0 against Rails 8.0 and 8.1. Tests include real RPCs, file-change reloading, all-pool fork cleanup, executable generated configuration and Linux four-worker sample lifecycle. Sibling checkout overrides follow [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
54
|
+
|
|
55
|
+
The initial package is `pkg/gritz-rails.gem`. Stop before its first tag/publication and configure [Trusted Publishing](docs/guides/releasing.md) after the owner releases it.
|
|
56
|
+
|
|
57
|
+
## License
|
|
58
|
+
|
|
59
|
+
[MIT](LICENSE.txt).
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators/named_base"
|
|
4
|
+
|
|
5
|
+
module Gritz
|
|
6
|
+
module Generators
|
|
7
|
+
# Generates a controller bound to an application's generated service.
|
|
8
|
+
# @api public
|
|
9
|
+
class ControllerGenerator < ::Rails::Generators::NamedBase
|
|
10
|
+
source_root File.expand_path("templates", __dir__)
|
|
11
|
+
desc "Generate an RPC controller and register it in config/gritz.rb."
|
|
12
|
+
argument :actions, type: :array, default: [], banner: "RPC_ACTIONS"
|
|
13
|
+
class_option :service, type: :string, desc: "Generated service class (defaults to NAME::Service)"
|
|
14
|
+
|
|
15
|
+
def create_controller
|
|
16
|
+
raise Thor::Error, "service must be a Ruby constant name" unless /\A[A-Z]\w*(?:::[A-Z]\w*)*\z/.match?(service_class_name)
|
|
17
|
+
unless actions.all? { |action| /\A[a-z_]\w*\z/.match?(action.underscore) }
|
|
18
|
+
raise Thor::Error, "action must be a Ruby method name"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
template "controller.rb.tt", "app/rpc/#{file_path}_controller.rb"
|
|
22
|
+
append_to_file "config/gritz.rb", "\nregister_controller #{class_name}Controller\n"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def service_class_name
|
|
28
|
+
(options[:service] || "#{class_name}::Service").delete_prefix("::")
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class <%= class_name %>Controller < Gritz::Controller
|
|
4
|
+
bind ::<%= service_class_name %>
|
|
5
|
+
<% actions.each do |action| -%>
|
|
6
|
+
|
|
7
|
+
def <%= action.underscore %>
|
|
8
|
+
fail!(:unimplemented, "Implement <%= action.underscore %>")
|
|
9
|
+
end
|
|
10
|
+
<% end -%>
|
|
11
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
|
|
5
|
+
module Gritz
|
|
6
|
+
module Generators
|
|
7
|
+
# Creates the configuration and binstub for a Rails RPC server.
|
|
8
|
+
# @api public
|
|
9
|
+
class InstallGenerator < ::Rails::Generators::Base
|
|
10
|
+
source_root File.expand_path("templates", __dir__)
|
|
11
|
+
desc "Install Gritz configuration, protobuf loading, and bin/gritz."
|
|
12
|
+
|
|
13
|
+
def install
|
|
14
|
+
template "gritz.rb", "config/gritz.rb"
|
|
15
|
+
copy_file "initializer.rb", "config/initializers/gritz.rb"
|
|
16
|
+
copy_file "gritz", "bin/gritz"
|
|
17
|
+
chmod "bin/gritz", 0o755
|
|
18
|
+
empty_directory "lib/protos"
|
|
19
|
+
create_file "lib/protos/.keep"
|
|
20
|
+
empty_directory "app/rpc"
|
|
21
|
+
create_file "app/rpc/.keep"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "gritz/rails"
|
|
4
|
+
|
|
5
|
+
rails_app
|
|
6
|
+
workers ::Rails.env.production? ? 4 : 0 # rubocop:disable Style/RedundantConstantBase -- The config is evaluated inside Gritz::DSL.
|
|
7
|
+
threads 16
|
|
8
|
+
|
|
9
|
+
# Add controllers with: bin/rails generate gritz:controller Greeter SayHello --service Example::Greeter::Service
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gritz
|
|
4
|
+
module Rails
|
|
5
|
+
# Keeps Rails resources and reload interlocks scoped to the entire RPC.
|
|
6
|
+
# @api public
|
|
7
|
+
class Executor
|
|
8
|
+
def initialize(app, application:, development: false)
|
|
9
|
+
@app = app
|
|
10
|
+
@executor = development ? application.reloader : application.executor
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call(context)
|
|
14
|
+
@executor.wrap { @app.call(context) }
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gritz
|
|
4
|
+
module Rails
|
|
5
|
+
# Adds RPC controllers to Rails loading while keeping protoc output explicit.
|
|
6
|
+
# @api private
|
|
7
|
+
class Railtie < ::Rails::Railtie
|
|
8
|
+
initializer "gritz.paths", before: :set_autoload_paths do |application|
|
|
9
|
+
application.config.paths.add("app/rpc", eager_load: true)
|
|
10
|
+
application.autoloaders.each { |loader| loader.ignore(application.root.join("lib/protos")) }
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
data/lib/gritz/rails.rb
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "gritz/core"
|
|
4
|
+
require "rails"
|
|
5
|
+
require "active_support/fork_tracker"
|
|
6
|
+
require_relative "rails/version"
|
|
7
|
+
require_relative "rails/executor"
|
|
8
|
+
require_relative "rails/railtie"
|
|
9
|
+
|
|
10
|
+
module Gritz
|
|
11
|
+
module Rails
|
|
12
|
+
# Installs Rails execution and fork lifecycle hooks into one server configuration.
|
|
13
|
+
# @api public
|
|
14
|
+
def self.install(config, application: ::Rails.application)
|
|
15
|
+
raise ConfigurationError, "rails_app requires an initialized Rails application" unless application&.initialized?
|
|
16
|
+
|
|
17
|
+
installed = config.middleware.entries.find { |entry| entry.middleware == Executor }
|
|
18
|
+
if installed
|
|
19
|
+
raise ConfigurationError, "Rails integration is already installed for another application" unless installed.options[:application].equal?(application)
|
|
20
|
+
|
|
21
|
+
return config
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
development = ::Rails.env.development?
|
|
25
|
+
config.reflection = true if development
|
|
26
|
+
first = config.middleware.entries.first
|
|
27
|
+
if first
|
|
28
|
+
config.middleware.insert_before(first.middleware, Executor, application:, development:)
|
|
29
|
+
else
|
|
30
|
+
config.middleware.use(Executor, application:, development:)
|
|
31
|
+
end
|
|
32
|
+
disconnect_pools = lambda do |_index = nil|
|
|
33
|
+
if defined?(ActiveRecord::Base)
|
|
34
|
+
ActiveRecord::Base.connection_handler.connection_pool_list(:all).each(&:disconnect!)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
config.add_preloader do
|
|
38
|
+
raise ConfigurationError, "Rails development mode requires workers 0" if development && config.workers.positive?
|
|
39
|
+
|
|
40
|
+
application.eager_load!
|
|
41
|
+
config.controllers.map! { |controller| reloadable_controller(controller) } if development
|
|
42
|
+
disconnect_pools.call if config.workers.positive?
|
|
43
|
+
end
|
|
44
|
+
config.add_hook(:before_fork, &disconnect_pools)
|
|
45
|
+
config
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Route descriptors stay fixed, while the controller constant is resolved after Rails reloads.
|
|
49
|
+
# @api private
|
|
50
|
+
def self.reloadable_controller(controller)
|
|
51
|
+
name = controller.name
|
|
52
|
+
raise ConfigurationError, "Rails development mode requires a named controller" unless name
|
|
53
|
+
|
|
54
|
+
Class.new(Gritz::Controller) do
|
|
55
|
+
bind controller.service_class
|
|
56
|
+
define_singleton_method(:name) { name }
|
|
57
|
+
define_singleton_method(:to_s) { name }
|
|
58
|
+
define_singleton_method(:action_defined?) { |action| name.constantize.action_defined?(action) }
|
|
59
|
+
define_singleton_method(:new) { |**options| name.constantize.new(**options) }
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# @api public
|
|
64
|
+
module ConfigurationDSL
|
|
65
|
+
def rails_app(path = "config/environment.rb")
|
|
66
|
+
require File.expand_path(path)
|
|
67
|
+
Gritz::Rails.install(@config)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
Gritz::DSL.include(Gritz::Rails::ConfigurationDSL)
|
metadata
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: gritz-rails
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Yudai Takada
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: gritz-core
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - '='
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 0.5.0
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - '='
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 0.5.0
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: railties
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '8.0'
|
|
33
|
+
- - "<"
|
|
34
|
+
- !ruby/object:Gem::Version
|
|
35
|
+
version: '9'
|
|
36
|
+
type: :runtime
|
|
37
|
+
prerelease: false
|
|
38
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
39
|
+
requirements:
|
|
40
|
+
- - ">="
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: '8.0'
|
|
43
|
+
- - "<"
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '9'
|
|
46
|
+
description: Rails autoloading, RPC execution, generators and safe preloading for
|
|
47
|
+
Gritz.
|
|
48
|
+
email:
|
|
49
|
+
- t.yudai92@gmail.com
|
|
50
|
+
executables: []
|
|
51
|
+
extensions: []
|
|
52
|
+
extra_rdoc_files: []
|
|
53
|
+
files:
|
|
54
|
+
- CHANGELOG.md
|
|
55
|
+
- LICENSE.txt
|
|
56
|
+
- README.md
|
|
57
|
+
- lib/generators/gritz/controller/controller_generator.rb
|
|
58
|
+
- lib/generators/gritz/controller/templates/controller.rb.tt
|
|
59
|
+
- lib/generators/gritz/install/install_generator.rb
|
|
60
|
+
- lib/generators/gritz/install/templates/gritz
|
|
61
|
+
- lib/generators/gritz/install/templates/gritz.rb
|
|
62
|
+
- lib/generators/gritz/install/templates/initializer.rb
|
|
63
|
+
- lib/gritz/rails.rb
|
|
64
|
+
- lib/gritz/rails/executor.rb
|
|
65
|
+
- lib/gritz/rails/railtie.rb
|
|
66
|
+
- lib/gritz/rails/version.rb
|
|
67
|
+
homepage: https://github.com/gritzrpc/gritz-rails
|
|
68
|
+
licenses:
|
|
69
|
+
- MIT
|
|
70
|
+
metadata:
|
|
71
|
+
allowed_push_host: https://rubygems.org
|
|
72
|
+
homepage_uri: https://github.com/gritzrpc/gritz-rails/
|
|
73
|
+
source_code_uri: https://github.com/gritzrpc/gritz-rails
|
|
74
|
+
changelog_uri: https://github.com/gritzrpc/gritz-rails/blob/main/CHANGELOG.md
|
|
75
|
+
rubygems_mfa_required: 'true'
|
|
76
|
+
rdoc_options: []
|
|
77
|
+
require_paths:
|
|
78
|
+
- lib
|
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
80
|
+
requirements:
|
|
81
|
+
- - ">="
|
|
82
|
+
- !ruby/object:Gem::Version
|
|
83
|
+
version: '3.3'
|
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '0'
|
|
89
|
+
requirements: []
|
|
90
|
+
rubygems_version: 4.0.16
|
|
91
|
+
specification_version: 4
|
|
92
|
+
summary: Rails integration for Gritz RPC servers
|
|
93
|
+
test_files: []
|