flipper-active_record 1.3.0 → 1.3.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cd8f5a24abcc3c82a2335e17b796fa84721522eb92bf138ebab4146b58da7fdc
4
- data.tar.gz: c39a1899083f55078e7ca28e23d8b1363609ca1322be6d4f32cfcd23922440e2
3
+ metadata.gz: e82da07ef8d67f32fbd0bd95cd69cf7fb2c2ab8788ed626b1cd79e6cd6e42329
4
+ data.tar.gz: 59465c6d945b524f1986a0062f80f0c3eaefdf54974a0800f03b59853edfb1f8
5
5
  SHA512:
6
- metadata.gz: 16b65bea23bf718930e3509b47ba4719ba4baf93dd86bccf69b292130a7732eff1ab7ea9843c9d4a7342e8180cafd08513bcdd14997be5189e6923afe575e499
7
- data.tar.gz: '01816a252d70e1a9c0bd7215893c54f6ac5dd9444cf9c86b571455edb9b45b0b6bae6072a1e678d283eb70c067a738071a7b7625ace489464dda551574a36121'
6
+ metadata.gz: 6e344370f349b7845b02e62a648bdbbd88bf3c335bd1e1c51d8ff0d4d41ed0cd71ee612fa3a0024ef1fc42ac9804b94608f8d3daf11f7ebce33e129d93797ddc
7
+ data.tar.gz: 86a3dee958e280c3fe2ee35f1fe0f4d29c5a0bcce6f830398bbc851e5d235ed318d10a3031972015834ce0d8c358321ac97d7dcb446e3ee24a3217081390d9d1
@@ -1,4 +1,5 @@
1
1
  require 'set'
2
+ require 'securerandom'
2
3
  require 'flipper'
3
4
  require 'active_record'
4
5
 
@@ -7,34 +8,36 @@ module Flipper
7
8
  class ActiveRecord
8
9
  include ::Flipper::Adapter
9
10
 
10
- # Abstract base class for internal models
11
- class Model < ::ActiveRecord::Base
12
- self.abstract_class = true
13
- end
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
- # Private: Do not use outside of this adapter.
16
- class Feature < Model
17
- self.table_name = [
18
- Model.table_name_prefix,
19
- "flipper_features",
20
- Model.table_name_suffix,
21
- ].join
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
22
24
 
23
- has_many :gates, foreign_key: "feature_key", primary_key: "key"
25
+ has_many :gates, foreign_key: "feature_key", primary_key: "key"
24
26
 
25
- validates :key, presence: true
26
- end
27
+ validates :key, presence: true
28
+ end
27
29
 
28
- # Private: Do not use outside of this adapter.
29
- class Gate < Model
30
- self.table_name = [
31
- Model.table_name_prefix,
32
- "flipper_gates",
33
- Model.table_name_suffix,
34
- ].join
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
35
37
 
36
- validates :feature_key, presence: true
37
- validates :key, presence: true
38
+ validates :feature_key, presence: true
39
+ validates :key, presence: true
40
+ end
38
41
  end
39
42
 
40
43
  VALUE_TO_TEXT_WARNING = <<-EOS
@@ -70,14 +73,15 @@ module Flipper
70
73
  # Public: Adds a feature to the set of known features.
71
74
  def add(feature)
72
75
  with_connection(@feature_class) do
73
- # race condition, but add is only used by enable/disable which happen
74
- # super rarely, so it shouldn't matter in practice
75
- @feature_class.transaction do
76
- unless @feature_class.where(key: feature.key).exists?
77
- begin
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?
78
81
  @feature_class.create!(key: feature.key)
79
- rescue ::ActiveRecord::RecordNotUnique
80
82
  end
83
+ rescue ::ActiveRecord::RecordNotUnique
84
+ # already added
81
85
  end
82
86
  end
83
87
  end
@@ -128,14 +132,14 @@ module Flipper
128
132
  end
129
133
 
130
134
  def get_all
