detour 0.0.1
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/.gitignore +18 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +261 -0
- data/Rakefile +1 -0
- data/detour.gemspec +33 -0
- data/lib/detour/acts_as_flaggable.rb +36 -0
- data/lib/detour/feature.rb +312 -0
- data/lib/detour/flag.rb +13 -0
- data/lib/detour/flaggable.rb +66 -0
- data/lib/detour/flaggable_flag.rb +10 -0
- data/lib/detour/group_flag.rb +8 -0
- data/lib/detour/opt_out_flag.rb +10 -0
- data/lib/detour/percentage_flag.rb +11 -0
- data/lib/detour/version.rb +3 -0
- data/lib/detour.rb +35 -0
- data/lib/generators/active_record_rollout_generator.rb +20 -0
- data/lib/generators/templates/active_record_rollout.rb +5 -0
- data/lib/generators/templates/migration.rb +30 -0
- data/lib/tasks/detour.rake +119 -0
- data/spec/integration/flag_rollout_spec.rb +27 -0
- data/spec/integration/group_rollout_spec.rb +20 -0
- data/spec/integration/percentage_rollout_spec.rb +13 -0
- data/spec/lib/active_record/rollout/acts_as_flaggable_spec.rb +31 -0
- data/spec/lib/active_record/rollout/feature_spec.rb +280 -0
- data/spec/lib/active_record/rollout/flag_spec.rb +8 -0
- data/spec/lib/active_record/rollout/flaggable_flag_spec.rb +9 -0
- data/spec/lib/active_record/rollout/flaggable_spec.rb +149 -0
- data/spec/lib/active_record/rollout/group_flag_spec.rb +8 -0
- data/spec/lib/active_record/rollout/opt_out_flag_spec.rb +9 -0
- data/spec/lib/active_record/rollout/percentage_flag_spec.rb +10 -0
- data/spec/lib/tasks/detour_rake_spec.rb +162 -0
- data/spec/spec_helper.rb +40 -0
- data/spec/support/schema.rb +13 -0
- data/spec/support/shared_contexts/rake.rb +20 -0
- metadata +258 -0
    
        data/lib/detour/flag.rb
    ADDED
    
    | @@ -0,0 +1,13 @@ | |
| 1 | 
            +
            # Indicates that a specific feature has been rolled out to an individual
         | 
| 2 | 
            +
            # Table for storing flaggable flag-ins, group flag-ins, or percentage-based
         | 
| 3 | 
            +
            # flag-ins.
         | 
| 4 | 
            +
            class Detour::Flag < ActiveRecord::Base
         | 
| 5 | 
            +
              self.table_name = :detour_flags
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              belongs_to :feature
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              validates :feature_id, presence: true
         | 
| 10 | 
            +
              validates :flaggable_type, presence: true
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              attr_accessible :flaggable_type
         | 
| 13 | 
            +
            end
         | 
| @@ -0,0 +1,66 @@ | |
| 1 | 
            +
            module Detour::Flaggable
         | 
| 2 | 
            +
              module ClassMethods
         | 
| 3 | 
            +
                # Finds a record by the field set by the :find_by param in
         | 
| 4 | 
            +
                # `acts_as_flaggable`. If no :find_by param was provided, :id is used.
         | 
| 5 | 
            +
                #
         | 
| 6 | 
            +
                # @param [String,Integer] value The value to find the record by.
         | 
| 7 | 
            +
                def flaggable_find!(value)
         | 
| 8 | 
            +
                  send("find_by_#{@detour_flaggable_find_by}!", value)
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              # Returns whether or not the object has access to the given feature. If given
         | 
| 13 | 
            +
              # a block, it will call the block if the user has access to the feature.
         | 
| 14 | 
            +
              #
         | 
| 15 | 
            +
              # If an exception is raised in the block, it will increment the
         | 
| 16 | 
            +
              # `failure_count` of the feature and raise the exception.
         | 
| 17 | 
            +
              #
         | 
| 18 | 
            +
              # @example
         | 
| 19 | 
            +
              #   # Exceptions will be tracked in the `failure_count` of :new_user_interface.
         | 
