I’m just starting with Elixir and I’m trying to create a small ‘core’ module to implement a logic of playing a game. I’m struggling with understanding whether/how I should use Ecto changesets as for input/return values for the functions in the ‘core’ module.
Ideally, I would like to make the ‘core’ unaware of the DB and of the caller’s needs. Right now I’m just going to use this module from a Phoenix context. But I’d like to implement a GenServer later for playing a game, using the same ‘core’ logic module.
This is what a function in that module can look like:
defmodule Game do def add_player_to_match(match, new_player) do # check if the new_player has already been added # add the new_player to match.players and return ??? ... end end
This function will be called by a module that will retrieve an existing ‘Match’ from the DB and save the results (match with a player added) back to the DB.
Is it ok for this function to accept an Ecto struct? Is it a good pattern for this function to return an Ecto changeset with the applied changes? This makes it super easy to save the result to the DB, but it kinda feels awkward. Also, in this case the unit tests of this function need to assert on the changes in the changeset, instead of the ‘business’ data itself. And piping these ‘core’ functions wouldn’t be possible either.
If this is not a good way to go, would you make the “core” functions work on pure (untyped) maps? Would you convert the Ecto struct to a Map using
Map.from_struct/1 before passing it into the function? The way I see it could maybe work is something like this:
match = Repo.get!(123) player = Repo.get!(456) updated_match = Game.add_player_to_match( match |> Map.from_struct, player |> Map.from_struct ) match |> Ecto.Changeset.change(updated_match) |> Repo.update
I would really appreciate some advice and/or pointers to articles/blogs about this kind of patters.
P.S. In case it’s relevant, these are the simple schemas (irrelevant fields redacted) I have for validations and interacting with the DB:
defmodule Match do use Ecto.Schema import Ecto.Changeset schema "matches" do field :finish, :utc_datetime field :start, :utc_datetime has_many :players, Player ... end
defmodule Player do use Ecto.Schema import Ecto.Changeset schema "players" do field :current_score, :integer belongs_to :match, Match ... end