It depends on the use case, as always, but it sounds like all of them should be actors. I have the feeling that you are approaching the problem with the wrong assumption: that only users can be actors. As you’ve realized, this would make things a bit complicated in some scenarios. I’ll provide examples at the end of the post.
Yes. By design. Group names are meant to be human friendly labels that you define ahead of time. By contrast, actors are dynamic entities whose identity you resolve at runtime. In your case, yes, you can either declare groups for all of your regions and departments, or you can make them actors with namespaced IDs. It really depends on what makes more sense for your access patterns.
Oh, yes, that would be awkward. That’s what makes me think that modelling them as actors makes sense.
If I get what you mean, these categories could still be groups. You are free to represent them as you want. A user can have a literal list of group names (like roles, or tags), or you can infer the group dynamically.
I really think that you’re describing actors here.
Some examples
Here are some examples of how I would model it. I’m going to use users, departments and countries. To keep things simple I’m going to use plain structs instead of using Ecto, and I’m going to use some invented functions and just say what they would do.
Let’s say we have these structs:
defmodule User do
defstruct [:id, :name]
end
defmodule Country do
defstruct [:name, :iso]
end
defmodule Department do
defstruct [:name]
end
Which are all actors:
defimpl FunWithFlags.Actor, for: User do
def id(%{id: id}) do
"user:#{id}"
end
end
defimpl FunWithFlags.Actor, for: Country do
def id(%{iso: iso}) do
"country:#{iso}"
end
end
defimpl FunWithFlags.Actor, for: Department do
def id(%{name: name}) do
"department:#{name}"
end
end
With these simple building blocks, I can build a matrix of actor gates. Just remember that disabled gates take precedence over the enabled ones.
So, for example, if I want to enable something for engineers in Japan, I can:
japan = %Country{name: "Japan", iso: "jp"}
engineering = %Department{name: "Engineering"}
FunWithFlags.enable(:beta_features, for_actor: japan)
FunWithFlags.enable(:beta_features, for_actor: engineering)
And then I can check it for individual users with:
def beta_features_enabled_for?(user = %User{}) do
country = Country.for(user)
department = Department.for(user)
FunWithFlags.enabled?(:beta_features, for: country) &&
FunWithFlags.enabled?(:beta_features, for: department)
end
And later you can even add some groups to the Country struct, to add more flexibility. For example, let’s say that you want to enable the flag for all asian countries:
defimpl FunWithFlags.Group, for: Country do
@asian_countries ~w{jp hk} # ...
@european_countries ~w{it fr} # ...
#...
def in?(%{iso: iso}, :asian_countries) do
iso in @asian_countries
end
def in?(%{iso: iso}, :european_countries) do
iso in @european_countries
end
# ...
end
FunWithFlags.enable(:beta_features, for_group: :asian_countries)
For the “beta testers group designations”, then you can use a more traditional group approach. For example you could find a way to either set the the designations on the %User{} struct, or make them fetchable. Some simple examples:
defimpl FunWithFlags.Group, for: User do
def in?(%{groups: groups}, group) when is_list(groups) do
group in groups
end
end
# or...
defimpl FunWithFlags.Group, for: User do
def in?(user, group) do
designations = BetaTesterGroupDesignation.get_list_for(user)
group in designations
end
end
# or even... if you feel fancy (think carefully before doing this because it's not optimal)
defimpl FunWithFlags.Group, for: User do
def in?(user, group) do
if Enum.member?(BetaTesterGroupDesignation.all(), group)
designations = BetaTesterGroupDesignation.get_list_for(user)
group in designations
else
# other group logic or false
false
end
end
end
Done that, you can add User to the checks I showed above, or you can use different logic to give explicit settings on user actors higher priority. As you can see, this library just gives you the building blocks.
By the way, you can also model everything with groups, just like you initially imagined.
For example:
defimpl FunWithFlags.Group, for: User do
def in?(user, group) do
country = Country.for(user)
department = Department.for(user)
String.downcase("#{country.name}_#{department.name}") == to_string(group)
end
end
FunWithFlags.enable(:beta_features, for_group: :japan_engineering)
FunWithFlags.enabled?(:beta_features, for: user)
I hope this clarifies your doubts, and I hope you’ll find it useful! ![]()






