| 20 | 
            +
              #   user.has_feature?(:new_user_interface) do
         | 
| 21 | 
            +
              #     # ...
         | 
| 22 | 
            +
              #   end
         | 
| 23 | 
            +
              #
         | 
| 24 | 
            +
              # @example
         | 
| 25 | 
            +
              #   # Exceptions will *not* be tracked in the `failure_count` of :new_user_interface.
         | 
| 26 | 
            +
              #   if user.has_feature?(:new_user_interface)
         | 
| 27 | 
            +
              #     # ...
         | 
| 28 | 
            +
              #   end
         | 
| 29 | 
            +
              #
         | 
| 30 | 
            +
              # @param [Symbol] feature_name The name of the
         | 
| 31 | 
            +
              #   {Detour::Feature Feature} being checked.
         | 
| 32 | 
            +
              # @param [Proc] &block A block to be called if the user is flagged in to the
         | 
| 33 | 
            +
              #   feature.
         | 
| 34 | 
            +
              def has_feature?(feature_name, &block)
         | 
| 35 | 
            +
                if detour_features.include? feature_name.to_s
         | 
| 36 | 
            +
                  match = true
         | 
| 37 | 
            +
                else
         | 
| 38 | 
            +
                  feature = Detour::Feature.find_by_name(feature_name)
         | 
| 39 | 
            +
                  return false unless feature
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  opt_out = opt_out_flags.find_by_feature_id(feature.id)
         | 
| 42 | 
            +
                  return false if opt_out
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  match = feature.match? self
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                  if match
         | 
| 47 | 
            +
                    detour_features << feature.name.to_s
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                if match && block_given?
         | 
| 52 | 
            +
                  begin
         | 
| 53 | 
            +
                    yield
         | 
| 54 | 
            +
                  rescue => e
         | 
| 55 | 
            +
                    feature.increment! :failure_count
         | 
| 56 | 
            +
                    raise e
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                match
         | 
| 61 | 
            +
              end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
              def detour_features
         | 
| 64 | 
            +
                @detour_features ||= []
         | 
| 65 | 
            +
              end
         | 
| 66 | 
            +
            end
         | 
| @@ -0,0 +1,10 @@ | |
| 1 | 
            +
            # An individual record of a certain type may be flagged into a feature with
         | 
| 2 | 
            +
            # this class.
         | 
| 3 | 
            +
            class Detour::FlaggableFlag < Detour::Flag
         | 
| 4 | 
            +
              belongs_to :flaggable, polymorphic: true
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              validates :flaggable_id, presence: true
         | 
| 7 | 
            +
              validates :feature_id, uniqueness: { scope: [:flaggable_type, :flaggable_id] }
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              attr_accessible :flaggable
         | 
| 10 | 
            +
            end
         | 
| @@ -0,0 +1,8 @@ | |
| 1 | 
            +
            # A group of flaggable records of a given class may be flagged into a feature
         | 
| 2 | 
            +
            # with this class.
         | 
| 3 | 
            +
            class Detour::GroupFlag < Detour::Flag
         | 
| 4 | 
            +
              validates :group_name, presence: true
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              attr_accessible :group_name
         | 
| 7 | 
            +
              validates :feature_id, uniqueness: { scope: [:flaggable_type, :group_name] }
         | 
| 8 | 
            +
            end
         | 
| @@ -0,0 +1,10 @@ | |
| 1 | 
            +
            # Ensures that a feature will never be available to the associated record,
         | 
| 2 | 
            +
            # even in the case of, for example, a 100% flag.
         | 
| 3 | 
            +
            class Detour::OptOutFlag < Detour::Flag
         | 
| 4 | 
            +
              belongs_to :flaggable, polymorphic: true
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              validates :flaggable_id, presence: true
         | 
| 7 | 
            +
              validates :feature_id, uniqueness: { scope: [:flaggable_type, :flaggable_id] }
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              attr_accessible :flaggable
         | 
| 10 | 
            +
            end
         | 
