snowflaked 0.1.4-arm-linux → 0.3.0-arm-linux

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: 02505dc9096e0536b671ea27d47019678c91b8bb1a7ac41e571bb846c68e0108
4
- data.tar.gz: 59e5c3bb9f89acb5e8eb4357349e664c956acdf4f43e559a3319b942f8d816e2
3
+ metadata.gz: ba6a514d46f61318ebb15ab41f1101e0532529da168147a822e08059c7750dca
4
+ data.tar.gz: 74255dfc057ab92d9b8f7173a1bcf95fedd55899343a34ba2cb9f36a0eda87b7
5
5
  SHA512:
6
- metadata.gz: 589c7986aa58bba1ddc7c56e3336a4ba591d3e42aa579318381dca3a1dcfc89317f91d62a2e5e9c364e68988f8294de4ff42d362619933d10404944e518f6594
7
- data.tar.gz: 277759aeef5df7954f5394b6d12e1b48b1074c1d2e63243bf7a1f3e856eb10484b548cabd4e630cadd07b7f53d8dd6552b047803ee2300e5647cde27126eec1a
6
+ metadata.gz: 912bab31c7cb549f1e5b159a8a6a84c063609941b3e1971a1b57cb001dbe7cb261e07d62d73b05833797daa3abe94f290b99f8ab5024d10a869d314421687296
7
+ data.tar.gz: ac385f10e3b951410dfd69f113c209e246019735deda89e9b198d7181f2cd2bde9eef6908a78861ca8d1aea53f58907929c788d48c6fefa8dc2c80b7b5745bee
data/README.md CHANGED
@@ -83,14 +83,16 @@ end
83
83
  ```ruby
84
84
  Snowflaked.configure do |config|
85
85
  config.machine_id = 42
86
- config.epoch = Time.utc(1989, 1, 3) # When not configured, the epoch is set to the Unix epoch (January 1, 1970)
86
+ config.epoch = Time.utc(1989, 1, 3) # Defaults to DEFAULT_EPOCH (2024-01-01) if not configured
87
87
  end
88
88
  ```
89
89
 
90
+ Configuration is locked after `Snowflaked.configure` or the first generated/parsed ID. Set it during application boot, before request threads start.
91
+
90
92
  ### Machine ID
91
93
 
92
94
  > [!TIP]
93
- > For multi-process servers like Puma, it is recommended to **not** configure `machine_id` explicitly. The gem automatically calculates a unique machine ID using `(hostname.hash ^ pid) % 1024`, which ensures each forked worker process gets a different ID and avoids duplicate Snowflake IDs.
95
+ > For multi-process servers like Puma, it is recommended to **not** configure `machine_id` explicitly. The gem automatically calculates a process-local machine ID using `(hostname.hash ^ pid) % 1024`, so forked workers do not reuse the parent's cached machine ID.
94
96
 
95
97
  If you must set `machine_id` explicitly, use environment variables that differ per worker process.
96
98
 
@@ -102,14 +104,12 @@ If `machine_id` is not explicitly configured, it resolves in this order:
102
104
  2. `MACHINE_ID` environment variable
103
105
  3. Auto-detected using the following formula: `(hostname.hash ^ pid) % 1024`
104
106
 
105
- For Kubernetes deployments, you can set the machine ID using an environment variable:
107
+ For Kubernetes deployments, set the machine ID to a numeric value between `0` and `1023`:
106
108
 
107
109
  ```yaml
108
110
  env:
109
111
  - name: SNOWFLAKED_MACHINE_ID
110
- valueFrom:
111
- fieldRef:
112
- fieldPath: metadata.name
112
+ value: "42"
113
113
  ```
114
114
 
115
115
  Or use a StatefulSet ordinal for guaranteed unique values:
@@ -145,15 +145,14 @@ Snowflaked.machine_id(id)
145
145
 
146
146
  ## Benchmarks
147
147
 
148
- See [BENCHMARKS.md](benchmarks/BENCHMARKS.md) for more details.
148
+ See [BENCHMARKS.md](benchmarks/README.md) for more details.
149
149
 
150
150
  tl;dr: Snowflake IDs have a negligible performance impact compared to database-backed IDs.
151
151
 
152
152
  ## Requirements
153
153
 
154
- - Ruby >= 3.2
155
- - rustc >= 1.81.0
156
- - cargo >= 1.81.0
154
+ - Ruby >= 3.3
155
+ - rustc / cargo >= 1.81.0 (development uses the toolchain pinned in `mise.toml`)
157
156
  - Mise
158
157
 
159
158
  ## Development
