1. RuHL
How can I see with all that code in my view?
Wednesday, December 9, 2009
2. The first thought...
Haml is so far away from HTML, why not take it further?
Repeat content instead of having to write loops.
%ul
- @school.special_features.each do |feature|
%li= feature.name
# Maybe something like...
%ul { repeat: special_features }
%li= feature.name
Wednesday, December 9, 2009
3. The next minute...
I could actually pull this off with HTML.
<ul ruby=”_collection: special_features”>
<li ruby=”name”/>
</ul>
# Which later changed to
<ul>
<li data-ruhl =”_collection: special_features, name”/>
</ul>
...more on this coming up
Wednesday, December 9, 2009
4. Thoughts from JSP days and beyond
• Working with designers inefficient after converting views.
• Why convert the code from HTML to a language that generates HTML if
it is not necessary?
• Views didn’t garner the same architecture attention ‘code’ did.
• Did formulating the Fat Model/Skinny Controller idea take everyone’s
energy?
• Over time, code in views grew as quick fixes were applied.
• Little restrictions on what is allowed encourages misuse.
• Testing views more difficult than models or controllers.
• Shouldn’t the multiple attempts to improve view testing be an indicator?
Wednesday, December 9, 2009
5. The 2 minute RuHL history.
• The attribute was initially called ruby.
• Peter Cooper suggested data-ruby for HTML5 compliance.
• Coworker suggested data-ruhl to prevent potential naming collisions.
• Crystallized the idea of ‘No code in views’
• This became the rule that could not be broken.
• Should not have to code extra HTML tags to make something work.
• Exceptions to this arose and _swap was introduced. (details later)
• Functionality to be driven by real use cases, not imagined scenarios.
Wednesday, December 9, 2009
6. Standalone RuHL
RuHL can be used outside Sinatra and Rails.
Ruhl::Engine.new(File.read(ʻhello_world.ruhlʼ)).render(self)
Breaking it down:
data = File.read(ʻhello_world.ruhlʼ) # read the html file
engine = Ruhl::Engine.new(data) # instantiate the engine
html = engine.render(self) # render the html
# using self for context
In the Rails helper, self is the @template object available to the
controller. This gives you access to all the helpers available within the
view.
Wednesday, December 9, 2009
7. RuHL: _if
How would I conditionally display data?
#Don’t show the div or it’s contents if there aren’t users.
<div data-ruhl="_if: users?">
<table>
<thead>
<tr>
<td>First Name</td>
<td>Last Name</td>
<td>Email</td>
</tr>
</thead>
<tr data-ruhl="_collection: user_list">
<td data-ruhl="first_name">Andrew</td>
<td data-ruhl="last_name">Stone</td>
<td data-ruhl="email">andy@stonean.com</td>
</tr>
</table>
</div>
Wednesday, December 9, 2009
8. RuHL: _unless
How would I conditionally display data?
#Show the message if there aren’t users.
<p data-ruhl="_unless: users?">No Users were found.</p>
If users, the <p> tag would not be included on output.
Wednesday, December 9, 2009
9. RuHL: _use
I want to use the user object as the local object within the scope of the div to
simplify the calls. The methods will first be tried against the local object, then
the global context.
<div data-ruhl="_use: current_user">
<h1> Editing <span data-ruhl="_swap: full_name"/></h1>
<table>
<thead>
<tr>
<td>First Name</td>
<td>Last Name</td>
<td>Email</td>
</tr>
</thead>
<tr>
<td data-ruhl="first_name">Andrew</td>
<td data-ruhl="last_name">Stone</td>
<td data-ruhl="email">andy@stonean.com</td>
</tr>
</table>
</div>
Wednesday, December 9, 2009
10. RuHL: _use
So what about iterating over a collection?
<select id="states">
<option data-ruhl="_use: state_options">
</select>
The option tag will be repeated for each item returned.
state_options can return an array of hashes:
def state_options
[ {:value => 'AL', :inner_html => 'Alabama'},
{:value => 'AK', :inner_html => 'Alaska'},
{:value => 'AZ', :inner_html => 'Arizona'},
...
]
end
:inner_html will set the contents of the tag. all other keys are treated as
attributes for the tag.
Wednesday, December 9, 2009
11. RuHL: _swap
I have some template text, but want to replace content within a paragraph.
#Replace the span tag with the results of language
<p>The programming language, <span data-ruhl='_swap:language'/>, is awesome.</p>
Wednesday, December 9, 2009
12. RuHL: partials, layouts and params
<div id="page">
<div data-ruhl="_partial:file|header_search"/>
<div data-ruhl="_render_" class="metro"/>
<div data-ruhl="_partial:file|footer"/>
</div>
Can pass parameters to methods with a pipe (‘|’).
Use _render_ to get the =yield result in Rails. Defining the layout works as
expected within Rails. Outside of Rails, you pass a :layout option to the
engine constructor. The value of the :layout option is the path to the file.
Use :layout_source if you’ve cached the reading of the file (as Rails does).
Use _partial to pull in page partials. The method, in this case :file, must
return a path to the partial.
Wednesday, December 9, 2009
13. More information on keywords:
http://stonean.com/page/ruhl
Moving on. A lot to cover...
Wednesday, December 9, 2009
14. If the code isn’t in the view, where
does it go?
• RuHL encourages the use of the presenter pattern.
• It’s easy with the helpers if you follow the convention in a
Rails app:
• Have presenter in app/presenters
• Name presenter similar to controller conventions (just
singular):
class UserPresenter < Ruhl::Rails::Presenter
end
(file name: user_presenter.rb)
Wednesday, December 9, 2009
15. What does my controller look like?
Controller methods look like:
def new
@post = Post.new
present
end
def create
@post = Post.new(params[:post])
if @post.save
flash[:notice] = 'Post was successfully created.'
redirect_to(@post)
else
present :new
end
end
Wednesday, December 9, 2009
16. And this works how?
For this example, let’s say we are working in the UsersController.
• Calling present in the controller method creates a new instance of the
UserPresenter.
• The UserPresenter instance is passed to the view as a local object.
• RuHL will first apply all methods to the local object. If the local object does
not respond to the method, RuHL then tries to apply the method to the
context.
• The context is everything you are accustomed to in a Rails view:
• Controller methods exposed with helper_method
• Helper methods (ApplicationHelper, UsersHelper)
Wednesday, December 9, 2009
17. And my model?
For this example, let’s say we are working with the UserPresenter.
• When the presenter is created, it has a local object called presentee
which holds the instance of the model you are presenting. (@user)
• If you have this call in your view:
data-ruhl=”first_name”
the order is:
1. Does the presenter respond_to?(:first_name)
1. Does the presenter have first_name defined?
2.Does the presentee (@user) have first_name defined?
2.Try the method against the context
1. Raises exception if method not found.
Wednesday, December 9, 2009
18. So what about testing?
• With RuHL, rendering your view is not required to test.
• Only interested in the correct placement and content of data-ruhl
attributes.
• Helper provided:
<div class="single_box">
<h2>Topic: <span data-ruhl="_swap: name"/></h2>
<div id="chart">
<img data-ruhl="src: chart_period"/>
</div>
</div>
# To test above:
it "calls to replace name" do
data_ruhl('div.single_box > h2 > span').should == "_swap: name"
end
Wednesday, December 9, 2009
19. But wait, there’s more...
Since RuHL encourages presenters, test your presenters as you would your
models.
This simplifies your testing!
• Don’t have to go through your controller to trigger conditions that decide
the output in your view.
• This decreases the amount of time required to run your tests.
Wednesday, December 9, 2009
20. Summary
• Allowing code in your view is a bad practice.
• This simple requirement ensures a holistic approach.
• Utilize the presenter pattern to hold the code you would otherwise have
placed in your view.
• Using RuHL encourages continued designer involvement with the
maintenance of views.
Wednesday, December 9, 2009