rust_on_background 0.1.1

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.
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module RustOnBackground
6
+ module Generators
7
+ class JobGenerator < Rails::Generators::Base
8
+ source_root File.expand_path("templates", __dir__)
9
+ argument :job_name, type: :string, banner: "job_name"
10
+ argument :fields, type: :array, default: [], banner: "field:type field:type"
11
+
12
+ TYPE_MAPPINGS = {
13
+ # Ruby/Rails types
14
+ "string" => "String", "text" => "String", "citext" => "String",
15
+ "integer" => "i64", "int" => "i64", "i64" => "i64", "bigint" => "i64",
16
+ "i32" => "i32",
17
+ "float" => "f64", "decimal" => "f64", "f64" => "f64",
18
+ "boolean" => "bool", "bool" => "bool",
19
+ "array" => "Vec<serde_json::Value>", "vec" => "Vec<serde_json::Value>",
20
+ "set" => "Vec<serde_json::Value>",
21
+ "hash" => "serde_json::Value", "object" => "serde_json::Value",
22
+ "json" => "serde_json::Value", "jsonb" => "serde_json::Value",
23
+ "date" => "String", "datetime" => "String", "time" => "String", "timestamp" => "String",
24
+ "uuid" => "String", "binary" => "Vec<u8>", "blob" => "Vec<u8>"
25
+ }.freeze
26
+
27
+ desc "Creates a new Rust background job"
28
+
29
+ def create_job_file
30
+ @args = parsed_attributes
31
+ template "job.rs.erb", "app/jobs/rust/src/jobs/#{file_name}.rs"
32
+ end
33
+
34
+ def add_module_declaration
35
+ mod_file = "app/jobs/rust/src/jobs/mod.rs"
36
+
37
+ inject_into_file mod_file, after: "use crate::worker::{Job, JobResult};\n" do
38
+ "pub mod #{file_name};\n"
39
+ end
40
+ end
41
+
42
+ def add_dispatch_route
43
+ mod_file = "app/jobs/rust/src/jobs/mod.rs"
44
+ content = File.read(mod_file)
45
+
46
+ new_content = content.sub(/^([ \t]*)unknown\s*=>/m) do |_match|
47
+ indent = $1
48
+ "#{indent}\"#{file_name}\" => {\n" \
49
+ "#{indent} #{file_name}::run(&job.args, database_url).await\n" \
50
+ "#{indent}}\n\n" \
51
+ "#{indent}unknown =>"
52
+ end
53
+
54
+ File.write(mod_file, new_content)
55
+ end
56
+
57
+ def show_post_generate_message
58
+ say ""
59
+ say "Job '#{file_name}' created!", :green
60
+ say ""
61
+ say "Files:"
62
+ say " app/jobs/rust/src/jobs/#{file_name}.rs"
63
+ say ""
64
+ say "Usage in Rails:"
65
+ say " RustOnBackground.perform(\"#{file_name}\"#{usage_example})"
66
+ say ""
67
+ say "Next steps:"
68
+ say " 1. Edit the job file to add your logic"
69
+ say " 2. Run: cd app/jobs/rust && cargo build"
70
+ say ""
71
+ end
72
+
73
+ private
74
+
75
+ def file_name
76
+ job_name.underscore
77
+ end
78
+
79
+ def parsed_attributes
80
+ fields.map do |attr|
81
+ name, type = attr.split(":")
82
+ type ||= "string"
83
+
84
+ if model_type?(type)
85
+ { name: name, rust_type: type, ruby_type: type, is_model: true }
86
+ else
87
+ { name: name, rust_type: to_rust_type(type), ruby_type: type, is_model: false }
88
+ end
89
+ end
90
+ end
91
+
92
+ def model_type?(type)
93
+ type.match?(/\A[A-Z]/)
94
+ end
95
+
96
+ def model_attributes
97
+ @args.select { |a| a[:is_model] }.map { |a| a[:ruby_type] }.uniq
98
+ end
99
+
100
+ def model_columns(model_name)
101
+ model_class = model_name.constantize
102
+ model_class.columns.map do |col|
103
+ {
104
+ name: col.name,
105
+ rust_type: to_rust_type(col.type, nullable: col.null)
106
+ }
107
+ end
108
+ rescue NameError
109
+ say "Warning: Model #{model_name} not found, using empty struct", :yellow
110
+ [{ name: "id", rust_type: "i64" }]
111
+ end
112
+
113
+ def to_rust_type(type, nullable: false)
114
+ key = type.to_s.downcase
115
+ base_type = TYPE_MAPPINGS[key] || "serde_json::Value"
116
+ nullable ? "Option<#{base_type}>" : base_type
117
+ end
118
+
119
+ def usage_example
120
+ return "" if @args.empty?
121
+
122
+ examples = @args.map do |arg|
123
+ value = if arg[:is_model]
124
+ "#{arg[:ruby_type]}.first"
125
+ else
126
+ case arg[:ruby_type]
127
+ when "string", "text" then "\"...\""
128
+ when "integer", "int", "i64", "i32" then "123"
129
+ when "float", "decimal", "f64" then "1.5"
130
+ when "boolean", "bool" then "true"
131
+ else "..."
132
+ end
133
+ end
134
+ "#{arg[:name]}: #{value}"
135
+ end
136
+
137
+ ", " + examples.join(", ")
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,37 @@
1
+ use serde::Deserialize;
2
+ use crate::worker::JobResult;
3
+ <% model_attributes.each do |model_name| -%>
4
+
5
+ #[derive(Debug, Deserialize)]
6
+ struct <%= model_name %> {
7
+ <% model_columns(model_name).each do |col| -%>
8
+ <%= col[:name] %>: <%= col[:rust_type] %>,
9
+ <% end -%>
10
+ }
11
+ <% end -%>
12
+ <% if @args.any? %>
13
+
14
+ #[derive(Debug, Deserialize)]
15
+ struct Args {
16
+ <% @args.each do |arg| -%>
17
+ <%= arg[:name] %>: <%= arg[:rust_type] %>,
18
+ <% end -%>
19
+ }
20
+ <% end %>
21
+
22
+ pub async fn run(args: &serde_json::Value, _database_url: &str) -> JobResult {
23
+ <% if @args.any? -%>
24
+ let args: Args = match serde_json::from_value(args.clone()) {
25
+ Ok(a) => a,
26
+ Err(e) => return JobResult::Failed(format!("Failed to parse args: {}", e)),
27
+ };
28
+
29
+ tracing::info!("<%= file_name %> job started: {:?}", args);
30
+ <% else -%>
31
+ tracing::info!("<%= file_name %> job started with args: {:?}", args);
32
+ <% end -%>
33
+
34
+ // TODO: Add your job logic here
35
+
36
+ JobResult::Success
37
+ }
@@ -0,0 +1,10 @@
1
+ require "rails/railtie"
2
+
3
+ module RustOnBackground
4
+ class Railtie < Rails::Railtie
5
+ generators do
6
+ require_relative "../generators/install/install_generator"
7
+ require_relative "../generators/job/job_generator"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RustOnBackground
4
+ VERSION = "0.1.1"
5
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "redis"
5
+ require "toml-rb"
6
+ require_relative "rust_on_background/version"
7
+ require_relative "rust_on_background/railtie" if defined?(Rails::Railtie)
8
+
9
+ module RustOnBackground
10
+ class Error < StandardError; end
11
+
12
+ SCHEDULE_KEY = "rust_on_background:schedule"
13
+
14
+ def self.config
15
+ @config ||= TomlRB.load_file(Rails.root.join("app/jobs/rust/config.#{Rails.env}.toml"))
16
+ end
17
+
18
+ def self.perform(job_name, queue: "default", retry_count: 3, **args)
19
+ payload = {
20
+ job: job_name.to_s,
21
+ args: serialize_args(args),
22
+ retry: retry_count,
23
+ created_at: Time.now.to_i
24
+ }
25
+
26
+ begin
27
+ redis.lpush("queue:#{queue}", payload.to_json)
28
+ rescue Redis::BaseError => e
29
+ raise Error, "Failed to enqueue job: #{e.message}"
30
+ end
31
+ end
32
+
33
+ def self.perform_at(time, job_name, queue: "default", retry_count: 3, **args)
34
+ timestamp = normalize_time(time)
35
+ return perform(job_name, queue: queue, retry_count: retry_count, **args) if timestamp <= Time.now.to_f
36
+
37
+ payload = {
38
+ job: job_name.to_s,
39
+ args: serialize_args(args),
40
+ retry: retry_count,
41
+ created_at: Time.now.to_i,
42
+ queue: queue,
43
+ scheduled_at: timestamp.to_i
44
+ }
45
+
46
+ begin
47
+ redis.zadd(SCHEDULE_KEY, timestamp, payload.to_json)
48
+ rescue Redis::BaseError => e
49
+ raise Error, "Failed to schedule job: #{e.message}"
50
+ end
51
+ end
52
+
53
+ def self.serialize_args(args)
54
+ args.transform_values { |v| serialize_value(v) }
55
+ end
56
+
57
+ def self.normalize_time(time)
58
+ case time
59
+ when Time then time.to_f
60
+ when DateTime then time.to_time.to_f
61
+ when Integer, Float then time.to_f
62
+ else
63
+ if time.respond_to?(:to_time)
64
+ time.to_time.to_f
65
+ else
66
+ raise ArgumentError, "Invalid time class: #{time.class}"
67
+ end
68
+ end
69
+ end
70
+
71
+ def self.serialize_value(value)
72
+ case value
73
+ when nil, true, false, Integer, Float, String
74
+ value
75
+ when Symbol
76
+ value.to_s
77
+ when Time, DateTime
78
+ value.iso8601
79
+ when Date
80
+ value.to_s
81
+ when BigDecimal
82
+ value.to_f
83
+ when Set
84
+ value.to_a.map { |v| serialize_value(v) }
85
+ when Range
86
+ { start: serialize_value(value.begin), end: serialize_value(value.end), exclude_end: value.exclude_end? }
87
+ when Array
88
+ value.map { |v| serialize_value(v) }
89
+ when Hash
90
+ value.transform_keys(&:to_s).transform_values { |v| serialize_value(v) }
91
+ else
92
+ if defined?(ActiveRecord::Base) && value.is_a?(ActiveRecord::Base)
93
+ value.as_json
94
+ elsif defined?(ActiveRecord::Relation) && value.is_a?(ActiveRecord::Relation)
95
+ value.as_json
96
+ elsif value.respond_to?(:as_json)
97
+ value.as_json
98
+ elsif value.respond_to?(:to_h)
99
+ serialize_value(value.to_h)
100
+ else
101
+ value.to_s
102
+ end
103
+ end
104
+ end
105
+
106
+ def self.redis
107
+ @redis ||= Redis.new(url: config.dig("redis", "url") || "redis://127.0.0.1:6379")
108
+ end
109
+
110
+ def self.redis=(client)
111
+ @redis = client
112
+ end
113
+ end
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rust_on_background
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - QuarkXZ
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2026-03-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '5.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '5.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: redis
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '4.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '4.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: toml-rb
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ description: A Rails gem that generates a Rust worker for processing background jobs
56
+ from Redis
57
+ email:
58
+ - quarkXZ@proton.me
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - CHANGELOG.md
64
+ - LICENSE.txt
65
+ - README.md
66
+ - Rakefile
67
+ - lib/generators/install/install_generator.rb
68
+ - lib/generators/install/templates/Cargo.toml.erb
69
+ - lib/generators/install/templates/config.toml.erb
70
+ - lib/generators/install/templates/jobs/mod.rs
71
+ - lib/generators/install/templates/main.rs
72
+ - lib/generators/install/templates/rust_on_background.rake
73
+ - lib/generators/install/templates/scheduler.rs
74
+ - lib/generators/install/templates/worker.rs
75
+ - lib/generators/job/job_generator.rb
76
+ - lib/generators/job/templates/job.rs.erb
77
+ - lib/rust_on_background.rb
78
+ - lib/rust_on_background/railtie.rb
79
+ - lib/rust_on_background/version.rb
80
+ homepage: https://github.com/QuarkOnRails/rust_on_background
81
+ licenses:
82
+ - MIT
83
+ metadata:
84
+ homepage_uri: https://github.com/QuarkOnRails/rust_on_background
85
+ source_code_uri: https://github.com/QuarkOnRails/rust_on_background
86
+ changelog_uri: https://github.com/QuarkOnRails/rust_on_background/blob/main/CHANGELOG.md
87
+ post_install_message:
88
+ rdoc_options: []
89
+ require_paths:
90
+ - lib
91
+ required_ruby_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: 2.5.0
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ requirements: []
102
+ rubygems_version: 3.3.26
103
+ signing_key:
104
+ specification_version: 4
105
+ summary: Run background jobs in Rust for Rails applications
106
+ test_files: []