elixir-ecto / ecto

A toolkit for data mapping and language integrated query.
https://hexdocs.pm/ecto
Apache License 2.0
6.17k stars 1.43k forks source link

__meta__.source issue with polymorphic association #1012

Closed zmoshansky closed 9 years ago

zmoshansky commented 9 years ago

__meta__.source doesn't seem to be set correctly when building a polymorphic association. This follows from my stackoverflow question. I would expect Ecto.Model.build/3 to set the appropriate source.

Stacktrace
 ** (Postgrex.Error) ERROR (undefined_table): relation "abstract table: addresses" does not exist
     stacktrace:
       (ecto) lib/ecto/adapters/sql.ex:480: Ecto.Adapters.SQL.model/6
       (ecto) lib/ecto/repo/model.ex:253: Ecto.Repo.Model.apply/4
       (ecto) lib/ecto/repo/model.ex:83: anonymous fn/10 in Ecto.Repo.Model.do_insert/4
       (ecto) lib/ecto/repo/model.ex:396: anonymous fn/3 in Ecto.Repo.Model.wrap_in_transaction/8
       (ecto) lib/ecto/pool.ex:292: Ecto.Pool.with_rollback/3
       (ecto) lib/ecto/adapters/sql.ex:566: Ecto.Adapters.SQL.transaction/8
       (ecto) lib/ecto/pool.ex:244: Ecto.Pool.outer_transaction/6
       (ecto) lib/ecto/adapters/sql.ex:535: Ecto.Adapters.SQL.transaction/3
Offending Code

user is a Ecto model as per Models section below; address looks like: # %{__meta__: #Ecto.Schema.Metadata<:built>, assoc_id: nil, city: "Mariamside", country: "Bouvet Island (Bouvetoya)", id: nil, inserted_at: nil, postal_code: "55155-8667", region: "New York", street_address: "07228 Dayton Tunnel", suite: "04", updated_at: nil}

This line is used to insert into db:

repo.insert! Ecto.Model.build(user, :address, address)

Inspecting it:

> IO.inspect Ecto.Model.build(user, :address, address).__meta__.source
 {nil, "abstract table: addresses"}
Workaround
meta = %{ address.__meta__ | source: {nil, "users_addresses"}}
address = %{address | __meta__: meta}
repo.insert! address
Models
  schema "user" do
    has_one :address, {"users_addresses", MyApp.Address}, foreign_key: :assoc_id
  end
  @required_fields ~w(address)

------

  # Materialized in users_addresses table 
  schema "abstract table: addresses" do
    field :assoc_id,        :integer
    field :street_address,  :string
  end
Mix Versions

phoenix: v1.0.3 phoenix_ecto: v1.2.0 postgrex: v0.9.1 ecto: 1.0.4

josevalim commented 9 years ago

I cannot reproduce this in our test suite. Sorry to ask but are you sure your user model was saved and compiled?

zmoshansky commented 9 years ago

No problem at all. Yes, I've double checked to make sure the user instance had been saved by re-loading it from the database. I also ran mix clean before running my test again.

I've included fuller code below, un-commenting the lines fixes the problem. The user is generated by a Fixture that uses Blacksmith. However, since I'm the reloading the model from the DB I wouldn't expect any problems from user.

  def save_one(repo, map) do
   IO.inspect map
    user = Fixture.User.saved_user(MyApp.Repo)
    user = repo.get!(MyApp.User, user.id)
    address = Ecto.Model.build(user, :address, Map.from_struct(map))
    # meta = %{ address.__meta__ | source: {nil, "users_addresses"}}
    # address = %{address | __meta__: meta}
    repo.insert! address
  end

The IO.inspect yields:

%MyApp.Address{__meta__: #Ecto.Schema.Metadata<:built>, assoc_id: nil,
 city: "Allyton", country: "Antigua and Barbuda", id: nil, inserted_at: nil,
 postal_code: "89470-8639", region: "Wyoming",
 street_address: "9980 Donald Extensions", suite: "4917", updated_at: nil}
josevalim commented 9 years ago

OH! This line:

 address = Ecto.Model.build(user, :address, Map.from_struct(map))

Is passing the __meta__ field which overrides the one we are building! :D If you delete the __meta__ key after you call from_struct you should be good to go. I will change Ecto to do the same.

zmoshansky commented 9 years ago

Map.delete(Map.from_struct(map), :__meta__) Worked like a charm. Thanks @josevalim!

josevalim commented 9 years ago

Reopening so I don't forget to fix this too. :)

gjaldon commented 9 years ago

@josevalim went ahead and created a PR for this :)