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 connection the operator wires in (config.plugin :flipper, connection: :your_writer); boot fails unless that connection is declared role: :writing, since Flipper writes flag state.

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)


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

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.



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

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.



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

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)


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

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.



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

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.



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

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



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

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.



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

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.



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

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