-
-
Notifications
You must be signed in to change notification settings - Fork 419
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial pass at active record adapter
- Loading branch information
1 parent
8840e61
commit 73752f1
Showing
5 changed files
with
218 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# -*- encoding: utf-8 -*- | ||
require File.expand_path('../lib/flipper/version', __FILE__) | ||
|
||
flipper_mongo_files = lambda { |file| | ||
file =~ /active_record/ | ||
} | ||
|
||
Gem::Specification.new do |gem| | ||
gem.authors = ["John Nunemaker"] | ||
gem.email = ["[email protected]"] | ||
gem.summary = "ActiveRecord adapter for Flipper" | ||
gem.description = "ActiveRecord adapter for Flipper" | ||
gem.license = "MIT" | ||
gem.homepage = "https://github.com/jnunemaker/flipper" | ||
|
||
gem.files = `git ls-files`.split("\n").select(&flipper_mongo_files) + ["lib/flipper/version.rb"] | ||
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n").select(&flipper_mongo_files) | ||
gem.name = "flipper-active_record" | ||
gem.require_paths = ["lib"] | ||
gem.version = Flipper::VERSION | ||
|
||
gem.add_dependency 'flipper', "~> #{Flipper::VERSION}" | ||
gem.add_dependency 'activerecord', '~> 4.2.0' | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
require 'flipper/adapters/active_record' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
require 'set' | ||
require 'flipper' | ||
require 'active_record' | ||
|
||
module Flipper | ||
module Adapters | ||
class ActiveRecord | ||
include Flipper::Adapter | ||
|
||
class Feature < ::ActiveRecord::Base | ||
self.table_name = "flipper_features" | ||
end | ||
|
||
class Gate < ::ActiveRecord::Base | ||
self.table_name = "flipper_gates" | ||
end | ||
|
||
# Public: The name of the adapter. | ||
attr_reader :name | ||
|
||
def initialize | ||
@name = :active_record | ||
end | ||
|
||
# Public: The set of known features. | ||
def features | ||
Feature.select(:key).map(&:key).to_set | ||
end | ||
|
||
# Public: Adds a feature to the set of known features. | ||
def add(feature) | ||
attributes = {key: feature.key} | ||
# race condition, but add is only used by enable/disable which happen | ||
# super rarely, so it shouldn't matter in practice | ||
Feature.where(attributes).first || Feature.create!(attributes) | ||
true | ||
end | ||
|
||
# Public: Removes a feature from the set of known features. | ||
def remove(feature) | ||
Feature.transaction do | ||
Feature.delete_all(key: feature.key) | ||
clear(feature) | ||
end | ||
true | ||
end | ||
|
||
# Public: Clears the gate values for a feature. | ||
def clear(feature) | ||
Gate.delete_all(feature_key: feature.key) | ||
true | ||
end | ||
|
||
# Public: Gets the values for all gates for a given feature. | ||
# | ||
# Returns a Hash of Flipper::Gate#key => value. | ||
def get(feature) | ||
result = {} | ||
|
||
db_gates = Gate.where(feature_key: feature.key) | ||
|
||
feature.gates.each do |gate| | ||
result[gate.key] = case gate.data_type | ||
when :boolean | ||
if db_gate = db_gates.detect { |db_gate| db_gate.key == gate.key.to_s } | ||
db_gate.value | ||
end | ||
when :integer | ||
if db_gate = db_gates.detect { |db_gate| db_gate.key == gate.key.to_s } | ||
db_gate.value | ||
end | ||
when :set | ||
db_gates.select { |db_gate| db_gate.key == gate.key.to_s }.map(&:value).to_set | ||
else | ||
unsupported_data_type gate.data_type | ||
end | ||
end | ||
|
||
result | ||
end | ||
|
||
# Public: Enables a gate for a given thing. | ||
# | ||
# feature - The Flipper::Feature for the gate. | ||
# gate - The Flipper::Gate to disable. | ||
# thing - The Flipper::Type being disabled for the gate. | ||
# | ||
# Returns true. | ||
def enable(feature, gate, thing) | ||
case gate.data_type | ||
when :boolean, :integer | ||
Gate.create!({ | ||
feature_key: feature.key, | ||
key: gate.key, | ||
value: thing.value.to_s, | ||
}) | ||
when :set | ||
Gate.create!({ | ||
feature_key: feature.key, | ||
key: gate.key, | ||
value: thing.value.to_s, | ||
}) | ||
else | ||
unsupported_data_type gate.data_type | ||
end | ||
|
||
true | ||
end | ||
|
||
# Public: Disables a gate for a given thing. | ||
# | ||
# feature - The Flipper::Feature for the gate. | ||
# gate - The Flipper::Gate to disable. | ||
# thing - The Flipper::Type being disabled for the gate. | ||
# | ||
# Returns true. | ||
def disable(feature, gate, thing) | ||
case gate.data_type | ||
when :boolean | ||
clear(feature) | ||
when :integer | ||
Gate.create!({ | ||
feature_key: feature.key, | ||
key: gate.key, | ||
value: thing.value.to_s, | ||
}) | ||
when :set | ||
Gate.delete_all(feature_key: feature.key, key: gate.key, value: thing.value) | ||
else | ||
unsupported_data_type gate.data_type | ||
end | ||
|
||
true | ||
end | ||
|
||
# Private | ||
def unsupported_data_type(data_type) | ||
raise "#{data_type} is not supported by this adapter" | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
require 'helper' | ||
require 'flipper/adapters/active_record' | ||
require 'flipper/spec/shared_adapter_specs' | ||
|
||
# Turn off migration logging for specs | ||
ActiveRecord::Migration.verbose = false | ||
|
||
class CreateFlipperTables < ActiveRecord::Migration | ||
def self.up | ||
create_table :flipper_features do |t| | ||
t.string :key, null: false | ||
t.timestamps null: false | ||
end | ||
add_index :flipper_features, :key, unique: true | ||
|
||
create_table :flipper_gates do |t| | ||
t.string :feature_key, null: false | ||
t.string :key, null: false | ||
t.string :value | ||
t.timestamps null: false | ||
end | ||
add_index :flipper_gates, [:feature_key, :key, :value], unique: true | ||
end | ||
|
||
def self.down | ||
drop_table :flipper_gates | ||
drop_table :flipper_features | ||
end | ||
end | ||
|
||
RSpec.describe Flipper::Adapters::ActiveRecord do | ||
subject { described_class.new } | ||
|
||
before(:all) do | ||
ActiveRecord::Base.establish_connection({ | ||
adapter: "sqlite3", | ||
database: ":memory:", | ||
}) | ||
end | ||
|
||
before(:each) do | ||
CreateFlipperTables.up | ||
end | ||
|
||
after(:each) do | ||
CreateFlipperTables.down | ||
end | ||
|
||
it_should_behave_like 'a flipper adapter' | ||
end |