| @@ -0,0 +1,11 @@ | |
| 1 | 
            +
            # A percentage of flaggable records of a given class may be flagged into a feature
         | 
| 2 | 
            +
            # with this class.
         | 
| 3 | 
            +
            class Detour::PercentageFlag < Detour::Flag
         | 
| 4 | 
            +
              validates :percentage,
         | 
| 5 | 
            +
                presence: true,
         | 
| 6 | 
            +
                numericality: { greater_than: 0, less_than_or_equal_to: 100 }
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              validates :feature_id, uniqueness: { scope: :flaggable_type }
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              attr_accessible :percentage
         | 
| 11 | 
            +
            end
         | 
    
        data/lib/detour.rb
    ADDED
    
    | @@ -0,0 +1,35 @@ | |
| 1 | 
            +
            require "active_record"
         | 
| 2 | 
            +
            require "detour/version"
         | 
| 3 | 
            +
            require "detour/feature"
         | 
| 4 | 
            +
            require "detour/flag"
         | 
| 5 | 
            +
            require "detour/flaggable_flag"
         | 
| 6 | 
            +
            require "detour/group_flag"
         | 
| 7 | 
            +
            require "detour/percentage_flag"
         | 
| 8 | 
            +
            require "detour/opt_out_flag"
         | 
| 9 | 
            +
            require "detour/flaggable"
         | 
| 10 | 
            +
            require "detour/acts_as_flaggable"
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            module Detour
         | 
| 13 | 
            +
              # Allows for configuration of Detour::Feature, mostly intended
         | 
| 14 | 
            +
              # for defining groups:
         | 
| 15 | 
            +
              #
         | 
| 16 | 
            +
              # @example
         | 
| 17 | 
            +
              #   Detour.configure do |config|
         | 
| 18 | 
            +
              #     config.define_user_group :admins do |user|
         | 
| 19 | 
            +
              #       user.admin?
         | 
| 20 | 
            +
              #     end
         | 
| 21 | 
            +
              #   end
         | 
| 22 | 
            +
              def self.configure(&block)
         | 
| 23 | 
            +
                yield Detour::Feature
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
            end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            class Detour::Task < Rails::Railtie
         | 
| 28 | 
            +
              rake_tasks do
         | 
| 29 | 
            +
                Dir[File.join(File.dirname(__FILE__), '../tasks/*.rake')].each { |f| load f }
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
            end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            if defined?(ActiveRecord::Base)
         | 
| 34 | 
            +
              ActiveRecord::Base.extend Detour::ActsAsFlaggable
         | 
| 35 | 
            +
            end
         | 
| @@ -0,0 +1,20 @@ | |
| 1 | 
            +
            require "detour"
         | 
| 2 | 
            +
            require "rails/generators"
         | 
| 3 | 
            +
            require "rails/generators/active_record"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            class DetourGenerator < Rails::Generators::Base
         | 
| 6 | 
            +
              include Rails::Generators::Migration
         | 
| 7 | 
            +
              extend ActiveRecord::Generators::Migration
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              source_root File.expand_path("../templates", __FILE__)
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              desc "Creates migration for Detour"
         | 
| 12 | 
            +
              def create_migration_file
         | 
| 13 | 
            +
                migration_template "migration.rb", "db/migrate/setup_detour.rb"
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              desc "Sets up an initializer for Detour"
         | 
| 17 | 
            +
              def create_initializer
         | 
| 18 | 
            +
                copy_file "detour.rb", "config/initializers/detour.rb"
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
            end
         | 
| @@ -0,0 +1,30 @@ | |
| 1 | 
            +
            class SetupDetour < ActiveRecord::Migration
         | 
| 2 | 
            +
              def change
         | 
| 3 | 
            +
                create_table :detour_features do |t|
         | 
| 4 | 
            +
                  t.string :name
         | 
| 5 | 
            +
                  t.integer :failure_count, default: 0
         | 
| 6 | 
            +
                  t.timestamps
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                add_index :detour_features, :name, unique: true
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                create_table :detour_flags do |t|
         | 
| 12 | 
            +
                  t.string  :type
         | 
