-
Notifications
You must be signed in to change notification settings - Fork 141
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
Open
ashleygwilliams
wants to merge
7
commits into
sinatra:main
Choose a base branch
from
ashleygwilliams:partials-helper
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
d73d711
add simple, intermediate, advanced, and complex partial recipes
d9c1e00
improve syntax for naming templates with underscore
f9947b0
update names of helper methods to be consistent with document and not…
3be7c04
remove unecessary adverbs
4f1b17a
fix wording for simple partial
0bf4169
fix homonym mistake/typo
308d878
fix wrap
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,44 +1,253 @@ | ||
# 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 helper should start by creating a | ||
method that takes a single 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 simple_partial(template) | ||
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 too sophisticated. Web development is just fancy string concatenation | ||
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 | ||
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}" | ||
``` | ||
|
||
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 intermediate_partial(template) | ||
template = :"_#{template}" | ||
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 | ||
It is also useful to be able to pass local variables to your partial. | ||
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 intermediate_partial(template, locals=nil) | ||
locals = locals.is_a?(Hash) ? locals : {template.to_sym => locals} | ||
template = :"_#{template}" | ||
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}" | ||
else | ||
locals=template | ||
template = template.is_a?(Array) ? :"_#{template.first.class.to_s.downcase}" : :"_#{template.class.to_s.downcase}" | ||
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}" : "_#{template.class.to_s.downcase}" | ||
``` | ||
|
||
### 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}" | ||
else | ||
haml(template, options) | ||
locals=template | ||
template = template.is_a?(Array) ? :"_#{template.first.class.to_s.downcase}" : :"_#{template.class.to_s.downcase}" | ||
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={}) | ||
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 | ||
|
||
``` |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
What about calling it
complex_partial
and documenting what it does?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 comment
The 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 comment
The 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 comment
The 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