Skip to content

Instantly share code, notes, and snippets.

@lenary
Created May 29, 2009 09:30
Show Gist options
  • Star 32 You must be signed in to star a gist
  • Fork 23 You must be signed in to fork a gist
  • Save lenary/119874 to your computer and use it in GitHub Desktop.
Save lenary/119874 to your computer and use it in GitHub Desktop.
# stolen from http://github.com/cschneid/irclogger/blob/master/lib/partials.rb
# and made a lot more robust by me
# this implementation uses erb by default. if you want to use any other template mechanism
# then replace `erb` on line 13 and line 17 with `haml` or whatever
module Sinatra::Partials
def partial(template, *args)
template_array = template.to_s.split('/')
template = template_array[0..-2].join('/') + "/_#{template_array[-1]}"
options = args.last.is_a?(Hash) ? args.pop : {}
options.merge!(:layout => false)
locals = options[:locals] || {}
if collection = options.delete(:collection) then
collection.inject([]) do |buffer, member|
buffer << erb(:"#{template}", options.merge(:layout =>
false, :locals => {template_array[-1].to_sym => member}.merge(locals)))
end.join("\n")
else
erb(:"#{template}", options)
end
end
end
@dvhthomas
Copy link

Just for completeness, I am also new to Ruby and Sinatra so I'm posting how I got it working. And a big thank you for the code -- I'm still trying to grok what's going on in those few lines, and know that it saved me a ton of work :-)

  1. saved the file to 'partials.rb' in my application root directory.

  2. Added require 'partials' in my main app file ('social.rb' in this case)

  3. Added helpers Sinatra::Partials in the same app file.

  4. Created a parent.erb and child _some_partial.erb

  5. Wanted to pass the variable inside an each iterator so that I can render a different partial based on some decision for each loop:

    <% @info_pane.stories.each do |s| %>

  6. <%if s.type == 'photo'%> <%= partial(:ip_photo, :locals => {:item => s}) %> <%else%> ...etc

Here's my code if it helps: http://socks.codeplex.com/SourceControl/changeset/view/37669983e5ee#views%2ffb_me.erb

@lenary
Copy link
Author

lenary commented Aug 25, 2010

@dvhthomas what exactly are you asking?

technically what you showed me should work, but at the same time, i haven't played with sinatra in far too long, so am not 100% sure if it will.

@dvhthomas
Copy link

I'm not asking anything, I'm sharing what worked for me because for a complete neophyte, the previous 'how-to' was not obvious.

@lenary
Copy link
Author

lenary commented Aug 26, 2010

ah, ok, thanks

@rkh
Copy link

rkh commented Oct 28, 2011

You don't have to pass in :layout => false. It defaults to false for nested templates.

@vladyn
Copy link

vladyn commented Oct 28, 2011

undefined method `extract_options!' for []:Array

I've got this error when I'm following the steps described above
Any ideas?

@lenary
Copy link
Author

lenary commented Oct 28, 2011

@vladyn are you sure you're using this version of the code? It doesn't call extract_options! any more.

@rkh I'll leave it in for backwards compatibility. Not used sinatra in waaaay too long

@tkellen
Copy link

tkellen commented Oct 28, 2011

Thanks for this super useful snippet of code!

I ran into one issue. If you have local variables assigned with a collection, they are not passed on during iteration. This is fixed in the sample code below:

def partial(template, *args)
  template_array = template.to_s.split('/')
  template = template_array[0..-2].join('/') + "/_#{template_array[-1]}"
  options = args.last.is_a?(Hash) ? args.pop : {}
  options.merge!(:layout => false)
  locals = options[:locals].nil? ? {} : options[:locals] # SAVE LOCALS
  if collection = options.delete(:collection) then
    collection.inject([]) do |buffer, member|
      buffer << slim(:"#{template}", options.merge(:layout =>
      false, :locals => {template_array[-1].to_sym => member}.merge(locals))) # MERGE THEM BACK TO EACH
    end.join("\n")
  else
    slim(:"#{template}", options)
  end
end

@vladyn
Copy link

vladyn commented Oct 29, 2011

Why the partial include using only .erb tempaltes with _prefix - I\m currently using HAML templates - is it possible to render haml partials - for example my footer is including with options for the language - can I use it in haml render engine ?

@rkh
Copy link

rkh commented Oct 29, 2011

You don't need the partial method for this, at all, simply call != haml :footer

@vladyn
Copy link

vladyn commented Oct 29, 2011

..and Im getting this way just "footer" as a string, parsed in my footer

@rkh
Copy link

rkh commented Oct 29, 2011

Really? You're sure you didn't do != haml "footer" by accident?

@vladyn
Copy link

vladyn commented Oct 29, 2011

ahh - sorry it's included, but how can I pass arguments like for lang and use it in the footer.haml

@rkh
Copy link

rkh commented Oct 29, 2011

!= haml :footer, :locals => { :lang => :en }

instance variables set in a route/before filter/view will also be available

@lenary
Copy link
Author

lenary commented Oct 29, 2011

@vladyn the other option is manually change line 14 and 18 to call haml() rather than erb().

@tkellen thanks! will add that!

@vladyn
Copy link

vladyn commented Oct 29, 2011

Many thanks guys - I think it's working fine.

@CarlosGabaldon
Copy link

You can also abstract the calls to the template engines by passing the engine name and then looking up the method on self:

self.method(engine).call

Then you would call the method like this:

=partial(:item, :haml, :collection => @items)

Here is the updated method:

module Sinatra::Partials
  def partial(template, engine, *args)
    template_array = template.to_s.split('/')
    template = template_array[0..-2].join('/') + "/_#{template_array[-1]}"
    options = args.last.is_a?(Hash) ? args.pop : {}
    options.merge!(:layout => false)
    locals = options[:locals] || {}
    if collection = options.delete(:collection) then
      collection.inject([]) do |buffer, member|
        buffer << self.method(engine).call(:"#{template}", options.merge(:layout =>
        false, :locals => {template_array[-1].to_sym => member}.merge(locals)))
      end.join("\n")
    else
      self.method(engine).call(:"#{template}", options)
    end
  end
end

@lenary
Copy link
Author

lenary commented Apr 9, 2012

So I'm not sure about including this amendment, but people can use it if they want to.

I have various issues with it, first of all being the additional complexity it adds to the code, and second being the approach of .method(:name).call(*args) above .send(:name, *args) (yes, not sure why i prefer the second, does anyone have any well-founded opinions?)

Iain Barnett (@yb66) made a gem of this code - https://github.com/yb66/Sinatra-Partial - with some improvements . I might submit a pull request later this week with some of my ideas about how we can get over the selection of template renderers.

@rkh
Copy link

rkh commented Apr 10, 2012

You could also call Sinatra's render method directly, some things like inline markaby or auto-detecting content-types (irrelevant for partials) just won't work.

@lenary
Copy link
Author

lenary commented Apr 10, 2012

@rkh that's actually what i was planning on doing, though I didn't know a render method existed, so I was going to write my own. Time to dive into the sinatra source

@CarlosGabaldon
Copy link

I spent some time studying base.rb before making my implementation, and I considered using render directly, but it I personally did not like the idea of bypassing the built-in template rendering methods.

@lenary
Copy link
Author

lenary commented Apr 10, 2012 via email

@CarlosGabaldon
Copy link

Excellent! I will be switching my current project over to using the gem. Thanks for all of the great work!

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