Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use throwing var methods for realm references to other objects #1035

Closed
tomholub opened this issue Nov 18, 2021 · 14 comments · Fixed by #1165
Closed

use throwing var methods for realm references to other objects #1035

tomholub opened this issue Nov 18, 2021 · 14 comments · Fixed by #1165
Assignees
Labels
actionable PR Submitted PR is submitted for this issue

Comments

@tomholub
Copy link
Collaborator

@tomholub ability to throw from var variable was added to the Swift recently:

var activeUser: UserRealmObject {
    get throws {
        guard let user = user else { throw AppErr... }
        return user
    }
  }

This looks good. Probably for another issue/PR?

The advantage is that an exception can be captured and handled (in the general error handling somewhere in VC, wherever user action was initiated) while a forced unwrap cannot.

Originally posted by @tomholub in #1030 (comment)

@tomholub tomholub added this to the 0.6.0: Maintenance milestone Nov 18, 2021
@ivan-ushakov ivan-ushakov added the in progress Work on the issue is in progress label Nov 26, 2021
@ivan-ushakov
Copy link
Contributor

@tomholub
Now we use optional reference in Realm objects for linked objects, as documentation says. What we need to do in this issue?

@ivan-ushakov ivan-ushakov removed the in progress Work on the issue is in progress label Nov 28, 2021
@tomholub
Copy link
Collaborator Author

tomholub commented Nov 28, 2021

I'm thinking, when converting from RealmObject to a general model, throw if these cross-referenced objects are missing.

Then general models would not have optional objects, if we meant the objects to be required.

For example, a private key is meaningless if it doesn't belong to a particular user. So a key with a nil user on it makes no sense: should throw when we convert that from PrivateKeyRealmObject to PrivateKey (or similar - didn't check the actual names)

@ivan-ushakov
Copy link
Contributor

ivan-ushakov commented Nov 29, 2021

We have KeyInfoRealmObject but when we create it, we provide user object like this:

convenience init(_ keyDetails: KeyDetails, passphrase: String?, source: KeySource, user: UserRealmObject) throws

and in KeyInfo type we don't have user property. All other places are same. We don't construct objects with optional links (the only exception is UserRealmObject with optional SessionRealmObject).

@tomholub
Copy link
Collaborator Author

Yes, but look at this:

extension KeyInfoRealmObject {
    /// associated user email
    var account: String? {
        user?.email
    }
}

user should never be nil, therefore there should be guard and throw, and it should return a String that's not optional. Usages of .account which is undesirably optional:

image

Same problem here:

extension ClientConfigurationRealmObject: CachedRealmObject {
    var identifier: String { userEmail ?? "" }

    var activeUser: UserRealmObject? { user }
}

actually there are two problems above:

  1. activeUser is badly named, should be just "user". There is nothing active about it. And it should not be nilable, so guard and throw?
  2. identifier should not be defaulted to empty string!!! If I saw this in newly added code, I'd be chopping heads :-) I must have overlooked it earlier. What exactly is an empty string for identifier? Need to guard and throw.

And so on

@ivan-ushakov
Copy link
Contributor

ivan-ushakov commented Nov 29, 2021

@tomholub
In one case we could have nil active user:

extension RecipientRealmObject: CachedRealmObject {
    // Contacts can be shared between accounts
    // https://github.com/FlowCrypt/flowcrypt-ios/issues/269
    var activeUser: UserRealmObject? { nil }

    var identifier: String { email }
}

How to deal with this case?

@ivan-ushakov
Copy link
Contributor

activeUser is badly named, should be just "user". There is nothing active about it. And it should not be nilable, so guard and throw?

This will overlap with user property in object. We have different semantic here: in object we could have optional user link because Realm requires it, but in cache protocol we should have user.

protocol CachedRealmObject: Object {
    associatedtype Identifier: Equatable
    var identifier: Identifier { get }
    var activeUser: UserRealmObject { get }
}

@tomholub
Copy link
Collaborator Author

tomholub commented Nov 29, 2021

