├── .formatter.exs ├── .gitignore ├── LICENSE ├── README.md ├── config └── config.exs ├── lib ├── ecto_fields.ex └── fields │ ├── atom.ex │ ├── email.ex │ ├── ip.ex │ ├── ip4.ex │ ├── ip6.ex │ ├── positive_integer.ex │ ├── slug.ex │ ├── static.ex │ └── url.ex ├── mix.exs ├── mix.lock └── test ├── ecto_fields_test.exs └── test_helper.exs /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: [".formatter.exs", "mix.exs", "{config,lib,priv,test}/**/*.{ex,exs}"], 4 | line_length: 132 5 | ] 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /cover 3 | /deps 4 | /doc 5 | erl_crash.dump 6 | *.ez 7 | /.elixir_ls 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jerel Unruh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EctoFields 2 | 3 | Provides commonly used fields for Ecto projects. 4 | 5 | ## Installation 6 | 7 | To install EctoFields: 8 | 9 | 1. Add ecto_fields to your list of dependencies in `mix.exs` : 10 | ```elixir 11 | def deps do 12 | [{:ecto_fields, "~> 1.3.0"}] 13 | end 14 | ``` 15 | 2. Use the fields in your Ecto schema: 16 | ```elixir 17 | schema "user" do 18 | field :name, :string 19 | field :email, EctoFields.Email 20 | field :website, EctoFields.URL 21 | field :ip_address, EctoFields.IP 22 | end 23 | ``` 24 | ## Current fields 25 | 26 | * EctoFields.Atom 27 | * EctoFields.Email 28 | * EctoFields.IP (accepts both ipv4 and ipv6) 29 | * EctoFields.IPv4 30 | * EctoFields.IPv6 31 | * EctoFields.PositiveInteger 32 | * EctoFields.Slug 33 | * EctoFields.Static 34 | * EctoFields.URL 35 | 36 | ## Roadmap 37 | 38 | ### Likely: 39 | 40 | * EctoFields.Duration 41 | 42 | ### Maybe: 43 | 44 | * EctoFields.File 45 | * EctoFields.Image 46 | 47 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # 3rd-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure for your application as: 12 | # 13 | # config :ecto_fields, key: :value 14 | # 15 | # And access this configuration in your application as: 16 | # 17 | # Application.get_env(:ecto_fields, :key) 18 | # 19 | # Or configure a 3rd-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env}.exs" 31 | -------------------------------------------------------------------------------- /lib/ecto_fields.ex: -------------------------------------------------------------------------------- 1 | defmodule EctoFields do 2 | end 3 | -------------------------------------------------------------------------------- /lib/fields/atom.ex: -------------------------------------------------------------------------------- 1 | defmodule EctoFields.Atom do 2 | @moduledoc """ 3 | Coerce a regular string into an atom 4 | 5 | ## Examples 6 | 7 | Note: only use this field when you have a fixed number of possible values (atoms are not garbage collected) 8 | 9 | iex> EctoFields.Atom.cast("started") 10 | {:ok, :started} 11 | 12 | iex> EctoFields.Atom.cast(:started) 13 | {:ok, :started} 14 | 15 | iex> EctoFields.Atom.cast(nil) 16 | {:ok, nil} 17 | """ 18 | @behaviour Ecto.Type 19 | 20 | @max_atom_length 0xFF 21 | 22 | def type(), do: :string 23 | 24 | def cast(nil), do: {:ok, nil} 25 | def cast(atom) when is_atom(atom), do: {:ok, atom} 26 | def cast(binary) when is_binary(binary) and byte_size(binary) <= @max_atom_length, do: {:ok, String.to_atom(binary)} 27 | def cast(_), do: :error 28 | 29 | # when loading from the database convert to an atom 30 | def load(term), do: cast(term) 31 | 32 | # save to the database 33 | def dump(nil), do: {:ok, nil} 34 | def dump(atom) when is_atom(atom), do: {:ok, Atom.to_string(atom)} 35 | def dump(binary) when is_binary(binary) and byte_size(binary) <= @max_atom_length, do: {:ok, binary} 36 | def dump(_), do: :error 37 | 38 | def embed_as(_), do: :self 39 | 40 | def equal?(a, b), do: a == b 41 | end 42 | -------------------------------------------------------------------------------- /lib/fields/email.ex: -------------------------------------------------------------------------------- 1 | defmodule EctoFields.Email do 2 | @behaviour Ecto.Type 3 | 4 | def type, do: :string 5 | 6 | @doc """ 7 | Validate that the given value is a valid email 8 | 9 | ## Examples 10 | 11 | iex> EctoFields.Email.cast("foo.bar@example.com ") 12 | {:ok, "foo.bar@example.com"} 13 | 14 | iex> EctoFields.Email.cast("foo.bar+baz/@long.example.photography.uk") 15 | {:ok, "foo.bar+baz/@long.example.photography.uk"} 16 | 17 | iex> EctoFields.Email.cast("test@localhost") 18 | {:ok, "test@localhost"} 19 | 20 | iex> EctoFields.Email.cast("test@192.168.10.1") 21 | {:ok, "test@192.168.10.1"} 22 | 23 | iex> EctoFields.Email.cast("test@2001:1620:28:1:b6f:8bca:93:a116") 24 | {:ok, "test@2001:1620:28:1:b6f:8bca:93:a116"} 25 | 26 | iex> EctoFields.Email.cast("foo.bar@example.com/") 27 | :error 28 | 29 | iex> EctoFields.Email.cast("foo.bar@example.com.") 30 | :error 31 | 32 | iex> EctoFields.Email.cast("bad email") 33 | :error 34 | 35 | iex> EctoFields.Email.cast("test@example.com