diff --git a/lib/ecto/schema.ex b/lib/ecto/schema.ex index b1bec14ccf..45f7ec7915 100644 --- a/lib/ecto/schema.ex +++ b/lib/ecto/schema.ex @@ -167,8 +167,8 @@ defmodule Ecto.Schema do used by `belongs_to` associations. It must be set in the same module that defines the `belongs_to`. Defaults to `:id`; - * `@timestamps_opts` - configures the default timestamps type - used by `timestamps`. Defaults to `[type: :naive_datetime]`; + * `@timestamps_opts` - configures the default timestamps options + used by the [`timestamps`](`timestamps/1`) macro. Defaults to `[type: :naive_datetime]`; * `@derive` - the same as `@derive` available in `Kernel.defstruct/1` as the schema defines a struct behind the scenes; @@ -730,6 +730,11 @@ defmodule Ecto.Schema do * `:inserted_at` - the Ecto schema name of the field for insertion times or `false` * `:updated_at` - the Ecto schema name of the field for update times or `false` * `:inserted_at_source` - the name of the database column for insertion times or `false` + * `:inserted_at_writable` - the value of the Ecto schema `:writable` option for the + `inserted_at` field generated by this macro. Raises if `:never` is provided, + as Ecto must be able to automatically set the field at insertion. + * `:inserted_at_on_writable_violation` - the value of the Ecto schema `:on_writable_violation` + option for the `inserted_at` field generated by this macro. * `:updated_at_source` - the name of the database column for update times or `false` * `:type` - the timestamps type, defaults to `:naive_datetime`. * `:autogenerate` - a module-function-args tuple used for generating @@ -2110,6 +2115,23 @@ defmodule Ecto.Schema do if inserted_at do opts = if source = timestamps[:inserted_at_source], do: [source: source], else: [] + + opts = if writable = timestamps[:inserted_at_writable] do + if writable == :never do + raise ArgumentError, ":inserted_at_writable option cannot be set to :never as `inserted_at` will never be populated" + end + + Keyword.put(opts, :writable, writable) + else + opts + end + + opts = if on_writable_violation = timestamps[:inserted_at_on_writable_violation] do + Keyword.put(opts, :on_writable_violation, on_writable_violation) + else + opts + end + Ecto.Schema.__field__(mod, inserted_at, type, opts) end diff --git a/test/ecto/schema_test.exs b/test/ecto/schema_test.exs index 71f06905c5..3828752b03 100644 --- a/test/ecto/schema_test.exs +++ b/test/ecto/schema_test.exs @@ -359,6 +359,40 @@ defmodule Ecto.SchemaTest do [{[:updated_at], {:m, :f, [:a]}}] end + defmodule TimestampsWritableDefault do + use Ecto.Schema + + schema "timestamps" do + timestamps() + end + end + + defmodule TimestampsWritableCustom do + use Ecto.Schema + + schema "timestamps" do + timestamps(inserted_at_writable: :insert, inserted_at_on_writable_violation: :raise) + end + end + + test "timestamps with writable options" do + assert TimestampsWritableDefault.__schema__(:updatable_fields) == {[:updated_at, :inserted_at, :id], []} + assert TimestampsWritableDefault.__schema__(:on_writable_violation, :inserted_at) == :nothing + + assert TimestampsWritableCustom.__schema__(:updatable_fields) == {[:updated_at, :id], [:inserted_at]} + assert TimestampsWritableCustom.__schema__(:on_writable_violation, :inserted_at) == :raise + + assert_raise ArgumentError, ":inserted_at_writable option cannot be set to :never as `inserted_at` will never be populated", fn -> + defmodule TimestampsWritableNever do + use Ecto.Schema + + schema "timestamps" do + timestamps(inserted_at_writable: :never) + end + end + end + end + defmodule TimestampsCustom do use Ecto.Schema