@tomholub In one case we could have nil active user:

extension RecipientRealmObject: CachedRealmObject {
    // Contacts can be shared between accounts
    // https://github.com/FlowCrypt/flowcrypt-ios/issues/269
    var activeUser: UserRealmObject? { nil }

    var identifier: String { email }
}

How to deal with this case?

There is no reason for RecipientRealmObject to inherit of CachedRealmObject. It should be separated in any case.

Then that will also solve this problem.

@tomholub
Copy link
Collaborator Author

activeUser is badly named, should be just "user". There is nothing active about it. And it should not be nilable, so guard and throw?

This will overlap with user property in object. We have different semantic here: in object we could have optional user link because Realm requires it, but in cache protocol we should have user.

protocol CachedRealmObject: Object {
    associatedtype Identifier: Equatable
    var identifier: Identifier { get }
    var activeUser: UserRealmObject { get }
}

Then we can call it owner maybe?

@ivan-ushakov
Copy link
Contributor

@Kharchevskyi

Could you please explain what was the idea of EncryptedCacheService? Is it the same as EncryptedStorage?

For example if we want to remove CachedRealmObject conformance from RecipientRealmObject, we can not use this:

struct LocalContactsProvider {
    private let localContactsCache: EncryptedCacheService<RecipientRealmObject>
    let core: Core

    init(
        encryptedStorage: EncryptedStorageType = EncryptedStorage(),
        core: Core = .shared
    ) {
        self.localContactsCache = EncryptedCacheService<RecipientRealmObject>(encryptedStorage: encryptedStorage)
        self.core = core
    }
}

@ivan-ushakov
Copy link
Contributor

@tomholub

I think it makes sense to wait for #1117.

@tomholub
Copy link
Collaborator Author

yes.. sorry about another ridiculous PR

@Kharchevskyi
Copy link
Contributor

@Kharchevskyi

Could you please explain what was the idea of EncryptedCacheService? Is it the same as EncryptedStorage?

For example if we want to remove CachedRealmObject conformance from RecipientRealmObject, we can not use this:

struct LocalContactsProvider {
    private let localContactsCache: EncryptedCacheService<RecipientRealmObject>
    let core: Core

    init(
        encryptedStorage: EncryptedStorageType = EncryptedStorage(),
        core: Core = .shared
    ) {
        self.localContactsCache = EncryptedCacheService<RecipientRealmObject>(encryptedStorage: encryptedStorage)
        self.core = core
    }
}

Initially idea was to have some service responsible for caching with injected storage, so Realm is not exposed all over the app. For convenience you are dealing with models, not realm models, so on client side of this service there is only object which conforms to CachedRealmObject (was different naming). So realm is not exposed to all over the app

Initially, I wanted to make it more generic with Object and storage for the object. But now I'm not sure it's already needed.
So, you can get rid of it, if it's not needed anymore.

@Kharchevskyi
Copy link
Contributor

also from usage perspective you don't need to deal with realm interface to get, remove or save objects

guard let objectToDelete = realm
            .objects(T.self)
            .first(where: { $0.identifier == identifier })
        else { return }

        try? realm.write {
           realm.delete(objectToDelete)
        }

@tomholub
Copy link
Collaborator Author

just a note, this

       try? realm.write {
           realm.delete(objectToDelete)
        }

the ? should be avoided, in all similar situations we should throw and let the exception bubble upwards the chain all the way to whichever Task originated the call, where the error will be handled like any other Error.

@ivan-ushakov ivan-ushakov added the in progress Work on the issue is in progress label Dec 1, 2021
@ivan-ushakov ivan-ushakov added the PR Submitted PR is submitted for this issue label Dec 4, 2021
tomholub pushed a commit that referenced this issue Dec 6, 2021
…jects (#1165)

* Use throwing var methods for realm references to other objects

* Code review fixes

* Code review fixes

* Code review fixes
@tomholub tomholub removed the in progress Work on the issue is in progress label Jan 12, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
actionable PR Submitted PR is submitted for this issue
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants