### Start Console with Ecto Support Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Commands to launch an interactive shell with specific database persistence configurations. ```bash bin/console_ecto postgres bin/console_ecto mysql ``` -------------------------------- ### Setup Test Database for Ecto Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Commands to prepare the database for Ecto persistence tests across different RDBMS types. ```bash MIX_ENV=test PERSISTENCE=ecto mix do ecto.create, ecto.migrate # for postgres rm -rf _build/test/lib/fun_with_flags/ MIX_ENV=test PERSISTENCE=ecto RDBMS=mysql mix do ecto.create, ecto.migrate # for mysql rm -rf _build/test/lib/fun_with_flags/ MIX_ENV=test PERSISTENCE=ecto RDBMS=sqlite mix do ecto.create, ecto.migrate # for sqlite ``` -------------------------------- ### Disable Automatic FunWithFlags Application Start (Option A) Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Prevent the :fun_with_flags application from starting automatically by setting `runtime: false` or `app: false` for the dependency in your Mixfile. This is one method to control startup order. ```diff - {:fun_with_flags, "~> 1.6"}, + {:fun_with_flags, "~> 1.6", runtime: false}, ``` -------------------------------- ### Configure Phoenix PubSub Without Phoenix Framework Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Configure FunWithFlags to use the Phoenix.PubSub adapter when not running within the Phoenix framework. This involves starting the PubSub process and configuring FunWithFlags with the PubSub client name. ```elixir children = [ {Phoenix.PubSub, [name: :my_pubsub_process_name, adapter: Phoenix.PubSub.PG2]} ] opts = [strategy: :one_for_one, name: MyApp.Supervisor] {:ok, _pid} = Supervisor.start_link(children, opts) config :fun_with_flags, :cache_bust_notifications, enabled: true, adapter: FunWithFlags.Notifications.PhoenixPubSub, client: :my_pubsub_process_name ``` -------------------------------- ### Define gate priority interactions Source: https://context7.com/tompave/fun_with_flags/llms.txt Setup structures to demonstrate gate priority: Actor > Group > Boolean > Percentage. ```elixir defmodule MyApp.User do defstruct [:id, :name, groups: []] end defimpl FunWithFlags.Actor, for: MyApp.User do def id(%{id: id}), do: "user:#{id}" end defimpl FunWithFlags.Group, for: MyApp.User do def in?(%{groups: groups}, group), do: group in groups end ``` -------------------------------- ### Implement Actor Protocol for Map Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Implement the FunWithFlags.Actor protocol for the Map type. This example shows how to handle maps with an 'actor_id' key and a fallback for other maps using their MD5 hash. ```elixir defimpl FunWithFlags.Actor, for: Map do def id(%{actor_id: actor_id}) do "map:" <> actor_id end def id(map) do map |> inspect() |> (&:crypto.hash(:md5, &1)).() |> Base.encode16() |> ("map:" <> &1) end end ``` -------------------------------- ### Enable Flag for Percentage of Time Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Enables a flag for a specific percentage of time, allowing for gradual rollout or A/B testing of new features. This example sets the 'alternative_implementation' flag to be enabled 5% of the time. ```elixir FunWithFlags.clear(:alternative_implementation) FunWithFlags.enable(:alternative_implementation, for_percentage_of: {:time, 0.05}) def foo(bar) do if FunWithFlags.enabled?(:alternative_implementation) do new_foo(bar) else old_foo(bar) end end ``` -------------------------------- ### Retrieve all flags Source: https://context7.com/tompave/fun_with_flags/llms.txt Get all configured flags as data structures for debugging purposes. ```elixir FunWithFlags.all_flags() # => {:ok, [ # %FunWithFlags.Flag{name: :feature_a, gates: [...]}, # %FunWithFlags.Flag{name: :feature_b, gates: [...]} # ]} ``` -------------------------------- ### Using percentage-of-actors gates in a Phoenix view Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Example of conditional rendering in a Phoenix view based on a feature flag enabled for a percentage of actors. ```elixir FunWithFlags.clear(:new_design) FunWithFlags.enable(:new_design, for_percentage_of: {:actors, 0.2}) FunWithFlags.enable(:new_design, for_group: "beta_testers") defmodule MyPhoenixApp.MyView do use MyPhoenixApp, :view def render("my_template.html", assigns) do if FunWithFlags.enabled?(:new_design, for: assigns.user) do render("new_template.html", assigns) else render("old_template.html", assigns) end end end ``` -------------------------------- ### Add FunWithFlags Supervisor to Phoenix Application Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Integrate FunWithFlags.Supervisor directly into your Phoenix application's supervision tree to manage its startup alongside other core processes. This is useful when the default automatic start causes race conditions. ```diff defmodule MyPhoenixApp.Application do @moduledoc false use Application def start(_type, _args) do children = [ MyPhoenixApp.Repo, MyPhoenixAppWeb.Telemetry, {Phoenix.PubSub, name: MyPhoenixApp.PubSub}, MyPhoenixAppWeb.Endpoint, + FunWithFlags.Supervisor, ] opts = [strategy: :one_for_one, name: MyPhoenixApp.Supervisor] Supervisor.start_link(children, opts) end # ... ``` -------------------------------- ### Namespace Actor IDs Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Example of namespacing actor IDs to ensure global uniqueness when supporting multiple actor types. This pattern is recommended for clarity and to avoid collisions. ```elixir defimpl FunWithFlags.Actor, for: MyApp.User do def id(user) do "user:" <> to_string(user.id) end end defimpl FunWithFlags.Actor, for: MyApp.Country do def id(country) do "country:" <> country.iso3166 end end ``` -------------------------------- ### Run All Tests Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Executes the full test suite across the matrix of configurations and adapters. ```bash $ mix test.all ``` -------------------------------- ### Add Dependencies Source: https://context7.com/tompave/fun_with_flags/llms.txt Include the library and your chosen persistence adapter in your mix.exs file. ```elixir # mix.exs def deps do [ {:fun_with_flags, "~> 1.13.0"}, # Choose one persistence adapter: {:redix, "~> 1.0"}, # For Redis # OR {:ecto_sql, "~> 3.0"}, # For Ecto (PostgreSQL/MySQL/SQLite) {:postgrex, "~> 0.16"}, # For PostgreSQL # Optional: For Phoenix.PubSub instead of Redis PubSub {:phoenix_pubsub, "~> 2.0"}, ] end ``` -------------------------------- ### Configure Supervision Tree Source: https://context7.com/tompave/fun_with_flags/llms.txt Integrates FunWithFlags into the application supervision tree and configures mix dependencies for manual startup. ```elixir # lib/my_app/application.ex defmodule MyApp.Application do use Application def start(_type, _args) do children = [ MyApp.Repo, MyAppWeb.Telemetry, {Phoenix.PubSub, name: MyApp.PubSub}, MyAppWeb.Endpoint, FunWithFlags.Supervisor, # Add after Phoenix.PubSub ] opts = [strategy: :one_for_one, name: MyApp.Supervisor] Supervisor.start_link(children, opts) end end # mix.exs - Disable auto-start defp deps do [ {:fun_with_flags, "~> 1.13.0", runtime: false}, # ... ] end # For releases, ensure the application is loaded def project do [ app: :my_app, releases: [ my_app: [ applications: [fun_with_flags: :load] ] ] ] end ``` -------------------------------- ### Run Development Tools Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Executes static analysis and type checking tools for local development. ```bash mix credo mix dialyzer ``` -------------------------------- ### Configure Phoenix PubSub Adapter Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Configure FunWithFlags to use the Phoenix.PubSub adapter for cache bust notifications. This requires enabling notifications and providing the PubSub client. ```elixir config :my_app, MyApp.Web.Endpoint, pubsub_server: MyApp.PubSub config :fun_with_flags, :cache_bust_notifications, enabled: true, adapter: FunWithFlags.Notifications.PhoenixPubSub, client: MyApp.PubSub ``` -------------------------------- ### Run Benchmarks with Redis Persistence Source: https://github.com/tompave/fun_with_flags/blob/master/benchmarks/README.md Execute benchmark scripts using Redis for persistence and with caching enabled. Ensure the build directory is clean before running. ```shell rm -r _build/dev/lib/fun_with_flags/ && PERSISTENCE=redis CACHE_ENABLED=true mix run benchmarks/flag.exs ``` -------------------------------- ### Run Benchmarks with Ecto Persistence Source: https://github.com/tompave/fun_with_flags/blob/master/benchmarks/README.md Execute benchmark scripts using Ecto with PostgreSQL as the RDBMS and caching disabled. Ensure the build directory is clean before running. ```shell rm -r _build/dev/lib/fun_with_flags/ && PERSISTENCE=ecto RDBMS=postgres CACHE_ENABLED=false mix run benchmarks/persistence.exs ``` -------------------------------- ### Configure Ecto Persistence Source: https://context7.com/tompave/fun_with_flags/llms.txt Configure the Ecto adapter and Phoenix.PubSub for cache-busting notifications. ```elixir # config/config.exs config :my_app, ecto_repos: [MyApp.Repo] config :my_app, MyApp.Repo, username: "my_db_user", password: "my_secret_password", database: "my_app_dev", hostname: "localhost", pool_size: 10 config :fun_with_flags, :persistence, adapter: FunWithFlags.Store.Persistent.Ecto, repo: MyApp.Repo, ecto_table_name: "fun_with_flags_toggles", # optional, this is the default ecto_primary_key_type: :id # optional, :id or :binary_id # Use Phoenix.PubSub for cache-busting (required with Ecto) config :fun_with_flags, :cache_bust_notifications, enabled: true, adapter: FunWithFlags.Notifications.PhoenixPubSub, client: MyApp.PubSub ``` -------------------------------- ### Configure Custom Persistence Adapter Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Configure FunWithFlags to use a custom persistence adapter by specifying the adapter module in the Mix configuration. ```elixir config :fun_with_flags, :persistence, adapter: MyApp.MyAlternativeFlagStore ``` -------------------------------- ### Implement Percentage-Based Rollouts Source: https://context7.com/tompave/fun_with_flags/llms.txt Configures gradual feature rollouts for a percentage of actors, ensuring deterministic results per actor. ```elixir defmodule MyApp.User do defstruct [:id, :name] end defimpl FunWithFlags.Actor, for: MyApp.User do def id(%{id: id}), do: "user:#{id}" end # Enable for 20% of actors FunWithFlags.enable(:new_checkout, for_percentage_of: {:actors, 0.2}) # Results are deterministic per actor-flag combination user_a = %MyApp.User{id: 100, name: "User A"} user_b = %MyApp.User{id: 200, name: "User B"} # Same user always gets same result for same flag FunWithFlags.enabled?(:new_checkout, for: user_a) # => consistent true/false FunWithFlags.enabled?(:new_checkout, for: user_a) # => same as above # Gradually increase rollout FunWithFlags.enable(:new_checkout, for_percentage_of: {:actors, 0.5}) # 50% FunWithFlags.enable(:new_checkout, for_percentage_of: {:actors, 0.8}) # 80% FunWithFlags.enable(:new_checkout, for_percentage_of: {:actors, 1.0}) # Would need boolean gate # Combine with group gate for beta testers FunWithFlags.enable(:new_checkout, for_group: "beta_testers") # Beta testers always see it, 20% of others see it ``` -------------------------------- ### Configure Ecto Persistence Adapter Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Define the Ecto repository and table settings for feature flag persistence. ```elixir # Normal Phoenix and Ecto configuration. # The repo can either use the Postgres or MySQL adapter. config :my_app, ecto_repos: [MyApp.Repo] config :my_app, MyApp.Repo, username: "my_db_user", password: "my secret db password", database: "my_app_dev", hostname: "localhost", pool_size: 10 # FunWithFlags configuration. config :fun_with_flags, :persistence, adapter: FunWithFlags.Store.Persistent.Ecto, repo: MyApp.Repo, ecto_table_name: "your_table_name", # optional, defaults to "fun_with_flags_toggles" ecto_primary_key_type: :binary_id # optional, defaults to :id # For the primary key type, see also: https://hexdocs.pm/ecto/3.10.3/Ecto.Schema.html#module-schema-attributes ``` -------------------------------- ### Manage FunWithFlags Application via Host Application (Option B) Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Declare that the :fun_with_flags application is managed directly by your host application by including it in `included_applications`. This is an alternative to disabling automatic startup. ```diff def application do [ mod: {MyPhoenixApp.Application, []}, + included_applications: [:fun_with_flags], extra_applications: [:logger, :runtime_tools] ] end ``` -------------------------------- ### Configure FunWithFlags Cache Settings Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Configure the cache behavior in your `config.exs` file. You can enable or disable the cache and set its Time-To-Live (TTL) in seconds. The Redis persistence adapter is the default. ```elixir config :fun_with_flags, :cache, enabled: true, ttl: 900 # in seconds ``` ```elixir # the Redis persistence adapter is the default, no need to set this. config :fun_with_flags, :persistence, [adapter: FunWithFlags.Store.Persistent.Redis] ``` ```elixir # this can be disabled if you are running on a single node and don't need to # sync different ETS caches. It won't have any effect if the cache is disabled. # The Redis PuSub adapter is the default, no need to set this. config :fun_with_flags, :cache_bust_notifications, [enabled: true, adapter: FunWithFlags.Notifications.Redis] ``` ```elixir # Notifications can also be disabled, which will also remove the Redis/Redix dependency config :fun_with_flags, :cache_bust_notifications, [enabled: false] ``` -------------------------------- ### Add FunWithFlags to Mix Dependencies Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Specify FunWithFlags and its optional adapter dependencies in your `mix.exs` file. Choose either Redix for Redis or Ecto SQL for database persistence. Phoenix PubSub is optional for cache-busting notifications. ```elixir def deps do [ {:fun_with_flags, "~> 1.13.0"}, # either: {:redix, "~> 0.9"}, # or: {:ecto_sql, "~> 3.0"}, # optionally, if you don't want to use Redis' builtin pubsub {:phoenix_pubsub, "~> 2.0"}, ] end ``` -------------------------------- ### Configure Redis Persistence Source: https://context7.com/tompave/fun_with_flags/llms.txt Set up Redis connection details for flag storage and cache-busting notifications. ```elixir # config/config.exs config :fun_with_flags, :cache, enabled: true, ttl: 900 # in seconds, 15 minutes config :fun_with_flags, :redis, host: "localhost", port: 6379, database: 0 # Alternative: Use a URL string config :fun_with_flags, :redis, "redis://localhost:6379/0" # Alternative: Read from environment variable config :fun_with_flags, :redis, {:system, "REDIS_URL"} # Redis Sentinel support config :fun_with_flags, :redis, sentinel: [ sentinels: ["redis://localhost:1234/1"], group: "primary" ], database: 5 ``` -------------------------------- ### Explicitly Load FunWithFlags in Releases Source: https://github.com/tompave/fun_with_flags/blob/master/README.md When using `runtime: false` or `app: false` with releases, explicitly load the :fun_with_flags application in the `releases` section of your `mix.exs` file to ensure it's included. ```diff def project do [ app: :my_phoenix_app, + releases: [ + my_phoenix_app: [ + applications: [ + fun_with_flags: :load + ] + ] ] end ``` -------------------------------- ### Configure Redis Connection for FunWithFlags Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Configure the connection details for your Redis instance if you are using it for persistence or cache-busting. These options are forwarded to Redix. A URL string or a {URL, [opts]} tuple can also be used. ```elixir # the Redis options will be forwarded to Redix. config :fun_with_flags, :redis, host: "localhost", port: 6379, database: 0 ``` ```elixir # a URL string can be used instead config :fun_with_flags, :redis, "redis://localhost:6379/0" ``` ```elixir # or a {URL, [opts]} tuple config :fun_with_flags, :redis, {"redis://localhost:6379/0", socket_opts: [:inet6]} ``` -------------------------------- ### Implementing FunWithFlags.Actor protocol and checking gate status Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Demonstrates defining an actor struct, implementing the protocol, and checking feature status based on percentage thresholds. ```elixir defmodule MyApp.User do defstruct [:id, :name] end defimpl FunWithFlags.Actor, for: MyApp.User do def id(%{id: id}) do "user:#{id}" end end frodo = %MyApp.User{id: 1, name: "Frodo Baggins"} sam = %MyApp.User{id: 2, name: "Samwise Gamgee"} pippin = %MyApp.User{id: 3, name: "Peregrin Took"} merry = %MyApp.User{id: 4, name: "Meriadoc Brandybuck"} FunWithFlags.Actor.Percentage.score(frodo, :pipeweed) 0.8658294677734375 FunWithFlags.Actor.Percentage.score(sam, :pipeweed) 0.68426513671875 FunWithFlags.Actor.Percentage.score(pippin, :pipeweed) 0.510528564453125 FunWithFlags.Actor.Percentage.score(merry, :pipeweed) 0.2617645263671875 {:ok, true} = FunWithFlags.enable(:pipeweed, for_percentage_of: {:actors, 0.60}) FunWithFlags.enabled?(:pipeweed, for: frodo) false FunWithFlags.enabled?(:pipeweed, for: sam) false FunWithFlags.enabled?(:pipeweed, for: pippin) true FunWithFlags.enabled?(:pipeweed, for: merry) true {:ok, true} = FunWithFlags.enable(:pipeweed, for_percentage_of: {:actors, 0.685}) FunWithFlags.enabled?(:pipeweed, for: sam) true FunWithFlags.Actor.Percentage.score(pippin, :pipeweed) 0.510528564453125 FunWithFlags.Actor.Percentage.score(pippin, :mushrooms) 0.6050872802734375 FunWithFlags.Actor.Percentage.score(pippin, :palantir) 0.144073486328125 ``` -------------------------------- ### Configure Redis Sentinel Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Set up Redis Sentinel connection details for high availability. ```elixir config :fun_with_flags, :redis, sentinel: [ sentinels: ["redis:://locahost:1234/1"], group: "primary", ], database: 5 ``` -------------------------------- ### Create Ecto Migration Source: https://context7.com/tompave/fun_with_flags/llms.txt Define the database table structure required for Ecto persistence. ```elixir # priv/repo/migrations/YYYYMMDDHHMMSS_create_feature_flags_table.exs defmodule MyApp.Repo.Migrations.CreateFeatureFlagsTable do use Ecto.Migration def up do create table(:fun_with_flags_toggles, primary_key: false) do add :id, :bigserial, primary_key: true # For UUID primary keys, use: add :id, :binary_id, primary_key: true add :flag_name, :string, null: false add :gate_type, :string, null: false add :target, :string, null: false add :enabled, :boolean, null: false end create index( :fun_with_flags_toggles, [:flag_name, :gate_type, :target], [unique: true, name: "fwf_flag_name_gate_target_idx"] ) end def down do drop table(:fun_with_flags_toggles) end end ``` -------------------------------- ### FunWithFlags.all_flag_names/0 Source: https://context7.com/tompave/fun_with_flags/llms.txt Retrieves a list of all configured flag names. ```APIDOC ## FunWithFlags.all_flag_names/0 ### Description Returns a list of all configured flag names as atoms. Useful for debugging and building admin interfaces. ### Response #### Success Response - **result** ({:ok, [atom()]}) - A list of flag names. ``` -------------------------------- ### Configure User Flags and Gates Source: https://context7.com/tompave/fun_with_flags/llms.txt Defines user structs and demonstrates how to enable or disable flags for specific groups or actors. ```elixir admin = %MyApp.User{id: 1, name: "Admin", groups: ["admins"]} beta_user = %MyApp.User{id: 2, name: "Beta", groups: ["beta_testers"]} regular = %MyApp.User{id: 3, name: "Regular", groups: []} blocked = %MyApp.User{id: 4, name: "Blocked", groups: ["beta_testers"]} # Configure flag with multiple gates FunWithFlags.disable(:new_dashboard) # Boolean: disabled FunWithFlags.enable(:new_dashboard, for_group: "beta_testers") # Group: enabled FunWithFlags.enable(:new_dashboard, for_actor: admin) # Actor: enabled FunWithFlags.disable(:new_dashboard, for_actor: blocked) # Actor: disabled # Results based on priority FunWithFlags.enabled?(:new_dashboard) # => false (boolean) FunWithFlags.enabled?(:new_dashboard, for: regular) # => false (boolean, no group match) FunWithFlags.enabled?(:new_dashboard, for: beta_user) # => true (group enabled) FunWithFlags.enabled?(:new_dashboard, for: admin) # => true (actor enabled) FunWithFlags.enabled?(:new_dashboard, for: blocked) # => false (actor disabled overrides group) ``` -------------------------------- ### Configure Redis Environment Variable Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Use a system tuple to read the Redis URL from environment variables. ```elixir config :fun_with_flags, :redis, {:system, "REDIS_URL"} ``` -------------------------------- ### FunWithFlags.all_flags/0 Source: https://context7.com/tompave/fun_with_flags/llms.txt Retrieves all configured flags as data structures. ```APIDOC ## FunWithFlags.all_flags/0 ### Description Returns all configured flags as data structures. Intended for debugging and building admin GUIs, not for runtime flag checks. ### Response #### Success Response - **result** ({:ok, [FunWithFlags.Flag]}) - A list of flag structs. ``` -------------------------------- ### Define Custom Persistence Adapter Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Define a custom persistence adapter module by adopting the `FunWithFlags.Store.Persistent` behaviour. This module will implement the necessary callbacks for custom data storage. ```elixir defmodule MyApp.MyAlternativeFlagStore do @behaviour FunWithFlags.Store.Persistent # implement all the behaviour's callback end ``` -------------------------------- ### Retrieve flag names Source: https://context7.com/tompave/fun_with_flags/llms.txt List all configured flag names as atoms. ```elixir FunWithFlags.all_flag_names() # => {:ok, [:feature_a, :feature_b, :beta_test]} ``` -------------------------------- ### FunWithFlags.enable/2 Source: https://context7.com/tompave/fun_with_flags/llms.txt Enable a feature flag globally or for specific actors, groups, or percentages. Returns {:ok, true} on success or {:error, reason} on failure. ```APIDOC ## FunWithFlags.enable/2 ### Description Enable a feature flag globally or for specific actors, groups, or percentages. ### Parameters - **flag_name** (atom) - Required - The name of the feature flag to enable. - **options** (keyword) - Optional - Configuration for the gate type: - **for_actor** (struct) - Enable for a specific actor. - **for_group** (string) - Enable for a specific group. - **for_percentage_of** (tuple) - Enable for a percentage of {:time, float} or {:actors, float}. ### Response - **{:ok, true}** - Success response. - **{:error, reason}** - Error response if the operation fails. ``` -------------------------------- ### Implement Group protocol Source: https://context7.com/tompave/fun_with_flags/llms.txt Define group membership logic by implementing the Group protocol. ```elixir defmodule MyApp.User do defstruct [:id, :name, :email, :role, groups: []] end defimpl FunWithFlags.Group, for: MyApp.User do def in?(%{email: email}, "employees") do String.ends_with?(email, "@mycompany.com") end def in?(%{role: role}, "admin") do role == :admin end def in?(%{groups: groups}, group_name) do group_name in groups end end # Usage employee = %MyApp.User{ id: 1, name: "Alice", email: "alice@mycompany.com", role: :admin, groups: ["engineering", "platform"] } FunWithFlags.Group.in?(employee, "employees") # => true FunWithFlags.Group.in?(employee, "admin") # => true FunWithFlags.Group.in?(employee, "engineering") # => true FunWithFlags.Group.in?(employee, "sales") # => false FunWithFlags.enable(:internal_tools, for_group: "employees") FunWithFlags.enabled?(:internal_tools, for: employee) # => true ``` -------------------------------- ### Implement Actor protocol Source: https://context7.com/tompave/fun_with_flags/llms.txt Define custom types as actors by implementing the Actor protocol. ```elixir defmodule MyApp.User do defstruct [:id, :name, :email] end defimpl FunWithFlags.Actor, for: MyApp.User do def id(%{id: id}) do "user:#{id}" end end defmodule MyApp.Organization do defstruct [:id, :name] end defimpl FunWithFlags.Actor, for: MyApp.Organization do def id(%{id: id}) do "org:#{id}" end end # Usage user = %MyApp.User{id: 42, name: "Alice"} FunWithFlags.Actor.id(user) # => "user:42" FunWithFlags.enable(:feature, for_actor: user) FunWithFlags.enabled?(:feature, for: user) # => true ``` -------------------------------- ### Implement Actor Protocol for Custom Struct Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Implement the FunWithFlags.Actor protocol for a custom struct like MyApp.User to define how its ID is generated. This allows the struct to be used as an actor. ```elixir defmodule MyApp.User do defstruct [:id, :name] end defimpl FunWithFlags.Actor, for: MyApp.User do def id(%{id: id}) do "user:" <> to_string(id) end end ``` -------------------------------- ### Implement FunWithFlags.Group Protocol for Custom Struct Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Defines a User struct and implements the FunWithFlags.Group protocol to determine group membership based on email, admin status, or an explicit groups list. This allows entities to be evaluated against group gates. ```elixir defmodule MyApp.User do defstruct [:email, admin: false, groups: []] end defimpl FunWithFlags.Group, for: MyApp.User do def in?(%{email: email}, "employee"), do: Regex.match?(~r/@mycompany.com$/, email) def in?(%{admin: is_admin}, "admin"), do: !!is_admin def in?(%{groups: list}, group_name), do: group_name in list end elisabeth = %MyApp.User{email: "elisabeth@mycompany.com", admin: true, groups: ["engineering", "product"]} FunWithFlags.Group.in?(elisabeth, "employee") true FunWithFlags.Group.in?(elisabeth, "admin") true FunWithFlags.Group.in?(elisabeth, "engineering") true FunWithFlags.Group.in?(elisabeth, "marketing") false ``` -------------------------------- ### Enable and Disable Flags for Actors Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Demonstrates enabling and disabling flags for specific actors using the FunWithFlags library. Note that actor gates take precedence over other flag settings. ```elixir {:ok, true} = FunWithFlags.enable(:restful_nights) {:ok, false} = FunWithFlags.disable(:restful_nights, for_actor: bruce) {:ok, true} = FunWithFlags.enable(:batmobile, for_actor: bruce) ``` -------------------------------- ### Enable Feature Flags Source: https://context7.com/tompave/fun_with_flags/llms.txt Activate flags globally or for specific targets like actors, groups, or percentages. ```elixir # Enable globally (boolean gate) FunWithFlags.enable(:new_feature) # => {:ok, true} # Enable for a specific actor user = %MyApp.User{id: 42, name: "Alice"} FunWithFlags.enable(:beta_feature, for_actor: user) # => {:ok, true} # Enable for a group FunWithFlags.enable(:admin_panel, for_group: "administrators") # => {:ok, true} # Enable for percentage of time (50%) FunWithFlags.enable(:a_b_test, for_percentage_of: {:time, 0.5}) # => {:ok, true} # Enable for percentage of actors (30% rollout) FunWithFlags.enable(:gradual_rollout, for_percentage_of: {:actors, 0.3}) # => {:ok, true} ``` -------------------------------- ### Handle Multi-tenancy in Ecto Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Exclude FunWithFlags queries from multi-tenancy requirements using the custom query option. ```elixir # Sample code, only relevant if you followed the Ecto guide on multi tenancy with foreign keys. defmodule MyApp.Repo do use Ecto.Repo, otp_app: :my_app require Ecto.Query @impl true def prepare_query(_operation, query, opts) do cond do # add the check for opts[:fun_with_flags] here: opts[:skip_org_id] || opts[:schema_migration] || opts[:fun_with_flags] -> {query, opts} org_id = opts[:org_id] -> {Ecto.Query.where(query, org_id: ^org_id), opts} true -> raise "expected org_id or skip_org_id to be set" end end end ``` -------------------------------- ### Assign Feature Flags to LiveView Socket Source: https://context7.com/tompave/fun_with_flags/llms.txt In a Phoenix LiveView, use FunWithFlags.enabled?/2 within the mount function to assign feature flag states to the socket's assigns for use in the template. ```elixir defmodule MyAppWeb.SettingsLive do use MyAppWeb, :live_view def mount(_params, session, socket) do user = get_user_from_session(session) socket = assign(socket, %{ user: user, show_beta_features: FunWithFlags.enabled?(:beta_features, for: user), experimental_ui: FunWithFlags.enabled?(:experimental_ui, for: user) }) {:ok, socket} end end ``` -------------------------------- ### Manage ETS Cache Source: https://context7.com/tompave/fun_with_flags/llms.txt Provides utilities to flush or inspect the local ETS cache for debugging purposes. ```elixir # Flush all cached flags (cache will rebuild on next queries) FunWithFlags.Store.Cache.flush() # => true # Dump current cache contents for inspection FunWithFlags.Store.Cache.dump() # => [ # {:my_flag, {%FunWithFlags.Flag{...}, 1234567890, 900}}, # {:another_flag, {%FunWithFlags.Flag{...}, 1234567891, 900}} # ] ``` -------------------------------- ### Conditionally Render Content in Phoenix Template Source: https://context7.com/tompave/fun_with_flags/llms.txt Use EEx syntax in a Phoenix template to conditionally render HTML elements based on assigns like `@show_beta_features` that were set in the LiveView. ```html <%= if @show_beta_features do %>

