Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature - Persistent groups #129

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 13 additions & 6 deletions lib/rollout.rb
@@ -1,11 +1,12 @@
require "rollout/version"
require "zlib"
require "set"
require "json"
require 'rollout/version'
require_relative './rollout/persistent_groups'
require 'zlib'
require 'set'
require 'json'

class Rollout
RAND_BASE = (2**32 - 1) / 100.0

class Feature
attr_accessor :groups, :users, :percentage, :data
attr_reader :name, :options
Expand Down Expand Up @@ -136,6 +137,7 @@ def initialize(storage, opts = {})
@storage = storage
@options = opts
@groups = { all: lambda { |user| true } }
@persistent_groups = PersistentGroups.new(@storage)
end

def activate(feature)
Expand Down Expand Up @@ -209,7 +211,8 @@ def define_group(group, &block)

def active?(feature, user = nil)
feature = get(feature)
feature.active?(self, user)
feature.active?(self, user) ||
@persistent_groups.include_in_groups?(feature.groups, user)
end

def user_in_active_users?(feature, user = nil)
Expand All @@ -233,6 +236,10 @@ def deactivate_percentage(feature)
end
end

def active_in_persistent_group?(group, user)
@persistent_groups.include_in_group?(group, user)
end

def active_in_group?(group, user)
f = @groups[group.to_sym]
f && f.call(user)
Expand Down
58 changes: 58 additions & 0 deletions lib/rollout/persistent_groups.rb
@@ -0,0 +1,58 @@
class Rollout
class PersistentGroups
PERSISTENT_GROUPS_KEY = 'feature:__persistent_groups__'.freeze
STORAGE_SEPARATOR_CHAR = ','.freeze

def initialize(storage, cache_age = 60)
@storage = storage
@cache_age = cache_age
end

def include_in_groups?(groups, user_identifier)
groups.any? do |group|
include_in_group?(group, user_identifier)
end
end

def include_in_group?(group, user_identifier)
group = group.to_s
user_identifier = user_identifier.to_s
refresh_persistent_groups

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would use more of cache pattern. move groups to be a method (getter) that refreshes the groups if needed.
refresh_persistent_groups not interesting in this method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks @LitalFiverr 👍

@groups[group] && @groups[group].include?(user_identifier)
end

def set_persistent_group(group, user_identifiers)
raise 'user_identifiers must be an Array' unless user_identifiers.is_a?(Array)

@groups[group] = user_identifiers
save_persistent_group(group)
end

def remove_persistent_group(group)
@groups[group].delete(group)
save_persistent_group(group)
end

private

def refresh_persistent_groups
current_time = Time.now.to_i
if !@last_query_time || @last_query_time + @cache_age < current_time
@groups = get_persistent_groups
@last_query_time = current_time
end
end

def get_persistent_groups
raw_groups = @storage.hgetall(PERSISTENT_GROUPS_KEY)
return {} if raw_groups.nil?

raw_groups.each {|key, val| raw_groups[key] = val.split(STORAGE_SEPARATOR_CHAR) }
end

def save_persistent_group(group_name)
group = @groups[group_name]
@storage.hset(PERSISTENT_GROUPS_KEY, group_name, group.join(STORAGE_SEPARATOR_CHAR))
end
end
end