Issue with validate_inclusion not failing

136 views
Skip to first unread message

Mike Binns

unread,
Mar 10, 2015, 3:16:07 PM3/10/15
to elixi...@googlegroups.com
I have this code inside a Model:

  IO.puts "validating #{member_of_field} is one of #{inspect allowed_values}"
  ret = validate_inclusion(model_or_changeset, member_of_field, allowed_values)
  IO.inspect ret

And it produces this output:

validating type is one of ["web_server", "db"]
%Ecto.Changeset{changes: %{}, errors: [], filters: %{},
 model: %MyProject.DB.Models.ProductComponent{__state__: :built,
  id: nil, inserted_at: nil, name: "woah now",
  product: #Ecto.Association.NotLoaded<association :product is not loaded>,
  product_component_options: #Ecto.Association.NotLoaded<association :product_component_options is not loaded>,
  product_id: 73, type: "crazy junk", updated_at: nil}, optional: [],
 params: %{}, repo: nil, required: [:product_id, :name, :type], valid?: true,
 validations: [type: {:inclusion, ["web_server", "db"]}]}

Notice that "type" is "crazy junk", and not one of the two allowed values. It even has the inclusion for type under validations, but it shows as valid and no errors. What am I missing?

José Valim

unread,
Mar 10, 2015, 3:38:18 PM3/10/15
to elixi...@googlegroups.com
The field has not changed, you can see that changes is an empty map inside the changeset, so the changeset is not going to validate it. :)



José Valim
Skype: jv.ptec
Founder and Lead Developer

--
You received this message because you are subscribed to the Google Groups "elixir-ecto" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-ecto...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-ecto/663aa171-4975-44ac-9758-b426b5ca8900%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Mike Binns

unread,
Mar 10, 2015, 3:52:40 PM3/10/15
to elixi...@googlegroups.com, jose....@plataformatec.com.br
Ok so to expand on what I am attempting, I have created a BaseModel which my Models all use:

defmodule MyProject.DB.Models.BaseModel do

  defmacro __using__(_opts) do
    quote do
      use Ecto.Model

      def changeset(model_or_changeset, params \\ %{}) do
        ret =  cast(model_or_changeset, params, @required_fields, @optional_fields)
        validate_member_of(ret, params, @member_of_fields)
      end

      defp validate_member_of(model_or_changeset, params, []), do: model_or_changeset
      defp validate_member_of(model_or_changeset, params, [{member_of_field, allowed_values} | tail]) do
        IO.puts "validating #{member_of_field} is one of #{inspect allowed_values}"
        ret = validate_inclusion(model_or_changeset, member_of_field, allowed_values)
        IO.inspect ret
        validate_member_of(ret, params, tail)
      end

      def vinsert(model) do
        my_changeset = changeset(model)
        case my_changeset.valid? do
          true ->  {:ok, MyProject.Repo.insert(model)}
          false -> {:error, my_changeset.errors}
        end
      end
    end
  end
end

I can then define required, optional, and member_of fields for each model:

defmodule MyProject.DB.Models.ProductComponent do
  @required_fields [:product_id, :name, :type]
  @optional_fields []
  @member_of_fields [{:type, ["web_server", "db"]}]
  use MyProject.DB.Models.BaseModel

and call a validated insert (vinsert) like this:

  test "validate - fail to create component with invalid type", context do
    component = %ProductComponent{product_id: context[:product].id, type: "crazy junk", name: "woah now"}

    {status, errors} = ProductComponent.vinsert(component)
    assert status == :error
    assert Keyword.has_key?(errors, :type)
  end

This test fails because status is :ok.

My guess is that my issue lies in the def changeset in BaseModel. Thoughts?

Mike Binns

unread,
Mar 10, 2015, 3:54:15 PM3/10/15
to elixi...@googlegroups.com, jose....@plataformatec.com.br
BTW, the required fields checking is functioning correctly, just not the validate_inclusion.

José Valim

unread,
Mar 10, 2015, 4:17:10 PM3/10/15
to elixi...@googlegroups.com
You need to pass the field to the changeset instead of changing the model beforehand. All changes should go through the changeset. Something like:

    component = %ProductComponent{}

    {status, errors} = ProductComponent.vinsert(component, %{product_id: context[:product].id, type: "crazy junk", name: "woah now"})



José Valim
Skype: jv.ptec
Founder and Lead Developer

Mike Binns

unread,
Mar 10, 2015, 4:25:34 PM3/10/15
to elixi...@googlegroups.com, jose....@plataformatec.com.br
Ok, makes sense, thanks!
Reply all
Reply to author
Forward
0 new messages