| 13 | 
            +
                  t.integer :feature_id
         | 
| 14 | 
            +
                  t.integer :flaggable_id
         | 
| 15 | 
            +
                  t.string  :flaggable_type
         | 
| 16 | 
            +
                  t.string  :group_name
         | 
| 17 | 
            +
                  t.integer :percentage
         | 
| 18 | 
            +
                  t.timestamps
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                add_index :detour_flags, :type
         | 
| 22 | 
            +
                add_index :detour_flags, :feature_id
         | 
| 23 | 
            +
                add_index :detour_flags,
         | 
| 24 | 
            +
                  [:type, :feature_id, :flaggable_type, :flaggable_id],
         | 
| 25 | 
            +
                  name: "flag_type_feature_flaggable_type_id"
         | 
| 26 | 
            +
                add_index :detour_flags,
         | 
| 27 | 
            +
                  [:type, :feature_id, :flaggable_type],
         | 
| 28 | 
            +
                name: "flag_type_feature_flaggable_type"
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
            end
         | 
| @@ -0,0 +1,119 @@ | |
| 1 | 
            +
            namespace :detour do
         | 
| 2 | 
            +
              desc "Create a feature"
         | 
| 3 | 
            +
              task :create, [:feature] => :environment do |task, args|
         | 
| 4 | 
            +
                Detour::Feature.find_or_create_by_name! args[:feature]
         | 
| 5 | 
            +
              end
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              desc "Destroy a feature"
         | 
| 8 | 
            +
              task :destroy, [:feature] => :environment do |task, args|
         | 
| 9 | 
            +
                feature = Detour::Feature.find_by_name! args[:feature]
         | 
| 10 | 
            +
                feature.destroy
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              desc "Activate a feature for a record"
         | 
| 14 | 
            +
              task :activate, [:feature, :flaggable_type, :flaggable_id] => :environment do |task, args|
         | 
| 15 | 
            +
                if args.to_a.length < 3 && Detour::Feature.default_flaggable_class_name
         | 
| 16 | 
            +
                  klass = Detour::Feature.default_flaggable_class_name.constantize
         | 
| 17 | 
            +
                  record_locator = args[:flaggable_type]
         | 
| 18 | 
            +
                else
         | 
| 19 | 
            +
                  klass = args[:flaggable_type].constantize
         | 
| 20 | 
            +
                  record_locator = args[:flaggable_id]
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                record = klass.flaggable_find! record_locator
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                Detour::Feature.add_record_to_feature record, args[:feature]
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              desc "Deactivate a feature for a record"
         | 
| 29 | 
            +
              task :deactivate, [:feature, :flaggable_type, :flaggable_id] => :environment do |task, args|
         | 
| 30 | 
            +
                if args.to_a.length < 3 && Detour::Feature.default_flaggable_class_name
         | 
| 31 | 
            +
                  klass = Detour::Feature.default_flaggable_class_name.constantize
         | 
| 32 | 
            +
                  record_locator = args[:flaggable_type]
         | 
| 33 | 
            +
                else
         | 
| 34 | 
            +
                  klass = args[:flaggable_type].constantize
         | 
| 35 | 
            +
                  record_locator = args[:flaggable_id]
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                record = klass.flaggable_find! record_locator
         | 
| 39 | 
            +
                Detour::Feature.remove_record_from_feature record, args[:feature]
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
              desc "Opt a record out of a feature"
         | 
| 43 | 
            +
              task :opt_out, [:feature, :flaggable_type, :flaggable_id] => :environment do |task, args|
         | 
| 44 | 
            +
                if args.to_a.length < 3 && Detour::Feature.default_flaggable_class_name
         | 
| 45 | 
            +
                  klass = Detour::Feature.default_flaggable_class_name.constantize
         | 
| 46 | 
            +
                  record_locator = args[:flaggable_type]
         | 
| 47 | 
            +
                else
         | 
| 48 | 
            +
                  klass = args[:flaggable_type].constantize
         | 
| 49 | 
            +
                  record_locator = args[:flaggable_id]
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                record = klass.flaggable_find! record_locator
         | 
