atlas

by chrismccord

chrismccord / atlas

Object Relational Mapper for Elixir

209 Stars 17 Forks Last release: Not found MIT License 121 Commits 0 Releases

Available items

No Items, yet!

The developer of this repository has not created any items for sale yet. Need a bug fixed? Help with integration? A different license? Create a request here:

Atlas

Atlas is an Object Relational Mapper for Elixir. (Work in progress. Expect breaking changes)

Build Status

Current Features

  • Postgres Adapter
  • Validations
  • Persistence
  • Schema definitions
  • Model query builder
  • Auto-generated 'accessor' functions for each field definition

Roadmap

  • Extend query builder to support joins
  • Add model relationships, ie
    belongs_to
    ,
    has_many
    ,
    has_many through:
  • Additional SQL adapters
  • Schema migrations

Example Usage:

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...}

Query Builder

Examples

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...}

Queries are composable

defmodule UserSearch do
  import User

def 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]"}]

Persistence

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

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.

assign
functions transform attributes when creating a new Struct via
Model.new
and before running model callbacks such as validations.

Example attribute assignment:

defmodule User do
  use Atlas.Model
  field :email, :string
  field :name,  :string

def 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,  :string

def email(user), do: user.email |> String.upcase end

iex> user = User.new(email: "[email protected]") iex> User.email(user) [email protected]

Auto-generated finders

with_[field name]
functions are automatically generated for all defined fields. For example, a User module with a
field :email, :string
definition would include a
User.with_email
function that returns the first record matching that field from the database.

Validation Support

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"]

Repo Configuration

Define at least one Repository in your project that uses Atlas.Repo with a supported adapter. Your Repo simply needs to be provide

config
functions for
:dev
,
:test
, and
:prod
environments. After defining your repo, start its process within your application.
defmodule Repo do
  use Atlas.Repo, adapter: Atlas.Adapters.Postgres

def 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

Testing requires a

lib/atlas/repos/dev_repo.ex
to exist. Here's an example:
defmodule Repo do
  use Atlas.Repo, adapter: Atlas.Adapters.Postgres

def 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

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.