flipper 1.1.0 → 1.1.2
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/.github/workflows/ci.yml +18 -0
- data/Changelog.md +304 -283
- data/Gemfile +2 -0
- data/README.md +20 -20
- data/flipper.gemspec +1 -1
- data/lib/flipper/engine.rb +2 -3
- data/lib/flipper/model/active_record.rb +23 -0
- data/lib/flipper/version.rb +1 -1
- data/lib/generators/flipper/templates/update/migrations/01_create_flipper_tables.rb.erb +22 -0
- data/lib/generators/flipper/templates/update/migrations/02_change_flipper_gates_value_to_text.rb.erb +18 -0
- data/lib/generators/flipper/update_generator.rb +35 -0
- data/spec/flipper/model/active_record_spec.rb +61 -0
- data/test_rails/generators/flipper/update_generator_test.rb +96 -0
- data/test_rails/helper.rb +19 -2
- metadata +10 -3
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -4,14 +4,14 @@
|
|
4
4
|
|
5
5
|
# Flipper
|
6
6
|
|
7
|
-
> Beautiful, performant feature flags for Ruby.
|
7
|
+
> Beautiful, performant feature flags for Ruby and Rails.
|
8
8
|
|
9
9
|
Flipper gives you control over who has access to features in your app.
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
- Enable or disable features for everyone, specific actors, groups of actors, a percentage of actors, or a percentage of time.
|
12
|
+
- Configure your feature flags from the console or a web UI.
|
13
|
+
- Regardless of what data store you are using, Flipper can performantly store your feature flags.
|
14
|
+
- Use [Flipper Cloud](#flipper-cloud) to cascade features from multiple environments, share settings with your team, control permissions, keep an audit history, and rollback.
|
15
15
|
|
16
16
|
Control your software — don't let it control you.
|
17
17
|
|
@@ -72,13 +72,13 @@ Read more about [getting started with Flipper](https://flippercloud.io/docs?utm_
|
|
72
72
|
|
73
73
|
Like Flipper and want more? Check out [Flipper Cloud](https://www.flippercloud.io?utm_source=oss&utm_medium=readme&utm_campaign=check_out), which comes with:
|
74
74
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
75
|
+
- **multiple environments** — production, staging, per continent, whatever you need. Every environment inherits from production by default and every project comes with a [project overview page](https://blog.flippercloud.io/project-overview/) that shows each feature and its status in each environment.
|
76
|
+
- **personal environments** — everyone on your team gets a personal environment (that inherits from production) which they can modify however they want without stepping on anyone else's toes.
|
77
|
+
- **permissions** — grant access to everyone in your organization or lockdown each project to particular people. You can even limit access to a particular environment (like production) to specific people.
|
78
|
+
- **audit history** — every feature change and who made it.
|
79
|
+
- **rollbacks** — enable or disable a feature accidentally? No problem. You can roll back to any point in the audit history with a single click.
|
80
|
+
- **maintenance** — we'll keep the lights on for you. We also have handy webhooks and background polling for keeping your app in sync with Cloud, so **our availability won't affect yours**. All your feature flag reads are local to your app.
|
81
|
+
- **everything in one place** — no need to bounce around from different application UIs or IRB consoles.
|
82
82
|
|
83
83
|
[](https://www.flippercloud.io?utm_source=oss&utm_medium=readme&utm_campaign=screenshot)
|
84
84
|
|
@@ -103,11 +103,11 @@ We also have a [free plan](https://www.flippercloud.io?utm_source=oss&utm_medium
|
|
103
103
|
|
104
104
|
## Brought To You By
|
105
105
|
|
106
|
-
| pic
|
107
|
-
|
108
|
-
| 
|
109
|
-
| 
|
110
|
-
| 
|
111
|
-
|  | [@alexwheeler](https://github.com/alexwheeler) | api
|
112
|
-
| 
|
113
|
-
| 
|
106
|
+
| pic | @mention | area |
|
107
|
+
| ---------------------------------------------------------------------- | ---------------------------------------------- | ----------- |
|
108
|
+
|  | [@jnunemaker](https://github.com/jnunemaker) | most things |
|
109
|
+
|  | [@bkeepers](https://github.com/bkeepers) | most things |
|
110
|
+
|  | [@dpep](https://github.com/dpep) | tbd |
|
111
|
+
|  | [@alexwheeler](https://github.com/alexwheeler) | api |
|
112
|
+
|  | [@thetimbanks](https://github.com/thetimbanks) | ui |
|
113
|
+
|  | [@lazebny](https://github.com/lazebny) | docker |
|
data/flipper.gemspec
CHANGED
@@ -23,7 +23,7 @@ ignored_test_files.flatten!.uniq!
|
|
23
23
|
Gem::Specification.new do |gem|
|
24
24
|
gem.authors = ['John Nunemaker']
|
25
25
|
gem.email = 'support@flippercloud.io'
|
26
|
-
gem.summary = 'Beautiful, performant feature flags for Ruby.'
|
26
|
+
gem.summary = 'Beautiful, performant feature flags for Ruby and Rails.'
|
27
27
|
gem.homepage = 'https://www.flippercloud.io/docs'
|
28
28
|
gem.license = 'MIT'
|
29
29
|
|
data/lib/flipper/engine.rb
CHANGED
@@ -15,9 +15,8 @@ module Flipper
|
|
15
15
|
end
|
16
16
|
|
17
17
|
initializer "flipper.properties" do
|
18
|
-
require "flipper/model/active_record"
|
19
|
-
|
20
18
|
ActiveSupport.on_load(:active_record) do
|
19
|
+
require "flipper/model/active_record"
|
21
20
|
ActiveRecord::Base.include Flipper::Model::ActiveRecord
|
22
21
|
end
|
23
22
|
end
|
@@ -71,7 +70,7 @@ module Flipper
|
|
71
70
|
!!ENV["FLIPPER_CLOUD_TOKEN"]
|
72
71
|
end
|
73
72
|
|
74
|
-
def default_strict_value
|
73
|
+
def self.default_strict_value
|
75
74
|
value = ENV["FLIPPER_STRICT"]
|
76
75
|
if value.in?(["warn", "raise", "noop"])
|
77
76
|
value.to_sym
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Flipper
|
2
|
+
module Model
|
3
|
+
module ActiveRecord
|
4
|
+
# The id of the record when used as an actor.
|
5
|
+
#
|
6
|
+
# class User < ActiveRecord::Base
|
7
|
+
# end
|
8
|
+
#
|
9
|
+
# user = User.first
|
10
|
+
# Flipper.enable :some_feature, user
|
11
|
+
# Flipper.enabled? :some_feature, user #=> true
|
12
|
+
#
|
13
|
+
def flipper_id
|
14
|
+
"#{self.class.base_class.name};#{id}"
|
15
|
+
end
|
16
|
+
|
17
|
+
# Properties used to evaluate expressions
|
18
|
+
def flipper_properties
|
19
|
+
{"type" => self.class.name}.merge(attributes)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/flipper/version.rb
CHANGED
@@ -0,0 +1,22 @@
|
|
1
|
+
class CreateFlipperTables < ActiveRecord::Migration<%= migration_version %>
|
2
|
+
def up
|
3
|
+
create_table :flipper_features do |t|
|
4
|
+
t.string :key, null: false
|
5
|
+
t.timestamps null: false
|
6
|
+
end
|
7
|
+
add_index :flipper_features, :key, unique: true
|
8
|
+
|
9
|
+
create_table :flipper_gates do |t|
|
10
|
+
t.string :feature_key, null: false
|
11
|
+
t.string :key, null: false
|
12
|
+
t.string :value
|
13
|
+
t.timestamps null: false
|
14
|
+
end
|
15
|
+
add_index :flipper_gates, [:feature_key, :key, :value], unique: true
|
16
|
+
end
|
17
|
+
|
18
|
+
def down
|
19
|
+
drop_table :flipper_gates
|
20
|
+
drop_table :flipper_features
|
21
|
+
end
|
22
|
+
end
|
data/lib/generators/flipper/templates/update/migrations/02_change_flipper_gates_value_to_text.rb.erb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ChangeFlipperGatesValueToText < ActiveRecord::Migration<%= migration_version %>
|
4
|
+
def up
|
5
|
+
# Ensure this incremental update migration is idempotent
|
6
|
+
return unless connection.column_exists? :flipper_gates, :value, :string
|
7
|
+
|
8
|
+
if index_exists? :flipper_gates, [:feature_key, :key, :value]
|
9
|
+
remove_index :flipper_gates, [:feature_key, :key, :value]
|
10
|
+
end
|
11
|
+
change_column :flipper_gates, :value, :text
|
12
|
+
add_index :flipper_gates, [:feature_key, :key, :value], unique: true, length: { value: 255 }
|
13
|
+
end
|
14
|
+
|
15
|
+
def down
|
16
|
+
change_column :flipper_gates, :value, :string
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails/generators'
|
4
|
+
require 'rails/generators/active_record'
|
5
|
+
|
6
|
+
module Flipper
|
7
|
+
module Generators
|
8
|
+
#
|
9
|
+
# Rails generator used for updating Flipper in a Rails application.
|
10
|
+
# Run it with +bin/rails g flipper:update+ in your console.
|
11
|
+
#
|
12
|
+
class UpdateGenerator < Rails::Generators::Base
|
13
|
+
include ActiveRecord::Generators::Migration
|
14
|
+
|
15
|
+
TEMPLATES = File.join(File.dirname(__FILE__), 'templates/update')
|
16
|
+
source_paths << TEMPLATES
|
17
|
+
|
18
|
+
# Generates incremental migration files unless they already exist.
|
19
|
+
# All migrations should be idempotent e.g. +add_index+ is guarded with +if_index_exists?+
|
20
|
+
def update_migration_files
|
21
|
+
migration_templates = Dir.children(File.join(TEMPLATES, 'migrations')).sort
|
22
|
+
migration_templates.each do |template_file|
|
23
|
+
destination_file = template_file.match(/^\d*_(.*\.rb)/)[1] # 01_create_flipper_tables.rb.erb => create_flipper_tables.rb
|
24
|
+
migration_template "migrations/#{template_file}", File.join(db_migrate_path, destination_file), skip: true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def migration_version
|
31
|
+
"[#{ActiveRecord::VERSION::STRING.to_f}]"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'flipper/model/active_record'
|
3
|
+
|
4
|
+
# Turn off migration logging for specs
|
5
|
+
ActiveRecord::Migration.verbose = false
|
6
|
+
|
7
|
+
RSpec.describe Flipper::Model::ActiveRecord do
|
8
|
+
before(:all) do
|
9
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
|
10
|
+
end
|
11
|
+
|
12
|
+
before(:each) do
|
13
|
+
ActiveRecord::Base.connection.execute <<-SQL
|
14
|
+
CREATE TABLE users (
|
15
|
+
id integer PRIMARY KEY,
|
16
|
+
name string NOT NULL,
|
17
|
+
age integer,
|
18
|
+
is_confirmed boolean,
|
19
|
+
created_at datetime NOT NULL,
|
20
|
+
updated_at datetime NOT NULL
|
21
|
+
)
|
22
|
+
SQL
|
23
|
+
end
|
24
|
+
|
25
|
+
after(:each) do
|
26
|
+
ActiveRecord::Base.connection.execute("DROP table IF EXISTS `users`")
|
27
|
+
end
|
28
|
+
|
29
|
+
class User < ActiveRecord::Base
|
30
|
+
include Flipper::Model::ActiveRecord
|
31
|
+
end
|
32
|
+
|
33
|
+
class Admin < User
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "flipper_id" do
|
37
|
+
it "returns class name and id" do
|
38
|
+
expect(User.new(id: 1).flipper_id).to eq("User;1")
|
39
|
+
end
|
40
|
+
|
41
|
+
it "uses base class name" do
|
42
|
+
expect(Admin.new(id: 2).flipper_id).to eq("User;2")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "flipper_properties" do
|
47
|
+
subject { User.create!(name: "Test", age: 22, is_confirmed: true) }
|
48
|
+
|
49
|
+
it "includes all attributes" do
|
50
|
+
expect(subject.flipper_properties).to eq({
|
51
|
+
"type" => "User",
|
52
|
+
"id" => subject.id,
|
53
|
+
"name" => "Test",
|
54
|
+
"age" => 22,
|
55
|
+
"is_confirmed" => true,
|
56
|
+
"created_at" => subject.created_at,
|
57
|
+
"updated_at" => subject.updated_at
|
58
|
+
})
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require "helper"
|
2
|
+
require "generators/flipper/update_generator"
|
3
|
+
|
4
|
+
class UpdateGeneratorTest < Rails::Generators::TestCase
|
5
|
+
tests Flipper::Generators::UpdateGenerator
|
6
|
+
ROOT = File.expand_path("../../../../tmp/generators", __FILE__)
|
7
|
+
destination ROOT
|
8
|
+
setup :prepare_destination
|
9
|
+
|
10
|
+
setup do
|
11
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
|
12
|
+
end
|
13
|
+
|
14
|
+
teardown do
|
15
|
+
ActiveRecord::Base.connection.close
|
16
|
+
end
|
17
|
+
|
18
|
+
test "generates migrations" do
|
19
|
+
run_generator
|
20
|
+
|
21
|
+
assert_migration "db/migrate/create_flipper_tables.rb" do |migration|
|
22
|
+
assert_method :up, migration do |up|
|
23
|
+
assert_match(/create_table :flipper_features/, up)
|
24
|
+
assert_match(/create_table :flipper_gates/, up)
|
25
|
+
end
|
26
|
+
|
27
|
+
assert_method :down, migration do |down|
|
28
|
+
assert_match(/drop_table :flipper_features/, down)
|
29
|
+
assert_match(/drop_table :flipper_gates/, down)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
assert_migration "db/migrate/change_flipper_gates_value_to_text.rb" do |migration|
|
34
|
+
[:up, :down].each do |dir|
|
35
|
+
assert_method :up, migration do |method|
|
36
|
+
assert_match(/change_column/, method)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
require_migrations
|
42
|
+
|
43
|
+
silence { CreateFlipperTables.migrate(:up) }
|
44
|
+
assert ActiveRecord::Base.connection.table_exists?(:flipper_features)
|
45
|
+
assert ActiveRecord::Base.connection.table_exists?(:flipper_gates)
|
46
|
+
|
47
|
+
assert ActiveRecord::Base.connection.column_exists?(:flipper_gates, :value, :string)
|
48
|
+
silence { ChangeFlipperGatesValueToText.migrate(:up) }
|
49
|
+
assert ActiveRecord::Base.connection.column_exists?(:flipper_gates, :value, :text)
|
50
|
+
|
51
|
+
silence { ChangeFlipperGatesValueToText.migrate(:down) }
|
52
|
+
assert ActiveRecord::Base.connection.column_exists?(:flipper_gates, :value, :string)
|
53
|
+
|
54
|
+
silence { CreateFlipperTables.migrate(:down) }
|
55
|
+
refute ActiveRecord::Base.connection.table_exists?(:flipper_features)
|
56
|
+
refute ActiveRecord::Base.connection.table_exists?(:flipper_gates)
|
57
|
+
end
|
58
|
+
|
59
|
+
test "ChangeFlipperGatesValueToText is a noop if value is already text" do
|
60
|
+
self.class.generator_class = Flipper::Generators::ActiveRecordGenerator
|
61
|
+
run_generator
|
62
|
+
|
63
|
+
self.class.generator_class = Flipper::Generators::UpdateGenerator
|
64
|
+
run_generator
|
65
|
+
|
66
|
+
assert_migration "db/migrate/create_flipper_tables.rb" do |migration|
|
67
|
+
assert_method :up, migration do |up|
|
68
|
+
assert_match /text :value/, up
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
assert_migration "db/migrate/change_flipper_gates_value_to_text.rb"
|
73
|
+
|
74
|
+
require_migrations
|
75
|
+
|
76
|
+
silence { CreateFlipperTables.migrate(:up) }
|
77
|
+
assert ActiveRecord::Base.connection.column_exists?(:flipper_gates, :value, :text)
|
78
|
+
|
79
|
+
assert_nothing_raised do
|
80
|
+
silence { ChangeFlipperGatesValueToText.migrate(:up) }
|
81
|
+
end
|
82
|
+
assert ActiveRecord::Base.connection.column_exists?(:flipper_gates, :value, :text)
|
83
|
+
end
|
84
|
+
|
85
|
+
def require_migrations
|
86
|
+
# If these are not reloaded, then test order can cause failures
|
87
|
+
Object.send(:remove_const, :CreateFlipperTables) if defined?(::CreateFlipperTables)
|
88
|
+
Object.send(:remove_const, :ChangeFlipperGatesValueToText) if defined?(::ChangeFlipperGatesValueToText)
|
89
|
+
|
90
|
+
Dir.glob("#{ROOT}/db/migrate/*.rb").each do |file|
|
91
|
+
assert_nothing_raised do
|
92
|
+
load file
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/test_rails/helper.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require 'bundler'
|
3
|
-
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'minitest/autorun'
|
4
4
|
require 'rails'
|
5
5
|
require 'rails/test_help'
|
6
6
|
|
@@ -9,3 +9,20 @@ begin
|
|
9
9
|
rescue NoMethodError
|
10
10
|
# no biggie, means we are on older version of AS that doesn't have this option
|
11
11
|
end
|
12
|
+
|
13
|
+
def silence
|
14
|
+
# Store the original stderr and stdout in order to restore them later
|
15
|
+
original_stderr = $stderr
|
16
|
+
original_stdout = $stdout
|
17
|
+
|
18
|
+
# Redirect stderr and stdout
|
19
|
+
output = $stderr = $stdout = StringIO.new
|
20
|
+
|
21
|
+
yield
|
22
|
+
|
23
|
+
$stderr = original_stderr
|
24
|
+
$stdout = original_stdout
|
25
|
+
|
26
|
+
# Return output
|
27
|
+
output.string
|
28
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flipper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Nunemaker
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-12-
|
11
|
+
date: 2023-12-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -169,6 +169,7 @@ files:
|
|
169
169
|
- lib/flipper/metadata.rb
|
170
170
|
- lib/flipper/middleware/memoizer.rb
|
171
171
|
- lib/flipper/middleware/setup_env.rb
|
172
|
+
- lib/flipper/model/active_record.rb
|
172
173
|
- lib/flipper/poller.rb
|
173
174
|
- lib/flipper/registry.rb
|
174
175
|
- lib/flipper/serializers/gzip.rb
|
@@ -184,6 +185,9 @@ files:
|
|
184
185
|
- lib/flipper/types/percentage_of_actors.rb
|
185
186
|
- lib/flipper/types/percentage_of_time.rb
|
186
187
|
- lib/flipper/version.rb
|
188
|
+
- lib/generators/flipper/templates/update/migrations/01_create_flipper_tables.rb.erb
|
189
|
+
- lib/generators/flipper/templates/update/migrations/02_change_flipper_gates_value_to_text.rb.erb
|
190
|
+
- lib/generators/flipper/update_generator.rb
|
187
191
|
- spec/fixtures/feature.json
|
188
192
|
- spec/fixtures/flipper_pstore_1679087600.json
|
189
193
|
- spec/flipper/actor_spec.rb
|
@@ -258,6 +262,7 @@ files:
|
|
258
262
|
- spec/flipper/instrumenters/noop_spec.rb
|
259
263
|
- spec/flipper/middleware/memoizer_spec.rb
|
260
264
|
- spec/flipper/middleware/setup_env_spec.rb
|
265
|
+
- spec/flipper/model/active_record_spec.rb
|
261
266
|
- spec/flipper/poller_spec.rb
|
262
267
|
- spec/flipper/registry_spec.rb
|
263
268
|
- spec/flipper/serializers/gzip_spec.rb
|
@@ -282,6 +287,7 @@ files:
|
|
282
287
|
- test/adapters/memory_test.rb
|
283
288
|
- test/adapters/pstore_test.rb
|
284
289
|
- test/test_helper.rb
|
290
|
+
- test_rails/generators/flipper/update_generator_test.rb
|
285
291
|
- test_rails/helper.rb
|
286
292
|
homepage: https://www.flippercloud.io/docs
|
287
293
|
licenses:
|
@@ -310,7 +316,7 @@ requirements: []
|
|
310
316
|
rubygems_version: 3.4.10
|
311
317
|
signing_key:
|
312
318
|
specification_version: 4
|
313
|
-
summary: Beautiful, performant feature flags for Ruby.
|
319
|
+
summary: Beautiful, performant feature flags for Ruby and Rails.
|
314
320
|
test_files:
|
315
321
|
- spec/fixtures/feature.json
|
316
322
|
- spec/fixtures/flipper_pstore_1679087600.json
|
@@ -386,6 +392,7 @@ test_files:
|
|
386
392
|
- spec/flipper/instrumenters/noop_spec.rb
|
387
393
|
- spec/flipper/middleware/memoizer_spec.rb
|
388
394
|
- spec/flipper/middleware/setup_env_spec.rb
|
395
|
+
- spec/flipper/model/active_record_spec.rb
|
389
396
|
- spec/flipper/poller_spec.rb
|
390
397
|
- spec/flipper/registry_spec.rb
|
391
398
|
- spec/flipper/serializers/gzip_spec.rb
|