How do I put an IF statement in a templates file in Elixir Phoenix?

https://hexdocs.pm/elixir/String.html#at/2

1 Like

Lets say you have

defmodule Demo do

  def rut?(rut) when is_binary(rut)do
    rut
    |> clean()
    |> valid?()
  end
  def rut?(_) do
    false
  end

  defp clean(value),
    do: Regex.replace(~r/[^0-9K]/, String.upcase(value), "")

  defp valid?(rut) do
    reversed = String.reverse(rut)
    if verify(reversed) do
      {digits, check} = split(reversed)

      check == checksum(digits)

    else
      false
    end
  end

  defp verify(reversed),
    do: Regex.match?(~r/^(\d|K)\d{7,8}$/, reversed)

  defp split(reversed) do
    [check_digit | rest] = String.graphemes(reversed)

    digits = Enum.map(rest, &(String.to_integer(&1)))
    check =
      case check_digit do
        "K" -> 10
        _ -> String.to_integer(check_digit)
      end
    {digits, check}
  end

  defp checksum(digits) do
    result =
      digits
      |> length()
      |> make_coefficients()
      |> Enum.zip(digits)
      |> Enum.reduce(0,&to_sum/2)
      |> Integer.mod(11)

    case (11 - result) do
      11 -> 0
      n -> n
    end
  end

  defp make_coefficients(amount) do
    (2..7)
    |> Stream.cycle()
    |> Enum.take(amount)
  end

  def to_sum({a,b}, sum),
    do: a * b + sum

end

IO.inspect(Demo.rut?("30.686.957-4")) # true
IO.inspect(Demo.rut?("306869574"))    # true
IO.inspect(Demo.rut?("30.686.954-K")) # true
IO.inspect(Demo.rut?("30686954K"))    # true
IO.inspect(Demo.rut?("3068695K4"))    # false
IO.inspect(Demo.rut?("1938909K"))     # true
IO.inspect(Demo.rut?("19389093"))     # false
IO.inspect(Demo.rut?("0"))            # false
IO.inspect(Demo.rut?(""))             # false
IO.inspect(Demo.rut?(nil))            # false
IO.inspect(Demo.rut?(306869574))      # false

then you should be able to:

defmodule Medica.AppointmentView do
  import Demo, only: [ rut?: 1 ]

  # ...

end

and then

<%= if rut?(appointment.rut_paciente) do %>

should work.

4 Likes

@peerreynders My God, but… does it work if I used mix phoenix.new instead of mix phx.new?

1.3 only released about a year ago (which introduced the phx mix tasks) - the technique was in use well before that so I doubt that it will make a difference whether you used mix phx.new or mix phoenix.new - it should work in either case.

  • Functions defined inside or imported into the View module are available inside the rendered templates.
  • In general one should prefer to not have detailed logic inside the template itself. It’s usually better to put that logic into a function inside the associated View module (or import it into the View module from another module) - and then just use the function inside the template.
2 Likes

I’m really ashamed I have to ask this, but… From the script you gave me, what part I can paste exactly as is shown and what part I can’t?

As I see on the implementation you can use the whole module Demo, except the IO.inspect calls, just use a meaningful name such as RutChecker and place in a file on your source tree, then import on your view as shown just changing Demo to the name you have it.

2 Likes

By the way, I’m shocked you actually know the whole RUT algorithm, which is, as far as I know, a Chile thing only. You really gave me a surprise.

The Google translate of this is actually readable. And other resources like this and this are also available.

So just a simple (not particularly good) example:

$ mix phoenix.new --no-ecto medica
# file: medica/lib/paciente/identificacion.ex
# Note: code in web IS reloaded, and the code in lib IS NOT
#
defmodule Paciente.Identificacion do

  def rut?(rut) when is_binary(rut)do
    rut
    |> clean()
    |> valid?()
  end
  def rut?(_) do
    false
  end

  # the rest ...

end

# file: medica/web/views/page_view.ex
defmodule Medica.PageView do
  use Medica.Web, :view

  import Paciente.Identificacion, only: [rut?: 1]

end
      <!-- in medica/web/templates/page/index.html.eex -->
      <%= if rut?("30.686.957-4") do %>
        <p>PASS</p>
      <% else %>
        <p>FAIL</p>
      <% end %>
      <%= if rut?("30.686.957-5") do %>
        <p>PASS</p>
      <% else %>
        <p>FAIL</p>
      <% end %>

Works as expected.

1 Like

I’m sorry, but I don’t get how do I implement this so my form accepts o denies a RUT :sob: And also, I don´t get why you put:

  <%= if rut?("30.686.957-5") do %>
    <p>PASS</p>
  <% else %>
    <p>FAIL</p>
  <% end %>

