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

Issue finding columns with NULL values in RubyMotion #70

Closed
aceofspades opened this Issue Jan 28, 2013 · 20 comments

Comments

Projects
None yet
3 participants
@aceofspades

aceofspades commented Jan 28, 2013

This is a follow-up to Issue #64.

Background: RubyMotion allows the use of compiled ruby and maps datatypes to Objective-C, and allows the use of Objective-C libraries to build iOS apps.

RubyMotion collapses many datatypes, including the value of nil and NSNull.null. This breaks the assertions in initWithColumn:(NSFTableColumnType)type matching:(NSFMatchType)matching value:(id)aValue and the first if in + (NSString *)_querySegmentForAttributeColumnWithValue:(id)anAttributeValue matching:(NSFMatchType)match valueColumnWithValue:(id)aValue.

The referenced code appears to be trying to catch an illegal use of nil as a value for a column. It is not clear to me, looking at this, why NSNull.null is preferable.

Can you explain the reason for distinguishing the values nil and NSNull.null in this context?

@tciuro

This comment has been minimized.

Owner

tciuro commented Jan 30, 2013

Doug, can you explain what you mean with "RubyMotion collapses many datatypes, including the value of nil and NSNull.null"? Do you mean that RubyMotion treats nil and NSNull.null the same way?

I treat them differently because nil != NSNull.null (the former meaning "nothing" and the latter is an object that represents "nothing"). I'm not very familiar with Ruby/Rubymotion. The fact that RubyMotion "collapses" both types, is is considered standard practice or is this a RubyMotion-only decision?

@tciuro

This comment has been minimized.

Owner

tciuro commented Jan 30, 2013

I've found this: https://github.com/rubymotion/sugarcube

"When storing nil into NSUserDefaults, it is converted into false, because Cocoa complains if you give it nil, and the RubyMotion runtime refuses to allow the NSNull.null object. Without relying on an external project (like [nsnulldammit]https://github.com/colinta/nsnulldammit) I don't know of a sensible workaround..."

So if I understand correctly, RubyMotion doesn't allow to pass NSNull.null, right? If so, is there a reason? In Cocoa it's often the case where nil arguments are not allowed. In NanoStore, we use [NSNull null] because they can be stored in collections and it's a datatype that represents nil uniformly.

@aceofspades

This comment has been minimized.

aceofspades commented Jan 30, 2013

Full disclosure, I'm far from an Objective C expert. My iOS work consists mostly of RubyMotion and the bit of Objective C as required.

RubyMotion (and I assume MacRuby as well) merges the functionality of corresponding datatypes, i.e. NSMutableString and ruby String.

My understanding was that NSNull is designed to represent null in places where Objective C wants to have an object, but not that it is intended to have a different meaning--this because of limitations of static typing of C. In RubyMotion (and maybe MacRuby?), the dynamic typing of ruby comes into play. If I declare NSNull.null, I just get nil.

So this is why I was asking why you would want to check for nil in these assertions, where you would otherwise accept NSNull.null. Is there a different meaning there, or are you just trying to be helpful and catch mistakes?

As a head's up, I'm using several different Objective C libraries, cocoapods, etc. and I haven't run into this elsewhere.

Commentary: I'm hoping we can find a way to make this work. I built a light ORM using CoreData and it was a huge ordeal. When I ported to NanoStore things became very easy. So thanks for that. I'm using NanoStoreInMotion but there are some shortcomings that I'm working on, including this -- no way to find objects that have nil in columns, i.e. for unassociated objects. Kind of important! :P It seems that I may be one of the few people pushing RubyMotion's boundaries, but I think you'll find that it's going to gain popularity. It makes writing iOS apps really high level and fun.

@tciuro

This comment has been minimized.

Owner

tciuro commented Jan 30, 2013

Hi Doug,

Quick example:

NSString *someValue;
NSPredicate *predicate = [NSFNanoPredicate predicateWithColumn:NSFValueColumn matching:NSFEqualTo value:someValue];

Do you really intended to look for values matching nil? Or did you perhaps forget to specify the value? If we rewrite it like this, there is no ambiguity and the intent is clear:

NSNull *nullValue = [NSNull null];
NSPredicate *predicate = [NSFNanoPredicate predicateWithColumn:NSFValueColumn matching:NSFEqualTo value:nullValue];

The assertion is there to protect the developer from passing the "wrong" value by accident. As you point out, some libraries do not check for potential nil values. This doesn't help the developer, where one could end up with badly-initialized objects that don't do their job, as advertised. This is why we also see lots of client code around the framework performing sanity checks, hence making the code less readable and more redundant.

My question is why RubyMotion flattens NSNull.null into nil before calling Objective-C? What if the Obj-C code requires a valid object? In this case, we're hosed. While this might be fine between Ruby layers, it might not be (is definitely not!) right when you cross language barriers. When this happens, you start imposing foreign rules and constraints that might not follow standard practices, as is the case with NanoStoreInMotion and NanoStore.

@aceofspades

This comment has been minimized.

aceofspades commented Jan 30, 2013

Ruby doesn't (in general) use pointers, nil really is always an object, a singleton I think. RubyMotion adds pointers, but I think a pointer with a null reference wouldn't help here.

