Igor Šarčević wrote this on April 7, 2016
Ecto for Rails developers — The basics
We at Rendered Text are huge fans of Ruby on Rails. As a proof, SemaphoreCI — our biggest product — is still mostly written in Ruby. Lately, however, we started migrating toward Elixir and the OTP platform. Erlang has an excellent platform for developing giant, scalable and fault tolerant systems. Elixir, on the other hand, is a Ruby-like language that helped us to swiftly transition parts of our infrastructure, and harness the power of OTP by using a familiar syntax.
We noticed two big differences right from the start.
Elixir is a mostly pure functional programming language.
Ecto — the de facto way to talk to databases in Elixir — has a vastly different approach than ActiveRecord.
The first issue was easy to overcome. We are huge fans of functional programming. Several of us has a working knowledge of Lisp or Haskell, and we even aim to keep our Ruby code immutable and functional. The differences in the database abstraction was a bit harder for us. We were used to Active Record quite a lot. This article is dedicated to this issue, and it is my best attempt to help you transition easier.
Ecto is NOT an ORM
The first big difference you will notice is that Ecto doesn’t convert your
database rows into objects. Instead, it uses a database wrapper mechanism,
called Repository, and pure Elixir data structures to return your data.
Let’s see an example. In Rails, to fetch all the users
who are older than 18
would probably look like the following:
users = User.where("age > ?", 18) # users are instances of the User class user = users.first p user # => #<User id: 1, age: 20> p user.age # => 20 # we can update the object and save it back into the database user.age = 25 user.save!
In Elixir, you don’t have objects, only pure data. All the communication must be
passed through the Repo
.
users = User |> where([u], u.age > 18) |> Repo.all # users are hashes. `hd` is short for head of the list user = hd(users) IO.inspect user # => %User{age: 20, id: 1} IO.puts user.age # => 20 # first we create a changeset changeset = User.changeset(user, %{age: 25}) # then we send the changes into the repository changeset |> Repo.update
As you can see, even though Ecto is not an ORM, it does resemble it.
The separation of the wrapper — the Repository — from the query interface was probably the most interesting thing when I was switching to Elixir.
Let’s start our Ecto journey with some basics. The rest of the article will teach you how to invoke CRUD operations on your repository.
Creating new entries
Creating new rows in the database is achieved by constructing a structure and sending it to the repository.
{:ok, user} = %User{name: "Igor", age: 25} |> Repo.insert
Like in Rails, we have two formats for the insert
function. The regular
version shown in the previous example that returns either :ok
or :error
,
and a bang version Repo.insert!
that raises an exception.
user = %User{name: "Igor", age: 25} |> Repo.insert!
Reading values from the database
To verify that our insert
action succeeded, we will try to fetch it by using
its id
:
# id of the record created in the previous example id = user.id igor = User |> Repo.get(id)
The get
actions can return either nil
if the record is not present in the
database, or a structure representing your record.
if igor |> is_nil do IO.puts "User with id: #{id} not found" else IO.puts "User with id: #{id} is present" end
Updating rows in the database
Unlike in Rails where we would modify the returned object, in Elixir, we will
use changesets
. A changeset
represents some changes that we want to send to
our repository. For example, let’s make our user a bit older than he is:
# first we record the changes changeset = user |> User.changeset(%{age: 30, name: "shiroyasha"}) # then we send the changes into the repository changeset |> Repo.update
When you get comfortable with Elixir you will most likely write the above in only one line:
user |> User.changeset(%{age: 30, name: "shiroyasha"}) |> Repo.update
The changesets are also pipable, so it is easy to make two changes separately.
user |> User.changeset(%{age: 30}) |> User.changeset(%{name: "shiroyasha"}) |> Repo.update
Deleting records from the database
Finally, let’s delete the user from our database:
{:ok, _} = user |> Repo.delete
Final words
I hope that this article helped you to get started with Ecto. Here are some useful resources to continue your learning process:
Happy hacking in Elixir!