Binary file
Binary file
Binary file
@@ -6,7 +6,7 @@ module Snowflaked
6
6
 
7
7
  included do
8
8
  class_attribute :_snowflake_attributes, instance_writer: false, default: [:id]
9
- after_initialize :_generate_snowflake_ids, if: :new_record?
9
+ before_validation :_generate_snowflake_ids, on: :create
10
10
  end
11
11
 
12
12
  class_methods do
@@ -20,14 +20,14 @@ module Snowflaked
20
20
  def _snowflake_columns_from_comments
21
21
  return @_snowflake_columns_from_comments if defined?(@_snowflake_columns_from_comments)
22
22
 
23
- @_snowflake_columns_from_comments = if table_exists?
24
- columns.filter_map { |col| col.name.to_sym if col.comment == Snowflaked::SchemaDefinitions::COMMENT }
25
- else
26
- []
27
- end
23
+ return [] unless table_exists?
24
+
25
+ @_snowflake_columns_from_comments = columns.filter_map { |col| col.name.to_sym if col.comment == Snowflaked::SchemaDefinitions::COMMENT }
28
26
  end
29
27
 
30
28
  def _snowflake_attributes_with_columns
29
+ return _snowflake_attributes unless table_exists?
30
+
31
31
  @_snowflake_attributes_with_columns ||= (_snowflake_attributes | _snowflake_columns_from_comments)
32
32
  end
33
33
  end
@@ -35,10 +35,10 @@ module Snowflaked
35
35
  private
36
36
 
37
37
  def _generate_snowflake_ids
38
- attributes_to_generate = self.class._snowflake_attributes_with_columns
39
- return if attributes_to_generate.empty?
38
+ attributes = self.class._snowflake_attributes_with_columns
39
+ return if attributes.empty?
40
40
 
41
- attributes_to_generate.each do |attribute|
41
+ attributes.each do |attribute|
42
42
  next if self[attribute].present?
43
43
 
44
44
  self[attribute] = Snowflaked.id
@@ -42,10 +42,9 @@ module Snowflaked
42
42
  end
43
43
 
44
44
  initializer "snowflaked.model_extensions", after: "active_record.initialize_database" do
45
- ActiveSupport.on_load(:active_record) do
46
- require "snowflaked/model_extensions"
47
- ActiveRecord::Base.include(Snowflaked::ModelExtensions)
48
- end
45
+ require "snowflaked/model_extensions"
46
+
47
+ ActiveSupport.on_load(:active_record) { include Snowflaked::ModelExtensions }
49
48
  end
50
49
  end
51
50
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Snowflaked
4
- VERSION = "0.1.4"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/snowflaked.rb CHANGED
@@ -16,42 +16,81 @@ require_relative "snowflaked/railtie" if defined?(Rails::Railtie)
16
16
 
17
17
  module Snowflaked
18
18
  MAX_MACHINE_ID = 1023
19
+ DEFAULT_EPOCH = Time.utc(2024, 1, 1).freeze
19
20
 
20
21
  class Error < StandardError; end
21
22
  class ConfigurationError < Error; end
22
23
 
23
24
  class Configuration
24
- attr_accessor :machine_id, :epoch
25
+ attr_reader :machine_id, :epoch
25
26
 
26
27
  def initialize
27
28
  @machine_id = nil
28
- @epoch = nil
29
+ @epoch = DEFAULT_EPOCH
30
+ @sealed = false
31
+ end
32
+
33
+ def machine_id=(value)
34
+ raise_if_sealed!(:machine_id)
35
+
36
+ @machine_id = value.nil? ? nil : checked_machine_id(value)
37
+ @machine_id_value = nil
38
+ @machine_id_value_pid = nil
39
+ end
40
+
41
+ def epoch=(value)
42
+ raise_if_sealed!(:epoch)
43
+
44
+ @epoch = value
45
+ @epoch_ms = nil
46
+ end
47
+
48
+ def seal!
49
+ @sealed = true
29
50
  end
30
51
 
31
52
  def machine_id_value
32
- id = @machine_id || default_machine_id
33
- id % (MAX_MACHINE_ID + 1)
53
+ if @machine_id_value_pid != Process.pid
54
+ @machine_id_value = resolve_machine_id
55
+ @machine_id_value_pid = Process.pid
56
+ end
57
+
58
+ @machine_id_value
34
59
  end
35
60
 
36
61
  def epoch_ms
37
62
  return nil unless @epoch
38
63
 
39
- (@epoch.to_f * 1000).to_i
64
+ @epoch_ms ||= (@epoch.to_r * 1000).to_i
40
65
  end
41
66
 
