#619 ✓resolved
Lawrence Pit

Valid parent entity is not saved though child entity has FK set (to nil obviously)

Reported by Lawrence Pit | October 28th, 2008 @ 03:30 AM

class Account
  include DataMapper::Resource
  property :id, Serial
  property :name, String
end

class User
  include DataMapper::Resource
  property :id, Serial
  belongs_to :account
end

a = Account.new(:name => "foo")
u = User.new
u.account = a
if a.valid? && u.valid?
  # start txn
    a.save
    u.save
  # end
end

The saved user will have account_id column set to NULL.

see here as well:

>> a = Account.new(:name => "foo")
=> #<Account id=nil name="foo">
>> u = User.new(:login => "bar", :password => "admin", :password_confirmation => "admin")
=> #<User id=nil login="bar" account_id=nil crypted_password=nil salt=nil>
>> u.account = a
=> #<Account id=nil name="foo">
>> a.save
 ~ INSERT INTO `accounts` (`name`) VALUES ('foo')
=> true
>> u.account
=> #<Account id=3 name="foo">
>> u
=> #<User id=nil login="bar" account_id=nil crypted_password=nil salt=nil>
>> u.save
 ~ INSERT INTO `users` (`login`, `crypted_password`, `salt`) VALUES ('bar', 'f532ee0ffb5172b5d9d56377209a75f36db376b2', '19f28e7f12745aebbfdf80075dfe362f1480aa68')
=> true
>>

So even though u.account does return an account with an id (3), it does not save it upon saving the user.

Comments and changes to this ticket

  • Dan Kubb (dkubb)

    Dan Kubb (dkubb) January 8th, 2009 @ 02:19 PM

    • State changed from “new” to “unconfirmed”
    • Assigned user cleared.
  • Michael Klishin (antares)

    Michael Klishin (antares) January 9th, 2009 @ 06:05 PM

    • Assigned user set to “Michael Klishin (antares)”
    • Title changed from “Basic usage failure...” to “Valid parent entity is not saved though child entity has FK set (to nil obviously)”
  • Dirkjan Bussink

    Dirkjan Bussink January 10th, 2009 @ 02:19 AM

    • State changed from “unconfirmed” to “confirmed”

    I have experienced this issue in my own project just yesterday. I know work around it by explicitly setting the FK again just before saving, but that's pretty ugly.

  • Dan Kubb (dkubb)

    Dan Kubb (dkubb) January 10th, 2009 @ 03:47 AM

    • Assigned user changed from “Michael Klishin (antares)” to “Dan Kubb (dkubb)”

    I can confirm this is still an issue in dkubb/dm-core, and it isn't necessarily related to validations, so I am going to assign this to myself.

  • Michael Klishin (antares)

    Michael Klishin (antares) January 10th, 2009 @ 03:51 AM

    • Tag set to bug, dm-core

    I only picked this one because I have a friend who was bit by this issue and wanted to contribute a fix this weekend.

  • Dan Kubb (dkubb)

    Dan Kubb (dkubb) January 10th, 2009 @ 03:54 AM

    As a side note, the reason this is a bug is because when assigning the Account to the User#account= mutator, it look at the current Account object's key and sets the corresponding FK inside User.

    The problem with this is that when the Account is actually saved it doesn't go out and update the FK originally set. The Account has no knowledge of what objects depend on it, and I'm not entirely sure we want to go down that route.. with observers and other things.

    What I'm thinking we should do instead is not set the FK at all on assignment. We should cache the Account object inside @account in the User object. Anytime User#account_id is called, it should return whatever @account.id does. The moment the @account object is updated, and we save the User object, @account.id will return the correct value, and things will be persisted as expected.

    We should also make it so that User#account_id= temporarily saves the value in @account_id, but when User#account is called, we initialize the Account instance and remove the @account_id. That way User#account and User#account_id will work as above, and we won't have any cases where @account.id != @account_id.

  • Dan Kubb (dkubb)

    Dan Kubb (dkubb) January 11th, 2009 @ 02:14 AM

    • Assigned user changed from “Dan Kubb (dkubb)” to “Michael Klishin (antares)”

    @Michael: ahh ok, I'll assign it back to you. Please be aware though that in dkubb/dm-core relationships have been completely rewritten, so any fixes you make to sam/dm-core probably won't be carried forward into future releases.

    I tend to encourage people to make fixes to dkubb/dm-core, and backport anything to sam/dm-core that is critical, rather than directly in sam/dm-core. I am trying to keep sam/dm-core as close to frozen as possible, and only making critical fixes to it.

  • Dan Kubb (dkubb)

    Dan Kubb (dkubb) January 11th, 2009 @ 02:15 AM

    @Michael: BTW: if you decide to make the fix in sam/dm-core, because you think this is critical, please re-assign this to me because I will have to make sure dkubb/dm-core is fixed as well.

  • Martin Gamsjaeger (snusnu)

    Martin Gamsjaeger (snusnu) July 9th, 2009 @ 07:10 PM

    • State changed from “confirmed” to “resolved”

    As can be seen below, this is fixed in current datamapper/dm-core/next.

    require "rubygems"
    require "dm-core"
    require "dm-validations"
    require "spec"
    
    DataMapper::Logger.new(STDOUT, :debug)
    DataMapper.setup(:default, "sqlite3::memory:")
    
    class Account
      include DataMapper::Resource
      property :id, Serial
      property :name, String
    end
    
    class User
      include DataMapper::Resource
      property :id, Serial
      belongs_to :account
    end
    
    describe "saving m:1 relationships" do
      before(:all) do
        DataMapper.auto_migrate!
      end
      it "should save a valid parent" do
        a = Account.new(:name => "foo")
        u = User.new
        u.account = a
        a.save.should be_true
        u.save.should be_true
        
        Account.all.size.should == 1
        Account.get(a.id).name.should == 'foo'
        User.all.size.should == 1
      end
    end
    
    # mungo:snippets snusnu$ spec -cfs 619.rb 
    # 
    # saving m:1 relationships
    #  ~ (0.000143) SELECT sqlite_version(*)
    #  ~ (0.000110) DROP TABLE IF EXISTS "accounts"
    #  ~ (0.000026) DROP TABLE IF EXISTS "users"
    #  ~ (0.000023) PRAGMA table_info("accounts")
    #  ~ (0.000360) CREATE TABLE "accounts" ("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "name" VARCHAR(50))
    #  ~ (0.000017) PRAGMA table_info("users")
    #  ~ (0.000113) CREATE TABLE "users" ("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "account_id" INTEGER NOT NULL)
    #  ~ (0.000167) CREATE INDEX "index_users_account" ON "users" ("account_id")
    #  ~ (0.000037) INSERT INTO "accounts" ("name") VALUES ('foo')
    #  ~ (0.000057) INSERT INTO "users" ("account_id") VALUES (1)
    #  ~ (0.000037) SELECT "id", "name" FROM "accounts" ORDER BY "id"
    #  ~ (0.000024) SELECT "id", "name" FROM "accounts" WHERE "id" = 1
    #  ~ (0.000022) SELECT "id", "account_id" FROM "users" ORDER BY "id"
    # - should save a valid parent
    # 
    # Finished in 0.010888 seconds
    # 
    # 1 example, 0 failures
    

Please Sign in or create a free account to add a new ticket.

With your very own profile, you can contribute to projects, track your activity, watch tickets, receive and update tickets through your email and much more.

New-ticket Create new ticket

Create your profile

Help contribute to this project by taking a few moments to create your personal profile. Create your profile »

Tags

Pages