nesquena / rabl

General ruby templating with json, bson, xml, plist and msgpack support
http://blog.codepath.com/2011/06/27/building-a-platform-api-on-rails/
MIT License
3.65k stars 335 forks source link

Questions & Ideas #17

Closed czak closed 12 years ago

czak commented 13 years ago

Don't know if there's a Google Group or anything, so I'm just posting here. I've been working on a REST API for a Rails app for the past 2 months and just discovered rabl. It might be something I've been looking for. Converted a few resources from as_json to .json.rabl and it seems cool so far.

I've got a few questions/ideas.


1. What's the proper way to extend a @object template from a @collection template?

I've got a posts resource and want to re-use the show template in the index template vibratim. Here's what I've come up with:

posts/show.json.rabl:

object @post
attributes :id, :body

posts/index.json.rabl:

collection @posts
extends 'posts/show'

It seems clumsy, but it works. I was kind of expecting a partial somewhere here. What would be the proper way to do that?


2. Using regular ifs within a template

Instead of the :if => lambda { ... } pattern, I tried using regular ifs within a template:

posts/show.json.rabl

object @post
attributes :id, :body

if @post.published?
  code(:publisher) { |p| p.publisher.username }
end

This works also, but presents a problem when using extends as presented above. The controller for an index action only provides the @posts collection, so the @post is nil and the if always fails when rendering the index action. I think it would be cleaner to use regular ifs. Especially when adding multiple custom fields - this would just be a single if block instead of multiple code(:field, :if => lambda { ... repeating condition ... }) blocks.

Maybe the way to do it could be - instead of relying on instance variables, provide an object or collection helper, which can always access the current object? Kind of like RSpec utilises subject.


3. Conditional child elements

I needed to include an entire subresource based on a condition. This didn't work:

child(:user, :if => lambda { ... }) do
  ...
end

So I eventually went with:

code(:user, :if => lambda { ... }) do |p|
  {
    :id => p.user.id,
    :username => p.user.username
  }
end

This works, but breaks DRY. I have a users/show.json.rabl with properly defined attributes, and would like to re-use it here. How would I go about it?


All in all, thanks for the great work. These are just some of my thoughts, Markdown-formatted. I'd be glad to dig into the code if you see any point in my mumbling ;) Cheers!

nesquena commented 13 years ago

Hey thanks for your feedback, much appreciated. Here is some thoughts and answers.

  1. I am the first to admit my API isn't perfect but the way you demonstrated is how I do it too. I use extends in the object to inherit the properties. I don't view that inheritance case as a place for partials. I tend to use partials exclusively in code blocks when I want access to the json hash for another object. I tend to use extends in most other cases as you showed there. I don't view it as too clumsy but if you have a better approach I am happy to consider it for the future.
  2. I like this idea but for implementation reasons (when dealing with a collection of objects) with the way RABL is currently set up having access to the object in a collection directly isn't easily possible. It can only be accessed in a lambda and passed it at the time each object is rendered. I can imagine a refactoring of RABL that could support an current_object helper that achieves the effect but I probably won't be doing that personally anytime soon unless it becomes an essential use case for me. Thanks for the suggestion and I welcome a patch :)
  3. I completely agree that child should accept an if lambda. In fact I am pretty sure it does or did. I use child with if blocks in a couple of my APIs. Are you absolutely certain that they don't work? Notice the call to resolve_conditions here: https://github.com/nesquena/rabl/blob/master/lib/rabl/builder.rb#L77. If this doesn't work it is a bug and I will fix that soon hopefully.

Thanks again for your feedback and for trying RABL.

wtn commented 13 years ago

This post was very helpful. It was not immediately clear how to reuse templates from the README.

xecutioner commented 13 years ago

child (:auto_judge, :if => lambda {|station| station._type == "HumanWorker"}) {....}

gives out <pre><code>(eval):7: syntax error, unexpected ',', expecting ')' child (:auto_judge, :if =&gt; lambda {|station| sta... ^

nesquena commented 13 years ago

Is that a space after child? Might be the issue

child(:auto_judge, :if => lambda {|station| station._type == "HumanWorker"}) {....}

should work fine...what ruby are you using?

xecutioner commented 13 years ago

i'm using 1.9.2 child(:line,:if => lambda {@line!=nil}){... } still no effect??? i'm really stuck on this, am i doing something wrong???

nesquena commented 13 years ago

@xecutioner If it really doesn't work, hopefully I can fix it in the next release, I will have to play around. In the meantime try to use:

child @line => :line do
  # ...
end unless @line.nil?
nesquena commented 12 years ago

Closing this but adding a link the wiki, the bugs mentioned here should be fixed.

tymate-team commented 12 years ago

Seems bug is back child (@products, :if => lambda {|product| product.active?}) do

eval):4: syntax error, unexpected ',', expecting ')' child (@products, :if => lambda {|product| product.active?}) do

nesquena commented 12 years ago

It might be the space before the paranthesis, can you try:

child @products, :if => lambda {|product| product.active?} do
# ...
end