flipper-active_record 1.3.0.pre → 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: 7c5c569d1b6a5074ed23b730cb49012e31ea81ec72d0d1ee3bc7fd220e9d8de7
4
- data.tar.gz: 66c02755000391fc1ffd56944dad2e9ee5b8e563437cfe4fd4a6ccd437eb6aed
3
+ metadata.gz: e82da07ef8d67f32fbd0bd95cd69cf7fb2c2ab8788ed626b1cd79e6cd6e42329
4
+ data.tar.gz: 59465c6d945b524f1986a0062f80f0c3eaefdf54974a0800f03b59853edfb1f8
5
5
  SHA512:
6
- metadata.gz: db8c6b8e49eb66d7e680236c000c7b5333ed43f55ede52d868aa1cdf54e6729d7c42c289fa6e9eefa456afd8dbf9227c85385fa05bdab303f5d2520ca00634ad
7
- data.tar.gz: 3607f280b38cc442d142d17a4d56e1f1584bb4e1ca298fa52a03ee5b53d2c4adf86f1a25e8fd784df9d835357d34fe5cd76366b80c7cf92c29c3847e093ec665
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,29 +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"
24
- end
25
+ has_many :gates, foreign_key: "feature_key", primary_key: "key"
26
+
27
+ validates :key, presence: true
28
+ end
25
29
 
26
- # Private: Do not use outside of this adapter.
27
- class Gate < Model
28
- self.table_name = [
29
- Model.table_name_prefix,
30
- "flipper_gates",
31
- Model.table_name_suffix,
32
- ].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
37
+
38
+ validates :feature_key, presence: true
39
+ validates :key, presence: true
40
+ end
33
41
  end
34
42
 
35
43
  VALUE_TO_TEXT_WARNING = <<-EOS
@@ -65,14 +73,15 @@ module Flipper
65
73
  # Public: Adds a feature to the set of known features.
66
74
  def add(feature)
67
75
  with_connection(@feature_class) do
68
- # race condition, but add is only used by enable/disable which happen
69
- # super rarely, so it shouldn't matter in practice
70
- @feature_class.transaction do
71
- unless @feature_class.where(key: feature.key).exists?
72
- 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?
73
81
  @feature_class.create!(key: feature.key)
74
- rescue ::ActiveRecord::RecordNotUnique
75
82
  end
83
+ rescue ::ActiveRecord::RecordNotUnique
84
+ # already added
76
85
  end
77
86
  end
78
87
  end
@@ -123,14 +132,14 @@ module Flipper
123
132
  end
124
133
 
125
134
  def get_all
126
- with_connection(@feature_class) do
135
+ with_connection(@feature_class) do |connection|
127
136
  # query the gates from the db in a single query
128
137
  features = ::Arel::Table.new(@feature_class.table_name.to_sym)
129
138
  gates = ::Arel::Table.new(@gate_class.table_name.to_sym)
130
139
  rows_query = features.join(gates, ::Arel::Nodes::OuterJoin)
131
140
  .on(features[:key].eq(gates[:feature_key]))
132
141
  .project(features[:key].as('feature_key'), gates[:key], gates[:value])
133
- gates = @feature_class.connection.select_rows(rows_query)
142
+ gates = connection.select_rows(rows_query)
134
143
 
135
144
  # group the gates by feature key
136
145
  grouped_gates = gates.inject({}) do |hash, (feature_key, key, value)|
@@ -213,10 +222,9 @@ module Flipper
213
222
  raise VALUE_TO_TEXT_WARNING if json_feature && value_not_text?
214
223
 
215
224
  with_connection(@gate_class) do
216
- @gate_class.transaction do
225
+ @gate_class.transaction(requires_new: true) do
217
226
  clear(feature) if clear_feature
218
227
  delete(feature, gate)
219
- @gate_class.where(feature_key: feature.key, key: gate.key).destroy_all
220
228
  begin
221
229
  @gate_class.create! do |g|
222
230
  g.feature_key = feature.key
@@ -238,17 +246,21 @@ module Flipper
238
246
  end
239
247
 
240
248
  def enable_multi(feature, gate, thing)
241
- with_connection(@gate_class) do
242
- @gate_class.create! do |g|
243
- g.feature_key = feature.key
244
- g.key = gate.key
245
- 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
246
260
  end
247
261
  end
248
262
 
249
263
  nil
250
- rescue ::ActiveRecord::RecordNotUnique
251
- # already added so no need move on with life
252
264
  end
253
265
 
254
266
  def result_for_gates(feature, gates)
@@ -1,5 +1,5 @@
1
1
  module Flipper
2
- VERSION = '1.3.0.pre'.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.pre
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-03-14 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.pre
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.pre
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.pre
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