flipper 1.1.0 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -25,6 +25,8 @@ gem 'benchmark-ips'
25
25
  gem 'stackprof-webnav'
26
26
  gem 'flamegraph'
27
27
  gem 'climate_control'
28
+ gem 'mysql2'
29
+ gem 'pg'
28
30
 
29
31
  group(:guard) do
30
32
  gem 'guard'
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
- * 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.
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
- * **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.
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
  [![Flipper Cloud Screenshot](docs/images/flipper_cloud.png)](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 | @mention | area |
107
- |---|---|---|
108
- | ![@jnunemaker](https://avatars3.githubusercontent.com/u/235?s=64) | [@jnunemaker](https://github.com/jnunemaker) | most things |
109
- | ![@bkeepers](https://avatars3.githubusercontent.com/u/173?s=64) | [@bkeepers](https://github.com/bkeepers) | most things |
110
- | ![@dpep](https://avatars3.githubusercontent.com/u/918804?s=64) | [@dpep](https://github.com/dpep) | tbd |
111
- | ![@alexwheeler](https://avatars3.githubusercontent.com/u/3260042?s=64) | [@alexwheeler](https://github.com/alexwheeler) | api |
112
- | ![@thetimbanks](https://avatars1.githubusercontent.com/u/471801?s=64) | [@thetimbanks](https://github.com/thetimbanks) | ui |
113
- | ![@lazebny](https://avatars1.githubusercontent.com/u/6276766?s=64) | [@lazebny](https://github.com/lazebny) | docker |
106
+ | pic | @mention | area |
107
+ | ---------------------------------------------------------------------- | ---------------------------------------------- | ----------- |
108
+ | ![@jnunemaker](https://avatars3.githubusercontent.com/u/235?s=64) | [@jnunemaker](https://github.com/jnunemaker) | most things |
109
+ | ![@bkeepers](https://avatars3.githubusercontent.com/u/173?s=64) | [@bkeepers](https://github.com/bkeepers) | most things |
110
+ | ![@dpep](https://avatars3.githubusercontent.com/u/918804?s=64) | [@dpep](https://github.com/dpep) | tbd |
111
+ | ![@alexwheeler](https://avatars3.githubusercontent.com/u/3260042?s=64) | [@alexwheeler](https://github.com/alexwheeler) | api |
112
+ | ![@thetimbanks](https://avatars1.githubusercontent.com/u/471801?s=64) | [@thetimbanks](https://github.com/thetimbanks) | ui |
113
+ | ![@lazebny](https://avatars1.githubusercontent.com/u/6276766?s=64) | [@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
 
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Flipper
2
- VERSION = '1.1.0'.freeze
2
+ VERSION = '1.1.2'.freeze
3
3
  end
@@ -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
@@ -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
- Bundler.setup(:default)
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.0
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-08 00:00:00.000000000 Z
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