Beta Features

<% end %> ``` -------------------------------- ### FunWithFlags.get_flag/1 Source: https://context7.com/tompave/fun_with_flags/llms.txt Retrieves a specific flag struct by name. ```APIDOC ## FunWithFlags.get_flag/1 ### Description Retrieve a specific flag struct by name. Returns the flag struct, nil if not found, or an error tuple. ### Parameters #### Arguments - **flag_name** (atom) - Required - The name of the flag to retrieve. ### Response #### Success Response - **result** (FunWithFlags.Flag | nil) - The flag struct or nil if not found. ``` -------------------------------- ### Enable Boolean Feature Flag Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Use `FunWithFlags.enable/1` to globally enable a boolean feature flag. This function returns `{:ok, true}` upon successful enablement. ```elixir {:ok, true} = FunWithFlags.enable(:cool_new_feature) FunWithFlags.enabled?(:cool_new_feature) true ``` -------------------------------- ### Check Flag Status for Actors Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Check the enabled status of flags for specific actors. This shows how to query flag states considering actor-specific configurations. ```elixir FunWithFlags.enabled?(:restful_nights) true FunWithFlags.enabled?(:batmobile) false FunWithFlags.enabled?(:restful_nights, for: alfred) true FunWithFlags.enabled?(:batmobile, for: alfred) false FunWithFlags.enabled?(:restful_nights, for: bruce) false FunWithFlags.enabled?(:batmobile, for: bruce) true ``` -------------------------------- ### Attach Telemetry Handlers Source: https://context7.com/tompave/fun_with_flags/llms.txt Monitors persistence operations by attaching handlers to Telemetry events. ```elixir # Attach a debug handler (logs at :alert level) FunWithFlags.Telemetry.attach_debug_handler() # => :ok # Custom handler for specific events :telemetry.attach( "my-fwf-handler", [:fun_with_flags, :persistence, :write], fn event, measurements, metadata, _config -> IO.inspect({event, metadata.flag_name, metadata.gate}) end, nil ) ``` -------------------------------- ### Clear Entire Feature Flag Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Demonstrates clearing all rules for an entire feature flag. This effectively removes all explicit configurations for the flag, ensuring it behaves according to its default state. ```elixir FunWithFlags.clear(:wands) FunWithFlags.enabled?(:wands) false FunWithFlags.enabled?(:wands, for: harry) false FunWithFlags.enabled?(:wands, for: hagrid) false FunWithFlags.enabled?(:wands, for: dudley) false ``` -------------------------------- ### Implement FunWithFlags.Group Protocol for Map Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Implements the FunWithFlags.Group protocol for the generic Map type. This version checks if a map contains a specific :group key that matches the group name being queried. Useful for simple, ad-hoc group assignments. ```elixir defimpl FunWithFlags.Group, for: Map do def in?(%{group: group_name}, group_name), do: true def in?(_, _), do: false end FunWithFlags.Group.in?(%{group: "dumb_tests"}, "dumb_tests") true ``` -------------------------------- ### Check Flag Status Source: https://context7.com/tompave/fun_with_flags/llms.txt Verify if a feature flag is enabled, optionally scoped to a specific actor. ```elixir # Check global boolean state FunWithFlags.enabled?(:new_feature) # => false # Check for a specific actor user = %MyApp.User{id: 42, name: "Alice"} FunWithFlags.enabled?(:new_feature, for: user) # => true (if enabled for this actor) # Check for nil actor (same as global check) FunWithFlags.enabled?(:new_feature, for: nil) # => false ``` -------------------------------- ### Enable Flag for Specific Group Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Demonstrates how to disable a flag globally and then enable it specifically for the 'engineering' group. Subsequent checks for the flag will respect this group-specific enablement when an actor belonging to that group is provided. ```elixir FunWithFlags.disable(:database_access) FunWithFlags.enable(:database_access, for_group: "engineering") FunWithFlags.enabled?(:database_access) false FunWithFlags.enabled?(:database_access, for: elisabeth) true ``` -------------------------------- ### Clear Actor and Group Rules for a Feature Flag Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Demonstrates clearing rules for a specific actor and group for the ':wands' feature flag. Use this when you no longer need explicit control for certain actors or groups and want to revert to the default flag state. ```elixir alias FunWithFlags.TestUser, as: User harry = %User{id: 1, name: "Harry Potter", groups: ["wizards", "gryffindor"]} hagrid = %User{id: 2, name: "Rubeus Hagrid", groups: ["wizards", "gamekeeper"]} dudley = %User{id: 3, name: "Dudley Dursley", groups: ["muggles"]} FunWithFlags.disable(:wands) FunWithFlags.enable(:wands, for_group: "wizards") FunWithFlags.disable(:wands, for_actor: hagrid) FunWithFlags.enabled?(:wands) false FunWithFlags.enabled?(:wands, for: harry) true FunWithFlags.enabled?(:wands, for: hagrid) false FunWithFlags.enabled?(:wands, for: dudley) false FunWithFlags.clear(:wands, for_actor: hagrid) FunWithFlags.enabled?(:wands, for: hagrid) true FunWithFlags.clear(:wands, for_group: "wizards") FunWithFlags.enabled?(:wands, for: hagrid) false FunWithFlags.enabled?(:wands, for: harry) false FunWithFlags.enable(:magic_powers, for_percentage_of: {:time, 0.0001}) FunWithFlags.clear(:magic_powers, for_percentage: true) ``` -------------------------------- ### Check Feature Flag in Phoenix Controller Source: https://context7.com/tompave/fun_with_flags/llms.txt Use FunWithFlags.enabled?/2 in a Phoenix controller to conditionally render different views based on a feature flag for the current user. ```elixir defmodule MyAppWeb.DashboardController do use MyAppWeb, :controller def show(conn, _params) do user = conn.assigns.current_user if FunWithFlags.enabled?(:new_dashboard, for: user) do render(conn, "new_dashboard.html") else render(conn, "dashboard.html") end end end ``` -------------------------------- ### FunWithFlags.clear/2 Source: https://context7.com/tompave/fun_with_flags/llms.txt Clears flag data entirely or for specific gates to revert to default behavior. ```APIDOC ## FunWithFlags.clear/2 ### Description Clear flag data entirely or for specific gates. Use this to remove override rules and revert to default behavior. Returns :ok on success. ### Method Function Call ### Parameters #### Arguments - **flag_name** (atom) - Required - The name of the flag to clear. - **options** (keyword list) - Optional - Specific gates to clear (e.g., :for_actor, :for_group, :boolean, :for_percentage). ### Response #### Success Response - **result** (:ok) - Indicates the flag or gate was successfully cleared. ``` -------------------------------- ### Retrieve a specific flag Source: https://context7.com/tompave/fun_with_flags/llms.txt Fetch a flag struct by name or return nil if not found. ```elixir FunWithFlags.get_flag(:my_feature) # => %FunWithFlags.Flag{ # name: :my_feature, # gates: [ # %FunWithFlags.Gate{type: :boolean, for: nil, enabled: true}, # %FunWithFlags.Gate{type: :actor, for: "user:42", enabled: false} # ] # } FunWithFlags.get_flag(:nonexistent) # => nil ``` -------------------------------- ### Clear flag data Source: https://context7.com/tompave/fun_with_flags/llms.txt Remove override rules and revert to default behavior for specific flags or gates. ```elixir # Clear entire flag (removes all gates) FunWithFlags.clear(:old_feature) # => :ok # Clear actor-specific gate user = %MyApp.User{id: 42, name: "Alice"} FunWithFlags.clear(:feature, for_actor: user) # => :ok # Clear group-specific gate FunWithFlags.clear(:feature, for_group: "beta_testers") # => :ok # Clear boolean gate only FunWithFlags.clear(:feature, boolean: true) # => :ok # Clear percentage gate FunWithFlags.clear(:rollout, for_percentage: true) # => :ok ``` -------------------------------- ### Conceptual logic for actor scoring Source: https://github.com/tompave/fun_with_flags/blob/master/README.md The internal logic used to calculate a deterministic score for an actor against a specific flag. ```elixir actor |> FunWithFlags.Actor.id() |> sha256_hash(flag_name) |> hash_to_percentage() ``` -------------------------------- ### Check Boolean Feature Flag Status Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Use `FunWithFlags.enabled?/1` to check if a boolean feature flag is enabled. The flag defaults to disabled if undefined. ```elixir FunWithFlags.enabled?(:cool_new_feature) false ``` -------------------------------- ### Clear Boolean Rule for a Feature Flag Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Shows how to clear a boolean rule for a feature flag. This is useful for resetting a flag that was explicitly enabled or disabled as a simple true/false state, reverting it to its default. ```elixir FunWithFlags.enable(:wands) FunWithFlags.enabled?(:wands) true FunWithFlags.enabled?(:wands, for: harry) true FunWithFlags.enabled?(:wands, for: hagrid) false FunWithFlags.enabled?(:wands, for: dudley) true FunWithFlags.clear(:wands, boolean: true) FunWithFlags.enabled?(:wands) false FunWithFlags.enabled?(:wands, for: harry) true FunWithFlags.enabled?(:wands, for: hagrid) false FunWithFlags.enabled?(:wands, for: dudley) false ``` -------------------------------- ### FunWithFlags.enabled?/2 Source: https://context7.com/tompave/fun_with_flags/llms.txt Check if a feature flag is enabled, optionally for a specific actor. Returns a boolean indicating the flag state based on all configured gates and their priority rules. ```APIDOC ## FunWithFlags.enabled?/2 ### Description Check if a feature flag is enabled, optionally for a specific actor. Returns a boolean indicating the flag state based on all configured gates and their priority rules. ### Parameters - **flag_name** (atom) - Required - The name of the feature flag to check. - **for** (struct/nil) - Optional - The actor struct to check the flag against. ### Response - **boolean** - Returns true if the flag is enabled for the given context, false otherwise. ``` -------------------------------- ### FunWithFlags.disable/2 Source: https://context7.com/tompave/fun_with_flags/llms.txt Disables a feature flag globally or for specific actors, groups, or percentages. ```APIDOC ## FunWithFlags.disable/2 ### Description Disable a feature flag globally or for specific actors, groups, or percentages. Returns {:ok, false} on success or {:error, reason} on failure. ### Method Function Call ### Parameters #### Arguments - **flag_name** (atom) - Required - The name of the flag to disable. - **options** (keyword list) - Optional - Options include :for_actor, :for_group, or :for_percentage_of. ### Response #### Success Response - **result** ({:ok, false}) - Indicates the flag was successfully disabled. ``` -------------------------------- ### Disable Boolean Feature Flag Source: https://github.com/tompave/fun_with_flags/blob/master/README.md Use `FunWithFlags.disable/1` to globally disable a boolean feature flag. This function returns `{:ok, false}` upon successful disablement. ```elixir {:ok, false} = FunWithFlags.disable(:cool_new_feature) FunWithFlags.enabled?(:cool_new_feature) false ``` -------------------------------- ### Disable feature flags Source: https://context7.com/tompave/fun_with_flags/llms.txt Disable flags globally or for specific actors, groups, or percentages. ```elixir # Disable globally FunWithFlags.disable(:deprecated_feature) # => {:ok, false} # Disable for a specific actor (override) admin = %MyApp.User{id: 1, name: "Admin"} FunWithFlags.enable(:maintenance_mode) FunWithFlags.disable(:maintenance_mode, for_actor: admin) # => {:ok, false} FunWithFlags.enabled?(:maintenance_mode) # => true FunWithFlags.enabled?(:maintenance_mode, for: admin) # => false # Disable for a group FunWithFlags.disable(:premium_feature, for_group: "free_tier") # => {:ok, false} # Disable for percentage of time (same as enabling for complement) FunWithFlags.disable(:feature, for_percentage_of: {:time, 0.3}) # => {:ok, false} (internally enables for 70% of time) ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.