Elixir: assert does work properly for structs

Created on 23 May 2016  Â·  16Comments  Â·  Source: elixir-lang/elixir

Today I have upgraded to elixir 1.2.5 and for some reasom the following test failed:

test "registration's to_user function generates user with device and phone associations" do
    changeset = Registration.changeset(%Registration{}, @valid_attrs)
    registration = Ecto.Changeset.apply_changes(changeset)
    user = Registration.to_user(registration)
    assert struct(MyApp.Device, @valid_attrs) in user.devices
    assert struct(MyApp.Phone, @valid_attrs) in user.phones
  end

I receive the following error:

Assertion with in failed
     code: struct(MyApp.Device, @valid_attrs) in user.devices()
     lhs:  %MyApp.Device{__meta__: #Ecto.Schema.Metadata<:built>, id: nil,
            inserted_at: nil, updated_at: nil,
            user: #Ecto.Association.NotLoaded<association :user is not loaded>,
            user_id: nil, uuid: "599F9C00-92DC-4B5C-9464-7971F01F8370"}
     rhs:  [%MyApp.Device{__meta__: #Ecto.Schema.Metadata<:built>, id: nil,
             inserted_at: nil, updated_at: nil,
             user: #Ecto.Association.NotLoaded<association :user is not loaded>,
             user_id: nil, uuid: "599F9C00-92DC-4B5C-9464-7971F01F8370"}]

However when I change test to

   ...
    assert struct(MyApp.Device, Map.from_struct(registration)) in user.devices
    assert struct(MyApp.Phone, Map.from_struct(registration)) in user.phones

Test passes.

to_user function is pretty simple:

  def to_user(registration) do
    %MyApp.User{
      devices: [struct(MyApp.Device, Map.from_struct(registration))],
      phones: [struct(MyApp.Phone, Map.from_struct(registration))]
    }
  end

So is it a bug? or do I misunderstand something?

ExUnit Unresolved

Most helpful comment

Yes.

All 16 comments

@almasakchabayev it is very likely not a bug. There is certainly one of the fields that are still different. I would try the following:

dev1 = struct(MyApp.Device, @valid_attrs)
[dev2] = user.devices

assert dev1.id == dev2.id
assert dev1.__meta__ == dev2.__meta__
... and so on for all fields ...

And try to find which one does not match. This is probably a bug in Ecto. We may be hiding too much information.

@josevalim Thanks for quick reply. It appears that error happens in __meta__:

[device1 | _] = user.devices
    device2 = struct(Typi.Device, @valid_attrs)
    assert device1.__meta__ == device2.__meta__
    assert device1.id == device2.id
    assert device1.inserted_at == device2.inserted_at
    assert device1.updated_at == device2.updated_at
    assert device1.user == device2.user
    assert device1.user_id == device2.user_id
    assert device1.uuid == device2.uuid
Assertion with == failed
     code: device1.__meta__() == device2.__meta__()
     lhs:  #Ecto.Schema.Metadata<:built>
     rhs:  #Ecto.Schema.Metadata<:built>

without __meta__ no errors

@almasakchabayev can you paste the result of inspecting the Ecto.Schema.Metadata structs in your test? Be sure to use IO.inspect(meta, structs: false) so you get what's inside the struct :)

Can you please call Map.from_struct on both metas before comparing them so
we know what is different?

On Monday, May 23, 2016, Almas Akchabayev [email protected] wrote:

@josevalim https://github.com/josevalim Thanks for quick reply. It
appears that error happens in meta:

[device1 | _] = user.devices
device2 = struct(Typi.Device, @valid_attrs)
assert device1.__meta__ == device2.meta
assert device1.id == device2.id
assert device1.inserted_at == device2.inserted_at
assert device1.updated_at == device2.updated_at
assert device1.user == device2.user
assert device1.user_id == device2.user_id
assert device1.uuid == device2.uuid

Assertion with == failed
code: device1.meta() == device2.meta()
lhs: #Ecto.Schema.Metadata<:built>
rhs: #Ecto.Schema.Metadata<:built>

without meta no errors

—
You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
https://github.com/elixir-lang/elixir/issues/4668#issuecomment-220942689

_José Valimwww.plataformatec.com.br
http://www.plataformatec.com.br/Founder and Director of R&D_

Map.from_struct/1 is a better choice, we'll get the nice assertion formatting :)

@josevalim @whatyouhide I did

    assert Map.from_struct(device1.__meta__) == Map.from_struct(device2.__meta__)

and got the following error:

Assertion with == failed
     code: Map.from_struct(device1.__meta__()) == Map.from_struct(device2.__meta__())
     lhs:  %{context: nil, source: {nil, "registrations"}, state: :built}
     rhs:  %{context: nil, source: {nil, "devices"}, state: :built}

So that's the issue. You are merging registration metadata into the device.
I will improve the metadata to make those clearer on Ecto side, thank you.

On Monday, May 23, 2016, Almas Akchabayev [email protected] wrote:

@josevalim https://github.com/josevalim @whatyouhide
https://github.com/whatyouhide I did

assert Map.from_struct(device1.__meta__) == Map.from_struct(device2.__meta__)

and got the following error:

Assertion with == failed
code: Map.from_struct(device1.meta()) == Map.from_struct(device2.meta())
lhs: %{context: nil, source: {nil, "registrations"}, state: :built}
rhs: %{context: nil, source: {nil, "devices"}, state: :built}

—
You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
https://github.com/elixir-lang/elixir/issues/4668#issuecomment-220945439

_José Valimwww.plataformatec.com.br
http://www.plataformatec.com.br/Founder and Director of R&D_

@josevalim @whatyouhide Also I remembered how this error appeared. I had Registration model as embedded_schema (followed your post) and then changed it to schema "registrations" do. When I change it back to embedded no errors appear

@josevalim So I cannot write struct(MyApp.Device, Map.from_struct(registration)) in

def to_user(registration) do
    %MyApp.User{
      devices: [struct(MyApp.Device, Map.from_struct(registration))],
      phones: [struct(MyApp.Phone, Map.from_struct(registration))]
    }
  end

As it merges registrations metadata, right? What should I do instead though?

Right. I think there is a function in the Ecto module that would discard
the metadata field but I am not sure and I don't remember which. :) (I am
not at the computer right now).

On Monday, May 23, 2016, Almas Akchabayev [email protected] wrote:

@josevalim https://github.com/josevalim So I cannot write struct(MyApp.Device,
Map.from_struct(registration)) in

def to_user(registration) do
%MyApp.User{
devices: [struct(MyApp.Device, Map.from_struct(registration))],
phones: [struct(MyApp.Phone, Map.from_struct(registration))]
}
end

As it merges registrations metadata, right?

—
You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
https://github.com/elixir-lang/elixir/issues/4668#issuecomment-220946402

_José Valimwww.plataformatec.com.br
http://www.plataformatec.com.br/Founder and Director of R&D_

Fixed in Ecto master. Apparently the function I mentioned above does not exist, you will have to delete the meta field explicitly for now.

@josevalim I will just grab ecto master. Thanks

@josevalim I updated deps to Ecto master, but the error still exists

the error will still exist the difference is that you will be able to find out what is different between the two meta structs now.

@josevalim So would something like that be enough:

  def to_map(registration) do
    registration
    |> Map.from_struct
    |> Map.delete(:__meta__)
  end

Yes.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

GianFF picture GianFF  Â·  3Comments

andrewcottage picture andrewcottage  Â·  3Comments

ckampfe picture ckampfe  Â·  3Comments

Irio picture Irio  Â·  3Comments

alexrp picture alexrp  Â·  4Comments