Skip to content

Commit

Permalink
Merge pull request #692 from jnunemaker/learn-the-rules
Browse files Browse the repository at this point in the history
Expressions
  • Loading branch information
bkeepers authored Jul 17, 2023
2 parents a3cc493 + 189df7f commit 61d189c
Show file tree
Hide file tree
Showing 121 changed files with 3,318 additions and 520 deletions.
15 changes: 14 additions & 1 deletion .github/workflows/examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ on: [push, pull_request]
jobs:
test:
if: github.repository_owner == 'jnunemaker'
name: Test on ruby ${{ matrix.ruby }} and rails ${{ matrix.rails }}
name: Example on ruby ${{ matrix.ruby }} and rails ${{ matrix.rails }}
runs-on: ubuntu-latest
services:
redis:
Expand Down Expand Up @@ -59,4 +59,17 @@ jobs:
- name: Run Examples with Rails ${{ matrix.rails }}
env:
FLIPPER_CLOUD_TOKEN: ${{ secrets.FLIPPER_CLOUD_TOKEN }}
FLIPPER_CLOUD_TOKEN_26_52: ${{ secrets.FLIPPER_CLOUD_TOKEN_26_52 }}
FLIPPER_CLOUD_TOKEN_26_60: ${{ secrets.FLIPPER_CLOUD_TOKEN_26_60 }}
FLIPPER_CLOUD_TOKEN_26_61: ${{ secrets.FLIPPER_CLOUD_TOKEN_26_61 }}
FLIPPER_CLOUD_TOKEN_27_52: ${{ secrets.FLIPPER_CLOUD_TOKEN_27_52 }}
FLIPPER_CLOUD_TOKEN_27_60: ${{ secrets.FLIPPER_CLOUD_TOKEN_27_60 }}
FLIPPER_CLOUD_TOKEN_27_61: ${{ secrets.FLIPPER_CLOUD_TOKEN_27_61 }}
FLIPPER_CLOUD_TOKEN_27_70: ${{ secrets.FLIPPER_CLOUD_TOKEN_27_70 }}
FLIPPER_CLOUD_TOKEN_30_60: ${{ secrets.FLIPPER_CLOUD_TOKEN_30_60 }}
FLIPPER_CLOUD_TOKEN_30_61: ${{ secrets.FLIPPER_CLOUD_TOKEN_30_61 }}
FLIPPER_CLOUD_TOKEN_30_70: ${{ secrets.FLIPPER_CLOUD_TOKEN_30_70 }}
FLIPPER_CLOUD_TOKEN_31_61: ${{ secrets.FLIPPER_CLOUD_TOKEN_31_61 }}
FLIPPER_CLOUD_TOKEN_31_70: ${{ secrets.FLIPPER_CLOUD_TOKEN_31_70 }}
RAILS_VERSION: ${{ matrix.rails }}
run: script/examples
17 changes: 17 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@

All notable changes to this project will be documented in this file.

## Unreleased

### Additions/Changes

