-
Notifications
You must be signed in to change notification settings - Fork 140
add simple, intermediate, advanced, and complex partial recipes #69
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
d73d711
d9c1e00
f9947b0
3be7c04
4f1b17a
0bf4169
308d878
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,44 +1,252 @@ | ||
# Implementation of Rails style partials | ||
# Rails Style Partials | ||
|
||
Using partials in your views is a great way to keep them clean. Since Sinatra takes the hands off approach to framework | ||
design, you'll have to implement a partial handler yourself. | ||
Using partials in your views is a great way to keep them clean. Since Sinatra | ||
takes the hands off approach to framework design, you'll have to implement a | ||
partial handler yourself. | ||
|
||
Here is a really basic version: | ||
Depending on the complexity of partials you'd like to use, you should pick | ||
between one of the following three strategies, presented below. A simple demo | ||
of these strategies is available here: [Sinatra Partials Demo](https://github.com/ashleygwilliams/sinatra_partials_demo) | ||
|
||
Thanks to [DAZ](https://github.com/daz4126) for a lot of the code, originally published on his blog ["I Did it my Way"](http://ididitmyway.herokuapp.com/past/2010/5/31/partials/) | ||
|
||
## Simple Partial | ||
The simplest implementation of a partial create a method that takes a single | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "The simplest implementation of a partial helper should start by creating a method that takes..." There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed 4f1b17a |
||
parameter, `template` and uses the same mechanism that is used to render a | ||
view, i.e. `erb`. However, unlike the traditional view rendering, where we | ||
would yield the view within a layout, when using a partial, the layout option | ||
is set to false so that the layout is not rendered again. One used to have to | ||
specify this, but that is no longer the case. | ||
|
||
```ruby | ||
# Usage: partial :foo | ||
helpers do | ||
def partial(page, options={}) | ||
haml page, options.merge!(:layout => false) | ||
def beg_partial(template) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how about There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed f9947b0 |
||
erb template | ||
end | ||
end | ||
``` | ||
|
||
A more advanced version that would handle passing local options, and looping over a hash would look like: | ||
Using the code above, calling `partial :header` will render the view | ||
`header.erb` in your views folder. | ||
|
||
## Intermediate Partial | ||
The simple implementation is fine, but it simply renders a view within a view; | ||
nothing to sophisticated. Web development is just fancy string concatenation | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "too sophisticated" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed 0bf4169 |
||
after all :) | ||
|
||
Usually we want to do more than simply render a view within a view though, so | ||
let's step it up a notch. | ||
|
||
### _underscore Naming Convention | ||
First off: Rails uses a convention where partials are named beginning with an | ||
underscore, but can be referenced by using a symbol with the template name sans | ||
underscore. | ||
|
||
e.g. `partial :header` renders `_header.erb` | ||
|
||
In order to do this we can modify our partial to include the line: | ||
|
||
```ruby | ||
# Render the page once: | ||
# Usage: partial :foo | ||
# | ||
# foo will be rendered once for each element in the array, passing in a local | ||
# variable named "foo" | ||
# Usage: partial :foo, :collection => @my_foos | ||
template=('_' + template.to_s).to_sym | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i would prefer There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it works. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed in d9c1e00 |
||
``` | ||
|
||
This gives us: | ||
|
||
```ruby | ||
helpers do | ||
def partial(template, *args) | ||
options = args.extract_options! | ||
options.merge!(:layout => false) | ||
if collection = options.delete(:collection) then | ||
collection.inject([]) do |buffer, member| | ||
buffer << haml(template, options.merge( | ||
:layout => false, | ||
:locals => {template.to_sym => member} | ||
) | ||
) | ||
end.join("\n") | ||
def int_partial(template) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i would spell out the whole method name, ie There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please remove me from receiving these emails! On Sep 16, 2013, at 12:13 PM, Zachary Scott wrote:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should "unwatch" the repo if you don't want to receive these emails. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed f9947b0 |
||
template = ('_' + template.to_s).to_sym | ||
erb template | ||
end | ||
end | ||
``` | ||
|
||
** NOTE: If you are using [ActiveRecord](/p/models/active_record?#article) or | ||
any other gem that has ActiveSupport as a dependency, you do not need to | ||
implement this portion of the method, as it will be already assumed that your | ||
partials begin with an underscore and that you reference them with out it. | ||
|
||
### Passing Local Variables | ||
Secondly: it is also useful to be able to pass local variables to your partial. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When you start a paragraph like this, such as before ("First off:"), shouldn't you use a comma instead? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just took those silly adverbs out. 3be7c04 |
||
To implement this we can add a `locals` parameter to our partial. This | ||
parameter will hold a hash, and will default to `nil`. We will pass this | ||
`locals` parameter to `erb` alongside our template, like so: | ||
|
||
```ruby | ||
erb template, {}, locals | ||
``` | ||
|
||
** NOTE: `erb` takes ordered parameters, including an options hash between the | ||
`template` and the `locals` hash, so we need to pass an empty hash. | ||
|
||
Rails takes this a step further: if the name of the local variable is the same | ||
as the name of the partial, you do not need to explicitly set it. This is | ||
handled by the line: | ||
|
||
```ruby | ||
locals = locals.is_a?(Hash) ? locals : {template.to_sym => locals} | ||
``` | ||
|
||
This checks to see if locals is a hash, and if it is not, it will build a hash | ||
with a single key value pair, where the key is the name of the template and | ||
the value is the non-hash value of `locals`. | ||
|
||
This leaves us with our finished intermediate partial, which now supports the | ||
underscore naming convention and the explicit and implicit passing of local | ||
variables: | ||
|
||
```ruby | ||
helpers do | ||
def int_partial(template, locals=nil) | ||
locals = locals.is_a?(Hash) ? locals : {template.to_sym => locals} | ||
template = ('_' + template.to_s).to_sym | ||
erb template, {}, locals | ||
end | ||
end | ||
``` | ||
|
||
The syntax for using this partial is: | ||
|
||
- `<%= partial :partial_name, "local_value" %>` | ||
|
||
where `locals = {:partial_name => "local_value"}` | ||
|
||
- `<%= partial :partial_name, {:local_variable => "local_value"} %>` | ||
|
||
where `locals = {:local_variable => "local_value"}` | ||
|
||
All of the above will render the `_partial_name.erb` view. | ||
|
||
## Advanced Partial | ||
Passing local variables is great, but Rails lets us pass collections of local | ||
variables to a partial, rendering it once for each element of the collection. | ||
|
||
For example: | ||
|
||
If I have a before filter in my `app.rb` that assigns the variable `@cats` a | ||
random number of Cat objects, I would like to call `<%= partial @cats %>` to | ||
render an index of all of my cat objects. | ||
|
||
See this exact example in action here: [Sinatra Partials Demo]([Sinatra Partials Demo](https://github.com/ashleygwilliams/sinatra_partials_demo)) | ||
|
||
Let's take a look at how this code works: | ||
|
||
### Which template? | ||
First we check to see if we have passed the name of a partial at all, or if | ||
simply intend to use the partial that shares a name with the local variable | ||
we are passing. If a template name is passed, we define the template to be | ||
rendered as that (prepending the underscore like we did in the intermediate | ||
partial). This is all accomplished in the first conditional: | ||
|
||
```ruby | ||
if template.is_a?(String) || template.is_a?(Symbol) | ||
template=('_' + template.to_s).to_sym | ||
else | ||
locals=template | ||
template=template.is_a?(Array) ? ('_' + template.first.class.to_s.downcase).to_sym : ('_' + template.class.to_s.downcase).to_sym | ||
end | ||
``` | ||
|
||
If there is no string or symbol passed, it is assumed that we would like to | ||
render the partial named after the class of the object(s) we are passing as a | ||
local variable. We check whether the local variable is a collection, and then | ||
name the template based on the class of the object(s): | ||
|
||
```ruby | ||
template=template.is_a?(Array) ? ('_' + template.first.class.to_s.downcase).to_sym : ('_' + template.class.to_s.downcase).to_sym | ||
``` | ||
|
||
### What local variable(s)? | ||
Now that we know what template to render, we need to figure out what local | ||
variables are being passed; we have 4 potentials: | ||
|
||
- a value | ||
- a Hash | ||
- a collection of Objects | ||
- no locals | ||
|
||
This is all accomplised in the second conditional: | ||
|
||
```ruby | ||
if locals.is_a?(Hash) | ||
erb template, {}, locals | ||
elsif locals | ||
locals=[locals] unless locals.respond_to?(:inject) | ||
locals.inject([]) do |output,element| | ||
output << erb(template,{},{template.to_s.delete("_").to_sym => element}) | ||
end.join("\n") | ||
else | ||
erb template, {} | ||
end | ||
``` | ||
|
||
When we put this all together we get: | ||
|
||
```ruby | ||
helpers do | ||
|
||
def adv_partial(template,locals=nil) | ||
if template.is_a?(String) || template.is_a?(Symbol) | ||
template=('_' + template.to_s).to_sym | ||
else | ||
haml(template, options) | ||
locals=template | ||
template=template.is_a?(Array) ? ('_' + template.first.class.to_s.downcase).to_sym : ('_' + template.class.to_s.downcase).to_sym | ||
end | ||
if locals.is_a?(Hash) | ||
erb template, {}, locals | ||
elsif locals | ||
locals=[locals] unless locals.respond_to?(:inject) | ||
locals.inject([]) do |output,element| | ||
output << erb(template,{},{template.to_s.delete("_").to_sym => element}) | ||
end.join("\n") | ||
else | ||
erb template | ||
end | ||
end | ||
|
||
end | ||
``` | ||
|
||
We can use this partial just as we used the intermediate partial, but we can | ||
also use it in new ways: | ||
|
||
- `<%= partial @object %>` | ||
where it renders the template `_object.erb` | ||
and `locals = {:object => object}` | ||
|
||
- `<%= partial @objects %>` | ||
where it renders the template `_object.erb` | ||
for each element in `@objects` | ||
where, for each, `locals = {:object => an_object_from_objects}` | ||
|
||
## Complex Partial | ||
from [Padrino](https://github.com/padrino/padrino-framework/blob/master/padrino-helpers/lib/padrino-helpers/render_helpers.rb) | ||
|
||
```ruby | ||
def partial(template, options={}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about calling it Does padrino have any docs that you could link to specifically for the helpers? I would be worried this method gets removed or replaced somewhere else in padrino-framework master. I'm also not sure what the benefit of just pasting someone elses code does without explaining it thoroughly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah- i just haven't gotten to it yet, and would want to refactor the advanced partial so it leads to this one. the simple, intermediate, advanced ones tell a narrative that makes sense. this would come a little bit out of left field. so it needs more work. we could just take it out for now. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WHY AM I ON THIS THREAD... I keep getting email updates from Ashely Williams! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @webhealth please click 'unwatch' at the top of the screen. you are subscribed to notifications because you are watching this repo. additionally, this is not really the appropriate forum for these types of comments. i would recommend checking out github's docs: https://help.github.com/articles/watching-repositories |
||
options.reverse_merge!(:locals => {}, :layout => false) | ||
path = template.to_s.split(File::SEPARATOR) | ||
object_name = path[-1].to_sym | ||
path[-1] = "_#{path[-1]}" | ||
explicit_engine = options.delete(:engine) | ||
template_path = File.join(path).to_sym | ||
raise 'Partial collection specified but is nil' if options.has_key?(:collection) && options[:collection].nil? | ||
if collection = options.delete(:collection) | ||
options.delete(:object) | ||
counter = 0 | ||
collection.map { |member| | ||
counter += 1 | ||
options[:locals].merge!(object_name => member, "#{object_name}_counter".to_sym => counter) | ||
render(explicit_engine, template_path, options.dup) | ||
}.join("\n").html_safe | ||
else | ||
if member = options.delete(:object) | ||
options[:locals].merge!(object_name => member) | ||
end | ||
render(explicit_engine, template_path, options.dup).html_safe | ||
end | ||
end | ||
|
||
alias :render_partial :partial | ||
|
||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wrap
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed 308d878