2
\$\begingroup\$

I'm making a bot that will be used for "points" tracking and such, like a reward system and I'm using Ecto with postgrex, this is my approach so far, it works but I'm not sure if I'm doing it in an "Elixir", functional way or if I can improve this a bit ^-^

defmodule Cord.Commands do
 ...
 def add_me(msg) do 
 case Repo.get_by(Rewards, user: msg.author.id) do
 # Adds user if it doesn't exist
 nil ->
 case Repo.insert(%Rewards{user: msg.author.id}) do
 {:ok, _struct} ->
 Api.create_message(msg.channel_id, ":white_check_mark:")
 {:error, _changeset} ->
 Api.create_message(msg.channel_id, ":x:")
 end
 _ ->
 Api.create_message(msg.channel_id, "You're already registered")
 end
 end
 # Maps to each id in the list and updates each one
 def thanks(msg) do
 Enum.map msg.mentions, fn user ->
 repo_user = Repo.get_by(Rewards, user: user.id)
 points = repo_user.quantity
 repo_user = Ecto.Changeset.change(repo_user, quantity: points + 1)
 Repo.update!(repo_user)
 Api.create_message(msg.channel_id, "#{msg.author.username} gave a :cookie: to #{user.username}")
 end 
 end
 def not_found(_), do: IO.inspect :ignore
end
asked May 12, 2017 at 23:13
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Here are a few suggestions...

You could DRY up your code in add_me by returning the string and then making the API call in one place. Also, you should call Repo.insert/1 with a changeset. This will ensure your validation is checked. You can also remove the nesting with the with special form. Its a really handy function that I recommend you learn if haven't already.

 def add_me(msg) do 
 message = 
 with nil <- Repo.get_by(Rewards, user: msg.author.id),
 changeset <- Rewards.changeset(%Rewards{}, %{user: msg.author.id}),
 {:ok, _} <- Repo.insert(changeset) do 
 ":white_check_mark:"
 else
 {:error, _} -> ":x:"
 _ -> "You're already registered"
 end
 Api.create_message(msg.channel_id, message)
 end

You can reduce some of the one-time variables in thanks like this:

 def thanks(msg) do
 Enum.map msg.mentions, fn user ->
 repo_user = Repo.get_by(Rewards, user: user.id)
 repo_user
 |> Ecto.Changeset.change(quantity: repo_user.points + 1)
 |> Repo.update!
 Api.create_message(msg.channel_id, "#{msg.author.username} gave a :cookie: to #{user.username}")
 end
 end 

Finally, it appears that you don't care about the type of error on the Repo.insert. Keep in mind that the changeset returned with {:error, changeset} contains the validation errors.

NOTE:

I see 4 common style / issues with programmers learning Elixir.

  1. error Calling a side affect free function without using the return value. Data in Elixir is immutable, so you need need to either capture the results of a function, call it as the last statement so it returned to the caller, or create a side effect in the function (send an event, write to database, file, etc)
  2. warning Use a variable outside the scope of the block it was set in. In other words, don't set a variable inside an if, case, or cond, then use it later
  3. style Use lots of single use variables. This is typically a sign that the pipe |> operator may clean up the code.
  4. style Use logs of conditional statements where multi-clause function heads with pattern matching and guards may make the code cleaner, with less nesting
answered May 12, 2017 at 23:57
\$\endgroup\$
3
  • \$\begingroup\$ Hello Steve, thank you very much, I have some questions regarding this solution that I hope you can clear out. 1st. Do I have to use a Changeset even if I don't want a validation? Because when I insert a value the user doesn't input any information, I'm getting it from a struct attached to the user automatically, the only thing the user does is call the command. 2nd. I've read about with before but honestly I didn't quite understand it so I didn't know it could be used in this case, does it work like a nest case statement or something else? I will look further into it and... \$\endgroup\$ Commented May 13, 2017 at 0:31
  • \$\begingroup\$ ... 3rd. I don't actually care (in this case) for the validation errors on Repo.insert, because how I said, I'm not validating any data or I may be wrong? So, should I explicitly check the changeset returned with {:error, changeset} when I'm validating the data? Again, thanks for the help. \$\endgroup\$ Commented May 13, 2017 at 0:33
  • \$\begingroup\$ No, you don't need to pass Repo.insert a changeset. Your code will work fine. If you don't care about validation errors then you don't need to check. If you do care about validation errors, then changeset.errors will give you the list. Finally, when I first started using the with operator, I found it a little confusing and hard to remember. However, after using it for a while, its very easy to use and a very nice tool to reduce nesting and handling errors in the middle of a pipeline. \$\endgroup\$ Commented May 13, 2017 at 0:44

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.