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.
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