| 53 | 
            +
                Detour::Feature.opt_record_out_of_feature record, args[:feature]
         | 
| 54 | 
            +
              end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
              desc "Remove an opt out of a record from a feature"
         | 
| 57 | 
            +
              task :un_opt_out, [:feature, :flaggable_type, :flaggable_id] => :environment do |task, args|
         | 
| 58 | 
            +
                if args.to_a.length < 3 && Detour::Feature.default_flaggable_class_name
         | 
| 59 | 
            +
                  klass = Detour::Feature.default_flaggable_class_name.constantize
         | 
| 60 | 
            +
                  record_locator = args[:flaggable_type]
         | 
| 61 | 
            +
                else
         | 
| 62 | 
            +
                  klass = args[:flaggable_type].constantize
         | 
| 63 | 
            +
                  record_locator = args[:flaggable_id]
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                record = klass.flaggable_find! record_locator
         | 
| 67 | 
            +
                Detour::Feature.un_opt_record_out_of_feature record, args[:feature]
         | 
| 68 | 
            +
              end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
              desc "Activate a feature for a group"
         | 
| 71 | 
            +
              task :activate_group, [:feature, :flaggable_type, :group_name] => :environment do |task, args|
         | 
| 72 | 
            +
                if args.to_a.length < 3 && Detour::Feature.default_flaggable_class_name
         | 
| 73 | 
            +
                  klass = Detour::Feature.default_flaggable_class_name
         | 
| 74 | 
            +
                  group_name = args[:flaggable_type]
         | 
| 75 | 
            +
                else
         | 
| 76 | 
            +
                  klass = args[:flaggable_type]
         | 
| 77 | 
            +
                  group_name = args[:group_name]
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                Detour::Feature.add_group_to_feature klass, group_name, args[:feature]
         | 
| 81 | 
            +
              end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
              desc "Deactivate a feature for a group"
         | 
| 84 | 
            +
              task :deactivate_group, [:feature, :flaggable_type, :group_name] => :environment do |task, args|
         | 
| 85 | 
            +
                if args.to_a.length < 3 && Detour::Feature.default_flaggable_class_name
         | 
| 86 | 
            +
                  klass = Detour::Feature.default_flaggable_class_name
         | 
| 87 | 
            +
                  group_name = args[:flaggable_type]
         | 
| 88 | 
            +
                else
         | 
| 89 | 
            +
                  klass = args[:flaggable_type]
         | 
| 90 | 
            +
                  group_name = args[:group_name]
         | 
| 91 | 
            +
                end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                Detour::Feature.remove_group_from_feature klass, group_name, args[:feature]
         | 
| 94 | 
            +
              end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
              desc "Activate a feature for a percentage"
         | 
| 97 | 
            +
              task :activate_percentage, [:feature, :flaggable_type, :percentage] => :environment do |task, args|
         | 
| 98 | 
            +
                if args.to_a.length < 3 && Detour::Feature.default_flaggable_class_name
         | 
| 99 | 
            +
                  klass = Detour::Feature.default_flaggable_class_name
         | 
| 100 | 
            +
                  percentage = args[:flaggable_type]
         | 
| 101 | 
            +
                else
         | 
| 102 | 
            +
                  klass = args[:flaggable_type]
         | 
| 103 | 
            +
                  percentage = args[:percentage]
         | 
| 104 | 
            +
                end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                Detour::Feature.add_percentage_to_feature klass, percentage.to_i, args[:feature]
         | 
| 107 | 
            +
              end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
              desc "Deactivate a feature for a percentage"
         | 
| 110 | 
            +
              task :deactivate_percentage, [:feature, :flaggable_type] => :environment do |task, args|
         | 
| 111 | 
            +
                if args.to_a.length < 3 && Detour::Feature.default_flaggable_class_name
         | 
| 112 | 
            +
                  klass = Detour::Feature.default_flaggable_class_name
         | 
| 113 | 
            +
                else
         | 
| 114 | 
            +
                  klass = args[:flaggable_type]
         | 
