magick-feature-flags 0.9.34 → 0.9.35
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 +4 -4
- data/lib/magick/adapters/active_record.rb +6 -96
- data/lib/magick/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5a2d57b267028e1f3d30a1a5dc4e540d6b254d41ea1b436f5ab0e25278c0f213
|
|
4
|
+
data.tar.gz: 0c1a022445c14e1534b312f4cfc71ec71b9a576d4b66a5d860323a480df79380
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a0576cc8537ef6df5ff4ce95f973994a477936d94cde874b24945505139fbf411798fb54d100544f1848ad1b2388164b64a95421732daa33d753bcef7c14cce5
|
|
7
|
+
data.tar.gz: 803584894d4c2494c324df94613a80a34ca268f522ffabe36bc162e989ca25be8aaa9d1f919f10584e722b47863940a1610c6f7cbc75d6960b2070304e4efe55
|
|
@@ -3,18 +3,17 @@
|
|
|
3
3
|
module Magick
|
|
4
4
|
module Adapters
|
|
5
5
|
class ActiveRecord < Base
|
|
6
|
-
@table_created_mutex = Mutex.new
|
|
7
|
-
@table_created = false
|
|
8
|
-
|
|
9
6
|
def initialize(model_class: nil)
|
|
10
7
|
@model_class = model_class || default_model_class
|
|
11
|
-
|
|
8
|
+
# Verify table exists - raise clear error if it doesn't
|
|
9
|
+
unless @model_class.table_exists?
|
|
10
|
+
raise AdapterError, "Table 'magick_features' does not exist. Please run: rails generate magick:active_record && rails db:migrate"
|
|
11
|
+
end
|
|
12
12
|
rescue StandardError => e
|
|
13
13
|
raise AdapterError, "Failed to initialize ActiveRecord adapter: #{e.message}"
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def get(feature_name, key)
|
|
17
|
-
ensure_table_exists unless @model_class.table_exists?
|
|
18
17
|
feature_name_str = feature_name.to_s
|
|
19
18
|
record = @model_class.find_by(feature_name: feature_name_str)
|
|
20
19
|
return nil unless record
|
|
@@ -24,16 +23,10 @@ module Magick
|
|
|
24
23
|
value = data.is_a?(Hash) ? data[key.to_s] : nil
|
|
25
24
|
deserialize_value(value)
|
|
26
25
|
rescue StandardError => e
|
|
27
|
-
# If table doesn't exist, try to create it and retry once
|
|
28
|
-
if e.message.include?('no such table') || e.message.include?("doesn't exist")
|
|
29
|
-
ensure_table_exists
|
|
30
|
-
retry
|
|
31
|
-
end
|
|
32
26
|
raise AdapterError, "Failed to get from ActiveRecord: #{e.message}"
|
|
33
27
|
end
|
|
34
28
|
|
|
35
29
|
def set(feature_name, key, value)
|
|
36
|
-
ensure_table_exists unless @model_class.table_exists?
|
|
37
30
|
feature_name_str = feature_name.to_s
|
|
38
31
|
retries = 5
|
|
39
32
|
begin
|
|
@@ -48,54 +41,31 @@ module Magick
|
|
|
48
41
|
record.save!
|
|
49
42
|
rescue ::ActiveRecord::StatementInvalid, ::ActiveRecord::ConnectionTimeoutError => e
|
|
50
43
|
# SQLite busy/locked errors - retry with exponential backoff
|
|
51
|
-
if (e.message.include?('database is locked') || e.message.include?('busy') || e.message.include?('timeout')
|
|
44
|
+
if (e.message.include?('database is locked') || e.message.include?('busy') || e.message.include?('timeout')) && retries > 0
|
|
52
45
|
retries -= 1
|
|
53
|
-
# If it's a "no such table" error, ensure table exists
|
|
54
|
-
if e.message.include?('no such table')
|
|
55
|
-
ensure_table_exists
|
|
56
|
-
end
|
|
57
46
|
sleep(0.01 * (6 - retries)) # Exponential backoff: 0.01, 0.02, 0.03, 0.04, 0.05
|
|
58
47
|
retry
|
|
59
48
|
end
|
|
60
49
|
raise AdapterError, "Failed to set in ActiveRecord: #{e.message}"
|
|
61
50
|
rescue StandardError => e
|
|
62
|
-
# If table doesn't exist, try to create it and retry once
|
|
63
|
-
if (e.message.include?('no such table') || e.message.include?("doesn't exist")) && retries > 0
|
|
64
|
-
retries -= 1
|
|
65
|
-
ensure_table_exists
|
|
66
|
-
sleep(0.01)
|
|
67
|
-
retry
|
|
68
|
-
end
|
|
69
51
|
raise AdapterError, "Failed to set in ActiveRecord: #{e.message}"
|
|
70
52
|
end
|
|
71
53
|
end
|
|
72
54
|
|
|
73
55
|
def delete(feature_name)
|
|
74
|
-
ensure_table_exists unless @model_class.table_exists?
|
|
75
56
|
feature_name_str = feature_name.to_s
|
|
76
57
|
retries = 5
|
|
77
58
|
begin
|
|
78
59
|
@model_class.where(feature_name: feature_name_str).destroy_all
|
|
79
60
|
rescue ::ActiveRecord::StatementInvalid, ::ActiveRecord::ConnectionTimeoutError => e
|
|
80
61
|
# SQLite busy/locked errors - retry with exponential backoff
|
|
81
|
-
if (e.message.include?('database is locked') || e.message.include?('busy') || e.message.include?('timeout')
|
|
62
|
+
if (e.message.include?('database is locked') || e.message.include?('busy') || e.message.include?('timeout')) && retries > 0
|
|
82
63
|
retries -= 1
|
|
83
|
-
# If it's a "no such table" error, ensure table exists
|
|
84
|
-
if e.message.include?('no such table')
|
|
85
|
-
ensure_table_exists
|
|
86
|
-
end
|
|
87
64
|
sleep(0.01 * (6 - retries)) # Exponential backoff: 0.01, 0.02, 0.03, 0.04, 0.05
|
|
88
65
|
retry
|
|
89
66
|
end
|
|
90
67
|
raise AdapterError, "Failed to delete from ActiveRecord: #{e.message}"
|
|
91
68
|
rescue StandardError => e
|
|
92
|
-
# If table doesn't exist, try to create it and retry once
|
|
93
|
-
if (e.message.include?('no such table') || e.message.include?("doesn't exist")) && retries > 0
|
|
94
|
-
retries -= 1
|
|
95
|
-
ensure_table_exists
|
|
96
|
-
sleep(0.01)
|
|
97
|
-
retry
|
|
98
|
-
end
|
|
99
69
|
raise AdapterError, "Failed to delete from ActiveRecord: #{e.message}"
|
|
100
70
|
end
|
|
101
71
|
end
|
|
@@ -148,66 +118,6 @@ module Magick
|
|
|
148
118
|
end)
|
|
149
119
|
end
|
|
150
120
|
|
|
151
|
-
def ensure_table_exists
|
|
152
|
-
return if @model_class.table_exists?
|
|
153
|
-
|
|
154
|
-
# Use a non-blocking mutex to prevent deadlocks
|
|
155
|
-
mutex = self.class.instance_variable_get(:@table_created_mutex)
|
|
156
|
-
if mutex.try_lock
|
|
157
|
-
begin
|
|
158
|
-
# Double-check after acquiring lock
|
|
159
|
-
return if @model_class.table_exists?
|
|
160
|
-
|
|
161
|
-
create_table
|
|
162
|
-
self.class.instance_variable_set(:@table_created, true)
|
|
163
|
-
ensure
|
|
164
|
-
mutex.unlock
|
|
165
|
-
end
|
|
166
|
-
else
|
|
167
|
-
# Another thread is creating the table, wait for it to complete
|
|
168
|
-
# Use a longer timeout with exponential backoff to avoid hanging
|
|
169
|
-
20.times do |i|
|
|
170
|
-
sleep(0.01 * (i + 1)) # Exponential backoff: 0.01, 0.02, 0.03, ...
|
|
171
|
-
return if @model_class.table_exists?
|
|
172
|
-
end
|
|
173
|
-
# If we still don't have the table, try one more time
|
|
174
|
-
unless @model_class.table_exists?
|
|
175
|
-
# Last attempt: try to acquire lock and create
|
|
176
|
-
if mutex.try_lock
|
|
177
|
-
begin
|
|
178
|
-
return if @model_class.table_exists?
|
|
179
|
-
create_table
|
|
180
|
-
self.class.instance_variable_set(:@table_created, true)
|
|
181
|
-
ensure
|
|
182
|
-
mutex.unlock
|
|
183
|
-
end
|
|
184
|
-
end
|
|
185
|
-
end
|
|
186
|
-
end
|
|
187
|
-
rescue StandardError => e
|
|
188
|
-
# Don't raise if table exists now (might have been created by another thread)
|
|
189
|
-
return if @model_class.table_exists?
|
|
190
|
-
raise e
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
def create_table
|
|
194
|
-
connection = @model_class.connection
|
|
195
|
-
return if connection.table_exists?('magick_features')
|
|
196
|
-
|
|
197
|
-
connection.create_table :magick_features do |t|
|
|
198
|
-
t.string :feature_name, null: false, index: { unique: true }
|
|
199
|
-
t.text :data
|
|
200
|
-
t.timestamps
|
|
201
|
-
end
|
|
202
|
-
rescue StandardError => e
|
|
203
|
-
# Table might already exist or migration might be needed
|
|
204
|
-
# Check if table exists now (might have been created by another thread)
|
|
205
|
-
return if connection.table_exists?('magick_features')
|
|
206
|
-
|
|
207
|
-
warn "Magick: Could not create magick_features table: #{e.message}" if defined?(Rails) && Rails.env.development?
|
|
208
|
-
raise e if defined?(Rails) && Rails.env.test?
|
|
209
|
-
end
|
|
210
|
-
|
|
211
121
|
def serialize_value(value)
|
|
212
122
|
# For ActiveRecord 8.1+ with attribute :json, we can store booleans as-is
|
|
213
123
|
# For older versions with serialize, we convert to strings
|
data/lib/magick/version.rb
CHANGED