rexmas / RealmCrust

Simple Crust Extension for Mapping Realm Objects From JSON
MIT License
25 stars 1 forks source link

Testing failed with RealmMapping #3

Closed minuscorp closed 8 years ago

minuscorp commented 8 years ago

I have a class created with a RealmMapping:

extension User: Mappable { }

public class UserMapping: RealmMapping {

    public var adaptor: RealmAdaptor
    public var primaryKeys: Array<CRMappingKey> {
        return ["data.identifier"]
    }

    public required init(adaptor: RealmAdaptor) {
        self.adaptor = adaptor
    }

    public func mapping(inout toMap: User, context: MappingContext) {
        let userBirthdateMapping = DateMapping(dateFormatter: NSDateFormatter.birthdateFormatter())

        toMap.identifier    <- "data.identifier" >*<
        toMap.birthDate     <- KeyExtensions.Mapping("data.birthdate", userBirthdateMapping) >*<
        toMap.name          <- "data.name" >*<
        toMap.surname       <- "data.surname" >*<
        context
    }
}

And I'm writing a test to check everything's working fine with the JSON decoding but when I create the CRMapper compiler fails: let userMapper = CRMapper<User, UserMapping> -> `Type 'UserMapping' does not conform to protocol 'Mapping' I've added the file that is required in theReadme.md``

Any clue?

rexmas commented 8 years ago

You should be able to click a dropdown next to that error in Xcode within the issue navigator to see exactly what parts of the protocol are missing. It certainly looks to me like everything is correct as I glance at it, so I'm very curious what information the error will tell us. Please gather more info and let me know!

Additionally, if you post the model object and DateMapping code here I can try it out on my end and let you know what I see.

minuscorp commented 8 years ago

It seems like it was an issue with the code is necessary to include in the project, maybe the code in the README is wrong. I copied the code from the tests and now is working, thank you!

minuscorp commented 8 years ago

Altough I've finally made it compile, I'm unable to run properly the testing...

override func spec(){
        describe("User spec"){

            var realmUser: User!
            var realm: Realm!
            var adaptor: RealmAdaptor!

            beforeEach{
                Realm.Configuration.defaultConfiguration.inMemoryIdentifier = self.name
                realm = try! Realm()
                adaptor = RealmAdaptor(realm: realm)
            }

            it("Should decode JSON User objects"){

                let json: Dictionary<String, AnyObject> = ["data": ["id_hash": 170, "user_name": "Jorge", "user_surname": "Revuelta", "birthdate": "1991-03-31", "height": 175, "weight": 60, "sex": 2]]

                let mapping = CRMapper<User, UserMapping>()
                let jsonValue = try! JSONValue(object: json)
                let realmUser = try! mapping.mapFromJSONToNewObject(jsonValue, mapping: UserMapping(adaptor: adaptor))

                try! adaptor.saveObjects([ realmUser ])

                expect(realm.objects(User).count) == 1
            }
        }
    }

I get the next error in the mapFromJSONToNewObject method: fatal error: 'try!' expression unexpectedly raised an error: Error Domain=CRMappingDomain Code=-1 "(null)": file /Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-700.1.101.15/src/swift/stdlib/public/core/ErrorType.swift, line 50

EDIT

It seems like if I skip the DateMapping it works fine, is necessary to do something special to make a custom transform to work with RealMapping? This is my custom DateMapping Transform:

extension NSDate: AnyMappable { }

class DateMapping: Transform {

    typealias MappedObject = NSDate

    let dateFormatter: NSDateFormatter

    init(dateFormatter: NSDateFormatter){
        self.dateFormatter = dateFormatter
    }

    func fromJSON(json: JSONValue) throws -> MappedObject {
        switch json {
        case .JSONString(let date):
            return self.dateFormatter.dateFromString(date) ?? NSDate()
        default:
            throw NSError(domain: "", code: 0, userInfo: nil)
        }
    }

    func toJSON(obj: MappedObject) -> JSONValue {
        return .JSONString(self.dateFormatter.stringFromDate(obj))
    }
}

EDIT 2

Also you asked for the Realm Object implementation:

public class User: Object {

    public dynamic var identifier: Int = 0
    public dynamic var name: String? = nil
    public dynamic var surname: String? = nil
    public dynamic var birthDate: NSDate = NSDate()

    // Function i'd like to use but it seems to crash also the RealmCrust with 'Primary key can't be changed after an object is inserted.'

    /*override public static func primaryKey() -> String? {
        return "identifier"
    }*/
}
rexmas commented 8 years ago

Thanks for the info. I'm pretty busy the rest of the week, if you figure out anything soon feel free to open a pull request.

I'll work this weekend to fix

Function i'd like to use but it seems to crash also the RealmCrust with 'Primary key can't be changed after an object is inserted.'

Seems like something I just need to special case.

And for the crash you're experiencing I'll run an equivalent test with the code you provided and see where the error lies. Nothing looks incorrect from what I can tell. We'll get this figured out.

minuscorp commented 8 years ago

I've been trying to debug this several times. I get lost when method mapping gets called and when returns to the mapper, context has an error != nil but I haven't found where it gets set so I'm out of clues of where to start and so on.

Also, maybe it should be an enchancement for Crust itself, Realm supports other types like Int64 that doesn't seem to be supported by mapping or even for JSONValuesRX when it finds an mapping like toMap.identifier <- "data.identifier" >*< and identifier is an Int64 type.

minuscorp commented 8 years ago

Ok, so as far as I've got, no custom DateMapping is being called but the NSDate mapping from JSONValueRX so that's the main reason why it fails:

private func mapFromJson<T: JSONable where T.ConversionType == T>(json: JSONValue, inout toField field: T?) throws {

    if case .JSONNull = json {
        field = nil
        return
    }
    // This next line calls NSDate.fromJSON defined in JSONValueRX instead of mine...
    if let fromJson = T.fromJSON(json) {
        field = fromJson
    } else {
        throw NSError(domain: CRMappingDomain, code: -1, userInfo: nil)
    }
}

Should the second or the fourth one being called when using a KeyExtensions.Mapping? They're not being called at all:

// Map arbitrary object.
public func <- <T: JSONable, C: MappingContext where T == T.ConversionType>(inout field: T, map:(key: JSONKeypath, context: C)) -> C {
    return mapField(&field, map: map)
}

// Map a Mappable.
public func <- <T: Mappable, U: Mapping, C: MappingContext where U.MappedObject == T>(inout field: T, map:(key: KeyExtensions<U>, context: C)) -> C {
    return mapField(&field, map: map)
}

// NOTE: Must supply two separate versions for optional and non-optional types or we'll have to continuously
// guard against unsafe nil assignments.

public func <- <T: JSONable, C: MappingContext where T == T.ConversionType>(inout field: T?, map:(key: JSONKeypath, context: C)) -> C {
    return mapField(&field, map: map)
}

public func <- <T: Mappable, U: Mapping, C: MappingContext where U.MappedObject == T>(inout field: T?, map:(key: KeyExtensions<U>, context: C)) -> C {
    return mapField(&field, map: map)
}
minuscorp commented 8 years ago
public func <- <T: JSONable, C: MappingContext where T == T.ConversionType>(inout field: T?, map:(key: JSONKeypath, context: C)) -> C {
    return mapField(&field, map: map)
}

This gets called when trying to map with the DateMapping. I think it should call the KeyExtensions<U> ones?

This is the backtrace:

Printing description of map:
(key: JSONKeypath) map = {
  key = {
    payload_data_0 = 0x00007fb94b30ea20 -> 0x0000000108028ba0 "data.birthdate"
    payload_data_1 = 0x00000000000014e8
    payload_data_2 = 0x00000001086c63d7 Crust`ext.Crust.Crust.Mapping<A where A: Crust.Mapping>.startMappingWithContext <A where A: Crust.Mapping> (A)(Crust.MappingContext) throws -> () + 903 at CRMapper.swift:114
    instance_type = 0x000000011cb57138
    protocol_witness_0 = 0x00000001086eeb28 Crust`protocol witness table for <A where A: Crust.Mapping> Crust.KeyExtensions<A> : JSONValueRX.JSONKeypath in Crust
  }
}
Printing description of map.key.Mapping.1:
(Ebikemotion.DateMapping) 1 = 0x00007fb948c65240 {
  dateFormatter = 0x00007fb948c18910 {
    Foundation.NSFormatter = {
      ObjectiveC.NSObject = {}
    }
  }
}
minuscorp commented 8 years ago

I think it's fixed with the PR opened in Crust https://github.com/rexmas/Crust/pull/31

rexmas commented 8 years ago

Ya, I tried your example on one of my model objects and it worked fine. Then I tried your test verbatim and I ended up with the same crash. Seems like this is a case where the compiler resolves the operator of choice different just based on some internal context we aren't aware of. Thanks for looking into this. Going to review your PR.