redstone-dart / redstone

A metadata driven microframework for Dart.
http://redstone-dart.github.io/redstone
MIT License
342 stars 42 forks source link

Update with override false is overriding #82

Closed cgarciae closed 9 years ago

cgarciae commented 9 years ago

I am doing a PUT method where user is really delta user in the sense that it only contains the changes to the user, in most cases only one field is not null. For this I was using

await db.update
(
    Col.user,
    where.id (StringToId (id)),
    user,
    override: false
);

but everything is being overwritten. My user class is

class Resp
{
    @Field() bool get success => nullOrEmpty(error);
    @Field() bool get failed => ! success;

    @Field() String error;
    @Field() int errCode;
}

class Ref extends Resp
{
    @Id() String id;
    @Field() String href;
}

class User extends Ref
{
    @Field() String get href => id != null ? localHost + 'user/$id' : null;

    @Field() String nombre;
    @Field() String apellido;
    @Field() String email;
}

I even get a Mongo Dart error for trying to set _id to null. I tried to solve the problem by encoding to a map with MongoDB.encode, and I noticed that it doesn't exclude null fields, so I created this function

ModifierBuilder getModifierBuilder (Object obj, [MongoDb dbConn])
{
    dbConn = dbConn == null ? db : dbConn;
    Map<String, dynamic> map = dbConn.encode(obj);
    return new ModifierBuilder()
        ..map = {r'$set' : cleanMap (map)};
}

where cleanMap is a function that recursively traverses the json excluding null values. I'll put the implementation just in case its useful. I don't believe my workaround to be efficient but its working, a solution within the codecs would be much better.

dynamic cleanMap (dynamic json)
{
    if (json is List)
    {
        return json.map (cleanMap).toList();
    }
    else if (json is Map)
    {
        var map = {};
        for (String key in json.keys)
        {
            var value = json[key];

            if (value == null)
                continue;

            if (value is List || value is Map)
                map[key] = cleanMap (value);

            else
                map[key] = value;
        }
        return map;
    }
    else
    {
        return json;
    }
}
luizmineo commented 9 years ago

Thanks for the report! I've published redstone_mapper_mongo v0.1.3 with a fix for this.

cgarciae commented 9 years ago

Thanks!!

cgarciae commented 9 years ago

This still has issues. While it worked if you only update a single layer structure, updating hierarchical structures produces this error on mongo:

{connectionId: 374, err: The dollar ($) prefixed field '$set' in 'target_record.$set' is not valid for storage., code: 52, n: 0, ok: 1.0}

Here is my test, the problem arises in target_record, presumably setting its width variable.

@app.Route ('/testOverride')
@Encode()
testOverride () async
{

    var id = newId();

    VuforiaResponse resp = new VuforiaResponse()
        ..result_code = "Success"
        ..id = id
        ..target_record = (new VuforiaTargetRecord()
            ..name = "Garcia");

    var col = 'testVuforia';

    await db.insert(col, resp);

    var delta = new VuforiaResponse()
        ..target_record = (new VuforiaTargetRecord()
                ..width = 2);

    await db.update
    (
        col,
        where.id(StringToId(id)),
        delta,
        override: false
    );

    return db.findOne (col, VuforiaResponse, where.id(StringToId(id)));
}
luizmineo commented 9 years ago

It seems that mapper is generating more than one $set modifier for the same object. I'll test this better during this weekend.

luizmineo commented 9 years ago

a new release (v0.1.3+1) of redstone_mapper_mongo was published with a fix for this.

cgarciae commented 9 years ago

Worked great!!! Thanks :) Very useful fix.

cgarciae commented 9 years ago

@luizmineo There still an issue in this case: setting nested objects that where previously null

//NOTICE: target_record not set (null)
VuforiaResponse resp = new VuforiaResponse()
        ..result_code = "Success"
        ..id = id;

    var col = 'testVuforia';

    await db.insert(col, resp);

    //NOTICE: includes a "target_record" with width
    var delta = new VuforiaResponse()
        ..target_record = (new VuforiaTargetRecord()
                ..width = 2);

    //RAISES ERROR
    await db.update
    (
        col,
        where.id(StringToId(id)),
        delta,
        override: false
    );

Produces this error on Mongo

{connectionId: 162, err: cannot use the part (target_record of target_record.width) to traverse the element ({target_record: null}), code: 16837, n: 0, ok: 1.0}

services\test_services.dart 1:1  testOverride.