Object Relational Mapper for Elixir
Atlas is an Object Relational Mapper for Elixir. (Work in progress. Expect breaking changes)
belongs_to,
has_many,
has_many through:
defmodule User do use Atlas.Model@table :users @primary_key :id
field :id, :integer field :email, :string field :is_site_admin, :boolean field :archived, :boolean field :state, :string
validates_numericality_of :id validates_presence_of :email validates_length_of :email, within: 5..255 validates_format_of :email, with: %r/.@./, message: "Email must be valid" validates :lives_in_ohio
def lives_in_ohio(record) do unless record.state == "OH", do: {:state, "You must live in Ohio"} end
def admins do where(archived: false) |> where(is_site_admin: true) end
def admin_with_email(email) do admins |> where(email: email) end end
iex> admin = Repo.first User.admin_with_email("[email protected]") %User{id: 5, email: "[email protected]", archived: false, is_site_admin: true...}
iex> User.where(email: "[email protected]") |> User.where("state IS NOT NULL") |> User.order(update_at: :asc) |> Repo.all[%User{id: 5, archived: true, is_site_admin: false...}, %User{id: 5, archived: true, is_site_admin: false...}]
iex> user = User.where(email: "[email protected]") |> Repo.first %User{id: 5, archived: false, is_site_admin: false...} iex> user.email [email protected]
iex> User.where(archived: true) |> User.order(updated_at: :desc) |> Repo.first
%User{id: 5, archived: true, is_site_admin: false...}
defmodule UserSearch do import Userdef perform(options) do is_admin = Keyword.get options, :is_site_admin, false email = Keyword.get options, :email, nil scope = User.scoped
scope = scope |> where(is_site_admin: is_admin) if email, do: scope = scope |> where(email: email) scope |> Repo.all
end end
iex> UserSearch.perform(is_site_admin: true, email: "[email protected]") [%User{email: "[email protected]"}]
Atlas uses the Repository pattern to decouple persistence from behavior, as well as allow multiple database connections to different repositories for a robust and flexible persistence layer. When creating/updating/destroying data, a list of behaviors must be included to run validation callbacks against for the Repo to proceed or halt with requested actions via the
as:option.
Examples
defmodule User do use Atlas.Model@table :users @primary_key :id
field :age, :integer field :name, :string
validates_numericality_of :age, within: 1..150 validates_presence_of :name end
defmodule Manager do use Atlas.Validator
validates_numericality_of :age, greater_than_or_equal: 21, message: "managers must be at least 21" end
iex> Repo.create(User, [age: 12, name: "Dilbert"], as: User) {:ok, %User{age: 12...}}iex> user = Repo.first(User) iex> Repo.update(user, [age: 18], as: [User, Manager]) {:error, %User{age: 18...}, ["managers must be at least 21"]}
iex> Repo.create(User, [age: 0, name: "Chris"], as: User) {:error, %User{age: 0..}, ["age must be between 1 and 150"]}
Accessors for assigning and retrieving model attributes are automatically defined from the shema field definitions.
By default, Accessors are simply pass-throughs to the raw record setter and getter values; however, accessors can be overriden by the module for extended behavior and transformations before writing to, or after reading from the database.
assignfunctions transform attributes when creating a new Struct via
Model.newand before running model callbacks such as validations.
Example attribute assignment:
defmodule User do use Atlas.Model field :email, :string field :name, :stringdef assign(user, :email, value), do: user.update(email: String.downcase(value)) end
iex> User.assign(user, :email, "[email protected]") User[email: "[email protected]"]
iex> User.new(email, "[email protected]") User[email: "[email protected]"]
Example attribute retrieval:
defmodule User do use Atlas.Model field :email, :string field :name, :stringdef email(user), do: user.email |> String.upcase end
iex> user = User.new(email: "[email protected]") iex> User.email(user) [email protected]
with_[field name]functions are automatically generated for all defined fields. For example, a User module with a
field :email, :stringdefinition would include a
User.with_emailfunction that returns the first record matching that field from the database.
iex> user = User.new(email: "invalid") %User{id: nil, email: "invalid", is_site_admin: nil...}iex> User.validate user {:error, %User{newsletter_updated_at: ...}, [email: "Email must be valid", email: "_ must be between 5 and 255 characters", email: "_ must not be blank"]}
iex> User.full_error_messages user ["Email must be valid","email must be between 5 and 255 characters","email must not be blank","id must be a valid number"]
Define at least one Repository in your project that uses Atlas.Repo with a supported adapter. Your Repo simply needs to be provide
configfunctions for
:dev,
:test, and
:prodenvironments. After defining your repo, start its process within your application.
defmodule Repo do use Atlas.Repo, adapter: Atlas.Adapters.Postgresdef config(:dev) do [ database: "", username: "", password: "", host: "", pool: 5, log_level: :debug ] end
def config(:test) do [ database: "", username: "", password: "", host: "", pool: 5, log_level: :debug ] end
def config(:prod) do [ database: "", username: "", password: "", host: "", pool: 5, log_level: :warn ] end end
Repo.start_link
Testing requires a
lib/atlas/repos/dev_repo.exto exist. Here's an example:
defmodule Repo do use Atlas.Repo, adapter: Atlas.Adapters.Postgresdef config(:dev) do [ database: "", username: "", password: "", host: "localhost", pool: 5, log_level: :debug ] end
def config(:test) do [ database: "atlas_test", username: "chris", password: "", host: "localhost", pool: 5, log_level: :debug ] end
def config(:prod) do [ database: "", username: "", password: "", host: "", pool: 5, log_level: :warn ] end end