Module: TalkToYourApp::Plugins::Flipper

Defined in:
lib/talk_to_your_app/plugins/flipper/plugin.rb,
lib/talk_to_your_app/plugins/flipper/tools/read_flag.rb,
lib/talk_to_your_app/plugins/flipper/tools/list_flags.rb,
lib/talk_to_your_app/plugins/flipper/tools/enable_flag.rb,
lib/talk_to_your_app/plugins/flipper/tools/disable_flag.rb,
lib/talk_to_your_app/plugins/flipper/tools/enabled_flags.rb

Overview

The Flipper plugin: list flags, read a flag’s state (globally or for an actor), and enable/disable flags globally or per actor. All operations run through the writer-capable :flipper_writer connection — deliberately separate from the DB plugin’s read-only one.

Defined Under Namespace

Modules: Tools Classes: Actor, Plugin

Class Method Summary collapse

Class Method Details

.active?(gates) ⇒ Boolean

A flag is “enabled” if any gate is active.

Returns:

  • (Boolean)


109
110
111
112
113
114
115
# File 'lib/talk_to_your_app/plugins/flipper/plugin.rb', line 109

def active?(gates)
  gates[:boolean] ||
    gates[:actors].any? ||
    gates[:groups].any? ||
    gates[:percentage_of_actors].to_i.positive? ||
    gates[:percentage_of_time].to_i.positive?
end

.actor_for(actor_class, actor_id) ⇒ Object

Builds an actor from a class name + id, or nil when none was supplied.



26
27
28
29
30
# File 'lib/talk_to_your_app/plugins/flipper/plugin.rb', line 26

def actor_for(actor_class, actor_id)
  return nil if actor_class.nil? || actor_id.nil?

  Actor.new("#{actor_class};#{actor_id}")
end

.apply(operation, name, gate) ⇒ Object

Applies :enable or :disable across the resolved gate. Every branch is an explicit named call so the dispatch is statically obvious and an unknown gate type fails fast rather than silently toggling the boolean gate.



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/talk_to_your_app/plugins/flipper/plugin.rb', line 72

def apply(operation, name, gate)
  enabling = operation == :enable
  case gate[:type]
  when :boolean
    enabling ? ::Flipper.enable(name) : ::Flipper.disable(name)
  when :actor
    enabling ? ::Flipper.enable(name, gate[:actor]) : ::Flipper.disable(name, gate[:actor])
  when :group
    enabling ? ::Flipper.enable_group(name, gate[:group]) : ::Flipper.disable_group(name, gate[:group])
  when :percentage_of_actors
    enabling ? ::Flipper.enable_percentage_of_actors(name, gate[:percentage]) : ::Flipper.disable_percentage_of_actors(name)
  when :percentage_of_time
    enabling ? ::Flipper.enable_percentage_of_time(name, gate[:percentage]) : ::Flipper.disable_percentage_of_time(name)
  else
    raise ArgumentError, "unknown Flipper gate type: #{gate[:type].inspect}"
  end
end

.gate_conflict?(args) ⇒ Boolean

True when a call names more than one gate dimension (actor, group, percentage). gate_from would silently pick one by precedence and drop the rest, so the tools reject the call instead.

Returns:

  • (Boolean)


35
36
37
38
39
40
41
# File 'lib/talk_to_your_app/plugins/flipper/plugin.rb', line 35

def gate_conflict?(args)
  selectors = []
  selectors << :actor if args[:actor_class] && args[:actor_id]
  selectors << :group if args[:group]
  selectors << :percentage unless args[:percentage].nil?
  selectors.size > 1
end

.gate_from(args) ⇒ Object

Resolves which Flipper gate a tool call targets from its arguments. In precedence order: a specific actor, a named group, a percentage, else the global boolean gate.



56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/talk_to_your_app/plugins/flipper/plugin.rb', line 56

def gate_from(args)
  if (actor = actor_for(args[:actor_class], args[:actor_id]))
    { type: :actor, actor: actor }
  elsif args[:group]
    { type: :group, group: args[:group].to_sym }
  elsif !args[:percentage].nil?
    type = args[:percentage_type] == "time" ? :percentage_of_time : :percentage_of_actors
    { type: type, percentage: args[:percentage].to_i }
  else
    { type: :boolean }
  end
end

.gate_values(name) ⇒ Object

The full per-gate configuration of a flag, for read_flag.



97
98
99
100
101
102
103
104
105
106
# File 'lib/talk_to_your_app/plugins/flipper/plugin.rb', line 97

def gate_values(name)
  values = ::Flipper[name].gate_values
  {
    boolean: values.boolean,
    actors: values.actors.to_a,
    groups: values.groups.to_a,
    percentage_of_actors: values.percentage_of_actors,
    percentage_of_time: values.percentage_of_time,
  }
end

.invalid_gate_selector_message(args) ⇒ Object



43
44
45
46
47
48
49
50
51
# File 'lib/talk_to_your_app/plugins/flipper/plugin.rb', line 43

def invalid_gate_selector_message(args)
  if args.key?(:actor_class) != args.key?(:actor_id)
    "Specify both actor_class and actor_id when targeting an actor."
  elsif args.key?(:percentage_type) && args[:percentage].nil?
    "Specify percentage when using percentage_type."
  elsif gate_conflict?(args)
    "Specify exactly one gate: an actor, a group, or a percentage."
  end
end

.preload_timestamps(names) ⇒ Object

Last-change timestamps for many flags in a single query, keyed by flag name, read from the flipper_features table when the ActiveRecord adapter is in use. Flipper records no enable/disable history, so updated_at is the last time the feature row changed. Returns an empty hash (callers fall back to nil timestamps) for non-ActiveRecord adapters. Batched to avoid an N+1 across all flags.



123
124
125
126
127
128
129
130
131
# File 'lib/talk_to_your_app/plugins/flipper/plugin.rb', line 123

def preload_timestamps(names)
  return {} unless defined?(::Flipper::Adapters::ActiveRecord::Feature)

  ::Flipper::Adapters::ActiveRecord::Feature.where(key: names.map(&:to_s)).each_with_object({}) do |row, acc|
    acc[row.key] = { created_at: row.created_at&.utc&.iso8601, updated_at: row.updated_at&.utc&.iso8601 }
  end
rescue StandardError
  {}
end

.state(name, actor) ⇒ Object

Reads a flag’s effective state, globally or for an actor. Unknown flags read false.



92
93
94
# File 'lib/talk_to_your_app/plugins/flipper/plugin.rb', line 92

def state(name, actor)
  actor ? ::Flipper.enabled?(name, actor) : ::Flipper.enabled?(name)
end