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)


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

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.



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/talk_to_your_app/plugins/flipper/plugin.rb', line 62

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.



46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/talk_to_your_app/plugins/flipper/plugin.rb', line 46

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.



87
88
89
90
91
92
93
94
95
96
# File 'lib/talk_to_your_app/plugins/flipper/plugin.rb', line 87

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

.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.



113
114
115
116
117
118
119
120
121
# File 'lib/talk_to_your_app/plugins/flipper/plugin.rb', line 113

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.



82
83
84
# File 'lib/talk_to_your_app/plugins/flipper/plugin.rb', line 82

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