MongoAgile

Mongo Agile Library for Elixir, with a micro-language integrated query

Installation

def deps do
  [
    {:mongo_agile, "~> 0.7.0"}
  ]
end

Usage

The definition of queries are always in a module, this let us that all queries are organize in controllers that will have multiples queries of a collection.

defmodule Controller do
  use MongoAgile.Controller,
    collection: "test",
    pid_mongo: :mongo

  find_one "get",
    where: %{"_id" => id}

  # .. here more queries ..
end

Each query can have dynamic variables. When we run the queries, we will need give the value of the variables using a keyword.

Controller.run_query("get",[id: id_mongo])

We would can define easily a API with the following queries.

find_one "get", where: %{"_id" => id}

insert_one "post", document: doc

replace "put",
  where: %{"_id" => id},
  document: doc

delete_one "delete", where: %{"_id" => id}

update_one "patch",
  set: %{"$set" => changeset},
  where: %{"_id" => id}

The fact this queries can be include by default in the controller, if you active the flag install_api: true.

Then we can define a controller with the more common operations and after add the queries that we need it.

defmodule MongoAgile.Examples.Api.ApiController do

  use MongoAgile.Controller,
    collection: "test_api",
    pid_mongo: :mongo,
    install_api: true

  # When install_api: true
  # You will can use the following methods
  #
  # get(id)
  # post(doc)
  # put(id, doc)
  # delete(id)
  # patch(id, changeset)

  #... here more queries that you need ...

end

Now, we can use the methods of our fast api.

alias MongoAgile.Examples.Api.ApiController

test "crud" do
  {:ok, id_mongo} = ApiController.post(%{"msg" => "hello"})

  {:ok, doc_in_mongo} = ApiController.get(id_mongo)
  assert doc_in_mongo["msg"] == "hello"

  result = ApiController.patch(id_mongo, %{"msg" => "hello world"})
  assert result == {:ok, "updated"}

  {:ok, doc_in_mongo} = ApiController.get(id_mongo)
  assert doc_in_mongo["msg"] == "hello world"

  result = ApiController.put(id_mongo, %{"message" => "hi"})
  assert result == {:ok, "updated"}

  {:ok, doc_in_mongo} = ApiController.get(id_mongo)
  assert doc_in_mongo["msg"] == nil
  assert doc_in_mongo["message"] == "hi"

  result = ApiController.delete(id_mongo)
  assert result == {:ok, "it was deleted"}
end

Usage example

Writing queries into Controller

defmodule MongoAgile.Examples.Customer.CustomerController do
  use MongoAgile.Controller,
    collection: "test_customers",
    pid_mongo: :mongo

  find_one "get",
    where: %{"_id" => id}

  find "get_all", limit: limit

  find "get_active_customers",
    where: %{"state" => "active"},
    limit: limit

  update "validate",
    set: %{"$set" => %{"state" => "active"}},
    where: %{"$or" => [
      %{"contact.phone" => %{"$exists" => true}},
      %{"contact.email" => %{"$exists" => true}},
    ]}

  insert_one "create", document: customer
  insert "create_customers", documents: customers

  delete_one "remove",
    where: %{"_id" => id}

  delete "remove_all",
    where: %{}

end

Run queries

pep = CustomerModel.constructor("Pep","pep@email.com","909 909 909")
{flag, id_mongo} = CustomerController.run_query("create",[customer: pep])
assert flag == :ok

{flag, pep_from_db} = CustomerController.run_query("get",[id: id_mongo])
assert flag == :ok

assert CustomerModel.get_name(pep_from_db) == "Pep"
assert CustomerModel.get_contact_email(pep_from_db) == "pep@email.com"
assert CustomerModel.get_contact_phone(pep_from_db) == "909 909 909"

result = CustomerController.run_query("remove",[id: id_mongo])
assert result == {:ok, "it was deleted"}

Writing model using MapSchema Library

You can use :mongo_id => MongoAgile.MapSchema.IdObjectType like a custom type for can use BSON_ID type, this let you encode and decode the documents to Json

defmodule MongoAgile.Examples.Customer.CustomerModel do

  use MapSchema,
    schema: %{
        "_id" => :mongo_id,
        "name" => :string,
        "contact" => %{
          "email" => :string,
          "phone" => :string
       },
        "state" => :string
   },
    custom_types: %{
      :mongo_id => MongoAgile.MapSchema.IdObjectType
   }

  def constructor(name, email, phone) do
    new()
    |> put_name(name)
    |> put_contact_email(email)
    |> put_contact_phone(phone)
    |> put_state("active")
  end

end

Example Schema Versioning & Update:

For each querie we can define the functions after and before.

def get_before([id: id]) do
  #.. here can adapt the args before of make the query ..

  [id: id]
end

find_one "get",
  where: %{"_id" => id}

def get_after(result_query) do
  #.. here can adapt the result of query .. 

  result_query
end

In this example we are using after and before functions for update schema.

defmodule MongoAgile.Examples.Schema.SchemaController do
  use MongoAgile.Controller,
    collection: "test_schema_versioning",
    pid_mongo: :mongo

  alias MongoAgile.Examples.Schema.SchemaModel

  find_one "get",
    where: %{"_id" => id}

  # When recive a document it´s procesate with the model
  # to adapt the version if it needs.
  def get_after(result_query) do
    case result_query do
      {:ok, doc} ->
        {:ok, SchemaModel.versioning(doc)}
      _otherwise ->
        result_query
    end
  end

  insert_one "create", document: doc

  delete_one "remove", where: %{"_id" => id}

  replace "replace",
    document: doc,
    where: %{"_id" => id}

  # .. here others queries ..

end

The model check the version, adapt the documents that havent the actual schema and return the documents always with the actual schema that our app it´s waiting.

defmodule MongoAgile.Examples.Schema.SchemaModel do

  use MapSchema,
    schema: %{
        "_id" => :mongo_id,
        "schema_version" => :integer,
        #... here more fields of schema model
   },
    custom_types: %{
      :mongo_id => MongoAgile.MapSchema.IdObjectType
   }

  alias MongoAgile.Examples.Schema.SchemaController

  @schema_version 2

  def versioning(doc) do
   if doc["schema_version"] == @schema_version do
     {:versioning, "ok", doc}
   else
     # Here update version the process of update version

     doc = put_schema_version(doc, @schema_version)

     ## After adapt the schema can make the replace
     Task.start(fn() ->
        SchemaController.run_query("replace", [id: doc["_id"], doc: doc)
     end)

     {:versioning, "updating", doc}
   end
  end

  #... here more functions of model

end

About

The mission of :mongo_agile is create a elixir library that let work easily with MongoDB.

Thanks

We have create this library using the MongoDB driver for Elixir :mongodb and its official documentation. We want thanks the authors, and all contributors of the :mongodb for the Great Job! Thanks.

Sources: