zendesk/curly

Feature request: Adding support for loop blocks

luis-almeida opened this issue · 20 comments

Since we have support for conditional blocks:

{{#manager?}}
  you're a manager
{{/manager?}}

it would be freaking awesome to also have support for loop blocks:

<table>
  {{#users*}}
    <tr>
      <td>{{name}}</td>
      <td>{{email}}</td>
      <td>{{oganization}}</td>
    </tr>
  {{/users*}}
</table>

I guess we'd still need a presenter for the iteration block - the user row in this example - but not having to create a new partial just for that would be quite helpful.

libo commented

why do we need the trailing * ?

I'm still not quite convinced we want to do this, but let's try a hypothetical design here.

Given there's a method Groups::ShowPresenter#users that returns an Enumerable of User objects. If the User class implements the conversion protocol, Rails should be able to find a partial for it, by default users/_user. Since the list could be heterogenous, we'd have to make that call for each item, so it would be something like:

def compile_loop(inner_template, method_name)
  <<-RUBY
    presenter.#{method_name}.each do |item|
      virtual_path = item.to_partial_path # this is the protocol
      presenter_class = Curly::Presenter.presenter_for_path(virtual_path)
      compiled = Curly::Compiler.compile(#{inner_template.inspect}, presenter_class)

      # now we've got a compiled inner template, but usually Rails renders it for us. Since we'll
      # only know the presenter class at runtime, we've got a problem...
    end
  RUBY
end

@libo, conditional blocks have a trailing ? - means "question".
To follow the same pattern, loop blocks could have a trailing * - means "many".

libo commented

The question mark is because of the convention that when a method returns a boolean you name it with a trailing question mark.

def old?
  current_user.libo?
end
{{#old?}}
  you're old
{{/old?}}

I see. I though curly required the ? symbol to create a conditional block.

It actually does require it, but the convention comes from Ruby.

On 24/03/2014, at 16.51, Luís Almeida notifications@github.com wrote:

I see. I though curly required the ? symbol to create a conditional block.


Reply to this email directly or view it on GitHub.

Maybe something like users.each instead?

Curly should never turn into an imperative language - I'm still not even sure this is worth it. If there is a syntax for rendering collections, it should be declarative.

On 24/03/2014, at 22.03, Luís Almeida notifications@github.com wrote:


Reply to this email directly or view it on GitHub.

I've looked into this, but I can't find a way to do this and still keep static type checking, which is an important part of Curly right now. Consider this case:

{{#people}}
  {{name}}
{{/people}}

We wouldn't be able to know the presenter class that should be used inside the loop until render time, meaning we can't check if name is available at compile time. This in turn means we need to actually render the template with some data in order to check for reference errors, as opposed to now, where we just need to compile it. This will make it a lot harder to verify the correctness of user-supplied templates :-(

Any ideas?

Would it be possible to just use the same presenter outside and inside the loop?

But then the presenter wouldn't be presenting the objects being enumerated...

True that.

Funny how we're using Ruby to create a compiled language with type checks :-)

In such languages, you are often required to explicitly state the type of your variables. Could we do the same here?

{{#people via UserPresenter}}
  {{name}}
{{/people}}

That would allow the compile-time checks you’re looking for.

I’m open to change the syntax.

{{#people|user}}
   {{name}}
{{/people}}

No – that responsibility belongs to the presenter. Templates should be dumb and pure string concatenation functions.

The only way I see is to map the method name to a presenter. Say the method is Groups::ShowPresenter#people; we could use Groups::PeoplePresenter regardless of the type of objects returned by #people. It just doesn't seem very Railsy.

I would suggest the following syntax:

{{#people}}
  {{name}}
{{/people}}
class SomePresenter < Curly::Presenter
  render :people, :as => :users
end

@medcat if you wanted to use the UserPresenter, you could just as well do:

class SomePresenter < Curly::Presenter
  PersonPresenter = UserPresenter

  def people
    User.all
  end
end

The approach in #59 is scheduled for v2.0, so I'm closing this issue.