KrakenDev/PrediKit

Parameters are bound to the wrong field

djbe opened this issue ยท 10 comments

djbe commented

Let's say I have the following code:

let predicate = NSPredicate(Shipment.self) { includeIf in
    let includeIfState = includeIf.member(Selector(ShipmentRelationships.currentState.rawValue), ofType: State.self).string(Selector(StateAttributes.stateRaw.rawValue))

    let noOffer = includeIf.member(Selector(ShipmentRelationships.acceptedOffer.rawValue), ofType: Offer.self).equalsNil
    let cancelled = includeIfState.equals(State.State.Cancelled.rawValue)
    let expired = includeIf.number(Selector(ShipmentAttributes.pickupTimeTo.rawValue)).isLessThanOrEqualTo(soon)
    let sender = includeIf.member(Selector(ShipmentRelationships.sender.rawValue), ofType: Sender.self).equals(data)

    sender && noOffer && !cancelled && !expired
}

Unexpectedly, the resulting predicate is something like this:

sender == 1464553718.353965 AND acceptedOffer == nil AND (NOT currentState.stateRaw == "cancelled") AND (NOT pickupTimeTo <= <Sender: 0x7fe2cb708720> (entity: Sender; id: 0xd000000000080000 x-coredata://F5039107-C745-4046-B0B5-5F71ED5738B7/Sender/p2 ; data: {...}))

As you can see, the parameters of the sender and expired sub-predicates have been exchanged.

Side note: I'm using variables inside the block because this is a simplified example. In the actual code, the final predicate is composed of different sub-predicates depending on some filter parameter.

Looking into this now to see if I can replicate it. Will also write a test for it to make sure it doesn't happen again!

Also, for readability, I would definitely suggest an extension on Selector!

By converting to a Selector extension:

extension Selector {
    static let currentState = Selector(ShipmentRelationships.currentState.rawValue)
    static let stateRaw = Selector(StateAttributes.stateRaw.rawValue)
    static let acceptedOffer = Selector(ShipmentRelationships.acceptedOffer.rawValue)
    static let pickupTimeTo = Selector(ShipmentAttributes.pickupTimeTo.rawValue)
    static let sender = Selector(ShipmentRelationships.sender.rawValue)
}

You can further simplify the code by using type omission:

let predicate = NSPredicate(Shipment.self) { includeIf in
    let includeIfState = includeIf.member(.currentState), ofType: State.self).string(.stateRaw)

    let noOffer = includeIf.member(.acceptedOffer, ofType: Offer.self).equalsNil
    let cancelled = includeIfState.equals(State.State.Cancelled.rawValue)
    let expired = includeIf.number(.pickupTimeTo).isLessThanOrEqualTo(soon)
    let sender = includeIf.member(.sender), ofType: Sender.self).equals(data)

    sender && noOffer && !cancelled && !expired
}
djbe commented

Thanks!
I'll look into the selector extension pattern later, once I get this bit working.

You got it. I'm literally testing this right now.

I'll have to do arbitrary values and stuff but I'm pretty sure I can replicate it. Will let you know when it's done.

I figured it out! The values added to the arguments array wasn't being respected when combining includers. I suspect adding the arguments at the time of combination should fix it.

As a patch, you can order the bottom four combiners in the order they were defined. However, the framework should be accounting for situations where the developer wants to order it however the hell they want. Working on the solution now.

@djbe I have now updated PrediKit to handle issues like this correctly. Please update and let me know if it works out for you!

djbe commented

Great work! From some initial testing, it seems to be working correctly.

Side note: There is quite a lot of logging now related to LHS, RHS and Final arguments. Could this be disabled (or removed)?

Just removed them! I hate when I do that. Update to 2.0.6 to get them removed!