groue / GRMustache

Flexible and production-ready Mustache templates for MacOS Cocoa and iOS
http://mustache.github.com/
MIT License
1.44k stars 190 forks source link

Accessing NSArray count #21

Closed priteshshah1983 closed 12 years ago

priteshshah1983 commented 12 years ago

Is it possible to access NSArray's count property in the Mustache template? For example, I have an attendees NSArray and I would like display some snippet exactly once if attendees.count > 0.

{{#attendees.count}}Display me!{{/attendees.count}} // crashes!

If I use {{#attendees}}Display me!{{/attendees}} // Display me! is displayed multiple times.

Do I need to define a filter? Or is there an easier way?

groue commented 12 years ago

Hi, Pritesh. If you do not mind sharing your templates with some other platforms, the easiest way is to use ’count’, as you have tried. The new way, with filters, is ’{{^ isEmpty(attendees) }}Display me!{{/}}’. It does not invalidate the ’count’ trick, though.

Testing for ’count’ should not crash, and indeed ’{{#attendees.count}}Display me!{{/attendees.count}}’ does NOT crash.

The crash you're experiencing is thus unrelated to this emptiness test. It happens somewhere else. Do you have a stack trace or something?

priteshshah1983 commented 12 years ago

My bad, it doesn't crash the app, but it throws an exception and I have Xcode set to add a breakpoint on all exceptions. Here is the stack trace: http://tinypic.com/r/1fe7it/6

Please note that I have added the code posted by you (see below), but {{attendees.count}} still throws an exception and doesn't work.

ifdef DEBUG

[GRMustache preventNSUndefinedKeyExceptionAttack];

endif

--- Edit ---

Adding a simple example:

NSArray *array1 = [NSArray arrayWithObjects:[NSNumber numberWithInt:1], [NSNumber numberWithInt:2], nil];

NSString *rendering = [GRMustacheTemplate renderObject:array1
                                            fromString:@"Count is {{count}}"
                                                 error:NULL];

Should this work? I get the same error and stack trace as explained above.

Thanks for your help!

groue commented 12 years ago

OK. Tell me if I understand well:

First, the rendering is done just fine, and you do not experience any crash.

However, despite the prevention of exceptions that should keep your debugger quiet, your breakpoint is still activated.

Does this describe your situation?

groue commented 12 years ago

OK I get it.

valueForKey has a special behavior when applied to arrays: it returns another array filled with the key applied to its element. Thus {{ count }} generates an array of the key ‘count‘ applied to the NSNumber items, which do not know this key, and raise exceptions which are not prevented by preventNSUndefinedKeyExceptionAttack (it only prevents exceptions raised by the target object itself, the array, not other objects).

Well, this is interesting. I will consider having this guard method catch exceptions sent by array items (its purpose it not to catch them all, but only most of them - I'll check the documentation in case this is not absolutely clear. If there is a bug, it's a documentation bug since its impossible to prevent all exceptions)

So, today, here are your options:

Sorry for the inconvenience.

groue commented 12 years ago

Second thought:

I plan to keep valueForKey: as the one and only way to provide values to the rendering engine. NSArray implementation returns another array, even if the key is count. So, there is absolutely no way {{ count }} can ever return an actual number.

This leads to a real bug in GRMustache: {{attendees.name}} {{.}} {{/attendees.name}} outputs the concatenation of the names of the attendees, instead of the empty string. That's because attendees.name yields an array of names, instead of the expected value nil (array do not have any name property).

So, thank you very much Pritesh for letting this bug emerge. Its resolution may affect your count problem, I'll let you know.

priteshshah1983 commented 12 years ago

Thanks Gwendal! I didn't understand the valueForKey: behavior before reading your explanation. I'm planning to declare another property 'hasAttendees' as you suggested to keep it simple for now.

groue commented 12 years ago

That would have been my choice as well :-) Happy Mustache, and thanks again for the indirect bug report :-)

groue commented 12 years ago

@priteshshah1983 GRMustache 4.3.2 fixes the {{#attendees.name}} {{.}} {{/attendees.name}} bug. As a consequence, {{#attendees.count}}Display me!{{/attendees.count}} will never render, even for non-empty arrays, but won't raise any exception, because the default implementation of valueForKey: is now avoided for NSArray, NSSet and NSOrderedSet, the three foundation classes that return new collections instead of performing a property/method lookup.

priteshshah1983 commented 12 years ago

Thanks for fixing this!

groue commented 12 years ago

@priteshshah1983 GRMustache 5.5.1 restores the ability to use the count key in templates, while preventing the bugs we have discovered together. See https://github.com/groue/GRMustache/blob/master/Guides/runtime/context_stack.md#nsarray-nsset-nsorderedset