We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
On this page
Custom checks and policies
an example of how to use policy within phoenix controller/liveview to have secure crud operations. for e.g. A user can see a list of tickets A user can create tickets A user can edit his tickets A user cannot edit other users tickets? ... ...
Yes. I just put this gist together for you. See the comment on one of the ways to use it in a liveview.
Steps:
- Define a check.
- Include it in your resource policies
- Pass actor with permissions to your query.
Let me know if it helps.
https://gist.github.com/kamaroly/986c40d1b950373f6577d973a414f01bSee twitter convo here: https://x.com/kamaroly/status/1842867465610514704
Table of Contents
Skip to article contentOriginally posted on Medium
I kinda like Elixir. I even think that Elixir is a huge leap towards the future and all the right things that should be happening in the Erlang world (see related post here). I still get irritated by it :)
What follows is a highly opinionated … erm … opinion. I am right and you are wrong ;)
It’s not the syntax that’s important, it’s the consistency
I will be hanged out to dry for this, then brought in and hanged dry again. But here goes.
Let’s start with Erlang. Erlang has one of the most concise and consistent syntaxes in programming languages.
Erlang syntax primer
1: A sequence of expressions is separated by commas:
Var = fun_call(),
Var2 = fun_call2(), Var3 = fun_call3(Param1, Param2)
2: A pattern match after which we return a value is followed by an arrow. From a programmer’s point of view, there’s no difference if this is a pattern match introduced by the function declaration or by a case
, receive
or if
function_declaration(Param1, Param2) ->
Var = fun_call(),
Var2 = fun_call2(), Var3 = fun_call3(Param1, Param2),
receive
{some, pattern} ->
do_one(),
do_three(),
do_four() %% the value of this call will be returned
end.
3: Choices are spearated by semicolons. Once again, there’s no real difference (at least to the programmer) between choices in function heads (we choose between different patterns) and choices in case
, if
or receive
:
function1({a, b}) ->
do_a_b();
function1({c, D}) ->
case D of
{d, e} -> do_c_d_e();
{f, g} -> do_c_f_g()
end.
4: There are also blocks, and a block is terminated by an end (there’s a bit of inconsistency in blocks themselves: begin...end
, case of...end
, try...catch...end
, if...end
). E.g.:
function() ->
begin
case X of
b ->
if true -> ok end
end
end.
What of Elixir?
And here’s my problem with Elixir: You have to be always be aware of things and of what goes where when.
do..end vs ->
def f(a, b) do
case a do
{:c, :d} ->
do_a_b_c_d()
{:e, :f} ->
receive do
{:x} -> do_smth()
{:y} -> something_else()
end
end
end
What? Why is there suddenly an arrow? In a language where (almost) everything is a do...end
the arrow is jarringly out of place. For the longest time ever I honestly thought that the only thing separating pattern matches in a case statement is identation. Which would be weird in a language where whitespace is insignificant.
There’s an argument that these are individual patterns within a do...end
block separated by newlines and denoted by arrows (see here). cond
, case
and receive
all use that. The argument is moot, however.
Here’s how you define an anonymous function in Elixir:
a_fun = fn x -> x end
This is an even weirder construct. It’s not a do...end
. But it has an arrow and an end
. Moreover, regular functions are also individual patterns within a do...end
block:
defmodule M do
def x(:x), do: :x end
def x(:y), do: :y end
end
And here’s how you define similar constructs with arrows:
a_fun = fn :x -> :x
:y -> :y
end
result = case some_var do
:x -> :x
:z -> :z
end
If arrow is just a syntactic sugar for do...end
, why can’t it be used in other places? If it’s used for sequences of patterns, why can’t it be used for function declarations?
There’s definitely an argument for readability. Compare:
## arrows
case some_var do
:x -> :x
:z ->
some
multiline
statement
:z
end
## do..end as everywhere
case some_var do
:x, do: :x
:z do
some
multiline
statement
:z
end
end
But then… Why not use the more readable arrows in other places as well? ;)
## regular do..end
defmodule M do
def x(:x), do: :x end
def x(:y) do
some
multiline
statement
:z
end
end
## arrows
defmodule M do
def x(:x) -> :x
def x(:y) ->
some
multiline
statement
:z
end
So, in my opinion, arrows is a very weird incosistent hardcoded syntactic sugar. And it irritates me :)
Moving on
Parentheses are optional. Except they aren’t
One of the arguably useful features of Elixir is that function calls do not require parantheses:
> String.upcase(“a”)
“A”
> String.upcase “a”
“A”
Except that if you want to keep your sanity instead of juggling things in your mind, you’d better off using the parentheses anyway.
First, consider Elixir’s amazing pipe operator:
## instead of writing this
Enum.map(List.flatten([1, [2], 3]), fn x -> x * 2 end)
## you can write this
[1, [2], 3] |> List.flatten |> Enum.map(fn x -> x * 2 end)
Whatever is on the left side of the pipe will be passed as the first argument to the whatever’s on the right. Then the result of that will be passed as the first argument to the next thing. And so on.
However. Notice the example above, taken straight from the docs. List.flatten
, a function, is written without parentheses. Enum.map
, also a function, requires parentheses. And here’s why:
> [1, [2], 3] |> List.flatten |> Enum.map fn x -> x * 2 end
iex:14: warning: you are piping into a function call without parentheses, which may be ambiguous. Please wrap the function you are piping into in parentheses. For example:foo 1 |> bar 2 |> baz 3Should be written as:foo(1) |> bar(2) |> baz(3)
This is just a warning, and things can get ambiguous. That’s why you’d better always use parentheses with pipes. It gets worse though.
Remember anonymous functions we talked about earlier? Well. Let me show an example.
Here’s how a function named flatten
declared in module List
behaves:
> List.flatten([1, [2], 3])
[1, 2, 3]
> List.flatten([1, [2], 3])
[1, 2, 3]
> [1, [2], 3] |> List.flatten
[1, 2, 3]
Here’s how an anonymous function behaves:
> a_fun = fn x -> List.flatten x end
#Function<6.54118792/1 in :erl_eval.expr/5>> a_fun([1, [2], 3])
** (CompileError) iex:17: undefined function a_fun/1> a_fun [1, [2], 3]
** (CompileError) iex:17: undefined function a_fun/1iex(17)> [1, [2], 3] |> a_fun
** (CompileError) iex:17: undefined function a_fun/1
(elixir) expanding macro: Kernel.|>/2
iex:17: (file)
Wait. What?!
Oh. Right. Anonymous functions, what a nice joke. Let’s all have a good laugh. An anonymous function must always be called with .()
> a_fun.([1, [2], 3])
[1, 2, 3]
> [1, [2], 3] |> a_fun.()
[1, 2, 3]
Because parentheses are optional, right. And functions are first class citizens, and there’s no difference if it’s a function or a variable holding a function, right? (In the example below &List.flatten/1
is equivalent to Erlang’s fun lists:flatten/1
. It’s called a capture operator.)
> a_fun = List.flatten
** (UndefinedFunctionError) undefined function List.flatten/0
(elixir) List.flatten()> a_fun = &List.flatten
** (CompileError) iex:19: invalid args for &, expected an expression in the format of &Mod.fun/arity, &local/arity or a capture containing at least one argument as &1, got: List.flatten()> a_fun = &List.flatten/1
&List.flatten/1> a_fun([1, [2], 3])
** (CompileError) iex:21: undefined function a_fun/1> a_fun.([1, [2], 3])
[1, 2, 3]
Yup. Because parentheses are optional.
If you think it’s not a big problem, and that anonymous or “captured” functions are not that common… They may not be common, but they are still used. An empty project using Phoenix:
Every time you use a “captured” or an anonymous functions, you need to keep the utterly entirely useless information: they are invoked differently than the rest of the functions, they require parentheses etc. etc.
> 1 |> &(&1 * 2).()
** (ArgumentError) cannot pipe 1 into &(&1 * 2.()), can only pipe into local calls foo(), remote calls Foo.bar() or anonymous functions calls foo.()> 1 |> (&(&1 * 2)).()
2
Local. Remote. Anonymous. Riiiight
Compare with Erlang:
1> lists:flatten([1, [2], 3]).
[1,2,3]2> F = fun lists:flatten/1.
#Fun<lists.flatten.1>
3> F([1, [2], 3]).
[1,2,3]
Before we move on. Calls without parentheses lead to the unfortunate situation when you have no idea what you’re doing: assigning a variable value or the result of a function call.
some_var = x ## is it x or x(). Let's hope the IDE tells you
And yes, this also happens.
do..end vs ,do:
This has been explained to me here. I will still mention it, because it’s annoying the hell out of me and is definitely confusing to beginners. Basically, it’s one more thing to keep track of:
def oneliner(params), do: resultdef multiliner(params) do one two result end
Yup. A comma and a colon in one case. No comma and no colon in the second case. Because syntactic sugar and reasons.
Originally published on Medium
Recently I got into a shouting match about QuickCheck implementation for Elixir.
My original reaction:
Why? Quckcheck is for companies only. Elixir is used by zero companies (except maybe 2 startups)
My highly non-scientific view of Erlang/Elixir world is something like this (don’t get offended by circle sizes, I’m bad at Venn diagrams):
If there is a company that sponsored the development of QuickCheck for Elixir, it’s great (both for the company and Quviq). However, it offers almost zero value to the rest of us for obvious reasons:
-
QuickCheck is virtually non-existent outside companies. I’m aware of the reason behind it and I can’t really say anything against that reason (because it makes sense ☺). However, it remains a fact: if you want to access more advanced features (like testing concurrency) you need a license, which has to be negotiated with Quviq, and costs an unspecified (and, as I hear, quite large) amount of money.
-
Elixir is still at an “early adoption” stage. So there are very few companies that use it, and even fewer who will decide to go for a QuickCheck license.
-
Most companies who already have the QuickCheck license will most likely not be willing to have Elixir in their toolbelt for an obvious reason (it introduces a new language/environment/build steps etc.).
-
Another point for inaccessibility. Remember the announcement that the OTP team will start using QuickCheck (awesome!). The very first reaction was “why use something most of us will not even be able to run?”. An expected reaction, really.
So that’s the main problem I have with this announcement and presentation: you’ve whetted our appetites. Now what?
What I would really love to see instead is just a clear licensing mechanism. Let’s say something like this:
- Plan 1. $100 a month. You get a license for 1(3?) computers (that you can auth/de-auth from your profile), and 1–5 projects in QuickCheck CI. No support
- Plan 2. $500 a month. A license for 10 computers, 10 projects in CI. 5 hours support
- etc.
Then the “Testing concurrency with Elixir” presentations will be “oh, cool, I can use it” instead of “meh, I can’t use it, because I don’t even know how much it costs or whether I can afford it”.
Hence my reaction ☺ But it’s me, and I’m always bitter
Oh. By the way. If you think that I’m spreading some imagined hate about Elixir… go and take a cold shower.