From fea8f3f8e3bb9697103ade6caa0065d0a9ff426b Mon Sep 17 00:00:00 2001 From: Trevor Vallender Date: Thu, 25 Jan 2024 15:55:49 +0000 Subject: [PATCH] Initial commit --- MIT-LICENSE | 20 ++++++ README.md | 68 ++++++++++++++++++ flaggle_rock-0.1.0.gem | Bin 0 -> 6144 bytes flaggle_rock-0.1.1.gem | Bin 0 -> 6144 bytes flaggle_rock.gemspec | 11 +++ lib/flaggle_rock.rb | 49 +++++++++++++ .../flaggle_rock/install/install_generator.rb | 21 ++++++ .../install/templates/create_feature_flags.rb | 10 +++ 8 files changed, 179 insertions(+) create mode 100644 MIT-LICENSE create mode 100644 README.md create mode 100644 flaggle_rock-0.1.0.gem create mode 100644 flaggle_rock-0.1.1.gem create mode 100644 flaggle_rock.gemspec create mode 100644 lib/flaggle_rock.rb create mode 100644 lib/generators/flaggle_rock/install/install_generator.rb create mode 100644 lib/generators/flaggle_rock/install/templates/create_feature_flags.rb diff --git a/MIT-LICENSE b/MIT-LICENSE new file mode 100644 index 0000000..a306f55 --- /dev/null +++ b/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2023 David Heinemeier Hansson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7e51b58 --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ +# Flaggle Rock + +[View on RubyGems](https://rubygems.org/gems/flaggle_rock) + +Flaggle Rock is a simple feature flagging gem for use with Ruby on Rails applications. + +It is designed for easily hiding features from end users to help enable a fast-moving +CI workflow. It currently does not aim to support more advanced features such as A/B +testing. + +## Installation + +Add Flaggle Rock to your Gemfile: + +```ruby +gem "flaggle_rock" +``` + +And install + +```sh +bundle install +``` + +Generate the migrations, and run them to add feature flags to your database: +```sh +bundle exec rails generate flaggle_rock:install +bundle exec rails db:migrate +``` + +## Usage + +All flags are _off by default_. There is no need to explicitly create a new flag—turning +it on will do this. + +To enable/disable a flag: +```ruby +Rock.on(:flag_name) +Rock.off(:flag_name) +``` +To check whether a flag is on: +```ruby +Rock.on?(:flag_name) +Rock.off?(:flag_name) +``` + +To remove a flag which is no longer used: +```ruby +Rock.delete(:flag_name) +``` + +To remove all disabled flags: +```ruby +Rock.delete_all_disabled +``` + +## Future + +Goals with Flaggle Rock include the creation of a web UI for easily administering flags. + +## Compatibility + +Flaggle Rock has been tested with Rails 7 and PostgreSQL 11, but should work with older versions of +Rails and other database engines. + +## License + +Flaggle Rock is released under the [MIT License](MIT-LICENSE.md). diff --git a/flaggle_rock-0.1.0.gem b/flaggle_rock-0.1.0.gem new file mode 100644 index 0000000000000000000000000000000000000000..192df8238e5500152d32cf73eee7ba5319d2311a GIT binary patch literal 6144 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 WCnkz8|jHGkfo~X0KW6TltO3^d(%0X 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 WpZl|c|K;x&0>2RWg}{FpfxiP_MlTTn literal 0 HcmV?d00001 diff --git a/flaggle_rock.gemspec b/flaggle_rock.gemspec new file mode 100644 index 0000000..ff1905b --- /dev/null +++ b/flaggle_rock.gemspec @@ -0,0 +1,11 @@ +Gem::Specification.new do |s| + s.name = "flaggle_rock" + s.version = "0.1.1" + s.summary = "A simple feature flag gem" + s.authors = ["T S Vallender"] + s.email = "t@tsvallender.co.uk" + s.homepage = "https://git.tsvallender.co.uk/tsv/flaggle_rock" + s.files = Dir["lib/**/*", "MIT-LICENSE", "README.md"] + s.license = "MIT" +end + diff --git a/lib/flaggle_rock.rb b/lib/flaggle_rock.rb new file mode 100644 index 0000000..61c8308 --- /dev/null +++ b/lib/flaggle_rock.rb @@ -0,0 +1,49 @@ +class Rock + def self.on(flag_name) + enable(flag_name, true) + end + + def self.off(flag_name) + enable(flag_name, false) + end + + def self.on?(flag_name) + result = sql <<-SQL + SELECT enabled FROM feature_flags + WHERE name = '#{flag_name}'; + SQL + result&.first&.dig("enabled") || false + end + + def self.off?(flag_name) + !on?(flag_name) + end + + def self.delete(flag_name) + sql <<-SQL + DELETE FROM feature_flags + WHERE name = '#{flag_name}'; + SQL + end + + def self.delete_all_disabled + sql <<-SQL + DELETE FROM feature_flags + WHERE enabled = false; + SQL + end + + private + + def self.enable(flag_name, enabled) + sql <<-SQL + INSERT INTO feature_flags (name, enabled, created_at, updated_at) + VALUES ('#{flag_name}', #{enabled}, now(), now()) + ON CONFLICT (name) DO UPDATE SET enabled = #{enabled}; + SQL + end + + def self.sql(query_string) + ActiveRecord::Base.connection.execute(query_string) + end +end diff --git a/lib/generators/flaggle_rock/install/install_generator.rb b/lib/generators/flaggle_rock/install/install_generator.rb new file mode 100644 index 0000000..d776bb5 --- /dev/null +++ b/lib/generators/flaggle_rock/install/install_generator.rb @@ -0,0 +1,21 @@ +require "rails/generators" +require "rails/generators/migration" + +module FlaggleRock + module Generators + class InstallGenerator < Rails::Generators::Base + include Rails::Generators::Migration + source_root File.expand_path("templates", __dir__) + desc "Generates a migration to create the feature flags table" + + def self.next_migration_number(path) + next_migration_number = current_migration_number(path) + 1 + ActiveRecord::Migration.next_migration_number(next_migration_number) + end + + def copy_migrations + migration_template "create_feature_flags.rb", "db/migrate/create_feature_flags.rb" + end + end + end +end diff --git a/lib/generators/flaggle_rock/install/templates/create_feature_flags.rb b/lib/generators/flaggle_rock/install/templates/create_feature_flags.rb new file mode 100644 index 0000000..8407880 --- /dev/null +++ b/lib/generators/flaggle_rock/install/templates/create_feature_flags.rb @@ -0,0 +1,10 @@ +class CreateFeatureFlags < ActiveRecord::Migration[7.0] + def change + create_table :feature_flags do |t| + t.string :name, null: false, index: { unique: true } + t.boolean :enabled, null: false, default: false + + t.timestamps + end + end +end