* Expressions are now available and considered "alpha". They are not yet documented, but you can see examples in [examples/expressions.rb](examples/expressions.rb). (https://github.com/jnunemaker/flipper/pull/692)

### Breaking Changes

* Removed top level `Flipper.bool`, `Flipper.actors`, `Flipper.time`, `Flipper.actor`, `Flipper.percentage_of_actors`, `Flipper.time`, and `Flipper.percentage_of_time`. Also removed correlated Flipper::DSL instance method. They conflict with some new expression stuff and are rarely if ever used. If you are using them, you can migrate via a search and replace like so:
* Change Flipper.bool => Flipper::Types::Boolean.new
* Change Flipper.boolean => Flipper::Types::Boolean.new
* Change Flipper.actor => Flipper::Types::Actor.new
* Change Flipper.percentage_of_actors => Flipper::Types::PercentageOfActors.new
* Change Flipper.actors => Flipper::Types::PercentageOfActors.new
* Change Flipper.percentage_of_time => Flipper::Types::PercentageOfTime.new
* Change Flipper.time => Flipper::Types::PercentageOfTime.new

## 0.28.1

### Additions/Changes
Expand Down
4 changes: 4 additions & 0 deletions Guardfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ rspec_options = {
guard 'rspec', rspec_options do
watch(%r{^spec/.+_spec\.rb$})
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
watch('lib/flipper/expression.rb') { 'spec/flipper_integration_spec.rb' }
watch('lib/flipper/ui/middleware.rb') { 'spec/flipper/ui_spec.rb' }
watch('lib/flipper/api/middleware.rb') { 'spec/flipper/api_spec.rb' }
watch(/shared_adapter_specs\.rb$/) { 'spec' }
watch('spec/helper.rb') { 'spec' }

# To run all specs on every change... (useful with focus and fit)
# watch(%r{.*}) { 'spec' }
end
8 changes: 8 additions & 0 deletions benchmark/typecast_ips.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,12 @@
x.report("Typecast.to_float '1'") { Flipper::Typecast.to_float('1'.freeze) }
x.report("Typecast.to_float 1.01") { Flipper::Typecast.to_float(1) }
x.report("Typecast.to_float '1.01'") { Flipper::Typecast.to_float('1'.freeze) }

x.report("Typecast.to_number 1") { Flipper::Typecast.to_number(1) }
x.report("Typecast.to_number 1.1") { Flipper::Typecast.to_number(1.1) }
x.report("Typecast.to_number '1'") { Flipper::Typecast.to_number('1'.freeze) }
x.report("Typecast.to_number '1.1'") { Flipper::Typecast.to_number('1.1'.freeze) }
x.report("Typecast.to_number nil") { Flipper::Typecast.to_number(nil) }
time = Time.now
x.report("Typecast.to_number Time.now") { Flipper::Typecast.to_number(time) }
end
12 changes: 12 additions & 0 deletions examples/cloud/cloud_setup.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,15 @@
warn "FLIPPER_CLOUD_TOKEN missing so skipping cloud example."
exit
end

suffix_rails = ENV["RAILS_VERSION"].split(".").take(2).join
suffix_ruby = RUBY_VERSION.split(".").take(2).join
matrix_key = "FLIPPER_CLOUD_TOKEN_#{suffix_ruby}_#{suffix_rails}"

if matrix_token = ENV[matrix_key]
puts "Using #{matrix_key} for FLIPPER_CLOUD_TOKEN"
ENV["FLIPPER_CLOUD_TOKEN"] = matrix_token
else
warn "Missing #{matrix_key}. Go create an environment in flipper cloud and set #{matrix_key} to the adapter token for that environment in github actions secrets."
exit 1
end
213 changes: 213 additions & 0 deletions examples/expressions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
require 'bundler/setup'
require 'flipper'

def assert(value)
if value
p value
else
puts "Expected true but was #{value}. Please correct."
exit 1
end
end

def refute(value)
if value
puts "Expected false but was #{value}. Please correct."
exit 1
else
p value
end
end

def reset
Flipper.disable_expression :something
end

class User < Struct.new(:id, :flipper_properties)
include Flipper::Identifier
end

class Org < Struct.new(:id, :flipper_properties)
include Flipper::Identifier
end

NOW = Time.now.to_i
DAY = 60 * 60 * 24

org = Org.new(1, {
"type" => "Org",
"id" => 1,
"now" => NOW,
})

user = User.new(1, {
"type" => "User",
"id" => 1,
"plan" => "basic",
"age" => 39,
"team_user" => true,
"now" => NOW,
})

admin_user = User.new(2, {
"type" => "User",
"id" => 2,
"admin" => true,
"team_user" => true,
"now" => NOW,
})

other_user = User.new(3, {
"type" => "User",
"id" => 3,
"plan" => "plus",
"age" => 18,
"org_admin" => true,
"now" => NOW - DAY,
})

age_expression = Flipper.property(:age).gte(21)
plan_expression = Flipper.property(:plan).eq("basic")
admin_expression = Flipper.property(:admin).eq(true)

puts "Single Expression"
refute Flipper.enabled?(:something, user)

puts "Enabling single expression"
Flipper.enable :something, plan_expression
assert Flipper.enabled?(:something, user)
refute Flipper.enabled?(:something, admin_user)
refute Flipper.enabled?(:something, other_user)

puts "Disabling single expression"
reset
refute Flipper.enabled?(:something, user)

puts "\n\nAny Expression"
any_expression = Flipper.any(plan_expression, age_expression)
refute Flipper.enabled?(:something, user)

puts "Enabling any expression"
Flipper.enable :something, any_expression
assert Flipper.enabled?(:something, user)
refute Flipper.enabled?(:something, admin_user)
refute Flipper.enabled?(:something, other_user)

puts "Disabling any expression"
reset
refute Flipper.enabled?(:something, user)

puts "\n\nAll Expression"
all_expression = Flipper.all(plan_expression, age_expression)
refute Flipper.enabled?(:something, user)

puts "Enabling all expression"
Flipper.enable :something, all_expression
assert Flipper.enabled?(:something, user)
refute Flipper.enabled?(:something, admin_user)
refute Flipper.enabled?(:something, other_user)

puts "Disabling all expression"
reset
refute Flipper.enabled?(:something, user)

puts "\n\nNested Expression"
nested_expression = Flipper.any(admin_expression, all_expression)
refute Flipper.enabled?(:something, user)
refute Flipper.enabled?(:something, admin_user)
refute Flipper.enabled?(:something, other_user)

puts "Enabling nested expression"
Flipper.enable :something, nested_expression
assert Flipper.enabled?(:something, user)
assert Flipper.enabled?(:something, admin_user)
refute Flipper.enabled?(:something, other_user)

puts "Disabling nested expression"
reset
refute Flipper.enabled?(:something, user)
refute Flipper.enabled?(:something, admin_user)
refute Flipper.enabled?(:something, other_user)

puts "\n\nBoolean Expression"
boolean_expression = Flipper.boolean(true)
Flipper.enable :something, boolean_expression
assert Flipper.enabled?(:something)
assert Flipper.enabled?(:something, user)
reset

puts "\n\nSet of Actors Expression"
set_of_actors_expression = Flipper.any(
Flipper.property(:flipper_id).eq("User;1"),
Flipper.property(:flipper_id).eq("User;3"),
)
Flipper.enable :something, set_of_actors_expression
assert Flipper.enabled?(:something, user)
assert Flipper.enabled?(:something, other_user)
refute Flipper.enabled?(:something, admin_user)
reset

puts "\n\n% of Actors Expression"
percentage_of_actors = Flipper.property(:flipper_id).percentage_of_actors(30)
Flipper.enable :something, percentage_of_actors
refute Flipper.enabled?(:something, user)
refute Flipper.enabled?(:something, other_user)
assert Flipper.enabled?(:something, admin_user)
reset

puts "\n\n% of Actors Per Type Expression"
percentage_of_actors_per_type = Flipper.any(
Flipper.all(
Flipper.property(:type).eq("User"),
Flipper.property(:flipper_id).percentage_of_actors(40),
),
Flipper.all(
Flipper.property(:type).eq("Org"),
Flipper.property(:flipper_id).percentage_of_actors(10),
)
)
Flipper.enable :something, percentage_of_actors_per_type
refute Flipper.enabled?(:something, user) # not in the 40% enabled for Users
assert Flipper.enabled?(:something, other_user)
assert Flipper.enabled?(:something, admin_user)
refute Flipper.enabled?(:something, org) # not in the 10% of enabled for Orgs
reset

puts "\n\nPercentage of Time Expression"
percentage_of_time_expression = Flipper.random(100).lt(50)
Flipper.enable :something, percentage_of_time_expression
results = (1..10000).map { |n| Flipper.enabled?(:something, user) }
enabled, disabled = results.partition { |r| r }
p enabled: enabled.size
p disabled: disabled.size
assert (4_700..5_200).include?(enabled.size)
assert (4_700..5_200).include?(disabled.size)
reset

puts "\n\nChanging single expression to all expression"
Flipper.enable :something, plan_expression
Flipper.enable :something, Flipper.expression(:something).all.add(age_expression)
assert Flipper.enabled?(:something, user)
refute Flipper.enabled?(:something, admin_user)
refute Flipper.enabled?(:something, other_user)

puts "\n\nChanging single expression to any expression"
Flipper.enable :something, plan_expression
Flipper.enable :something, Flipper.expression(:something).any.add(age_expression, admin_expression)
assert Flipper.enabled?(:something, user)
assert Flipper.enabled?(:something, admin_user)
refute Flipper.enabled?(:something, other_user)

puts "\n\nChanging single expression to any expression by adding to condition"
Flipper.enable :something, plan_expression
Flipper.enable :something, Flipper.expression(:something).add(admin_expression)
assert Flipper.enabled?(:something, user)
assert Flipper.enabled?(:something, admin_user)
refute Flipper.enabled?(:something, other_user)

puts "\n\nEnabling based on time"
scheduled_time_expression = Flipper.property(:now).gte(NOW)
Flipper.enable :something, scheduled_time_expression
assert Flipper.enabled?(:something, user)
assert Flipper.enabled?(:something, admin_user)
refute Flipper.enabled?(:something, other_user)
41 changes: 37 additions & 4 deletions lib/flipper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,50 @@ def instance=(flipper)
# Public: All the methods delegated to instance. These should match the
# interface of Flipper::DSL.
def_delegators :instance,
:enabled?, :enable, :disable, :bool, :boolean,
:enable_actor, :disable_actor, :actor,
:enabled?, :enable, :disable,
:enable_expression, :disable_expression,
:expression, :add_expression, :remove_expression,
:enable_actor, :disable_actor,
:enable_group, :disable_group,
:enable_percentage_of_actors, :disable_percentage_of_actors,
:actors, :percentage_of_actors,
:enable_percentage_of_time, :disable_percentage_of_time,
:time, :percentage_of_time,
:features, :feature, :[], :preload, :preload_all,
:adapter, :add, :exist?, :remove, :import, :export,
:memoize=, :memoizing?,
:sync, :sync_secret # For Flipper::Cloud. Will error for OSS Flipper.

def any(*args)
Expression.build({ Any: args.flatten })
end

def all(*args)
Expression.build({ All: args.flatten })
end

def constant(value)
Expression.build(value)
end

def property(name)
Expression.build({ Property: name })
end

def string(value)
Expression.build({ String: value })
end

def number(value)
Expression.build({ Number: value })
end

def boolean(value)
Expression.build({ Boolean: value })
end

def random(max)
Expression.build({ Random: max })
end

# Public: Use this to register a group by name.
#
# name - The Symbol name of the group.
Expand Down Expand Up @@ -157,6 +189,7 @@ def groups_registry=(registry)
require 'flipper/middleware/setup_env'
require 'flipper/poller'
require 'flipper/registry'
require 'flipper/expression'
require 'flipper/type'
require 'flipper/types/actor'
require 'flipper/types/boolean'
Expand Down
9 changes: 6 additions & 3 deletions lib/flipper/actor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@
# to Flipper::Feature#enabled?.
module Flipper
class Actor
attr_reader :flipper_id
attr_reader :flipper_id, :flipper_properties

def initialize(flipper_id)
def initialize(flipper_id, flipper_properties = {})
@flipper_id = flipper_id
@flipper_properties = flipper_properties
end

def eql?(other)
self.class.eql?(other.class) && @flipper_id == other.flipper_id
self.class.eql?(other.class) &&
@flipper_id == other.flipper_id &&
@flipper_properties == other.flipper_properties
end
alias_method :==, :eql?

Expand Down
1 change: 1 addition & 0 deletions lib/flipper/adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ def default_config
boolean: nil,
groups: Set.new,
actors: Set.new,
expression: nil,
percentage_of_actors: nil,
percentage_of_time: nil,
}
Expand Down
Loading

0 comments on commit 61d189c

Please sign in to comment.