I get what you're trying to do from a C perspective. In Rails ActiveRecord, #where(value: nil) is mapped to "VALUE IS NULL" in SQL, rather than using a special object, or a string or whatever. To me that makes sense, if I accidentally pass in a nil then I have a bug to fix there. I would tend to add application level checks for this rather than rely on the library. Another thing you'll sometimes see in ruby is the use of symbols (effectively immutable strings which map to integers) as parameters that have special values. So you might define :nil or :null and pass that around if wanted to differentiate between them and nil. But ActiveRecord is pretty war-hardened and they don't bother.

RubyMotion makes NSNull.null == nil just like it does with many other types, to make seamless the use of calls between ruby and Objective C. It's actually quite impressive and makes things very easy. If you think about it, since ruby's nil is an object, it really /is/ the same thing as NSNull.null. What's not true, is that it's a literal zero like nil is in C (is that right?). There's no such thing. So, to answer your question, we are indeed passing in an object. We can't pass in what C would call a null pointer.

@tciuro

This comment has been minimized.

Owner

tciuro commented Jan 30, 2013

In C:

#define NULL ((char *)0)

I'm confused with your comment: "we are indeed passing in an object. We can't pass in what C would call a null pointer." So if you're passing an object and you can't pass a NULL pointer, where is the problem then? You should be able to pass an NSNull object and the init's assertion wouldn't be triggered. What are we talking about then? :-/

@aceofspades

This comment has been minimized.

aceofspades commented Jan 30, 2013

Since NSNull.null == nil, nil != aValue will always be false if I pass in either.

@tciuro

This comment has been minimized.

Owner

tciuro commented Jan 30, 2013

Check this issue:

I'm trying to set a property to nil for the Parse framework. In Obj-C this requires using [NSNull null], but when I try
the equivalent in RubyMotion:

  parse_object['name'] = NSNull.null

I get:

  <RuntimeError: NSInvalidArgumentException: Can't use nil for keys or values on PFObject. Use NSNull for values.>

It seems the NSNull is being converted to nil before the Parse framework can handle it.

Any way around this?

Andy

Here we go: RubyMotion clashes with Cocoa's best practices. [NSNull null] != nil and RubyMotion is enforcing this behavior, which is simply wrong in Cocoa land.

Reference: https://groups.google.com/forum/?fromgroups=#!topic/rubymotion/WwRejMdC_Mc

@aceofspades

This comment has been minimized.

aceofspades commented Jan 30, 2013

Thanks for the reference. Maybe that will do the trick well enough. I am surprised I haven't needed this before if it's indeed not isolated.

@tciuro

This comment has been minimized.

Owner

tciuro commented Jan 30, 2013

No problem!

Another reference: https://github.com/rubymotion/sugarcube

When storing nil into NSUserDefaults, it is converted into false, because Cocoa complains
if you give it nil, and the RubyMotion runtime refuses to allow the NSNull.null object. Without
relying on an external project (like [nsnulldammit]https://github.com/colinta/nsnulldammit) I don't
know of a sensible workaround...

I personally consider this a bug in RubyMotion. The NSNull.null object should make it all the way to framework. Manipulating it along the way changes the behavior underneath (as you've seen.) That is a bad thing™. Looks like this issue is far from isolated.

@aceofspades

This comment has been minimized.

aceofspades commented Jan 31, 2013

Hard to say, I'm sure it's there for a reason. In any event, not sure this workaround is what I thought. Mind leaving this issue open for a bit while I investigate more?

@defvol

This comment has been minimized.

Contributor

defvol commented Jan 31, 2013

@aceofspades

why not continue the discussion in the RubyMotion repo? https://github.com/HipByte/RubyMotion/issues

@aceofspades

This comment has been minimized.

aceofspades commented Feb 3, 2013

I've talked to Laurent (the author of RubyMotion) a bit. This is currently not possible, so I may be forced to use a fork with the assertions disabled.

@tciuro

This comment has been minimized.

Owner

tciuro commented Feb 3, 2013

That's unfortunate. What's the reasoning behind this NSNull behavior in RubyMotion?

@aceofspades

This comment has been minimized.

aceofspades commented Feb 3, 2013

It's more general than that -- type conversions need to be done by default to make calling between ruby and Objective-C seamless. What is needed is a way to disable the conversion for specific cases. So then I would have a special way to pass in the literal NSNull.null to NanaStore.

Hopefully this is a temporary workaround until he can implement a fix. Just not sure when that will be.

No biggie, since it's just an assertion check.

@tciuro

This comment has been minimized.

Owner

tciuro commented Feb 3, 2013

Interesting. By manipulating an NSNull object into nil, it's actually eliminating the solution it's trying to solve. I mean, why is this manipulation going to make any communication between RubyMotion and Objective-C seamless? I just don't get it. I wrote him an email, but he hasn't responded yet. I'll shed some light to my comments here if he does reply some day.

@aceofspades

This comment has been minimized.

aceofspades commented Feb 3, 2013

Yes he and I chatted about your email. He's pondering a fix.

@tciuro

This comment has been minimized.

Owner

tciuro commented Feb 6, 2013

Sounds good. Can we close this issue then?

@aceofspades

This comment has been minimized.

aceofspades commented Feb 6, 2013

I guess--maybe I'll just maintain my own fork. Gotta keep moving forward.

@tciuro

This comment has been minimized.

Owner

tciuro commented Feb 6, 2013

I understand. I hope we see a RubyMotion fix soon.

@tciuro tciuro closed this Feb 6, 2013

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment