dials 0.3.0 → 0.4.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/CHANGELOG.md +57 -0
- data/README.md +15 -0
- data/lib/dials/active_record/models.rb +23 -13
- data/lib/dials/active_record/store.rb +37 -28
- data/lib/dials/actor.rb +5 -5
- data/lib/dials/config.rb +84 -39
- data/lib/dials/errors.rb +18 -0
- data/lib/dials/generated.rb +22 -19
- data/lib/dials/namespace.rb +402 -0
- data/lib/dials/registry.rb +14 -12
- data/lib/dials/storage.rb +153 -0
- data/lib/dials/testing.rb +25 -13
- data/lib/dials/version.rb +1 -1
- data/lib/dials.rb +133 -201
- data/lib/generators/dials/install/install_generator.rb +1 -1
- data/lib/generators/dials/install/templates/initializer.rb.tt +2 -2
- metadata +3 -1
data/lib/dials.rb
CHANGED
|
@@ -19,7 +19,9 @@ require_relative "dials/cache"
|
|
|
19
19
|
require_relative "dials/change_record"
|
|
20
20
|
require_relative "dials/actor"
|
|
21
21
|
require_relative "dials/stores/memory"
|
|
22
|
+
require_relative "dials/storage"
|
|
22
23
|
require_relative "dials/config"
|
|
24
|
+
require_relative "dials/namespace"
|
|
23
25
|
require_relative "dials/testing"
|
|
24
26
|
|
|
25
27
|
# Dials: operator-adjustable values with per-scope overrides.
|
|
@@ -42,15 +44,19 @@ require_relative "dials/testing"
|
|
|
42
44
|
# The key-taking primitives (get, set, clear) stay public underneath — they
|
|
43
45
|
# are the dynamic-access layer for code that receives the key at runtime
|
|
44
46
|
# (an admin surface iterating the registry, a console one-liner).
|
|
47
|
+
#
|
|
48
|
+
# Every method here belongs to the DEFAULT namespace (see Namespace): the
|
|
49
|
+
# app's own dials, in the app's own table. A subsystem that owns its
|
|
50
|
+
# settings end to end declares a namespace of its own instead:
|
|
51
|
+
#
|
|
52
|
+
# Shipping = Dials.namespace(:shipping) { |config| config.store = :active_record }
|
|
53
|
+
#
|
|
54
|
+
# and gets the same API on that object, against a table of its own.
|
|
45
55
|
module Dials
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
|
|
50
|
-
# other threads would read it, and where it would survive a rollback).
|
|
51
|
-
TXN_WRITE_KEY = :dials_wrote_in_open_transaction
|
|
52
|
-
|
|
53
|
-
CACHE_LOCK = Mutex.new
|
|
56
|
+
# Guards the check-then-set in `namespace`, so two engine initializers
|
|
57
|
+
# declaring at once cannot both win. Declarations only; a fetch reads the
|
|
58
|
+
# table without it.
|
|
59
|
+
NAMESPACE_LOCK = Mutex.new
|
|
54
60
|
|
|
55
61
|
# The stale-write token of an override that is not stored. Pass it as
|
|
56
62
|
# `expected_version:` to assert "there was no override here when I looked"
|
|
@@ -58,9 +64,85 @@ module Dials
|
|
|
58
64
|
ABSENT_VERSION = StoreVersion::ABSENT
|
|
59
65
|
|
|
60
66
|
class << self
|
|
61
|
-
# --
|
|
67
|
+
# -- namespaces ----------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
# The root namespace, the one every method on this module delegates to.
|
|
70
|
+
attr_reader :default
|
|
71
|
+
|
|
72
|
+
# Every namespace, root first, then registration order — what an admin
|
|
73
|
+
# surface iterates to group dials by subsystem without naming one.
|
|
74
|
+
def namespaces
|
|
75
|
+
@namespaces.values
|
|
76
|
+
end
|
|
62
77
|
|
|
63
|
-
|
|
78
|
+
# Declare a namespace (with a block or a label), or fetch one by name:
|
|
79
|
+
#
|
|
80
|
+
# Shipping = Dials.namespace(:shipping, label: "Shipping") do |config|
|
|
81
|
+
# config.store = :active_record # table: "shipping_dials"
|
|
82
|
+
# end
|
|
83
|
+
#
|
|
84
|
+
# Dials.namespace(:shipping) # the same object, later
|
|
85
|
+
#
|
|
86
|
+
# Options the block leaves alone inherit the root's config. Declaring a
|
|
87
|
+
# name twice raises DuplicateNamespace; fetching one that was never
|
|
88
|
+
# declared raises UnknownNamespace.
|
|
89
|
+
def namespace(name, label: nil, &block)
|
|
90
|
+
key = name.to_sym
|
|
91
|
+
return fetch_namespace(key, name) if label.nil? && block.nil?
|
|
92
|
+
|
|
93
|
+
NAMESPACE_LOCK.synchronize { assert_undeclared!(key) }
|
|
94
|
+
namespace = Namespace.new(key, label: label, parent: default)
|
|
95
|
+
|
|
96
|
+
# The block runs application code (it can build a store, touch
|
|
97
|
+
# ActiveRecord, even declare dials), so it runs outside the lock — and
|
|
98
|
+
# before the namespace is published, so no other thread can reach one
|
|
99
|
+
# that is still on the inherited store.
|
|
100
|
+
namespace.configure(&block) if block
|
|
101
|
+
NAMESPACE_LOCK.synchronize do
|
|
102
|
+
assert_undeclared!(key)
|
|
103
|
+
assert_unclaimed_table!(namespace, namespace.config.table_name)
|
|
104
|
+
@namespaces[key] = namespace
|
|
105
|
+
end
|
|
106
|
+
namespace
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Internal, called by Storage when a declared namespace is renamed. A
|
|
110
|
+
# namespace owns its table outright: two namespaces on one would
|
|
111
|
+
# interleave their keys, history and stale-write sequences with nothing
|
|
112
|
+
# to tell them apart again.
|
|
113
|
+
def assert_table_unclaimed!(namespace, table_name)
|
|
114
|
+
NAMESPACE_LOCK.synchronize { assert_unclaimed_table!(namespace, table_name) }
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Test hook: discard every namespace but the root, and with them their
|
|
118
|
+
# registries and generated methods. A suite that declares namespaces
|
|
119
|
+
# needs a blank slate per example.
|
|
120
|
+
def reset_namespaces!
|
|
121
|
+
discarded = NAMESPACE_LOCK.synchronize do
|
|
122
|
+
dropped = @namespaces.except(Namespace::ROOT_NAME).values
|
|
123
|
+
@namespaces = { Namespace::ROOT_NAME => @default }
|
|
124
|
+
dropped
|
|
125
|
+
end
|
|
126
|
+
@default.forget_children!
|
|
127
|
+
discarded.each { |namespace| Thread.current[namespace.txn_write_key] = nil }
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Force every namespace's next read to rebuild from its store, and clear
|
|
131
|
+
# every in-transaction-write marker — one call for a test suite that
|
|
132
|
+
# wraps examples in transactions.
|
|
133
|
+
def reload_all!
|
|
134
|
+
namespaces.each(&:reload!)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# -- the default namespace -----------------------------------------------
|
|
138
|
+
|
|
139
|
+
def registry
|
|
140
|
+
default.registry
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def config
|
|
144
|
+
default.config
|
|
145
|
+
end
|
|
64
146
|
|
|
65
147
|
# Declare dials:
|
|
66
148
|
#
|
|
@@ -75,245 +157,95 @@ module Dials
|
|
|
75
157
|
# reader), adjust_merchant_fee_bps, clear_merchant_fee_bps (see
|
|
76
158
|
# Generated).
|
|
77
159
|
def define(&)
|
|
78
|
-
|
|
160
|
+
default.define(&)
|
|
79
161
|
end
|
|
80
162
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def configure
|
|
84
|
-
yield config
|
|
163
|
+
def configure(&)
|
|
164
|
+
default.configure(&)
|
|
85
165
|
end
|
|
86
166
|
|
|
87
167
|
def store
|
|
88
|
-
|
|
168
|
+
default.store
|
|
89
169
|
end
|
|
90
170
|
|
|
91
171
|
def cache
|
|
92
|
-
|
|
172
|
+
default.cache
|
|
93
173
|
end
|
|
94
174
|
|
|
95
|
-
# Discard the cache object entirely (used when the store is swapped).
|
|
96
175
|
def reset_cache!
|
|
97
|
-
|
|
176
|
+
default.reset_cache!
|
|
98
177
|
end
|
|
99
178
|
|
|
100
|
-
# Force the next read to rebuild from the store — e.g. after writing
|
|
101
|
-
# through a console in another process, or in a test. Also clears this
|
|
102
|
-
# thread's in-transaction-write marker (test suites that wrap examples
|
|
103
|
-
# in transactions call this between examples).
|
|
104
179
|
def reload!
|
|
105
|
-
|
|
106
|
-
cache.bust!
|
|
180
|
+
default.reload!
|
|
107
181
|
end
|
|
108
182
|
|
|
109
|
-
# -- reads ---------------------------------------------------------------
|
|
110
|
-
|
|
111
|
-
# Resolve a dial by key — the primitive under the generated readers,
|
|
112
|
-
# for callers that receive the key at runtime. Scope is passed
|
|
113
|
-
# as keyword arguments and must name every dimension the dial declares —
|
|
114
|
-
# no more, no less:
|
|
115
|
-
#
|
|
116
|
-
# Dials.get(:signups_enabled) # global-only dial
|
|
117
|
-
# Dials.get(:merchant_fee_bps, market: "KE") # varied dial
|
|
118
|
-
#
|
|
119
|
-
# Raises UnknownDial / InvalidScope on misuse; never raises for a merely
|
|
120
|
-
# missing override (that is what defaults are for).
|
|
121
183
|
def get(key, **scope)
|
|
122
|
-
|
|
123
|
-
normalized = Scope.validate!(definition, scope, exact: true)
|
|
124
|
-
|
|
125
|
-
# After scope validation, so a test override can never mask a read that
|
|
126
|
-
# would raise in production.
|
|
127
|
-
pinned = Testing.override_for(definition.key)
|
|
128
|
-
return pinned.first if pinned
|
|
129
|
-
|
|
130
|
-
Resolver.resolve(definition, normalized, current_snapshot)
|
|
184
|
+
default.get(key, **scope)
|
|
131
185
|
end
|
|
132
186
|
|
|
133
|
-
# Read a dial's Global layer by key: the stored global override when
|
|
134
|
-
# present, else the code default — the tail every un-overridden scope
|
|
135
|
-
# falls through to. This is the front door for the caller that has NO
|
|
136
|
-
# scope to give — resolving a value for a subject whose dimension is
|
|
137
|
-
# unknowable (a recipient with no resolvable market) — not a way around
|
|
138
|
-
# exact-scope reads: a caller that knows its scope must still pass it
|
|
139
|
-
# to get, which raises InvalidScope precisely so a lazy read cannot
|
|
140
|
-
# skip a scoped override. For a dial with no dimensions this is
|
|
141
|
-
# equivalent to get. Raises UnknownDial; honors Testing pins.
|
|
142
187
|
def global(key)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
pinned = Testing.override_for(definition.key)
|
|
146
|
-
return pinned.first if pinned
|
|
147
|
-
|
|
148
|
-
# The empty scope matches no stored scoped override, so Resolver
|
|
149
|
-
# takes exactly the global-override → code-default tail.
|
|
150
|
-
Resolver.resolve(definition, {}, current_snapshot)
|
|
188
|
+
default.global(key)
|
|
151
189
|
end
|
|
152
190
|
|
|
153
|
-
# One dial's stored scoped overrides as { parsed scope => value }, e.g.
|
|
154
|
-
# { { market: "BD" } => 24, { market: "NG" } => 48 } — "which markets
|
|
155
|
-
# override this dial?". Scopes come back as parsed hashes, never
|
|
156
|
-
# canonical scope strings. A dial with nothing scoped stored (or no
|
|
157
|
-
# dimensions at all) returns {}; the global override is not included
|
|
158
|
-
# (see overview). Reads from the same snapshot path as the generated
|
|
159
|
-
# readers, including the in-transaction rule. The result is deep-frozen —
|
|
160
|
-
# it shares structure with the process-wide snapshot.
|
|
161
191
|
def scoped_overrides(key)
|
|
162
|
-
|
|
163
|
-
parsed_scoped_overrides(current_snapshot, definition.key)
|
|
192
|
+
default.scoped_overrides(key)
|
|
164
193
|
end
|
|
165
194
|
|
|
166
|
-
# Every registered dial's full state — definition (with its JSON Schema),
|
|
167
|
-
# global override (explicitly present-or-absent), scoped overrides, and
|
|
168
|
-
# the per-override stale-write tokens — read from ONE snapshot, so the
|
|
169
|
-
# picture is coherent. Feed an override's token back as
|
|
170
|
-
# `expected_version:` when writing it (Dials::ABSENT_VERSION for
|
|
171
|
-
# overrides the page showed as not stored).
|
|
172
195
|
def overview
|
|
173
|
-
|
|
174
|
-
dials = registry.map do |definition|
|
|
175
|
-
stamps = snapshot.row_versions[definition.key] || {}
|
|
176
|
-
DialState.new(
|
|
177
|
-
definition: definition,
|
|
178
|
-
global_override: snapshot.globals.key?(definition.key),
|
|
179
|
-
global_value: snapshot.globals[definition.key],
|
|
180
|
-
global_version: StoreVersion.token(stamps[Scope::GLOBAL] || 0),
|
|
181
|
-
scoped_overrides: parsed_scoped_overrides(snapshot, definition.key),
|
|
182
|
-
scoped_override_versions: parsed_versions(snapshot, definition.key)
|
|
183
|
-
)
|
|
184
|
-
end.freeze
|
|
185
|
-
Overview.new(version: StoreVersion.token(snapshot.version), dials: dials)
|
|
196
|
+
default.overview
|
|
186
197
|
end
|
|
187
198
|
|
|
188
|
-
# The full change log, newest first. `key:` filters to one dial.
|
|
189
199
|
def changes(key: nil, limit: 50)
|
|
190
|
-
|
|
191
|
-
store.changes(key: key, limit: limit)
|
|
200
|
+
default.changes(key: key, limit: limit)
|
|
192
201
|
end
|
|
193
202
|
|
|
194
|
-
# -- writes --------------------------------------------------------------
|
|
195
|
-
|
|
196
|
-
# Store an override by key — the primitive under the generated
|
|
197
|
-
# adjust_<key> methods. With no scope, overrides the global; with a
|
|
198
|
-
# scope, creates or updates the override for exactly that scope. The
|
|
199
|
-
# value is validated against the dial's type and schema; `actor:` is
|
|
200
|
-
# required and lands in the change log.
|
|
201
|
-
#
|
|
202
|
-
# `expected_version:` makes the write compare-and-swap against THIS
|
|
203
|
-
# override (the global when no scope keywords, the named scoped override
|
|
204
|
-
# otherwise): pass the override's token from Dials.overview (or a
|
|
205
|
-
# previous CAS write; Dials::ABSENT_VERSION when the page showed no
|
|
206
|
-
# override) and the write is refused with StaleWrite — unapplied,
|
|
207
|
-
# unlogged — if that override has changed since. A CAS write returns the
|
|
208
|
-
# override's NEW token (chain it into the next write); an unconditional
|
|
209
|
-
# write returns the value, as always.
|
|
210
203
|
def set(key, value, actor:, scope: nil, expected_version: nil)
|
|
211
|
-
|
|
212
|
-
actor_attrs = Actor.normalize(actor)
|
|
213
|
-
definition.validate_value!(value)
|
|
214
|
-
|
|
215
|
-
if scope.nil? || scope.empty?
|
|
216
|
-
canonical = Scope::GLOBAL
|
|
217
|
-
else
|
|
218
|
-
raise InvalidScope, "dial #{definition.key} declares no dimensions" unless definition.dimensions?
|
|
219
|
-
|
|
220
|
-
normalized = Scope.validate!(definition, scope, exact: true)
|
|
221
|
-
canonical = Scope.canonical(normalized)
|
|
222
|
-
end
|
|
223
|
-
_old, written = store.set_override(definition.key, canonical, value, actor_attrs,
|
|
224
|
-
expected_version: expected_version)
|
|
225
|
-
|
|
226
|
-
after_write
|
|
227
|
-
# The token comes from the write we KNOW happened — never from a
|
|
228
|
-
# second read a concurrent writer could slip in front of.
|
|
229
|
-
expected_version ? StoreVersion.token(written) : value
|
|
204
|
+
default.set(key, value, actor: actor, scope: scope, expected_version: expected_version)
|
|
230
205
|
end
|
|
231
206
|
|
|
232
|
-
# Remove an override by key — the primitive under the generated
|
|
233
|
-
# clear_<key> methods — returning resolution to the next layer down: a
|
|
234
|
-
# cleared scoped override inherits the global; a cleared global inherits the
|
|
235
|
-
# code default. Returns true if an override existed. Clearing what is not
|
|
236
|
-
# there is a no-op (and logs nothing).
|
|
237
|
-
#
|
|
238
|
-
# `expected_version:` works exactly as on set — the staleness check runs
|
|
239
|
-
# even when the clear would be a no-op (a page that shows an override
|
|
240
|
-
# which no longer exists IS stale), and a CAS clear returns the
|
|
241
|
-
# tombstone's token instead of the boolean (chainable: a later set
|
|
242
|
-
# carrying it succeeds; an "absent" assertion from an older page does
|
|
243
|
-
# not — cleared is not the same as never-written).
|
|
244
207
|
def clear(key, actor:, scope: nil, expected_version: nil)
|
|
245
|
-
|
|
246
|
-
actor_attrs = Actor.normalize(actor)
|
|
247
|
-
|
|
248
|
-
if scope.nil? || scope.empty?
|
|
249
|
-
canonical = Scope::GLOBAL
|
|
250
|
-
else
|
|
251
|
-
normalized = Scope.validate!(definition, scope, exact: true)
|
|
252
|
-
canonical = Scope.canonical(normalized)
|
|
253
|
-
end
|
|
254
|
-
removed, written = store.clear_override(definition.key, canonical, actor_attrs,
|
|
255
|
-
expected_version: expected_version)
|
|
256
|
-
|
|
257
|
-
after_write
|
|
258
|
-
expected_version ? StoreVersion.token(written) : removed
|
|
208
|
+
default.clear(key, actor: actor, scope: scope, expected_version: expected_version)
|
|
259
209
|
end
|
|
260
210
|
|
|
261
211
|
private
|
|
262
212
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
stored.to_h { |canonical, value| [Freeze.deep(Scope.parse(canonical)), value] }.freeze
|
|
269
|
-
end
|
|
270
|
-
|
|
271
|
-
# { parsed scope hash => version token } for a dial's scoped overrides.
|
|
272
|
-
def parsed_versions(snapshot, key)
|
|
273
|
-
stamps = snapshot.row_versions[key] || {}
|
|
274
|
-
stamps.except(Scope::GLOBAL)
|
|
275
|
-
.to_h { |canonical, stamp| [Freeze.deep(Scope.parse(canonical)), StoreVersion.token(stamp)] }.freeze
|
|
213
|
+
def fetch_namespace(key, name)
|
|
214
|
+
@namespaces.fetch(key) do
|
|
215
|
+
raise UnknownNamespace,
|
|
216
|
+
"no namespace named #{name.inspect} (declared: #{@namespaces.keys.join(', ')})"
|
|
217
|
+
end
|
|
276
218
|
end
|
|
277
219
|
|
|
278
|
-
def
|
|
279
|
-
|
|
280
|
-
return unless store_transaction_open?
|
|
281
|
-
|
|
282
|
-
# The write is inside an application transaction and not committed
|
|
283
|
-
# yet. Two things follow. This thread's reads must bypass the shared
|
|
284
|
-
# cache until the transaction closes (see current_snapshot). And the
|
|
285
|
-
# bust above happened PRE-commit — another thread can legitimately
|
|
286
|
-
# republish the pre-transaction state before the commit lands — so the
|
|
287
|
-
# cache must be busted again ON commit, or a writer that never reads
|
|
288
|
-
# again would leave every process serving the old value until the TTL
|
|
289
|
-
# probe notices (forever, with ttl = nil). On rollback the hook is
|
|
290
|
-
# discarded: the shared cache never held the transaction's data.
|
|
291
|
-
Thread.current[TXN_WRITE_KEY] = true
|
|
292
|
-
store.after_commit { cache.bust! } if store.respond_to?(:after_commit)
|
|
220
|
+
def assert_undeclared!(key)
|
|
221
|
+
raise DuplicateNamespace, "namespace #{key.inspect} is already declared" if @namespaces.key?(key)
|
|
293
222
|
end
|
|
294
223
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
# shared cache, busting first so the next snapshot reflects the
|
|
301
|
-
# outcome rather than anything published mid-transaction.
|
|
302
|
-
Thread.current[TXN_WRITE_KEY] = nil
|
|
303
|
-
cache.bust!
|
|
224
|
+
# Callers hold NAMESPACE_LOCK, so a name is claimed and published
|
|
225
|
+
# without another declaration slipping between the two.
|
|
226
|
+
def assert_unclaimed_table!(namespace, table_name)
|
|
227
|
+
claimed = @namespaces.each_value.find do |other|
|
|
228
|
+
!other.equal?(namespace) && other.config.table_name == table_name
|
|
304
229
|
end
|
|
230
|
+
return unless claimed
|
|
305
231
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
def store_transaction_open?
|
|
310
|
-
s = store
|
|
311
|
-
s.respond_to?(:transaction_open?) && s.transaction_open?
|
|
232
|
+
raise InvalidTableName,
|
|
233
|
+
"namespace #{namespace.name} would share the #{table_name.inspect} table with " \
|
|
234
|
+
"namespace #{claimed.name}; a namespace owns its table (config.table_name)"
|
|
312
235
|
end
|
|
313
236
|
end
|
|
314
237
|
|
|
315
|
-
@
|
|
316
|
-
@
|
|
238
|
+
@default = Namespace.new(Namespace::ROOT_NAME)
|
|
239
|
+
@namespaces = { Namespace::ROOT_NAME => @default }
|
|
240
|
+
|
|
241
|
+
# The root namespace's in-transaction marker (see Namespace#after_write);
|
|
242
|
+
# every namespace has one of its own.
|
|
243
|
+
TXN_WRITE_KEY = @default.txn_write_key
|
|
244
|
+
|
|
245
|
+
# The generated readers of the root namespace answer on this module too,
|
|
246
|
+
# so `Dials.merchant_fee_bps` keeps working: the methods are defined once,
|
|
247
|
+
# in the namespace's module, and `self` decides whose dials they resolve.
|
|
248
|
+
extend @default.generated_module
|
|
317
249
|
end
|
|
318
250
|
|
|
319
251
|
begin
|
|
@@ -17,7 +17,7 @@ module Dials
|
|
|
17
17
|
source_root File.expand_path("templates", __dir__)
|
|
18
18
|
|
|
19
19
|
class_option :table_name_prefix, type: :string, default: "",
|
|
20
|
-
desc: 'Prefix for the gem-owned table, used verbatim ("
|
|
20
|
+
desc: 'Prefix for the gem-owned table, used verbatim ("ops_" creates ops_dials)'
|
|
21
21
|
|
|
22
22
|
def create_migration_file
|
|
23
23
|
migration_template "migration.rb.tt", "db/migrate/create_#{table_name}_table.rb"
|
|
@@ -11,8 +11,8 @@ Dials.configure do |config|
|
|
|
11
11
|
<% if options[:table_name_prefix].empty? -%>
|
|
12
12
|
# Prefix the gem-owned table when "dials" collides with an existing one.
|
|
13
13
|
# Used verbatim, so include the trailing underscore. Must match the
|
|
14
|
-
# migration (`rails g dials:install --table-name-prefix=
|
|
15
|
-
# config.table_name_prefix = "
|
|
14
|
+
# migration (`rails g dials:install --table-name-prefix=ops_`).
|
|
15
|
+
# config.table_name_prefix = "ops_"
|
|
16
16
|
<% else -%>
|
|
17
17
|
# Names the gem-owned table <%= table_name %>, as created by the install
|
|
18
18
|
# migration.
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dials
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Keith Gould
|
|
@@ -40,6 +40,7 @@ files:
|
|
|
40
40
|
- lib/dials/errors.rb
|
|
41
41
|
- lib/dials/freeze.rb
|
|
42
42
|
- lib/dials/generated.rb
|
|
43
|
+
- lib/dials/namespace.rb
|
|
43
44
|
- lib/dials/overview.rb
|
|
44
45
|
- lib/dials/railtie.rb
|
|
45
46
|
- lib/dials/registry.rb
|
|
@@ -47,6 +48,7 @@ files:
|
|
|
47
48
|
- lib/dials/schema.rb
|
|
48
49
|
- lib/dials/scope.rb
|
|
49
50
|
- lib/dials/snapshot.rb
|
|
51
|
+
- lib/dials/storage.rb
|
|
50
52
|
- lib/dials/store_version.rb
|
|
51
53
|
- lib/dials/stores/memory.rb
|
|
52
54
|
- lib/dials/testing.rb
|