demo_mode 3.5.0 → 3.6.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 +4 -4
- data/lib/demo_mode/clever_sequence/in_memory_backend.rb +51 -0
- data/lib/demo_mode/clever_sequence/lower_bound_finder.rb +42 -6
- data/lib/demo_mode/clever_sequence/postgres_backend.rb +124 -38
- data/lib/demo_mode/clever_sequence.rb +41 -16
- data/lib/demo_mode/config.rb +1 -0
- data/lib/demo_mode/persona.rb +16 -0
- data/lib/demo_mode/version.rb +1 -1
- metadata +7 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5b2e916a3b2b58e6f3cbd027fad72a91b8cb550443a50e64988caa0dbd1ff6fa
|
|
4
|
+
data.tar.gz: ff00e7655ecf6d9e5ae118b84542bf97ec6e568143e4c2961ef42b1c28e0894c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b240952b7f353fff7cb723a409cc07806ffc477d83640968d486d4050f69d37d51777fa93e603f3e41b869226314cffba020fc7df96f640220dd5389591c0895
|
|
7
|
+
data.tar.gz: ee5ebfd9364b612abaa6dbff7b0f6d6f0e3624dbd7383486282c9504e85d4d96ee54c39c76b143f4d4f10cb06f557ee0355595b57ca35faf5384ca951f6d883f
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CleverSequence
|
|
4
|
+
module InMemoryBackend
|
|
5
|
+
class << self
|
|
6
|
+
def nextval(klass, attribute, block)
|
|
7
|
+
key = [klass.name, attribute.to_s]
|
|
8
|
+
sequence_state[key] = current_value(klass, attribute, block, key) + 1
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def reset!
|
|
12
|
+
@sequence_state = {}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def starting_value(klass, attribute, block)
|
|
16
|
+
column_name = resolve_column_name(klass, attribute)
|
|
17
|
+
|
|
18
|
+
if column_exists?(klass, column_name)
|
|
19
|
+
LowerBoundFinder.new(klass, column_name, block).lower_bound
|
|
20
|
+
else
|
|
21
|
+
0
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def with_sequence_adjustment(**)
|
|
26
|
+
# No-op for InMemoryBackend. After reset!, nextval already
|
|
27
|
+
# recalculates from the database via starting_value/LowerBoundFinder,
|
|
28
|
+
# which finds the correct lower bound past existing data.
|
|
29
|
+
yield
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def sequence_state
|
|
33
|
+
@sequence_state ||= {}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def current_value(klass, attribute, block, key)
|
|
39
|
+
sequence_state[key] || starting_value(klass, attribute, block)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def resolve_column_name(klass, attribute)
|
|
43
|
+
klass.attribute_aliases[attribute.to_s] || attribute.to_s
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def column_exists?(klass, column_name)
|
|
47
|
+
klass && klass.column_names.include?(column_name)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -1,19 +1,55 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class CleverSequence
|
|
4
|
-
LowerBoundFinder
|
|
5
|
-
|
|
4
|
+
class LowerBoundFinder
|
|
5
|
+
attr_reader :klass, :column_name, :block
|
|
6
|
+
|
|
7
|
+
def initialize(klass, column_name, block)
|
|
8
|
+
@klass = klass
|
|
9
|
+
@column_name = column_name
|
|
10
|
+
@block = block
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def lower_bound(hint: nil)
|
|
14
|
+
with_lock do
|
|
15
|
+
start = hint && hint >= 1 ? hint : 1
|
|
16
|
+
# If the hint overshoots the actual data, return it directly.
|
|
17
|
+
# The hint is a previously-known high-water mark, so it's a valid
|
|
18
|
+
# lower bound. Callers pass the result through GREATEST against the
|
|
19
|
+
# PG sequence, so a higher value is always safe and avoids a costly
|
|
20
|
+
# binary search back down to data that won't be used anyway.
|
|
21
|
+
next hint if start > 1 && !exists?(start)
|
|
22
|
+
|
|
23
|
+
_lower_bound(start, 0, Float::INFINITY)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def with_lock(&)
|
|
30
|
+
if ActiveRecord::Base.connection.adapter_name.casecmp?('postgresql')
|
|
31
|
+
ActiveRecord::Base.with_transactional_lock("lower-bound-#{klass}-#{column_name}", &)
|
|
32
|
+
else
|
|
33
|
+
yield
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def _lower_bound(current, lower, upper)
|
|
6
38
|
if exists?(current)
|
|
7
|
-
|
|
39
|
+
# When upper is at most current + 1, we know current is the highest
|
|
40
|
+
# existing value (upper is always a known-false or Infinity bound).
|
|
41
|
+
# next_between would return current due to integer division, causing
|
|
42
|
+
# infinite recursion, so return early.
|
|
43
|
+
return current if upper <= current + 1
|
|
44
|
+
|
|
45
|
+
_lower_bound(next_between(current, upper), [current, lower].max, upper)
|
|
8
46
|
elsif current - lower > 1
|
|
9
|
-
|
|
47
|
+
_lower_bound(next_between(lower, current), lower, [current, upper].min)
|
|
10
48
|
else # current should == lower + 1
|
|
11
49
|
lower
|
|
12
50
|
end
|
|
13
51
|
end
|
|
14
52
|
|
|
15
|
-
private
|
|
16
|
-
|
|
17
53
|
def next_between(lower, upper)
|
|
18
54
|
[((lower + 1) / 2) + (upper / 2), lower * 2].min
|
|
19
55
|
end
|
|
@@ -24,35 +24,35 @@ class CleverSequence
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
class << self
|
|
27
|
+
def reset!
|
|
28
|
+
Thread.current[:clever_sequence_cache] = {}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def starting_value(klass, attribute, block)
|
|
32
|
+
calculate_sequence_value(klass, attribute, block)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def with_sequence_adjustment(last_values: {})
|
|
36
|
+
previous = Thread.current[:clever_sequence_adjustment_enabled]
|
|
37
|
+
previous_last_values = Thread.current[:clever_sequence_last_values]
|
|
38
|
+
log "[DemoMode] Enabling sequence adjustment for retry"
|
|
39
|
+
Thread.current[:clever_sequence_adjustment_enabled] = true
|
|
40
|
+
Thread.current[:clever_sequence_last_values] = last_values
|
|
41
|
+
yield
|
|
42
|
+
ensure
|
|
43
|
+
Thread.current[:clever_sequence_adjustment_enabled] = previous
|
|
44
|
+
Thread.current[:clever_sequence_last_values] = previous_last_values
|
|
45
|
+
log "[DemoMode] Disabled sequence adjustment"
|
|
46
|
+
end
|
|
47
|
+
|
|
27
48
|
def nextval(klass, attribute, block)
|
|
28
49
|
name = sequence_name(klass, attribute)
|
|
50
|
+
log "[DemoMode] nextval called for #{klass.name}##{attribute} (sequence: #{name})"
|
|
29
51
|
|
|
30
52
|
if sequence_exists?(name)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
result = ActiveRecord::Base.connection.execute(
|
|
34
|
-
"SELECT nextval('#{name}')",
|
|
35
|
-
)
|
|
36
|
-
result.first['nextval'].to_i
|
|
53
|
+
nextval_from_sequence(name, klass, attribute, block)
|
|
37
54
|
else
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
sequence_cache[name] = SequenceResult::Missing.new(
|
|
41
|
-
sequence_name: name,
|
|
42
|
-
klass: klass,
|
|
43
|
-
attribute: attribute,
|
|
44
|
-
calculated_start_value: start_value + 1,
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
if CleverSequence.enforce_sequences_exist
|
|
48
|
-
raise SequenceNotFoundError.new(
|
|
49
|
-
sequence_name: name,
|
|
50
|
-
klass: klass,
|
|
51
|
-
attribute: attribute,
|
|
52
|
-
)
|
|
53
|
-
else
|
|
54
|
-
start_value + 1
|
|
55
|
-
end
|
|
55
|
+
nextval_without_sequence(name, klass, attribute, block)
|
|
56
56
|
end
|
|
57
57
|
end
|
|
58
58
|
|
|
@@ -61,36 +61,122 @@ class CleverSequence
|
|
|
61
61
|
attr = attribute.to_s.gsub(/[^a-z0-9_]/i, '_')
|
|
62
62
|
# Handle PostgreSQL identifier limit:
|
|
63
63
|
limit = (63 - SEQUENCE_PREFIX.length) / 2
|
|
64
|
-
|
|
64
|
+
# Lowercase to avoid PostgreSQL case-sensitivity issues with unquoted identifiers
|
|
65
|
+
"#{SEQUENCE_PREFIX}#{table[0, limit]}_#{attr[0, limit]}".downcase
|
|
65
66
|
end
|
|
66
67
|
|
|
67
68
|
def sequence_cache
|
|
68
|
-
|
|
69
|
+
Thread.current[:clever_sequence_cache] ||= {}
|
|
69
70
|
end
|
|
70
71
|
|
|
71
72
|
private
|
|
72
73
|
|
|
74
|
+
def log(message, level: DemoMode.log_level)
|
|
75
|
+
Rails.logger.public_send(level, message)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def nextval_from_sequence(name, klass, attribute, block)
|
|
79
|
+
# On first use with adjustment enabled, ensure sequence is past existing data
|
|
80
|
+
if adjust_sequences_enabled? && !sequence_cache[name].is_a?(SequenceResult::Exists)
|
|
81
|
+
log "[DemoMode] Sequence adjustment enabled, adjusting #{name}"
|
|
82
|
+
adjust_sequence_if_needed(name, klass, attribute, block)
|
|
83
|
+
end
|
|
84
|
+
sequence_cache[name] = SequenceResult::Exists.new(name)
|
|
85
|
+
|
|
86
|
+
result = ActiveRecord::Base.connection.execute(
|
|
87
|
+
"SELECT nextval('#{name}')",
|
|
88
|
+
)
|
|
89
|
+
value = result.first['nextval'].to_i
|
|
90
|
+
log "[DemoMode] nextval for #{klass.name}##{attribute} returned #{value}"
|
|
91
|
+
value
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def nextval_without_sequence(name, klass, attribute, block)
|
|
95
|
+
next_value = calculate_next_missing_value(name, klass, attribute, block)
|
|
96
|
+
|
|
97
|
+
if CleverSequence.enforce_sequences_exist
|
|
98
|
+
log "[DemoMode] Raising SequenceNotFoundError for #{name}", level: :warn
|
|
99
|
+
raise SequenceNotFoundError.new(
|
|
100
|
+
sequence_name: name, klass: klass, attribute: attribute,
|
|
101
|
+
)
|
|
102
|
+
else
|
|
103
|
+
log "[DemoMode] nextval returning #{next_value} (fallback, #{name} missing)"
|
|
104
|
+
next_value
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def calculate_next_missing_value(name, klass, attribute, block)
|
|
109
|
+
cached = sequence_cache[name]
|
|
110
|
+
|
|
111
|
+
next_value = if cached.is_a?(SequenceResult::Missing)
|
|
112
|
+
cached.calculated_start_value + 1
|
|
113
|
+
else
|
|
114
|
+
calculate_sequence_value(klass, attribute, block) + 1
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
sequence_cache[name] = SequenceResult::Missing.new(
|
|
118
|
+
sequence_name: name, klass: klass,
|
|
119
|
+
attribute: attribute, calculated_start_value: next_value
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
next_value
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def adjust_sequences_enabled?
|
|
126
|
+
Thread.current[:clever_sequence_adjustment_enabled]
|
|
127
|
+
end
|
|
128
|
+
|
|
73
129
|
def sequence_exists?(sequence_name)
|
|
74
130
|
if sequence_cache.key?(sequence_name)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
else
|
|
79
|
-
return false
|
|
80
|
-
end
|
|
131
|
+
exists = sequence_cache[sequence_name].is_a?(SequenceResult::Exists)
|
|
132
|
+
log "[DemoMode] Sequence #{sequence_name} #{exists ? 'exists' : 'missing'} (cached)"
|
|
133
|
+
return exists
|
|
81
134
|
end
|
|
82
135
|
|
|
83
|
-
ActiveRecord::Base.connection.execute(
|
|
84
|
-
"SELECT 1 FROM information_schema.sequences
|
|
136
|
+
exists = ActiveRecord::Base.connection.execute(
|
|
137
|
+
"SELECT 1 FROM information_schema.sequences " \
|
|
138
|
+
"WHERE sequence_name = '#{sequence_name}' LIMIT 1",
|
|
85
139
|
).any?
|
|
140
|
+
log "[DemoMode] Sequence #{sequence_name} #{exists ? 'found' : 'not found'}"
|
|
141
|
+
exists
|
|
86
142
|
end
|
|
87
143
|
|
|
88
|
-
def calculate_sequence_value(klass, attribute, block)
|
|
144
|
+
def calculate_sequence_value(klass, attribute, block, hint: nil)
|
|
89
145
|
column_name = klass.attribute_aliases.fetch(attribute.to_s, attribute.to_s)
|
|
90
|
-
|
|
146
|
+
unless klass.column_names.include?(column_name)
|
|
147
|
+
log "[DemoMode] Column #{column_name} not found on #{klass.name}", level: :warn
|
|
148
|
+
return 0
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
value = LowerBoundFinder.new(klass, column_name, block).lower_bound(hint:)
|
|
152
|
+
log "[DemoMode] Calculated sequence value for #{klass.name}##{attribute}: #{value} (hint: #{hint || 'none'})"
|
|
153
|
+
value
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def hint_for(klass, attribute)
|
|
157
|
+
last_values = Thread.current[:clever_sequence_last_values]
|
|
158
|
+
last_values && last_values[[klass.name, attribute.to_s]]
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def adjust_sequence_if_needed(sequence_name, klass, attribute, block)
|
|
162
|
+
ActiveRecord::Base.with_transactional_lock("adjust-sequence-#{sequence_name}") do
|
|
163
|
+
hint = hint_for(klass, attribute)
|
|
164
|
+
max_value = calculate_sequence_value(klass, attribute, block, hint:)
|
|
165
|
+
if max_value < 1
|
|
166
|
+
log "[DemoMode] No adjustment needed for #{sequence_name}"
|
|
167
|
+
return
|
|
168
|
+
end
|
|
91
169
|
|
|
92
|
-
|
|
93
|
-
|
|
170
|
+
log "[DemoMode] Adjusting #{sequence_name} to at least #{max_value}"
|
|
171
|
+
# setval sets the sequence's last_value. With the default 3rd argument (true),
|
|
172
|
+
# the next nextval() will return last_value + 1.
|
|
173
|
+
# We only want to advance (never go backwards), so we use GREATEST.
|
|
174
|
+
result = ActiveRecord::Base.connection.execute(<<~SQL.squish)
|
|
175
|
+
SELECT setval('#{sequence_name}',
|
|
176
|
+
GREATEST(#{max_value}, (SELECT last_value FROM #{sequence_name})))
|
|
177
|
+
SQL
|
|
178
|
+
new_last_value = result.first['setval'].to_i
|
|
179
|
+
log "[DemoMode] #{sequence_name} adjusted to #{new_last_value}"
|
|
94
180
|
end
|
|
95
181
|
end
|
|
96
182
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative 'clever_sequence/lower_bound_finder'
|
|
4
|
+
require_relative 'clever_sequence/in_memory_backend'
|
|
4
5
|
require_relative 'clever_sequence/postgres_backend'
|
|
5
6
|
|
|
6
7
|
class CleverSequence
|
|
@@ -9,15 +10,32 @@ class CleverSequence
|
|
|
9
10
|
cattr_accessor(:sequences) { {} }
|
|
10
11
|
cattr_accessor(:use_database_sequences) { false }
|
|
11
12
|
cattr_accessor(:enforce_sequences_exist) { false }
|
|
13
|
+
cattr_accessor(:retry_on_uniqueness_violation) { true }
|
|
12
14
|
|
|
13
15
|
class << self
|
|
14
16
|
alias use_database_sequences? use_database_sequences
|
|
15
17
|
alias enforce_sequences_exist? enforce_sequences_exist
|
|
18
|
+
alias retry_on_uniqueness_violation? retry_on_uniqueness_violation
|
|
19
|
+
|
|
20
|
+
def backend
|
|
21
|
+
use_database_sequences? ? PostgresBackend : InMemoryBackend
|
|
22
|
+
end
|
|
16
23
|
|
|
17
24
|
def reset!
|
|
25
|
+
backend.reset!
|
|
18
26
|
sequences.each_value(&:reset!)
|
|
19
27
|
end
|
|
20
28
|
|
|
29
|
+
def with_sequence_adjustment(&)
|
|
30
|
+
last_values = snapshot_last_values
|
|
31
|
+
reset!
|
|
32
|
+
backend.with_sequence_adjustment(last_values:, &)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def snapshot_last_values
|
|
36
|
+
sequences.transform_values { |seq| seq.send(:last_value) }.compact
|
|
37
|
+
end
|
|
38
|
+
|
|
21
39
|
def next(klass, name)
|
|
22
40
|
lookup(klass, name)&.next
|
|
23
41
|
end
|
|
@@ -47,41 +65,48 @@ class CleverSequence
|
|
|
47
65
|
end
|
|
48
66
|
|
|
49
67
|
def next
|
|
50
|
-
|
|
51
|
-
|
|
68
|
+
value = if klass
|
|
69
|
+
self.class.backend.nextval(klass, attribute, block)
|
|
52
70
|
else
|
|
53
|
-
last_value + 1
|
|
71
|
+
(last_value || 0) + 1
|
|
54
72
|
end
|
|
73
|
+
self.last_value = value
|
|
55
74
|
last
|
|
56
75
|
end
|
|
57
76
|
|
|
58
77
|
def last
|
|
59
|
-
block.call(last_value)
|
|
78
|
+
block.call(last_value || (klass ? self.class.backend.starting_value(klass, attribute, block) : 0))
|
|
60
79
|
end
|
|
61
80
|
|
|
62
81
|
def reset!
|
|
63
|
-
|
|
82
|
+
clear_last_value
|
|
64
83
|
end
|
|
65
84
|
|
|
66
85
|
private
|
|
67
86
|
|
|
68
87
|
def last_value
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def starting_value
|
|
73
|
-
if column_exists?
|
|
74
|
-
LowerBoundFinder.new(klass, column_name, block).lower_bound
|
|
88
|
+
if klass
|
|
89
|
+
Thread.current[:clever_sequence_last_value]&.dig(klass.name, attribute)
|
|
75
90
|
else
|
|
76
|
-
|
|
91
|
+
@last_value
|
|
77
92
|
end
|
|
78
93
|
end
|
|
79
94
|
|
|
80
|
-
def
|
|
81
|
-
klass
|
|
95
|
+
def last_value=(value)
|
|
96
|
+
if klass
|
|
97
|
+
Thread.current[:clever_sequence_last_value] ||= {}
|
|
98
|
+
Thread.current[:clever_sequence_last_value][klass.name] ||= {}
|
|
99
|
+
Thread.current[:clever_sequence_last_value][klass.name][attribute] = value
|
|
100
|
+
else
|
|
101
|
+
@last_value = value
|
|
102
|
+
end
|
|
82
103
|
end
|
|
83
104
|
|
|
84
|
-
def
|
|
85
|
-
|
|
105
|
+
def clear_last_value
|
|
106
|
+
if klass
|
|
107
|
+
Thread.current[:clever_sequence_last_value]&.[](klass.name)&.delete(attribute)
|
|
108
|
+
else
|
|
109
|
+
@last_value = nil
|
|
110
|
+
end
|
|
86
111
|
end
|
|
87
112
|
end
|
data/lib/demo_mode/config.rb
CHANGED
|
@@ -13,6 +13,7 @@ module DemoMode
|
|
|
13
13
|
configurable_value(:signinable_username_method) { :email }
|
|
14
14
|
configurable_value(:personas_path) { 'config/personas' }
|
|
15
15
|
configurable_value(:session_timeout) { 30.minutes }
|
|
16
|
+
configurable_value(:log_level) { :debug }
|
|
16
17
|
configurable_boolean(:display_credentials)
|
|
17
18
|
configurations << :stylesheets
|
|
18
19
|
configurations << :logo
|
data/lib/demo_mode/persona.rb
CHANGED
|
@@ -63,16 +63,32 @@ module DemoMode
|
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
def generate!(variant: :default, password: nil, options: {})
|
|
66
|
+
retried = false
|
|
66
67
|
ActiveSupport::Notifications.instrument('demo_mode.persona.generate', name: name, variant: variant) do
|
|
67
68
|
variant = variants[variant]
|
|
68
69
|
CleverSequence.reset! if defined?(CleverSequence)
|
|
69
70
|
DemoMode.current_password = password if password
|
|
70
71
|
DemoMode.around_persona_generation.call(variant.signinable_generator, **options)
|
|
72
|
+
rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid => e
|
|
73
|
+
raise if retried || !should_retry_with_sequence_adjustment?(e)
|
|
74
|
+
|
|
75
|
+
retried = true
|
|
76
|
+
CleverSequence.with_sequence_adjustment do
|
|
77
|
+
DemoMode.around_persona_generation.call(variant.signinable_generator, **options)
|
|
78
|
+
end
|
|
71
79
|
ensure
|
|
72
80
|
DemoMode.current_password = nil
|
|
73
81
|
end
|
|
74
82
|
end
|
|
75
83
|
|
|
84
|
+
def should_retry_with_sequence_adjustment?(error)
|
|
85
|
+
return false unless defined?(CleverSequence)
|
|
86
|
+
return false unless CleverSequence.retry_on_uniqueness_violation?
|
|
87
|
+
|
|
88
|
+
Rails.logger.warn("[DemoMode] Uniqueness violation during persona generation, retrying with sequence adjustment: #{error.message}")
|
|
89
|
+
true
|
|
90
|
+
end
|
|
91
|
+
|
|
76
92
|
def callout(callout = true) # rubocop:disable Style/OptionalBooleanParameter
|
|
77
93
|
@callout = callout
|
|
78
94
|
end
|
data/lib/demo_mode/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: demo_mode
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nathan Griffith
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2026-03-20 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: actionpack
|
|
@@ -338,6 +339,7 @@ files:
|
|
|
338
339
|
- db/migrate/20250210222933_add_demo_mode_sessions_status.rb
|
|
339
340
|
- lib/demo_mode.rb
|
|
340
341
|
- lib/demo_mode/clever_sequence.rb
|
|
342
|
+
- lib/demo_mode/clever_sequence/in_memory_backend.rb
|
|
341
343
|
- lib/demo_mode/clever_sequence/lower_bound_finder.rb
|
|
342
344
|
- lib/demo_mode/clever_sequence/postgres_backend.rb
|
|
343
345
|
- lib/demo_mode/cli.rb
|
|
@@ -366,6 +368,7 @@ licenses:
|
|
|
366
368
|
metadata:
|
|
367
369
|
rubygems_mfa_required: 'true'
|
|
368
370
|
changelog_uri: https://github.com/Betterment/demo_mode/blob/main/CHANGELOG.md
|
|
371
|
+
post_install_message:
|
|
369
372
|
rdoc_options: []
|
|
370
373
|
require_paths:
|
|
371
374
|
- lib
|
|
@@ -380,7 +383,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
380
383
|
- !ruby/object:Gem::Version
|
|
381
384
|
version: '0'
|
|
382
385
|
requirements: []
|
|
383
|
-
rubygems_version: 4.
|
|
386
|
+
rubygems_version: 3.4.10
|
|
387
|
+
signing_key:
|
|
384
388
|
specification_version: 4
|
|
385
389
|
summary: A configurable demo mode for your Rails app.
|
|
386
390
|
test_files: []
|