flipper-active_record 1.0.0 → 1.3.6
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 +10 -3
- data/lib/flipper/adapters/active_record.rb +91 -46
- 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 +11 -13
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8e57f7c82518727c259e650255afa1d8efd820e903f77e6c674e023bfdc7d127
|
|
4
|
+
data.tar.gz: a2c9efa80d93b1f4b110eb63aa7b52bede748dd434ea4833cc41aa3833482930
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a14d913ed677ffbbeca3d2adc45ca704009e4e214b152a5758d5936ef42a2e9419b41e6fdbf9d2a704a61a2758f9add7d5a4d1da56dd08a71a5be42487e654a2
|
|
7
|
+
data.tar.gz: 7909e3048c672f61c2a92ac2a639cb301615178748b2e97fc4c2885161bce81b21977ccc7ac67b7c61edf173189f87dc391062e25cdf4125a4e4ffbb9925a8b4
|
|
@@ -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,15 +1,22 @@
|
|
|
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
18
|
gem.email = 'support@flippercloud.io'
|
|
12
|
-
gem.summary = 'ActiveRecord adapter for Flipper'
|
|
19
|
+
gem.summary = 'ActiveRecord feature flag adapter for Flipper'
|
|
13
20
|
gem.license = 'MIT'
|
|
14
21
|
gem.homepage = 'https://www.flippercloud.io/docs/adapters/active-record'
|
|
15
22
|
|
|
@@ -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,41 @@ 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
|
+
class Model < ::ActiveRecord::Base
|
|
13
|
+
self.abstract_class = true
|
|
14
|
+
end
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
# Private: Do not use outside of this adapter.
|
|
17
|
+
class Feature < Model
|
|
18
|
+
self.table_name = [
|
|
19
|
+
Model.table_name_prefix,
|
|
20
|
+
"flipper_features",
|
|
21
|
+
Model.table_name_suffix,
|
|
22
|
+
].join
|
|
23
|
+
|
|
24
|
+
has_many :gates, foreign_key: "feature_key", primary_key: "key"
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
validates :key, presence: true
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Private: Do not use outside of this adapter.
|
|
30
|
+
class Gate < Model
|
|
31
|
+
self.table_name = [
|
|
32
|
+
Model.table_name_prefix,
|
|
33
|
+
"flipper_gates",
|
|
34
|
+
Model.table_name_suffix,
|
|
35
|
+
].join
|
|
36
|
+
|
|
37
|
+
validates :feature_key, presence: true
|
|
38
|
+
validates :key, presence: true
|
|
39
|
+
end
|
|
31
40
|
end
|
|
32
41
|
|
|
33
|
-
|
|
34
|
-
|
|
42
|
+
VALUE_TO_TEXT_WARNING = <<-EOS
|
|
43
|
+
Your database needs to be migrated to use the latest Flipper features.
|
|
44
|
+
Run `rails generate flipper:update` and `rails db:migrate`.
|
|
45
|
+
EOS
|
|
35
46
|
|
|
36
47
|
# Public: Initialize a new ActiveRecord adapter instance.
|
|
37
48
|
#
|
|
@@ -47,26 +58,27 @@ module Flipper
|
|
|
47
58
|
# can roll your own tables and what not, if you so desire.
|
|
48
59
|
def initialize(options = {})
|
|
49
60
|
@name = options.fetch(:name, :active_record)
|
|
50
|
-
@feature_class = options.fetch(:feature_class) { Feature }
|
|
51
|
-
@gate_class = options.fetch(:gate_class) { Gate }
|
|
61
|
+
@feature_class = options.fetch(:feature_class) { Flipper::Adapters::ActiveRecord::Feature }
|
|
62
|
+
@gate_class = options.fetch(:gate_class) { Flipper::Adapters::ActiveRecord::Gate }
|
|
52
63
|
end
|
|
53
64
|
|
|
54
65
|
# Public: The set of known features.
|
|
55
66
|
def features
|
|
56
|
-
with_connection(@feature_class) { @feature_class.
|
|
67
|
+
with_connection(@feature_class) { @feature_class.distinct.pluck(:key).to_set }
|
|
57
68
|
end
|
|
58
69
|
|
|
59
70
|
# Public: Adds a feature to the set of known features.
|
|
60
71
|
def add(feature)
|
|
61
72
|
with_connection(@feature_class) do
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
@feature_class.create!
|
|
68
|
-
rescue ::ActiveRecord::RecordNotUnique
|
|
73
|
+
@feature_class.transaction(requires_new: true) do
|
|
74
|
+
begin
|
|
75
|
+
# race condition, but add is only used by enable/disable which happen
|
|
76
|
+
# super rarely, so it shouldn't matter in practice
|
|
77
|
+
unless @feature_class.where(key: feature.key).exists?
|
|
78
|
+
@feature_class.create!(key: feature.key)
|
|
69
79
|
end
|
|
80
|
+
rescue ::ActiveRecord::RecordNotUnique
|
|
81
|
+
# already added
|
|
70
82
|
end
|
|
71
83
|
end
|
|
72
84
|
end
|
|
@@ -117,14 +129,14 @@ module Flipper
|
|
|
117
129
|
end
|
|
118
130
|
|
|
119
131
|
def get_all
|
|
120
|
-
with_connection(@feature_class) do
|
|
132
|
+
with_connection(@feature_class) do |connection|
|
|
121
133
|
# query the gates from the db in a single query
|
|
122
134
|
features = ::Arel::Table.new(@feature_class.table_name.to_sym)
|
|
123
135
|
gates = ::Arel::Table.new(@gate_class.table_name.to_sym)
|
|
124
136
|
rows_query = features.join(gates, ::Arel::Nodes::OuterJoin)
|
|
125
137
|
.on(features[:key].eq(gates[:feature_key]))
|
|
126
138
|
.project(features[:key].as('feature_key'), gates[:key], gates[:value])
|
|
127
|
-
gates =
|
|
139
|
+
gates = connection.select_rows(rows_query)
|
|
128
140
|
|
|
129
141
|
# group the gates by feature key
|
|
130
142
|
grouped_gates = gates.inject({}) do |hash, (feature_key, key, value)|
|
|
@@ -156,6 +168,8 @@ module Flipper
|
|
|
156
168
|
set(feature, gate, thing, clear: true)
|
|
157
169
|
when :integer
|
|
158
170
|
set(feature, gate, thing)
|
|
171
|
+
when :json
|
|
172
|
+
set(feature, gate, thing, json: true)
|
|
159
173
|
when :set
|
|
160
174
|
enable_multi(feature, gate, thing)
|
|
161
175
|
else
|
|
@@ -178,6 +192,8 @@ module Flipper
|
|
|
178
192
|
clear(feature)
|
|
179
193
|
when :integer
|
|
180
194
|
set(feature, gate, thing)
|
|
195
|
+
when :json
|
|
196
|
+
delete(feature, gate)
|
|
181
197
|
when :set
|
|
182
198
|
with_connection(@gate_class) do
|
|
183
199
|
@gate_class.where(feature_key: feature.key, key: gate.key, value: thing.value).destroy_all
|
|
@@ -198,15 +214,19 @@ module Flipper
|
|
|
198
214
|
|
|
199
215
|
def set(feature, gate, thing, options = {})
|
|
200
216
|
clear_feature = options.fetch(:clear, false)
|
|
217
|
+
json_feature = options.fetch(:json, false)
|
|
218
|
+
|
|
219
|
+
raise VALUE_TO_TEXT_WARNING if json_feature && value_not_text?
|
|
220
|
+
|
|
201
221
|
with_connection(@gate_class) do
|
|
202
|
-
@gate_class.transaction do
|
|
222
|
+
@gate_class.transaction(requires_new: true) do
|
|
203
223
|
clear(feature) if clear_feature
|
|
204
|
-
|
|
224
|
+
delete(feature, gate)
|
|
205
225
|
begin
|
|
206
226
|
@gate_class.create! do |g|
|
|
207
227
|
g.feature_key = feature.key
|
|
208
228
|
g.key = gate.key
|
|
209
|
-
g.value = thing.value.to_s
|
|
229
|
+
g.value = json_feature ? Typecast.to_json(thing.value) : thing.value.to_s
|
|
210
230
|
end
|
|
211
231
|
rescue ::ActiveRecord::RecordNotUnique
|
|
212
232
|
# assume this happened concurrently with the same thing and its fine
|
|
@@ -218,18 +238,26 @@ module Flipper
|
|
|
218
238
|
nil
|
|
219
239
|
end
|
|
220
240
|
|
|
241
|
+
def delete(feature, gate)
|
|
242
|
+
@gate_class.where(feature_key: feature.key, key: gate.key).destroy_all
|
|
243
|
+
end
|
|
244
|
+
|
|
221
245
|
def enable_multi(feature, gate, thing)
|
|
222
|
-
with_connection(@gate_class) do
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
246
|
+
with_connection(@gate_class) do |connection|
|
|
247
|
+
begin
|
|
248
|
+
connection.transaction(requires_new: true) do
|
|
249
|
+
@gate_class.create! do |g|
|
|
250
|
+
g.feature_key = feature.key
|
|
251
|
+
g.key = gate.key
|
|
252
|
+
g.value = thing.value.to_s
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
rescue ::ActiveRecord::RecordNotUnique
|
|
256
|
+
# already added so move on with life
|
|
227
257
|
end
|
|
228
258
|
end
|
|
229
259
|
|
|
230
260
|
nil
|
|
231
|
-
rescue ::ActiveRecord::RecordNotUnique
|
|
232
|
-
# already added so no need move on with life
|
|
233
261
|
end
|
|
234
262
|
|
|
235
263
|
def result_for_gates(feature, gates)
|
|
@@ -238,13 +266,13 @@ module Flipper
|
|
|
238
266
|
feature.gates.each do |gate|
|
|
239
267
|
result[gate.key] =
|
|
240
268
|
case gate.data_type
|
|
241
|
-
when :boolean
|
|
269
|
+
when :boolean, :integer
|
|
242
270
|
if row = gates.detect { |key, value| !key.nil? && key.to_sym == gate.key }
|
|
243
271
|
row.last
|
|
244
272
|
end
|
|
245
|
-
when :
|
|
273
|
+
when :json
|
|
246
274
|
if row = gates.detect { |key, value| !key.nil? && key.to_sym == gate.key }
|
|
247
|
-
row.last
|
|
275
|
+
Typecast.from_json(row.last)
|
|
248
276
|
end
|
|
249
277
|
when :set
|
|
250
278
|
gates.select { |key, value| !key.nil? && key.to_sym == gate.key }.map(&:last).to_set
|
|
@@ -255,9 +283,26 @@ module Flipper
|
|
|
255
283
|
result
|
|
256
284
|
end
|
|
257
285
|
|
|
286
|
+
# Check if value column is text instead of string
|
|
287
|
+
# See https://github.com/flippercloud/flipper/pull/692
|
|
288
|
+
def value_not_text?
|
|
289
|
+
with_connection(@gate_class) do |connection|
|
|
290
|
+
@gate_class.column_for_attribute(:value).type != :text
|
|
291
|
+
end
|
|
292
|
+
rescue ::ActiveRecord::ActiveRecordError => error
|
|
293
|
+
# If the table doesn't exist, the column doesn't exist either
|
|
294
|
+
warn "#{error.message}. You likely need to run `rails g flipper:active_record` and/or `rails db:migrate`."
|
|
295
|
+
end
|
|
296
|
+
|
|
258
297
|
def with_connection(model = @feature_class, &block)
|
|
298
|
+
warn VALUE_TO_TEXT_WARNING if !warned_about_value_not_text? && value_not_text?
|
|
259
299
|
model.connection_pool.with_connection(&block)
|
|
260
300
|
end
|
|
301
|
+
|
|
302
|
+
def warned_about_value_not_text?
|
|
303
|
+
return @warned_about_value_not_text if defined?(@warned_about_value_not_text)
|
|
304
|
+
@warned_about_value_not_text = true
|
|
305
|
+
end
|
|
261
306
|
end
|
|
262
307
|
end
|
|
263
308
|
end
|
data/lib/flipper/version.rb
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
1
|
module Flipper
|
|
2
|
-
VERSION = '1.
|
|
2
|
+
VERSION = '1.3.6'.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: 1.
|
|
4
|
+
version: 1.3.6
|
|
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: 1980-01-02 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: 1.
|
|
18
|
+
version: 1.3.6
|
|
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: 1.
|
|
25
|
+
version: 1.3.6
|
|
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,8 +42,7 @@ dependencies:
|
|
|
43
42
|
version: '4.2'
|
|
44
43
|
- - "<"
|
|
45
44
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: '
|
|
47
|
-
description:
|
|
45
|
+
version: '9'
|
|
48
46
|
email: support@flippercloud.io
|
|
49
47
|
executables: []
|
|
50
48
|
extensions: []
|
|
@@ -56,6 +54,7 @@ files:
|
|
|
56
54
|
- examples/active_record/ar_setup.rb
|
|
57
55
|
- examples/active_record/basic.rb
|
|
58
56
|
- examples/active_record/cached.rb
|
|
57
|
+
- examples/active_record/group_migration.rb
|
|
59
58
|
- examples/active_record/internals.rb
|
|
60
59
|
- flipper-active_record.gemspec
|
|
61
60
|
- lib/flipper-active_record.rb
|
|
@@ -74,8 +73,8 @@ metadata:
|
|
|
74
73
|
homepage_uri: https://www.flippercloud.io
|
|
75
74
|
source_code_uri: https://github.com/flippercloud/flipper
|
|
76
75
|
bug_tracker_uri: https://github.com/flippercloud/flipper/issues
|
|
77
|
-
changelog_uri: https://github.com/flippercloud/flipper/
|
|
78
|
-
|
|
76
|
+
changelog_uri: https://github.com/flippercloud/flipper/releases/tag/v1.3.6
|
|
77
|
+
funding_uri: https://github.com/sponsors/flippercloud
|
|
79
78
|
rdoc_options: []
|
|
80
79
|
require_paths:
|
|
81
80
|
- lib
|
|
@@ -90,10 +89,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
90
89
|
- !ruby/object:Gem::Version
|
|
91
90
|
version: '0'
|
|
92
91
|
requirements: []
|
|
93
|
-
rubygems_version: 3.
|
|
94
|
-
signing_key:
|
|
92
|
+
rubygems_version: 3.6.9
|
|
95
93
|
specification_version: 4
|
|
96
|
-
summary: ActiveRecord adapter for Flipper
|
|
94
|
+
summary: ActiveRecord feature flag adapter for Flipper
|
|
97
95
|
test_files:
|
|
98
96
|
- spec/flipper/adapters/active_record_spec.rb
|
|
99
97
|
- test/adapters/active_record_test.rb
|