realm / realm-swift

Realm is a mobile database: a replacement for Core Data & SQLite
https://realm.io
Apache License 2.0
16.32k stars 2.15k forks source link

Cannot nil a property on an object by updating with a nil value. #5000

Closed jhrasco closed 7 years ago

jhrasco commented 7 years ago

Goals

Able to save nil attributes.

Expected Results

Existing attribute with non-nil value will become nil.

Actual Results

Could not overwrite existing value with nil value. The existing value is always returned.

Steps to Reproduce

  1. Create an object.
  2. Assign a non-nil value to object's attribute.
  3. Save.
  4. Create another object with same primary key.
  5. Assign a nil value to the same object's attribute.
  6. Save.
  7. Fetch.
  8. Notice that the attribute is not nil.

Code Sample

class RealmObject: Object {
  dynamic var id = 0
  dynamic var attribute: String?

  override static func primaryKey() -> String? {
    return "id"
  }
}

let realmObject = RealmObject()
realmObject.attribute = "My Attribute"
try! Realm().add(realmObject, update: true)

let realmObject = RealmObject()
realmObject.attribute = nil
try! Realm().add(realmObject, update: true)

// At this point, realmObject.attribute is still equal to "My Attribute."

Version of Realm and Tooling

Realm framework version: 2.8.0

Realm Object Server version: ?

Xcode version: 8.3.2

iOS/OSX version: 10.3

Dependency manager + version: Cocoapods 1.2.0

austinzheng commented 7 years ago

Thanks for getting in touch with us. Unfortunately this is a known issue: we don't have a good way to distinguish intent between "nil means set this property to nil" and "nil means don't touch the current value of this property", and currently assume the latter. We're looking into ways to address this problem.

In the meantime, you can work around this issue by getting the managed instance of the object based on its primary key id, then changing its attribute value within a write transaction block.

(c.f. https://github.com/realm/realm-cocoa/issues/4706)

jhrasco commented 7 years ago

Hi @austinzheng. Thank you for your response. Just want to clarify, this doesn't happen in the older version of Realm (2.0.7 below). Is there any significant refactoring that was made in this version?

austinzheng commented 7 years ago

The relevant code path has been refactored a number of times since then. Unfortunately, we never formally established the semantics of nil properties used in the way above, so we didn't catch it as a breaking change. I'll talk to our other engineers to decide how we want to proceed in this manner.

FelixLisczyk commented 7 years ago

I con confirm that this issue was introduced in the latest 2.8.0 release. It doesn't occur in 2.7.0. Updating the managed object directly works fine in 2.8.0.

austinzheng commented 7 years ago

Thanks. I'm going to mark this as a bug and put it in our list of things to do.

bdash commented 7 years ago

Related to #4926.

PhilippeBoisney commented 7 years ago

Same problem here since 2.8.0 update.

mezhevikin commented 7 years ago

Hi, guys! It is not working for RealmOptional property. I use Realm 2.8.2

class RealmObject: Object {
  dynamic var id = 0
  dynamic var attribute: String?
  dynamic var age = RealmOptional<Int>()

  override static func primaryKey() -> String? {
    return "id"
  }
}

let realmObject = RealmObject()
realmObject.age.value = 30
try! Realm().add(realmObject, update: true)

let realmObject = RealmObject()
realmObject.age.value = nil
try! Realm().add(realmObject, update: true)
sroik commented 7 years ago

Guys you have to fix create( type:* value:* update: true) for nil RealmOptional properties too or for 'optional to-one relationships'

Here it is

screen shot 2017-06-23 at 3 51 01 pm

Handle this pls :)

austinzheng commented 7 years ago

Fixing the create or update case of this bug falls under #5042.

@tgoyne, with #5046 merged is there anything else you are aware of that needs to be done for this issue?

jpsim commented 7 years ago

Closing as #5042 appears to have sufficiently addressed this.

austinzheng commented 7 years ago

5042 is a different problem, I believe (the two APIs use different codepaths). I'll confirm this is fixed this week and close or address as appropriate.

austinzheng commented 7 years ago

I went back and checked; the linked PR indeed ensures that the proper behavior occurs for this case.

markst commented 7 years ago

@austinzheng is this resolved in 3.0.0?

Can I confirm how one might distinguish between setting a property to nil and not touching a property at all when updating an existing object in store? As I'd like both behaviour when using optionals...

austinzheng commented 7 years ago

We now set nil on a property if nil is specified. If you want to not touch the property at all you'll have to retrieve the object and set its properties explicitly.

markst commented 6 years ago

@austinzheng how about any difference between using nil as default value?

@objc dynamic var fitting: Fitting?
@objc dynamic var fitting: Fitting? = nil
bdash commented 6 years ago

Those two declarations give the same behavior since in Swift an optional property is automatically initialized to nil unless otherwise specified.