├── .gitignore ├── README.md ├── lib ├── mongoex.ex └── mongoex │ ├── base.ex │ ├── base │ └── fields.ex │ ├── mix.lock │ └── server.ex ├── mix.exs ├── mix.lock └── test ├── mongoex_test.exs └── test_helper.exs /.gitignore: -------------------------------------------------------------------------------- 1 | /ebin 2 | /deps 3 | erl_crash.dump 4 | /tmp 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mongoex 2 | 3 | Mongoex is an ODM-like module for MongoDb in Elixir. 4 | 5 | This is still under development. 6 | 7 | ## Usage 8 | 9 | ```elixir 10 | defmodule User do 11 | use Mongoex.Base 12 | fields name: nil, sex: nil, age: 20 13 | end 14 | 15 | iex> Mongoex.Server.setup(address: 'example.com', port: 27017, database: :your_app) 16 | iex> Mongoex.Server.start 17 | 18 | iex> user = User.new(name: "mururu", sex: :male, age: 22) 19 | User[_id: nil, name: "mururu", sex: :male, age: 22] 20 | 21 | iex> user.save 22 | :ok 23 | 24 | iex> mururu = User.find({:name, "mururu"}) 25 | User[_id: {<<80,130,218,110,117,35,125,79,90,0,0,1>>}, name: "mururu", sex: :male, age: 22] 26 | 27 | iex> mururu.destroy 28 | :ok 29 | ``` 30 | 31 | ## Options 32 | 33 | You can authenticate by passing username and password options in the setup. 34 | 35 | Mongoex can maintain a pool of connections that are created on server start. By default only one connction is created but you can change this by passing a pool option. If all connections are in use then a DB function will return 36 | 37 | ```elixir 38 | {:error, :no_available_connections} 39 | ``` 40 | -------------------------------------------------------------------------------- /lib/mongoex.ex: -------------------------------------------------------------------------------- 1 | defmodule Mongoex do 2 | defmacro __using__(_) do 3 | Mongoex.Server.setup 4 | Mongoex.Server.start 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/mongoex/base.ex: -------------------------------------------------------------------------------- 1 | defmodule Mongoex.Base do 2 | defmacro __using__(_) do 3 | quote do 4 | import Mongoex.Base.Fields 5 | 6 | def new_record?(record) do 7 | !record._id 8 | end 9 | 10 | def persisted?(record) do 11 | !new_record?(record) 12 | end 13 | 14 | def save(record) do 15 | if record.persisted? do 16 | replace(record) 17 | else 18 | insert(record) 19 | end 20 | end 21 | 22 | def insert(record) do 23 | {:ok, _id} = Mongoex.Server.insert(table_name,record_to_tuple(record)) 24 | record._id(_id) 25 | end 26 | 27 | def replace(record) do 28 | selector = {:_id, record._id} 29 | Mongoex.Server.replace(table_name, selector, record_to_tuple(record)) 30 | record 31 | end 32 | 33 | def delete(record) do 34 | selector = {:_id, record._id} 35 | Mongoex.Server.delete_all(table_name, selector) 36 | end 37 | 38 | def delete_all(selector // []) do 39 | Mongoex.Server.delete_all(table_name, selector) 40 | end 41 | 42 | def destroy(record) do 43 | delete(record) 44 | end 45 | 46 | def destroy_all(selector // []) do 47 | delete_all(selector) 48 | end 49 | 50 | def find(selector) do 51 | {:ok, res} = Mongoex.Server.find(table_name, selector) 52 | case res do 53 | {} -> nil 54 | {result} -> result_to_record(result) 55 | end 56 | end 57 | 58 | def find_all(selector, options // []) do 59 | {:ok, res} = Mongoex.Server.find_all(table_name, selector, options) 60 | case :mongo_cursor.rest(res) do 61 | [] -> 62 | [] 63 | list when is_list(list) -> 64 | Enum.map list, fn(x) -> result_to_record(x) end 65 | end 66 | end 67 | 68 | def count(selector // []) do 69 | {:ok, res} = Mongoex.Server.count(table_name, selector) 70 | res 71 | end 72 | 73 | defp table_name do 74 | binary_to_atom(String.downcase(List.last(String.split inspect(__MODULE__), ".", global: true))) 75 | end 76 | 77 | defp record_to_tuple(record) do 78 | keywords = record.to_keywords 79 | if record.new_record? do 80 | keywords = Keyword.delete keywords, :_id 81 | end 82 | list_to_tuple List.flatten Enum.map keywords, fn(x) -> tuple_to_list(x) end 83 | end 84 | 85 | defp result_to_record(result) do 86 | if size_is_even?(result) do 87 | list = tuple_to_keyvalue(result) 88 | new(list) 89 | end 90 | end 91 | 92 | defp size_is_even?(tuple) do 93 | rem(tuple_size(tuple), 2) == 0 94 | end 95 | 96 | defp tuple_to_keyvalue(tuple) do 97 | list = tuple_to_list(tuple) 98 | even_odd = Enum.map :lists.seq(0, length(list)-1), fn(x) -> rem(x,2) end 99 | list_zipped_even_odd = Enum.zip list, even_odd 100 | {even,odds} = List.foldl list_zipped_even_odd, {[],[]}, fn({v,i}, {even,odds}) -> if i==0, do: {Enum.concat(even,[v]),odds}, else: {even,Enum.concat(odds,[v])} end 101 | Enum.zip even, odds 102 | end 103 | end 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /lib/mongoex/base/fields.ex: -------------------------------------------------------------------------------- 1 | defmodule Mongoex.Base.Fields do 2 | defmacro fields(values // []) do 3 | Record.deffunctions(Keyword.merge(values, [_id: nil]), __CALLER__) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/mongoex/mix.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mururu/mongoex/de58649150a408f6ac3a97fe9b14870173e4dc4a/lib/mongoex/mix.lock -------------------------------------------------------------------------------- /lib/mongoex/server.ex: -------------------------------------------------------------------------------- 1 | defmodule Mongoex.Server do 2 | def start do 3 | :ok = :application.start(:mongodb) 4 | setup_pool 5 | end 6 | 7 | def setup(options // []) do 8 | :ets.new(:mongoex_server, [:set, :protected, :named_table]) 9 | :ets.insert(:mongoex_server, {:mongoex_server, Keyword.merge(default_options, options)}) 10 | end 11 | 12 | def config do 13 | :ets.lookup(:mongoex_server, :mongoex_server)[:mongoex_server] 14 | end 15 | 16 | def insert(table, tuple) do 17 | execute(fn() -> :mongo.insert(table, tuple) end) 18 | end 19 | 20 | def replace(table, selector, tuple) do 21 | execute(fn() -> :mongo.replace(table, selector, tuple) end) 22 | end 23 | 24 | def delete_all(table, tuple) do 25 | execute(fn() -> :mongo.delete(table, tuple) end) 26 | end 27 | 28 | def find(table, selector) do 29 | execute(fn() -> :mongo.find_one(table, selector) end) 30 | end 31 | 32 | def find_all(table, selector, options // []) do 33 | skip = options[:skip] 34 | if skip == nil do 35 | skip = 0 36 | end 37 | 38 | batch_size = options[:batch_size] 39 | if batch_size == nil do 40 | batch_size = 0 41 | end 42 | 43 | execute(fn() -> :mongo.find(table, selector, {}, skip, batch_size) end) 44 | end 45 | 46 | def count(table, selector) do 47 | execute(fn() -> :mongo.count(table,selector) end) 48 | end 49 | 50 | def execute(fun) do 51 | conn = get_connection_from_pool 52 | 53 | mongo_do = Module.function(:mongo, :do, 5) 54 | result = mongo_do.(:safe, :master, conn, config[:database], fun) 55 | 56 | return_connection_to_pool conn 57 | 58 | result 59 | end 60 | 61 | defp connect do 62 | :mongo.connect({config[:address], config[:port]}) 63 | end 64 | 65 | defp setup_pool do 66 | sequence = :lists.seq(1, config[:pool]) 67 | {_seqs, pool} = Enum.map_reduce sequence, [], fn(seq, acc) -> 68 | 69 | {:ok, conn} = connect 70 | 71 | if config[:username] !== nil and config[:password] !== nil do 72 | auth = fn() -> 73 | :mongo.auth(config[:username], config[:password]) 74 | end 75 | mongo_do = Module.function(:mongo, :do, 5) 76 | mongo_do.(:safe, :master, conn, config[:database], auth) 77 | end 78 | 79 | {seq, [conn|acc]} 80 | end 81 | 82 | :ets.new(:mongoex_pool, [:set, :public, :named_table]) 83 | :ets.insert(:mongoex_pool, {:mongoex_pool, pool}) 84 | end 85 | 86 | defp get_connection_from_pool do 87 | pool = :ets.lookup(:mongoex_pool, :mongoex_pool)[:mongoex_pool] 88 | 89 | case Enum.count(pool) do 90 | 0 -> 91 | {:error, :no_available_connections} 92 | _ -> 93 | conn = :erlang.hd(pool) 94 | new_pool = :erlang.tl(pool) 95 | :ets.insert(:mongoex_pool, {:mongoex_pool, new_pool}) 96 | conn 97 | end 98 | end 99 | 100 | defp return_connection_to_pool(conn) do 101 | pool = :ets.lookup(:mongoex_pool, :mongoex_pool)[:mongoex_pool] 102 | new_pool = [conn|pool] 103 | :ets.insert(:mongoex_pool, {:mongoex_pool, new_pool}) 104 | end 105 | 106 | defp default_options do 107 | [ address: 'localhost', 108 | port: 27017, 109 | database: :mongoex_test, 110 | username: nil, 111 | password: nil, 112 | pool: 1 113 | ] 114 | end 115 | end 116 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Mongoex.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [ app: :mongoex, 6 | version: "0.0.1", 7 | deps: deps ] 8 | end 9 | 10 | # Configuration for the OTP application 11 | def application do 12 | [] 13 | end 14 | 15 | # Returns the list of dependencies in the format: 16 | # { :foobar, "0.1", git: "https://github.com/elixir-lang/foobar.git" } 17 | defp deps do 18 | [ { :mongodb, github: "mururu/mongodb-erlang" } ] 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | [ "bson": {:git, "git://github.com/mongodb/bson-erlang", "6d3cc910eac6d4c21402427b41c411e201eca424", [{:ref, "HEAD"}]}, 2 | "mongodb": {:git, "git://github.com/mururu/mongodb-erlang.git", "4c640620bf2ab82d9be85d527a229cd6b60a4c02", []} ] 3 | -------------------------------------------------------------------------------- /test/mongoex_test.exs: -------------------------------------------------------------------------------- 1 | Code.require_file "../test_helper.exs", __FILE__ 2 | 3 | defmodule MongoexTest do 4 | use ExUnit.Case 5 | 6 | test "the truth" do 7 | assert true 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start 2 | --------------------------------------------------------------------------------