| 115 | 
            +
                end
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                Detour::Feature.remove_percentage_from_feature klass, args[:feature]
         | 
| 118 | 
            +
              end
         | 
| 119 | 
            +
            end
         | 
| @@ -0,0 +1,27 @@ | |
| 1 | 
            +
            require "spec_helper"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe "flag rollouts" do
         | 
| 4 | 
            +
              let(:user) { User.create }
         | 
| 5 | 
            +
              let(:feature) { Detour::Feature.create(name: "foo") }
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              describe "creating a flag rollout" do
         | 
| 8 | 
            +
                before do
         | 
| 9 | 
            +
                  Detour::Feature.add_record_to_feature user, feature.name
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                it "sets the feature on the user" do
         | 
| 13 | 
            +
                  feature.match_id?(user).should be_true
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              describe "removing a flag rollout" do
         | 
| 18 | 
            +
                before do
         | 
| 19 | 
            +
                  Detour::Feature.add_record_to_feature user, feature.name
         | 
| 20 | 
            +
                  Detour::Feature.remove_record_from_feature user, feature.name
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                it "removes the feature from the user" do
         | 
| 24 | 
            +
                  feature.match_id?(user).should be_false
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
            end
         | 
| @@ -0,0 +1,20 @@ | |
| 1 | 
            +
            require "spec_helper"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe "group rollouts" do
         | 
| 4 | 
            +
              let(:user) { User.create(name: "foo") }
         | 
| 5 | 
            +
              let(:feature) { Detour::Feature.create(name: "foo") }
         | 
| 6 | 
            +
              let!(:flag) { feature.group_flags.create(flaggable_type: "User", group_name: "foo_users") }
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              describe "creating a group rollout" do
         | 
| 9 | 
            +
                before do
         | 
| 10 | 
            +
                  Detour::Feature.define_user_group "foo_users" do |user|
         | 
| 11 | 
            +
                    user.name == "foo"
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                it "sets the feature on the user" do
         | 
| 16 | 
            +
                  feature.match_groups?(user).should be_true
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
            end
         | 
| 20 | 
            +
             | 
| @@ -0,0 +1,13 @@ | |
| 1 | 
            +
            require "spec_helper"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe "percentage rollouts" do
         | 
| 4 | 
            +
              let(:users) { 10.times.collect { User.create } }
         | 
| 5 | 
            +
              let(:feature) { Detour::Feature.create(name: "foo") }
         | 
| 6 | 
            +
              let!(:flag) { feature.percentage_flags.create(flaggable_type: "User", percentage: 20) }
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              describe "creating a percentage rollout" do
         | 
| 9 | 
            +
                it "makes the feature available to the given percentage of instances" do
         | 
| 10 | 
            +
                  users.select { |user| feature.match_percentage?(user) }.length.should eq users.length / 5
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
            end
         | 
| @@ -0,0 +1,31 @@ | |
| 1 | 
            +
            require "spec_helper"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe Detour::ActsAsFlaggable do
         | 
| 4 | 
            +
              subject { User.new }
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              it { should have_many :flaggable_flags }
         | 
| 7 | 
            +
              it { should have_many :opt_out_flags }
         | 
| 8 | 
            +
              it { should have_many(:features).through(:flaggable_flags) }
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              it "includes Detour::Flaggable" do
         | 
| 11 | 
            +
                subject.class.ancestors.should include Detour::Flaggable
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              describe "#acts_as_flaggable" do
         | 
| 15 | 
            +
                context "when given a :find_by parameter" do
         | 
| 16 | 
            +
                  class Foo < ActiveRecord::Base
         | 
| 17 | 
            +
                    acts_as_flaggable find_by: :email
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  it "sets the appropriate class variable on the class" do
         | 
| 21 | 
            +
                    Foo.instance_variable_get("@detour_flaggable_find_by").should eq :email
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                context "when not given a :find_by parameter" do
         | 
| 26 | 
            +
                  it "uses the default :id value for flaggable_find_by" do
         | 
| 27 | 
            +
                    User.instance_variable_get("@detour_flaggable_find_by").should eq :id
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
            end
         |