public
Last active

  • Download Gist
partials.rb
Ruby
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
# 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

I hate being the n00b, but I don't understand how to use this. I know how I'd like to use it, but it's not working for me.

I save this file to my app's home directory, with app.rb, and in app.rb I 'require "partials" '. No complaints from Ruby, so I'm fairly certain it's being included. But when I navigate to the page that uses a partial, I get the error "NoMethodError at /thoughts; undefined method `partial' ".

I'm sure this is painfully obvious and that's why I'm having such a hard time finding anyone saying how to do it, but I can't figure it out. I probably just don't understand how to use modules. Any help would be greatly appreciated.

Thanks!

hey chadoh,

To use this, in your application, have the following line:

helpers Sinatra::Partials

this adds the module to the helpers, which can be used in both the controllers and the views. then in erb call it with <%= partial :foo %>

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

@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.

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

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

undefined method `extract_options!' for []:Array

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

@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

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

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 ?

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

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

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

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

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

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

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

@tkellen thanks! will add that!

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

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

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.

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.

@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

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.

I went with your approach (above using send) in the end when I sent a pull request to the Sinatra-Partial gem maintainer. The request has been merged, so it won't be long until there's another gem release i hope.

I recommend that you swap over to using the gem. It's unobtrusive and I'll make an effort to support any changes etc. First up on my list: a test suite and travis-ci.

And yes, I agree render felt a little low-level for my needs too. Soon I'll find an approach I prefer, but a priority is tests.

On 10 Apr 2012, at 03:23 PM, Carlos Gabaldonreply@reply.github.com wrote:

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.


Reply to this email directly or view it on GitHub:
https://gist.github.com/119874

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

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.