parameterized_type.ex (5987B)
1 defmodule Ecto.ParameterizedType do 2 @moduledoc """ 3 Parameterized types are Ecto types that can be customized per field. 4 5 Parameterized types allow a set of options to be specified in the schema 6 which are initialized on compilation and passed to the callback functions 7 as the last argument. 8 9 For example, `field :foo, :string` behaves the same for every field. 10 On the other hand, `field :foo, Ecto.Enum, values: [:foo, :bar, :baz]` 11 will likely have a different set of values per field. 12 13 Note that options are specified as a keyword, but it is idiomatic to 14 convert them to maps inside `c:init/1` for easier pattern matching in 15 other callbacks. 16 17 Parameterized types are a superset of regular types. In other words, 18 with parameterized types you can do everything a regular type does, 19 and more. For example, parameterized types can handle `nil` values 20 in both `load` and `dump` callbacks, they can customize `cast` behavior 21 per query and per changeset, and also control how values are embedded. 22 23 However, parameterized types are also more complex. Therefore, if 24 everything you need to achieve can be done with basic types, they 25 should be preferred to parameterized ones. 26 27 ## Examples 28 29 To create a parameterized type, create a module as shown below: 30 31 defmodule MyApp.MyType do 32 use Ecto.ParameterizedType 33 34 def type(_params), do: :string 35 36 def init(opts) do 37 validate_opts(opts) 38 Enum.into(opts, %{}) 39 end 40 41 def cast(data, params) do 42 ... 43 {:ok, cast_data} 44 end 45 46 def load(data, _loader, params) do 47 ... 48 {:ok, loaded_data} 49 end 50 51 def dump(data, dumper, params) do 52 ... 53 {:ok, dumped_data} 54 end 55 56 def equal?(a, b, _params) do 57 a == b 58 end 59 end 60 61 To use this type in a schema field, specify the type and parameters like this: 62 63 schema "foo" do 64 field :bar, MyApp.MyType, opt1: :baz, opt2: :boo 65 end 66 67 To use this type in places where you need it to be initialized (for example, 68 schemaless changesets), you can use `init/2`. 69 """ 70 71 @typedoc """ 72 The keyword options passed from the Schema's field macro into `c:init/1` 73 """ 74 @type opts :: keyword() 75 76 @typedoc """ 77 The parameters for the ParameterizedType 78 79 This is the value passed back from `c:init/1` and subsequently passed 80 as the last argument to all callbacks. Idiomatically it is a map. 81 """ 82 @type params :: term() 83 84 @doc """ 85 Callback to convert the options specified in the field macro into parameters 86 to be used in other callbacks. 87 88 This function is called at compile time, and should raise if invalid values are 89 specified. It is idiomatic that the parameters returned from this are a map. 90 `field` and `schema` will be injected into the options automatically. 91 92 For example, this schema specification 93 94 schema "my_table" do 95 field :my_field, MyParameterizedType, opt1: :foo, opt2: nil 96 end 97 98 will result in the call: 99 100 MyParameterizedType.init([schema: "my_table", field: :my_field, opt1: :foo, opt2: nil]) 101 102 """ 103 @callback init(opts :: opts()) :: params() 104 105 @doc """ 106 Casts the given input to the ParameterizedType with the given parameters. 107 108 If the parameterized type is also a composite type, 109 the inner type can be cast by calling `Ecto.Type.cast/2` 110 directly. 111 112 For more information on casting, see `c:Ecto.Type.cast/1`. 113 """ 114 @callback cast(data :: term, params()) :: 115 {:ok, term} | :error | {:error, keyword()} 116 117 @doc """ 118 Loads the given term into a ParameterizedType. 119 120 It receives a `loader` function in case the parameterized 121 type is also a composite type. In order to load the inner 122 type, the `loader` must be called with the inner type and 123 the inner value as argument. 124 125 For more information on loading, see `c:Ecto.Type.load/1`. 126 Note that this callback *will* be called when loading a `nil` 127 value, unlike `c:Ecto.Type.load/1`. 128 """ 129 @callback load(value :: any(), loader :: function(), params()) :: {:ok, value :: any()} | :error 130 131 @doc """ 132 Dumps the given term into an Ecto native type. 133 134 It receives a `dumper` function in case the parameterized 135 type is also a composite type. In order to dump the inner 136 type, the `dumper` must be called with the inner type and 137 the inner value as argument. 138 139 For more information on dumping, see `c:Ecto.Type.dump/1`. 140 Note that this callback *will* be called when dumping a `nil` 141 value, unlike `c:Ecto.Type.dump/1`. 142 """ 143 @callback dump(value :: any(), dumper :: function(), params()) :: {:ok, value :: any()} | :error 144 145 @doc """ 146 Returns the underlying schema type for the ParameterizedType. 147 148 For more information on schema types, see `c:Ecto.Type.type/0` 149 """ 150 @callback type(params()) :: Ecto.Type.t() 151 152 @doc """ 153 Checks if two terms are semantically equal. 154 """ 155 @callback equal?(value1 :: any(), value2 :: any(), params()) :: boolean() 156 157 @doc """ 158 Dictates how the type should be treated inside embeds. 159 160 For more information on embedding, see `c:Ecto.Type.embed_as/1` 161 """ 162 @callback embed_as(format :: atom(), params()) :: :self | :dump 163 164 @doc """ 165 Generates a loaded version of the data. 166 167 This is callback is invoked when a parameterized type is given 168 to `field` with the `:autogenerate` flag. 169 """ 170 @callback autogenerate(params()) :: term() 171 172 @optional_callbacks autogenerate: 1 173 174 @doc """ 175 Inits a parameterized type given by `type` with `opts`. 176 177 Useful when manually initializing a type for schemaless changesets. 178 """ 179 def init(type, opts) do 180 {:parameterized, type, type.init(opts)} 181 end 182 183 @doc false 184 defmacro __using__(_) do 185 quote location: :keep do 186 @behaviour Ecto.ParameterizedType 187 188 @doc false 189 def embed_as(_, _), do: :self 190 191 @doc false 192 def equal?(term1, term2, _params), do: term1 == term2 193 194 defoverridable embed_as: 2, equal?: 3 195 end 196 end 197 end