flipper-active_record 0.28.3 → 1.3.4
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/benchmark/active_record_adapter_ips.rb +5 -2
- data/examples/active_record/ar_setup.rb +15 -0
- data/examples/active_record/group_migration.rb +68 -0
- data/flipper-active_record.gemspec +12 -5
- data/lib/flipper/adapters/active_record.rb +85 -45
- data/lib/flipper/version.rb +11 -1
- data/lib/generators/flipper/templates/migration.erb +4 -4
- data/spec/flipper/adapters/active_record_spec.rb +232 -146
- data/test/adapters/active_record_test.rb +4 -6
- data/test_rails/generators/flipper/active_record_generator_test.rb +4 -6
- metadata +17 -16
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ae15abe1a25ef415ec5f7783b24192ae52cbf86b6d8e8026a7b23d3182db2ea3
|
|
4
|
+
data.tar.gz: 92c793efbd0539545f73d363f61df2f3a43d949aafb0bb303ecb5d8a747e660a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a01ae2b84eea1af1a46ed86a0727a38418ec88682fddf0e8829e340f39e368adf7e3451e07b05efc423d874cc6bf70910bfc7afe61267db84c6e8ef90d6f1764
|
|
7
|
+
data.tar.gz: 280adedbab88143432826be99f65e17673f9bc0012ddb4c89392b32c6dd2c6ec9ce71a68bf1f79dce2347a631afc5d0f3804092ddbd2d6ef6906fb2af6142534
|
|
@@ -6,10 +6,13 @@ require 'benchmark/ips'
|
|
|
6
6
|
|
|
7
7
|
flipper = Flipper.new(Flipper::Adapters::ActiveRecord.new)
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
10.times do |n|
|
|
10
|
+
2000.times do |i|
|
|
11
|
+
flipper.enable_actor 'feature' + n.to_s, Flipper::Actor.new("User;#{i}")
|
|
12
|
+
end
|
|
11
13
|
end
|
|
12
14
|
|
|
13
15
|
Benchmark.ips do |x|
|
|
14
16
|
x.report("get_all") { flipper.preload_all }
|
|
17
|
+
x.report("features") { flipper.features }
|
|
15
18
|
end
|
|
@@ -28,3 +28,18 @@ SQL
|
|
|
28
28
|
ActiveRecord::Base.connection.execute <<-SQL
|
|
29
29
|
CREATE UNIQUE INDEX index_gates_on_keys_and_value on flipper_gates (feature_key, key, value)
|
|
30
30
|
SQL
|
|
31
|
+
|
|
32
|
+
ActiveRecord::Base.connection.execute <<-SQL
|
|
33
|
+
CREATE TABLE users (
|
|
34
|
+
id integer PRIMARY KEY,
|
|
35
|
+
name string NOT NULL,
|
|
36
|
+
influencer boolean,
|
|
37
|
+
created_at datetime NOT NULL,
|
|
38
|
+
updated_at datetime NOT NULL
|
|
39
|
+
)
|
|
40
|
+
SQL
|
|
41
|
+
|
|
42
|
+
require 'flipper/model/active_record'
|
|
43
|
+
class User < ActiveRecord::Base
|
|
44
|
+
include Flipper::Model::ActiveRecord
|
|
45
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# This is an example script that shows how to migrate from a bunch of individual
|
|
2
|
+
# actors to a group. Should be useful for those who have ended up with large
|
|
3
|
+
# actor sets and want to slim them down for performance reasons.
|
|
4
|
+
|
|
5
|
+
require_relative "./ar_setup"
|
|
6
|
+
require 'flipper/adapters/active_record'
|
|
7
|
+
require 'active_support/all'
|
|
8
|
+
|
|
9
|
+
# 1. enable feature for 100 actors, make 80 influencers
|
|
10
|
+
users = 100.times.map do |n|
|
|
11
|
+
influencer = n < 80 ? true : false
|
|
12
|
+
user = User.create(name: n, influencer: influencer)
|
|
13
|
+
Flipper.enable :stats, user
|
|
14
|
+
user
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# check enabled, should all be because individual actors are enabled
|
|
18
|
+
print 'Should be [[true, 100]]: '
|
|
19
|
+
print users.group_by { |user| Flipper.enabled?(:stats, user) }.map { |result, users| [result, users.size]}
|
|
20
|
+
puts
|
|
21
|
+
|
|
22
|
+
# 2. register a group so flipper knows what to do with it
|
|
23
|
+
Flipper.register(:influencers) do |actor, context|
|
|
24
|
+
actor.respond_to?(:influencer) && actor.influencer
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# 3. enable group for feature, THIS IS IMPORTANT
|
|
28
|
+
Flipper.enable :stats, :influencers
|
|
29
|
+
|
|
30
|
+
# check enabled again, should all still be true because individual actors are
|
|
31
|
+
# enabled, but also the group gate would return true for 80 influencers. At this
|
|
32
|
+
# point, it's kind of double true but flipper just cares if any gate returns true.
|
|
33
|
+
print 'Should be [[true, 100]]: '
|
|
34
|
+
print users.group_by { |user| Flipper.enabled?(:stats, user) }.map { |result, users| [result, users.size]}
|
|
35
|
+
puts
|
|
36
|
+
|
|
37
|
+
# 4. now we want to clean up the actors that are covered by the group to slim down
|
|
38
|
+
# the actor set size. So we loop through actors and remove them if group returns
|
|
39
|
+
# true for the provided actor and context.
|
|
40
|
+
Flipper[:stats].actors_value.each do |flipper_id|
|
|
41
|
+
# Hydrate the flipper_id into an active record object. Modify this based on
|
|
42
|
+
# your flipper_id's if you use anything other than active record models and
|
|
43
|
+
# the default flipper_id provided by flipper.
|
|
44
|
+
class_name, id = flipper_id.split(';')
|
|
45
|
+
klass = class_name.constantize
|
|
46
|
+
user = klass.find(id)
|
|
47
|
+
|
|
48
|
+
# if user is in group then disable for actor because they'll still get the feature
|
|
49
|
+
context = Flipper::FeatureCheckContext.new(
|
|
50
|
+
feature_name: :stats,
|
|
51
|
+
values: Flipper[:stats].gate_values,
|
|
52
|
+
actors: [Flipper::Types::Actor.wrap(user)]
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
if Flipper::Gates::Group.new.open?(context)
|
|
56
|
+
Flipper.disable(:stats, user)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# check enabled again, should be the same result as previous checks
|
|
61
|
+
print 'Should be [[true, 100]]: '
|
|
62
|
+
print users.group_by { |user| Flipper.enabled?(:stats, user) }.map { |result, users| [result, users.size]}
|
|
63
|
+
puts
|
|
64
|
+
|
|
65
|
+
puts "Actors enabled: #{Flipper[:stats].actors_value.size}"
|
|
66
|
+
puts "Groups enabled: #{Flipper[:stats].groups_value.size}"
|
|
67
|
+
|
|
68
|
+
puts "All actors that could be migrated to groups were migrated. Yay!"
|
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
2
|
require File.expand_path('../lib/flipper/version', __FILE__)
|
|
3
3
|
require File.expand_path('../lib/flipper/metadata', __FILE__)
|
|
4
|
+
require "set"
|
|
5
|
+
|
|
6
|
+
# Files that should exist in main flipper gem.
|
|
7
|
+
main_flipper_active_record_files = Set[
|
|
8
|
+
"lib/flipper/model/active_record.rb",
|
|
9
|
+
"spec/flipper/model/active_record_spec.rb",
|
|
10
|
+
]
|
|
4
11
|
|
|
5
12
|
flipper_active_record_files = lambda do |file|
|
|
6
|
-
file =~ /active_record/
|
|
13
|
+
file =~ /active_record/ && !main_flipper_active_record_files.include?(file)
|
|
7
14
|
end
|
|
8
15
|
|
|
9
16
|
Gem::Specification.new do |gem|
|
|
10
17
|
gem.authors = ['John Nunemaker']
|
|
11
|
-
gem.email =
|
|
12
|
-
gem.summary = 'ActiveRecord adapter for Flipper'
|
|
18
|
+
gem.email = 'support@flippercloud.io'
|
|
19
|
+
gem.summary = 'ActiveRecord feature flag adapter for Flipper'
|
|
13
20
|
gem.license = 'MIT'
|
|
14
|
-
gem.homepage = 'https://
|
|
21
|
+
gem.homepage = 'https://www.flippercloud.io/docs/adapters/active-record'
|
|
15
22
|
|
|
16
23
|
extra_files = [
|
|
17
24
|
'lib/generators/flipper/templates/migration.erb',
|
|
@@ -25,5 +32,5 @@ Gem::Specification.new do |gem|
|
|
|
25
32
|
gem.metadata = Flipper::METADATA
|
|
26
33
|
|
|
27
34
|
gem.add_dependency 'flipper', "~> #{Flipper::VERSION}"
|
|
28
|
-
gem.add_dependency 'activerecord', '>= 4.2', '<
|
|
35
|
+
gem.add_dependency 'activerecord', '>= 4.2', '< 9'
|
|
29
36
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require 'set'
|
|
2
|
+
require 'securerandom'
|
|
2
3
|
require 'flipper'
|
|
3
4
|
require 'active_record'
|
|
4
5
|
|
|
@@ -7,31 +8,42 @@ module Flipper
|
|
|
7
8
|
class ActiveRecord
|
|
8
9
|
include ::Flipper::Adapter
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
ActiveSupport.on_load(:active_record) do
|
|
12
|
+
# Abstract base class for internal models
|
|
13
|
+
class Model < ::ActiveRecord::Base
|
|
14
|
+
self.abstract_class = true
|
|
15
|
+
end
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
# Private: Do not use outside of this adapter.
|
|
18
|
+
class Feature < Model
|
|
19
|
+
self.table_name = [
|
|
20
|
+
Model.table_name_prefix,
|
|
21
|
+
"flipper_features",
|
|
22
|
+
Model.table_name_suffix,
|
|
23
|
+
].join
|
|
24
|
+
|
|
25
|
+
has_many :gates, foreign_key: "feature_key", primary_key: "key"
|
|
26
|
+
|
|
27
|
+
validates :key, presence: true
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Private: Do not use outside of this adapter.
|
|
31
|
+
class Gate < Model
|
|
32
|
+
self.table_name = [
|
|
33
|
+
Model.table_name_prefix,
|
|
34
|
+
"flipper_gates",
|
|
35
|
+
Model.table_name_suffix,
|
|
36
|
+
].join
|
|
23
37
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
Model.table_name_prefix,
|
|
28
|
-
"flipper_gates",
|
|
29
|
-
Model.table_name_suffix,
|
|
30
|
-
].join
|
|
38
|
+
validates :feature_key, presence: true
|
|
39
|
+
validates :key, presence: true
|
|
40
|
+
end
|
|
31
41
|
end
|
|
32
42
|
|
|
33
|
-
|
|
34
|
-
|
|
43
|
+
VALUE_TO_TEXT_WARNING = <<-EOS
|
|
44
|
+
Your database needs migrated to use the latest Flipper features.
|
|
45
|
+
Run `rails generate flipper:update` and `rails db:migrate`.
|
|
46
|
+
EOS
|
|
35
47
|
|
|
36
48
|
# Public: Initialize a new ActiveRecord adapter instance.
|
|
37
49
|
#
|
|
@@ -49,24 +61,27 @@ module Flipper
|
|
|
49
61
|
@name = options.fetch(:name, :active_record)
|
|
50
62
|
@feature_class = options.fetch(:feature_class) { Feature }
|
|
51
63
|
@gate_class = options.fetch(:gate_class) { Gate }
|
|
64
|
+
|
|
65
|
+
warn VALUE_TO_TEXT_WARNING if value_not_text?
|
|
52
66
|
end
|
|
53
67
|
|
|
54
68
|
# Public: The set of known features.
|
|
55
69
|
def features
|
|
56
|
-
with_connection(@feature_class) { @feature_class.
|
|
70
|
+
with_connection(@feature_class) { @feature_class.distinct.pluck(:key).to_set }
|
|
57
71
|
end
|
|
58
72
|
|
|
59
73
|
# Public: Adds a feature to the set of known features.
|
|
60
74
|
def add(feature)
|
|
61
75
|
with_connection(@feature_class) do
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
@feature_class.create!
|
|
68
|
-
rescue ::ActiveRecord::RecordNotUnique
|
|
76
|
+
@feature_class.transaction(requires_new: true) do
|
|
77
|
+
begin
|
|
78
|
+
# race condition, but add is only used by enable/disable which happen
|
|
79
|
+
# super rarely, so it shouldn't matter in practice
|
|
80
|
+
unless @feature_class.where(key: feature.key).exists?
|
|
81
|
+
@feature_class.create!(key: feature.key)
|
|
69
82
|
end
|
|
83
|
+
rescue ::ActiveRecord::RecordNotUnique
|
|
84
|
+
# already added
|
|
70
85
|
end
|
|
71
86
|
end
|
|
72
87
|
end
|
|
@@ -117,14 +132,14 @@ module Flipper
|
|
|
117
132
|
end
|
|
118
133
|
|
|
119
134
|
def get_all
|
|
120
|
-
with_connection(@feature_class) do
|
|
135
|
+
with_connection(@feature_class) do |connection|
|
|
121
136
|
# query the gates from the db in a single query
|
|
122
137
|
features = ::Arel::Table.new(@feature_class.table_name.to_sym)
|
|
123
138
|
gates = ::Arel::Table.new(@gate_class.table_name.to_sym)
|
|
124
139
|
rows_query = features.join(gates, ::Arel::Nodes::OuterJoin)
|
|
125
140
|
.on(features[:key].eq(gates[:feature_key]))
|
|
126
141
|
.project(features[:key].as('feature_key'), gates[:key], gates[:value])
|
|
127
|
-
gates =
|
|
142
|
+
gates = connection.select_rows(rows_query)
|
|
128
143
|
|
|
129
144
|
# group the gates by feature key
|
|
130
145
|
grouped_gates = gates.inject({}) do |hash, (feature_key, key, value)|
|
|
@@ -156,6 +171,8 @@ module Flipper
|
|
|
156
171
|
set(feature, gate, thing, clear: true)
|
|
157
172
|
when :integer
|
|
158
173
|
set(feature, gate, thing)
|
|
174
|
+
when :json
|
|
175
|
+
set(feature, gate, thing, json: true)
|
|
159
176
|
when :set
|
|
160
177
|
enable_multi(feature, gate, thing)
|
|
161
178
|
else
|
|
@@ -178,6 +195,8 @@ module Flipper
|
|
|
178
195
|
clear(feature)
|
|
179
196
|
when :integer
|
|
180
197
|
set(feature, gate, thing)
|
|
198
|
+
when :json
|
|
199
|
+
delete(feature, gate)
|
|
181
200
|
when :set
|
|
182
201
|
with_connection(@gate_class) do
|
|
183
202
|
@gate_class.where(feature_key: feature.key, key: gate.key, value: thing.value).destroy_all
|
|
@@ -198,19 +217,23 @@ module Flipper
|
|
|
198
217
|
|
|
199
218
|
def set(feature, gate, thing, options = {})
|
|
200
219
|
clear_feature = options.fetch(:clear, false)
|
|
220
|
+
json_feature = options.fetch(:json, false)
|
|
221
|
+
|
|
222
|
+
raise VALUE_TO_TEXT_WARNING if json_feature && value_not_text?
|
|
223
|
+
|
|
201
224
|
with_connection(@gate_class) do
|
|
202
|
-
@gate_class.transaction do
|
|
225
|
+
@gate_class.transaction(requires_new: true) do
|
|
203
226
|
clear(feature) if clear_feature
|
|
204
|
-
|
|
227
|
+
delete(feature, gate)
|
|
205
228
|
begin
|
|
206
229
|
@gate_class.create! do |g|
|
|
207
230
|
g.feature_key = feature.key
|
|
208
231
|
g.key = gate.key
|
|
209
|
-
g.value = thing.value.to_s
|
|
232
|
+
g.value = json_feature ? Typecast.to_json(thing.value) : thing.value.to_s
|
|
210
233
|
end
|
|
211
234
|
rescue ::ActiveRecord::RecordNotUnique
|
|
212
235
|
# assume this happened concurrently with the same thing and its fine
|
|
213
|
-
# see https://github.com/
|
|
236
|
+
# see https://github.com/flippercloud/flipper/issues/544
|
|
214
237
|
end
|
|
215
238
|
end
|
|
216
239
|
end
|
|
@@ -218,18 +241,26 @@ module Flipper
|
|
|
218
241
|
nil
|
|
219
242
|
end
|
|
220
243
|
|
|
244
|
+
def delete(feature, gate)
|
|
245
|
+
@gate_class.where(feature_key: feature.key, key: gate.key).destroy_all
|
|
246
|
+
end
|
|
247
|
+
|
|
221
248
|
def enable_multi(feature, gate, thing)
|
|
222
|
-
with_connection(@gate_class) do
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
249
|
+
with_connection(@gate_class) do |connection|
|
|
250
|
+
begin
|
|
251
|
+
connection.transaction(requires_new: true) do
|
|
252
|
+
@gate_class.create! do |g|
|
|
253
|
+
g.feature_key = feature.key
|
|
254
|
+
g.key = gate.key
|
|
255
|
+
g.value = thing.value.to_s
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
rescue ::ActiveRecord::RecordNotUnique
|
|
259
|
+
# already added so move on with life
|
|
227
260
|
end
|
|
228
261
|
end
|
|
229
262
|
|
|
230
263
|
nil
|
|
231
|
-
rescue ::ActiveRecord::RecordNotUnique
|
|
232
|
-
# already added so no need move on with life
|
|
233
264
|
end
|
|
234
265
|
|
|
235
266
|
def result_for_gates(feature, gates)
|
|
@@ -238,13 +269,13 @@ module Flipper
|
|
|
238
269
|
feature.gates.each do |gate|
|
|
239
270
|
result[gate.key] =
|
|
240
271
|
case gate.data_type
|
|
241
|
-
when :boolean
|
|
272
|
+
when :boolean, :integer
|
|
242
273
|
if row = gates.detect { |key, value| !key.nil? && key.to_sym == gate.key }
|
|
243
274
|
row.last
|
|
244
275
|
end
|
|
245
|
-
when :
|
|
276
|
+
when :json
|
|
246
277
|
if row = gates.detect { |key, value| !key.nil? && key.to_sym == gate.key }
|
|
247
|
-
row.last
|
|
278
|
+
Typecast.from_json(row.last)
|
|
248
279
|
end
|
|
249
280
|
when :set
|
|
250
281
|
gates.select { |key, value| !key.nil? && key.to_sym == gate.key }.map(&:last).to_set
|
|
@@ -255,6 +286,15 @@ module Flipper
|
|
|
255
286
|
result
|
|
256
287
|
end
|
|
257
288
|
|
|
289
|
+
# Check if value column is text instead of string
|
|
290
|
+
# See https://github.com/flippercloud/flipper/pull/692
|
|
291
|
+
def value_not_text?
|
|
292
|
+
@gate_class.column_for_attribute(:value).type != :text
|
|
293
|
+
rescue ::ActiveRecord::ActiveRecordError => error
|
|
294
|
+
# If the table doesn't exist, the column doesn't exist either
|
|
295
|
+
warn "#{error.message}. You likely need to run `rails g flipper:active_record` and/or `rails db:migrate`."
|
|
296
|
+
end
|
|
297
|
+
|
|
258
298
|
def with_connection(model = @feature_class, &block)
|
|
259
299
|
model.connection_pool.with_connection(&block)
|
|
260
300
|
end
|
data/lib/flipper/version.rb
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
1
|
module Flipper
|
|
2
|
-
VERSION = '
|
|
2
|
+
VERSION = '1.3.4'.freeze
|
|
3
|
+
|
|
4
|
+
REQUIRED_RUBY_VERSION = '2.6'.freeze
|
|
5
|
+
NEXT_REQUIRED_RUBY_VERSION = '3.0'.freeze
|
|
6
|
+
|
|
7
|
+
REQUIRED_RAILS_VERSION = '5.2'.freeze
|
|
8
|
+
NEXT_REQUIRED_RAILS_VERSION = '6.1.0'.freeze
|
|
9
|
+
|
|
10
|
+
def self.deprecated_ruby_version?
|
|
11
|
+
Gem::Version.new(RUBY_VERSION) < Gem::Version.new(NEXT_REQUIRED_RUBY_VERSION)
|
|
12
|
+
end
|
|
3
13
|
end
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
class CreateFlipperTables < ActiveRecord::Migration<%= migration_version %>
|
|
2
|
-
def
|
|
2
|
+
def up
|
|
3
3
|
create_table :flipper_features do |t|
|
|
4
4
|
t.string :key, null: false
|
|
5
5
|
t.timestamps null: false
|
|
@@ -9,13 +9,13 @@ class CreateFlipperTables < ActiveRecord::Migration<%= migration_version %>
|
|
|
9
9
|
create_table :flipper_gates do |t|
|
|
10
10
|
t.string :feature_key, null: false
|
|
11
11
|
t.string :key, null: false
|
|
12
|
-
t.
|
|
12
|
+
t.text :value
|
|
13
13
|
t.timestamps null: false
|
|
14
14
|
end
|
|
15
|
-
add_index :flipper_gates, [:feature_key, :key, :value], unique: true
|
|
15
|
+
add_index :flipper_gates, [:feature_key, :key, :value], unique: true, length: { value: 255 }
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
def
|
|
18
|
+
def down
|
|
19
19
|
drop_table :flipper_gates
|
|
20
20
|
drop_table :flipper_features
|
|
21
21
|
end
|
|
@@ -1,181 +1,267 @@
|
|
|
1
|
-
require 'flipper/adapters/active_record'
|
|
1
|
+
SpecHelpers.silence { require 'flipper/adapters/active_record' }
|
|
2
2
|
|
|
3
3
|
# Turn off migration logging for specs
|
|
4
4
|
ActiveRecord::Migration.verbose = false
|
|
5
|
+
ActiveRecord::Tasks::DatabaseTasks.root = File.dirname(__FILE__)
|
|
5
6
|
|
|
6
7
|
RSpec.describe Flipper::Adapters::ActiveRecord do
|
|
7
8
|
subject { described_class.new }
|
|
8
9
|
|
|
9
10
|
before(:all) do
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
# Eval migration template so we can run migration against each database
|
|
12
|
+
template_path = File.join(File.dirname(__FILE__), '../../../lib/generators/flipper/templates/migration.erb')
|
|
13
|
+
migration = ERB.new(File.read(template_path))
|
|
14
|
+
migration_version = "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
|
15
|
+
eval migration.result_with_hash(migration_version: migration_version) # defines CreateFlipperTables
|
|
12
16
|
end
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
created_at datetime NOT NULL,
|
|
20
|
-
updated_at datetime NOT NULL
|
|
21
|
-
)
|
|
22
|
-
SQL
|
|
23
|
-
|
|
24
|
-
ActiveRecord::Base.connection.execute <<-SQL
|
|
25
|
-
CREATE TABLE flipper_gates (
|
|
26
|
-
id integer PRIMARY KEY,
|
|
27
|
-
feature_key text NOT NULL,
|
|
28
|
-
key text NOT NULL,
|
|
29
|
-
value text DEFAULT NULL,
|
|
30
|
-
created_at datetime NOT NULL,
|
|
31
|
-
updated_at datetime NOT NULL
|
|
32
|
-
)
|
|
33
|
-
SQL
|
|
34
|
-
|
|
35
|
-
ActiveRecord::Base.connection.execute <<-SQL
|
|
36
|
-
CREATE UNIQUE INDEX index_gates_on_keys_and_value on flipper_gates (feature_key, key, value)
|
|
37
|
-
SQL
|
|
38
|
-
end
|
|
18
|
+
[
|
|
19
|
+
{
|
|
20
|
+
"adapter" => "sqlite3",
|
|
21
|
+
"database" => ":memory:"
|
|
22
|
+
},
|
|
39
23
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
24
|
+
{
|
|
25
|
+
"adapter" => "mysql2",
|
|
26
|
+
"encoding" => "utf8mb4",
|
|
27
|
+
"host" => ENV["MYSQL_HOST"],
|
|
28
|
+
"username" => ENV["MYSQL_USER"] || "root",
|
|
29
|
+
"password" => ENV["MYSQL_PASSWORD"] || "",
|
|
30
|
+
"database" => ENV["MYSQL_DATABASE"] || "flipper_test",
|
|
31
|
+
"port" => ENV["DB_PORT"] || 3306
|
|
32
|
+
},
|
|
44
33
|
|
|
45
|
-
|
|
34
|
+
{
|
|
35
|
+
"adapter" => "postgresql",
|
|
36
|
+
"encoding" => "unicode",
|
|
37
|
+
"host" => "127.0.0.1",
|
|
38
|
+
"username" => ENV["POSTGRES_USER"] || "",
|
|
39
|
+
"password" => ENV["POSTGRES_PASSWORD"] || "",
|
|
40
|
+
"database" => ENV["POSTGRES_DATABASE"] || "flipper_test",
|
|
41
|
+
}
|
|
42
|
+
].each do |config|
|
|
43
|
+
context "with #{config['adapter']}" do
|
|
44
|
+
context "with tables created" do
|
|
45
|
+
before(:all) do
|
|
46
|
+
skip_on_error(ActiveRecord::ConnectionNotEstablished, "#{config['adapter']} not available") do
|
|
47
|
+
silence do
|
|
48
|
+
ActiveRecord::Tasks::DatabaseTasks.create(config)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
46
51
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
Flipper.configuration = nil
|
|
50
|
-
Flipper.instance = nil
|
|
52
|
+
Flipper.configuration = nil
|
|
53
|
+
end
|
|
51
54
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
before(:each) do
|
|
56
|
+
silence do
|
|
57
|
+
ActiveRecord::Tasks::DatabaseTasks.purge(config)
|
|
58
|
+
CreateFlipperTables.migrate(:up)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
55
61
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
end
|
|
62
|
+
after(:all) do
|
|
63
|
+
silence { ActiveRecord::Tasks::DatabaseTasks.drop(config) } unless $skip
|
|
64
|
+
end
|
|
60
65
|
|
|
61
|
-
|
|
62
|
-
before do
|
|
63
|
-
ActiveRecord::Base.clear_active_connections!
|
|
64
|
-
end
|
|
66
|
+
it_should_behave_like 'a flipper adapter'
|
|
65
67
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false)
|
|
69
|
-
subject.features
|
|
70
|
-
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false)
|
|
71
|
-
end
|
|
68
|
+
it "should load actor ids fine" do
|
|
69
|
+
flipper.enable_percentage_of_time(:foo, 1)
|
|
72
70
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
end
|
|
79
|
-
end
|
|
71
|
+
Flipper::Adapters::ActiveRecord::Gate.create!(
|
|
72
|
+
feature_key: "foo",
|
|
73
|
+
key: "actors",
|
|
74
|
+
value: "Organization;4",
|
|
75
|
+
)
|
|
80
76
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
subject.get_all
|
|
85
|
-
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false)
|
|
86
|
-
end
|
|
77
|
+
flipper = Flipper.new(subject)
|
|
78
|
+
flipper.preload([:foo])
|
|
79
|
+
end
|
|
87
80
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true)
|
|
91
|
-
subject.get_all
|
|
92
|
-
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true)
|
|
93
|
-
end
|
|
94
|
-
end
|
|
81
|
+
it 'should not poison wrapping transactions' do
|
|
82
|
+
flipper = Flipper.new(subject)
|
|
95
83
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
it "does not hold onto connections" do
|
|
100
|
-
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false)
|
|
101
|
-
subject.add(feature)
|
|
102
|
-
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false)
|
|
103
|
-
subject.remove(feature)
|
|
104
|
-
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false)
|
|
105
|
-
subject.clear(feature)
|
|
106
|
-
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false)
|
|
107
|
-
end
|
|
84
|
+
actor = Struct.new(:flipper_id).new('flipper-id-123')
|
|
85
|
+
flipper.enable_actor(:foo, actor)
|
|
108
86
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
subject.clear(feature)
|
|
117
|
-
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true)
|
|
118
|
-
end
|
|
119
|
-
end
|
|
87
|
+
ActiveRecord::Base.transaction do
|
|
88
|
+
flipper.enable_actor(:foo, actor)
|
|
89
|
+
# any read on the next line is fine, just need to ensure that
|
|
90
|
+
# poisoned transaction isn't raised
|
|
91
|
+
expect(Flipper::Adapters::ActiveRecord::Gate.count).to eq(1)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
120
94
|
|
|
121
|
-
|
|
122
|
-
|
|
95
|
+
context "ActiveRecord connection_pool" do
|
|
96
|
+
before do
|
|
97
|
+
ActiveRecord::Base.connection_handler.clear_active_connections!
|
|
98
|
+
end
|
|
123
99
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
100
|
+
context "#features" do
|
|
101
|
+
it "does not hold onto connections" do
|
|
102
|
+
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false)
|
|
103
|
+
subject.features
|
|
104
|
+
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false)
|
|
105
|
+
end
|
|
129
106
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
107
|
+
it "does not release previously held connection" do
|
|
108
|
+
ActiveRecord::Base.connection # establish a new connection
|
|
109
|
+
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true)
|
|
110
|
+
subject.features
|
|
111
|
+
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
137
114
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
115
|
+
context "#get_all" do
|
|
116
|
+
it "does not hold onto connections" do
|
|
117
|
+
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false)
|
|
118
|
+
subject.get_all
|
|
119
|
+
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false)
|
|
120
|
+
end
|
|
141
121
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
122
|
+
it "does not release previously held connection" do
|
|
123
|
+
ActiveRecord::Base.connection # establish a new connection
|
|
124
|
+
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true)
|
|
125
|
+
subject.get_all
|
|
126
|
+
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
149
129
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
130
|
+
context "#add / #remove / #clear" do
|
|
131
|
+
let(:feature) { Flipper::Feature.new(:search, subject) }
|
|
132
|
+
|
|
133
|
+
it "does not hold onto connections" do
|
|
134
|
+
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false)
|
|
135
|
+
subject.add(feature)
|
|
136
|
+
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false)
|
|
137
|
+
subject.remove(feature)
|
|
138
|
+
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false)
|
|
139
|
+
subject.clear(feature)
|
|
140
|
+
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it "does not release previously held connection" do
|
|
144
|
+
ActiveRecord::Base.connection # establish a new connection
|
|
145
|
+
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true)
|
|
146
|
+
subject.add(feature)
|
|
147
|
+
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true)
|
|
148
|
+
subject.remove(feature)
|
|
149
|
+
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true)
|
|
150
|
+
subject.clear(feature)
|
|
151
|
+
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
context "#get_multi" do
|
|
156
|
+
let(:feature) { Flipper::Feature.new(:search, subject) }
|
|
157
|
+
|
|
158
|
+
it "does not hold onto connections" do
|
|
159
|
+
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false)
|
|
160
|
+
subject.get_multi([feature])
|
|
161
|
+
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
it "does not release previously held connection" do
|
|
165
|
+
ActiveRecord::Base.connection # establish a new connection
|
|
166
|
+
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true)
|
|
167
|
+
subject.get_multi([feature])
|
|
168
|
+
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
context "#enable/#disable boolean" do
|
|
173
|
+
let(:feature) { Flipper::Feature.new(:search, subject) }
|
|
174
|
+
let(:gate) { feature.gate(:boolean)}
|
|
175
|
+
|
|
176
|
+
it "does not hold onto connections" do
|
|
177
|
+
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false)
|
|
178
|
+
subject.enable(feature, gate, gate.wrap(true))
|
|
179
|
+
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false)
|
|
180
|
+
subject.disable(feature, gate, gate.wrap(false))
|
|
181
|
+
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
it "does not release previously held connection" do
|
|
185
|
+
ActiveRecord::Base.connection # establish a new connection
|
|
186
|
+
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true)
|
|
187
|
+
subject.enable(feature, gate, gate.wrap(true))
|
|
188
|
+
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true)
|
|
189
|
+
subject.disable(feature, gate, gate.wrap(false))
|
|
190
|
+
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
context "#enable/#disable set" do
|
|
195
|
+
let(:feature) { Flipper::Feature.new(:search, subject) }
|
|
196
|
+
let(:gate) { feature.gate(:group) }
|
|
159
197
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
198
|
+
it "does not hold onto connections" do
|
|
199
|
+
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false)
|
|
200
|
+
subject.enable(feature, gate, gate.wrap(:admin))
|
|
201
|
+
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false)
|
|
202
|
+
subject.disable(feature, gate, gate.wrap(:admin))
|
|
203
|
+
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(false)
|
|
204
|
+
end
|
|
163
205
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
206
|
+
it "does not release previously held connection" do
|
|
207
|
+
ActiveRecord::Base.connection # establish a new connection
|
|
208
|
+
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true)
|
|
209
|
+
subject.enable(feature, gate, gate.wrap(:admin))
|
|
210
|
+
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true)
|
|
211
|
+
subject.disable(feature, gate, gate.wrap(:admin))
|
|
212
|
+
expect(ActiveRecord::Base.connection_handler.active_connections?).to be(true)
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
context 'requiring "flipper-active_record"' do
|
|
218
|
+
before do
|
|
219
|
+
Flipper.configuration = nil
|
|
220
|
+
Flipper.instance = nil
|
|
221
|
+
|
|
222
|
+
silence { load 'flipper/adapters/active_record.rb' }
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
it 'configures itself' do
|
|
226
|
+
expect(Flipper.adapter.adapter).to be_a(Flipper::Adapters::ActiveRecord)
|
|
227
|
+
end
|
|
228
|
+
end
|
|
170
229
|
end
|
|
171
230
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
231
|
+
context "without tables created" do
|
|
232
|
+
before(:all) do
|
|
233
|
+
skip_on_error(ActiveRecord::ConnectionNotEstablished, "#{config['adapter']} not available") do
|
|
234
|
+
silence do
|
|
235
|
+
ActiveRecord::Tasks::DatabaseTasks.create(config)
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
Flipper.configuration = nil
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
before(:each) do
|
|
243
|
+
ActiveRecord::Base.establish_connection(config)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
after(:each) do
|
|
247
|
+
ActiveRecord::Base.connection.close
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
after(:all) do
|
|
251
|
+
silence { ActiveRecord::Tasks::DatabaseTasks.drop(config) } unless $skip
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
it "does not raise an error" do
|
|
255
|
+
Flipper.configuration = nil
|
|
256
|
+
Flipper.instance = nil
|
|
257
|
+
|
|
258
|
+
silence do
|
|
259
|
+
expect {
|
|
260
|
+
load 'flipper/adapters/active_record.rb'
|
|
261
|
+
Flipper::Adapters::ActiveRecord.new
|
|
262
|
+
}.not_to raise_error
|
|
263
|
+
end
|
|
264
|
+
end
|
|
179
265
|
end
|
|
180
266
|
end
|
|
181
267
|
end
|
|
@@ -11,8 +11,6 @@ class ActiveRecordTest < MiniTest::Test
|
|
|
11
11
|
database: ':memory:')
|
|
12
12
|
|
|
13
13
|
def setup
|
|
14
|
-
@adapter = Flipper::Adapters::ActiveRecord.new
|
|
15
|
-
|
|
16
14
|
ActiveRecord::Base.connection.execute <<-SQL
|
|
17
15
|
CREATE TABLE flipper_features (
|
|
18
16
|
id integer PRIMARY KEY,
|
|
@@ -36,6 +34,8 @@ class ActiveRecordTest < MiniTest::Test
|
|
|
36
34
|
ActiveRecord::Base.connection.execute <<-SQL
|
|
37
35
|
CREATE UNIQUE INDEX index_gates_on_keys_and_value on flipper_gates (feature_key, key, value)
|
|
38
36
|
SQL
|
|
37
|
+
|
|
38
|
+
@adapter = Flipper::Adapters::ActiveRecord.new
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def teardown
|
|
@@ -47,8 +47,7 @@ class ActiveRecordTest < MiniTest::Test
|
|
|
47
47
|
ActiveRecord::Base.table_name_prefix = :foo_
|
|
48
48
|
ActiveRecord::Base.table_name_suffix = :_bar
|
|
49
49
|
|
|
50
|
-
Flipper::Adapters
|
|
51
|
-
Flipper::Adapters::ActiveRecord.send(:remove_const, :Gate)
|
|
50
|
+
Flipper::Adapters.send(:remove_const, :ActiveRecord)
|
|
52
51
|
load("flipper/adapters/active_record.rb")
|
|
53
52
|
|
|
54
53
|
assert_equal "foo_flipper_features_bar", Flipper::Adapters::ActiveRecord::Feature.table_name
|
|
@@ -58,8 +57,7 @@ class ActiveRecordTest < MiniTest::Test
|
|
|
58
57
|
ActiveRecord::Base.table_name_prefix = ""
|
|
59
58
|
ActiveRecord::Base.table_name_suffix = ""
|
|
60
59
|
|
|
61
|
-
Flipper::Adapters
|
|
62
|
-
Flipper::Adapters::ActiveRecord.send(:remove_const, :Gate)
|
|
60
|
+
Flipper::Adapters.send(:remove_const, :ActiveRecord)
|
|
63
61
|
load("flipper/adapters/active_record.rb")
|
|
64
62
|
end
|
|
65
63
|
end
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
require 'helper'
|
|
2
|
-
require 'active_record'
|
|
3
|
-
require 'rails/generators/test_case'
|
|
4
2
|
require 'generators/flipper/active_record_generator'
|
|
5
3
|
|
|
6
4
|
class FlipperActiveRecordGeneratorTest < Rails::Generators::TestCase
|
|
@@ -17,7 +15,7 @@ class FlipperActiveRecordGeneratorTest < Rails::Generators::TestCase
|
|
|
17
15
|
end
|
|
18
16
|
assert_migration 'db/migrate/create_flipper_tables.rb', <<~MIGRATION
|
|
19
17
|
class CreateFlipperTables < ActiveRecord::Migration#{migration_version}
|
|
20
|
-
def
|
|
18
|
+
def up
|
|
21
19
|
create_table :flipper_features do |t|
|
|
22
20
|
t.string :key, null: false
|
|
23
21
|
t.timestamps null: false
|
|
@@ -27,13 +25,13 @@ class FlipperActiveRecordGeneratorTest < Rails::Generators::TestCase
|
|
|
27
25
|
create_table :flipper_gates do |t|
|
|
28
26
|
t.string :feature_key, null: false
|
|
29
27
|
t.string :key, null: false
|
|
30
|
-
t.
|
|
28
|
+
t.text :value
|
|
31
29
|
t.timestamps null: false
|
|
32
30
|
end
|
|
33
|
-
add_index :flipper_gates, [:feature_key, :key, :value], unique: true
|
|
31
|
+
add_index :flipper_gates, [:feature_key, :key, :value], unique: true, length: { value: 255 }
|
|
34
32
|
end
|
|
35
33
|
|
|
36
|
-
def
|
|
34
|
+
def down
|
|
37
35
|
drop_table :flipper_gates
|
|
38
36
|
drop_table :flipper_features
|
|
39
37
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: flipper-active_record
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.3.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- John Nunemaker
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 2025-03-03 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: flipper
|
|
@@ -16,14 +15,14 @@ dependencies:
|
|
|
16
15
|
requirements:
|
|
17
16
|
- - "~>"
|
|
18
17
|
- !ruby/object:Gem::Version
|
|
19
|
-
version:
|
|
18
|
+
version: 1.3.4
|
|
20
19
|
type: :runtime
|
|
21
20
|
prerelease: false
|
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
22
|
requirements:
|
|
24
23
|
- - "~>"
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
|
-
version:
|
|
25
|
+
version: 1.3.4
|
|
27
26
|
- !ruby/object:Gem::Dependency
|
|
28
27
|
name: activerecord
|
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -33,7 +32,7 @@ dependencies:
|
|
|
33
32
|
version: '4.2'
|
|
34
33
|
- - "<"
|
|
35
34
|
- !ruby/object:Gem::Version
|
|
36
|
-
version: '
|
|
35
|
+
version: '9'
|
|
37
36
|
type: :runtime
|
|
38
37
|
prerelease: false
|
|
39
38
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -43,10 +42,8 @@ dependencies:
|
|
|
43
42
|
version: '4.2'
|
|
44
43
|
- - "<"
|
|
45
44
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: '
|
|
47
|
-
|
|
48
|
-
email:
|
|
49
|
-
- nunemaker@gmail.com
|
|
45
|
+
version: '9'
|
|
46
|
+
email: support@flippercloud.io
|
|
50
47
|
executables: []
|
|
51
48
|
extensions: []
|
|
52
49
|
extra_rdoc_files: []
|
|
@@ -57,6 +54,7 @@ files:
|
|
|
57
54
|
- examples/active_record/ar_setup.rb
|
|
58
55
|
- examples/active_record/basic.rb
|
|
59
56
|
- examples/active_record/cached.rb
|
|
57
|
+
- examples/active_record/group_migration.rb
|
|
60
58
|
- examples/active_record/internals.rb
|
|
61
59
|
- flipper-active_record.gemspec
|
|
62
60
|
- lib/flipper-active_record.rb
|
|
@@ -67,12 +65,16 @@ files:
|
|
|
67
65
|
- spec/flipper/adapters/active_record_spec.rb
|
|
68
66
|
- test/adapters/active_record_test.rb
|
|
69
67
|
- test_rails/generators/flipper/active_record_generator_test.rb
|
|
70
|
-
homepage: https://
|
|
68
|
+
homepage: https://www.flippercloud.io/docs/adapters/active-record
|
|
71
69
|
licenses:
|
|
72
70
|
- MIT
|
|
73
71
|
metadata:
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
documentation_uri: https://www.flippercloud.io/docs
|
|
73
|
+
homepage_uri: https://www.flippercloud.io
|
|
74
|
+
source_code_uri: https://github.com/flippercloud/flipper
|
|
75
|
+
bug_tracker_uri: https://github.com/flippercloud/flipper/issues
|
|
76
|
+
changelog_uri: https://github.com/flippercloud/flipper/releases/tag/v1.3.4
|
|
77
|
+
funding_uri: https://github.com/sponsors/flippercloud
|
|
76
78
|
rdoc_options: []
|
|
77
79
|
require_paths:
|
|
78
80
|
- lib
|
|
@@ -87,10 +89,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
87
89
|
- !ruby/object:Gem::Version
|
|
88
90
|
version: '0'
|
|
89
91
|
requirements: []
|
|
90
|
-
rubygems_version: 3.
|
|
91
|
-
signing_key:
|
|
92
|
+
rubygems_version: 3.6.5
|
|
92
93
|
specification_version: 4
|
|
93
|
-
summary: ActiveRecord adapter for Flipper
|
|
94
|
+
summary: ActiveRecord feature flag adapter for Flipper
|
|
94
95
|
test_files:
|
|
95
96
|
- spec/flipper/adapters/active_record_spec.rb
|
|
96
97
|
- test/adapters/active_record_test.rb
|