30.686.957-5 is not a valid RUT, the final digit should be 4, otherwise, it should be denied.

For example, in my appointment controller, I analyze the description of an appointment and watch if there’s a bad word in it:

def create(conn, %{“appointment” => appointment_params}) do
changeset = Appointment.changeset(%Appointment{}, appointment_params)
regex = ~r/(s[\w-]*e[\w-]*x[\w-]o[\w-])/
regex2 = ~r/(m[\w-]*i[\w-]*e[\w-]*r[\w-]*d[\w-]a[\w-])/
regex3 = ~r/(w[\w-]*e[\w-]*o[\w-]n[\w-])/
regex4 = ~r/(p[\w-]*u[\w-]*t[\w-]a[\w-])/
if((Regex.match?(regex, appointment_params[“descripcion”]))
or (Regex.match?(regex2, appointment_params[“descripcion”]))
or (Regex.match?(regex3, appointment_params[“descripcion”]))
or (Regex.match?(regex4, appointment_params[“descripcion”]))) do
conn
|> put_flash(:error, “Language!”)
|> render(“new.html”, changeset: changeset)
else
case Repo.insert(changeset) do
{:ok, appointment} ->
conn
|> put_flash(:info, “Appointment created succesfully”)
|> redirect(to: appointmentu_path(conn, :show, appointment))
{:error, changeset} ->
render(conn, “new.html”, changeset: changeset)
end
end
end

I would like to do something similar with rut_paciente.

I think the misspelled RUT was just an example, and the <p> tags as something you would need to do. See in the first example you gave you were using to filter the rows on the table in you were putting on the page, thus the example with <p> tags.

Now if you want to add a validation over the form input so you can alert your users when they type wrong RUT you justs need to apply before the regex if, or even better, apply it on Ecto.Changeset validations.

Here are some snippets.

First if you decide to use on your controller next to language check

def create(conn, %{"appointment" => appointment_params}) do
    changeset = Appointment.changeset(%Appointment{}, appointment_params)
    regex = ~r/(s[\w-]*e[\w-]*x[\w-]o[\w-])/
    regex2 = ~r/(m[\w-]*i[\w-]*e[\w-]*r[\w-]*d[\w-]a[\w-])/
    regex3 = ~r/(w[\w-]*e[\w-]*o[\w-]n[\w-])/
    regex4 = ~r/(p[\w-]*u[\w-]*t[\w-]a[\w-])/
    
    # we use cond here to check against many "ifs" think of nested if/else
    cond do
      (Regex.match?(regex, appointment_params["descripcion"]))
      or (Regex.match?(regex2, appointment_params["descripcion"]))
      or (Regex.match?(regex3, appointment_params["descripcion"]))
      or (Regex.match?(regex4, appointment_params["descripcion"])) ->
        conn
        |> put_flash(:error, "Language!")
        |> render("new.html", changeset: changeset)
      not Paciente.Identification.rut?(appointment_params["rut_paciente"]) ->
        conn
        |> put_flash(:error, "RUT no es vĂĄlido")
        |> render("new.html", changeset: changeset)
      true ->
        case Repo.insert(changeset) do
          {:ok, appointment} ->
            conn
            |> put_flash(:info, "Appointment created succesfully")
            |> redirect(to: appointment_path(conn, :show, appointment))
          {:error, changeset} ->
            render(conn, "new.html", changeset: changeset)
        end      
    end
  end  

Now a better version using as changeset validation

On your Appointment module you have the changeset/2 function, the one you are using on your controller, there you add a pipe to validate_change

def changeset(%Appointment{} = appointment, attrs \\ %{}) do
    appointment
    |> cast([....]) # your fields here
    # |> validate_length() ... some already present validations
    # add this, note that I consider the rut field as :rut_paciente
    |> validate_change(:rut_paciente, fn :rut_paciente, rut_value ->
      if Paciente.Identification.rut?(rut_value) do
        [:rut_paciente, "RUT no es vĂĄlido"]
      else
        []
      end
    end)
  end

The Paciente.Identification.rut?/1 function that perreynders implement will return true or false so is a very plugable function, whenever you need to check rut for true or false you can use it

2 Likes

WE ARE ALMOST THERE! :smile:
The RUT validator actually worked! But now, my bad words filter stopped working. It’s kinda funny, but I really don’t know why.

Are you using the cond version or the changeset one?

I don’t know what I did, but I fixed it. Thanks a lot to both of you @peerreynders and @joaoevangelista. You guys rock! <3

Glad we could help
:blush:

1 Like