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.
@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 (orimport
it into theView
module from another module) - and then just use the function inside the template.
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.
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.
Iâm sorry, but I donât get how do I implement this so my form accepts o denies a RUT 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
WE ARE ALMOST THERE!
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