42
67
  private
43
68
 
44
- def default_machine_id
45
- env_machine_id || hostname_pid_hash
69
+ # Resolution order: explicit config, then env vars, then an auto fallback.
70
+ # Explicit and env values are range-checked; the fallback is always valid.
71
+ def resolve_machine_id
72
+ return @machine_id unless @machine_id.nil?
73
+
74
+ env = ENV["SNOWFLAKED_MACHINE_ID"] || ENV.fetch("MACHINE_ID", nil)
75
+ return checked_machine_id(env) if env
76
+
77
+ # Unique-enough per process without coordination: varies by host and by
78
+ # pid, so forked workers each get a different id. % keeps it in range.
79
+ (Socket.gethostname.hash ^ Process.pid) % (MAX_MACHINE_ID + 1)
46
80
  end
47
81
 
48
- def env_machine_id
49
- id = ENV["SNOWFLAKED_MACHINE_ID"] || ENV.fetch("MACHINE_ID", nil)
50
- id&.to_i
82
+ # Coerce to Integer and ensure it fits in 0..MAX_MACHINE_ID, else raise.
83
+ def checked_machine_id(value)
84
+ id = Integer(value, exception: false)
85
+ return id if id&.between?(0, MAX_MACHINE_ID)
86
+
87
+ raise ConfigurationError, "machine_id must be an integer between 0 and #{MAX_MACHINE_ID}, got #{value.inspect}"
51
88
  end
52
89
 
53
- def hostname_pid_hash
54
- (Socket.gethostname.hash ^ Process.pid) % (MAX_MACHINE_ID + 1)
90
+ def raise_if_sealed!(attribute)
91
+ return unless @sealed
92
+
93
+ raise ConfigurationError, "#{attribute} cannot be changed after Snowflaked has been configured"
55
94
  end
56
95
  end
57
96
 
@@ -78,30 +117,39 @@ module Snowflaked
78
117
  end
79
118
 
80
119
  def timestamp(id)
81
- time_ms = Native.timestamp_ms(id)
82
- Time.at(time_ms / 1000, (time_ms % 1000) * 1000, :usec)
120
+ ensure_initialized!
121
+ seconds, milliseconds = Native.timestamp_ms(id).divmod(1000)
122
+
123
+ if defined?(Time.zone) && Time.zone
124
+ Time.zone.at(seconds, milliseconds * 1000, :usec)
125
+ else
126
+ Time.at(seconds, milliseconds * 1000, :usec).utc
127
+ end
83
128
  end
84
129
 
85
- def machine_id(id)
86
- ensure_initialized!
130
+ def machine_id(id) # rubocop:disable Rails/Delegate
87
131
  Native.machine_id(id)
88
132
  end
89
133
 
90
134
  def timestamp_ms(id)
135
+ ensure_initialized!
91
136
  Native.timestamp_ms(id)
92
137
  end
93
138
 
94
- def sequence(id)
139
+ def sequence(id) # rubocop:disable Rails/Delegate
95
140
  Native.sequence(id)
96
141
  end
97
142
 
98
143
  private
99
144
 
100
145
  def ensure_initialized!
101
- return if Native.initialized?
146
+ return if @native_initialized_pid == Process.pid
102
147
 
103
148
  config = configuration
149
+ config.seal!
150
+
104
151
  Native.init_generator(config.machine_id_value, config.epoch_ms)
152
+ @native_initialized_pid = Process.pid
105
153
  end
106
154
  end
107
155
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: snowflaked
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.3.0
5
5
  platform: arm-linux
6
6
  authors:
7
7
  - Luiz Eduardo Kowalski
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-01-14 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2026-07-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: '8.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '8.0'
13
27
  description: A Ruby gem for generating Twitter Snowflake IDs using a high-performance
14
28
  Rust backend. Thread-safe with configurable machine ID and custom epoch support.
15
29
  email:
@@ -20,7 +34,6 @@ files:
20
34
  - LICENSE.txt
21
35
  - README.md
22
36
  - lib/snowflaked.rb
23
- - lib/snowflaked/3.2/snowflaked.so
24
37
  - lib/snowflaked/3.3/snowflaked.so
25
38
  - lib/snowflaked/3.4/snowflaked.so
26
39
  - lib/snowflaked/4.0/snowflaked.so
@@ -47,7 +60,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
47
60
  requirements:
48
61
  - - ">="
49
62
  - !ruby/object:Gem::Version
50
- version: '3.2'
63
+ version: '3.3'
51
64
  - - "<"
52
65
  - !ruby/object:Gem::Version
53
66
  version: 4.1.dev
Binary file