131
- with_connection(@feature_class) do
135
+ with_connection(@feature_class) do |connection|
132
136
  # query the gates from the db in a single query
133
137
  features = ::Arel::Table.new(@feature_class.table_name.to_sym)
134
138
  gates = ::Arel::Table.new(@gate_class.table_name.to_sym)
135
139
  rows_query = features.join(gates, ::Arel::Nodes::OuterJoin)
136
140
  .on(features[:key].eq(gates[:feature_key]))
137
141
  .project(features[:key].as('feature_key'), gates[:key], gates[:value])
138
- gates = @feature_class.connection.select_rows(rows_query)
142
+ gates = connection.select_rows(rows_query)
139
143
 
140
144
  # group the gates by feature key
141
145
  grouped_gates = gates.inject({}) do |hash, (feature_key, key, value)|
@@ -218,10 +222,9 @@ module Flipper
218
222
  raise VALUE_TO_TEXT_WARNING if json_feature && value_not_text?
219
223
 
220
224
  with_connection(@gate_class) do
221
- @gate_class.transaction do
225
+ @gate_class.transaction(requires_new: true) do
222
226
  clear(feature) if clear_feature
223
227
  delete(feature, gate)
224
- @gate_class.where(feature_key: feature.key, key: gate.key).destroy_all
225
228
  begin
226
229
  @gate_class.create! do |g|
227
230
  g.feature_key = feature.key
@@ -243,17 +246,21 @@ module Flipper
243
246
  end
244
247
 
245
248
  def enable_multi(feature, gate, thing)
246
- with_connection(@gate_class) do
247
- @gate_class.create! do |g|
248
- g.feature_key = feature.key
249
- g.key = gate.key
250
- g.value = thing.value.to_s
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
251
260
  end
252
261
  end
253
262
 
254
263
  nil
255
- rescue ::ActiveRecord::RecordNotUnique
256
- # already added so no need move on with life
257
264
  end
258
265
 
259
266
  def result_for_gates(feature, gates)
@@ -1,5 +1,5 @@
1
1
  module Flipper
2
- VERSION = '1.3.0'.freeze
2
+ VERSION = '1.3.1'.freeze
3
3
 
4
4
  REQUIRED_RUBY_VERSION = '2.6'.freeze
5
5
  NEXT_REQUIRED_RUBY_VERSION = '3.0'.freeze
@@ -23,6 +23,7 @@ RSpec.describe Flipper::Adapters::ActiveRecord do
23
23
  {
24
24
  "adapter" => "mysql2",
25
25
  "encoding" => "utf8mb4",
26
+ "host" => ENV["MYSQL_HOST"],
26
27
  "username" => ENV["MYSQL_USER"] || "root",
27
28
  "password" => ENV["MYSQL_PASSWORD"] || "",
28
29
  "database" => ENV["MYSQL_DATABASE"] || "flipper_test",
@@ -42,22 +43,17 @@ RSpec.describe Flipper::Adapters::ActiveRecord do
42
43
  context "with tables created" do
43
44
  before(:all) do
44
45
  skip_on_error(ActiveRecord::ConnectionNotEstablished, "#{config['adapter']} not available") do
45
- silence { ActiveRecord::Tasks::DatabaseTasks.create(config) }
46
+ silence do
47
+ ActiveRecord::Tasks::DatabaseTasks.create(config)
48
+ end
46
49
  end
47
50
 
48
51
  Flipper.configuration = nil
49
52
  end
50
53
 
51
54
  before(:each) do
52
- skip_on_error(ActiveRecord::ConnectionNotEstablished, "#{config['adapter']} not available") do
53
- ActiveRecord::Base.establish_connection(config)
54
- CreateFlipperTables.migrate(:up)
55
- end
56
- end
57
-
58
- after(:each) do
59
55
  ActiveRecord::Tasks::DatabaseTasks.purge(config)
60
- ActiveRecord::Base.connection.close
56
+ CreateFlipperTables.migrate(:up)
61
57
  end
62
58
 
63
59
  after(:all) do
@@ -79,6 +75,20 @@ RSpec.describe Flipper::Adapters::ActiveRecord do
79
75
  flipper.preload([:foo])
80
76
  end
81
77
 
78
+ it 'should not poison wrapping transactions' do
79
+ flipper = Flipper.new(subject)
80
+
81
+ actor = Struct.new(:flipper_id).new('flipper-id-123')
82
+ flipper.enable_actor(:foo, actor)
83
+
84
+ ActiveRecord::Base.transaction do
85
+ flipper.enable_actor(:foo, actor)
86
+ # any read on the next line is fine, just need to ensure that
87
+ # poisoned transaction isn't raised
88
+ expect(Flipper::Adapters::ActiveRecord::Gate.count).to eq(1)
89
+ end
90
+ end
91
+
82
92
  context "ActiveRecord connection_pool" do
83
93
  before do
84
94
  ActiveRecord::Base.connection_handler.clear_active_connections!
@@ -215,18 +225,39 @@ RSpec.describe Flipper::Adapters::ActiveRecord do
215
225
  end
216
226
  end
217
227
 
218
- it "works when table doesn't exist" do
228
+ context "without tables created" do
229
+ before(:all) do
230
+ skip_on_error(ActiveRecord::ConnectionNotEstablished, "#{config['adapter']} not available") do
231
+ silence do
232
+ ActiveRecord::Tasks::DatabaseTasks.create(config)
233
+ end
234
+ end
235
+
236
+ Flipper.configuration = nil
237
+ end
238
+
239
+ before(:each) do
240
+ ActiveRecord::Base.establish_connection(config)
241
+ end
242
+
243
+ after(:each) do
244
+ ActiveRecord::Base.connection.close
245
+ end
219
246
 
220
- Flipper.configuration = nil
221
- Flipper.instance = nil
247
+ after(:all) do
248
+ silence { ActiveRecord::Tasks::DatabaseTasks.drop(config) } unless $skip
249
+ end
222
250
 
223
- Flipper::Adapters.send(:remove_const, :ActiveRecord) if Flipper::Adapters.const_defined?(:ActiveRecord)
251
+ it "does not raise an error" do
252
+ Flipper.configuration = nil
253
+ Flipper.instance = nil
224
254
 
225
- silence do
226
- expect {
227
- load 'flipper/adapters/active_record.rb'
228
- Flipper::Adapters::ActiveRecord.new
229
- }.not_to raise_error
255
+ silence do
256
+ expect {
257
+ load 'flipper/adapters/active_record.rb'
258
+ Flipper::Adapters::ActiveRecord.new
259
+ }.not_to raise_error
260
+ end
230
261
  end
231
262
  end
232
263
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flipper-active_record
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Nunemaker
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-04-17 00:00:00.000000000 Z
11
+ date: 2024-09-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: flipper
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 1.3.0
19
+ version: 1.3.1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 1.3.0
26
+ version: 1.3.1
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activerecord
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -74,7 +74,7 @@ metadata:
74
74
  homepage_uri: https://www.flippercloud.io
75
75
  source_code_uri: https://github.com/flippercloud/flipper
76
76
  bug_tracker_uri: https://github.com/flippercloud/flipper/issues
77
- changelog_uri: https://github.com/flippercloud/flipper/releases/tag/v1.3.0
77
+ changelog_uri: https://github.com/flippercloud/flipper/releases/tag/v1.3.1
78
78
  post_install_message:
79
79
  rdoc_options: []
80
80
  require_paths:
@@ -90,7 +90,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
90
90
  - !ruby/object:Gem::Version
91
91
  version: '0'
92
92
  requirements: []
93
- rubygems_version: 3.5.3
93
+ rubygems_version: 3.5.18
94
94
  signing_key:
95
95
  specification_version: 4
96
96
  summary: ActiveRecord feature flag adapter for Flipper