DRYing Up Rails Views and Controllers

17,768 views
17,571 views

Published on

This was the fifth speech of a three day Rails training I gave in Tulsa, OK in the spring 2010.

Published in: Technology
0 Comments
23 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
17,768
On SlideShare
0
From Embeds
0
Number of Embeds
157
Actions
Shares
0
Downloads
136
Comments
0
Likes
23
Embeds 0
No embeds

No notes for slide


































































































  • DRYing Up Rails Views and Controllers

    1. 1. DRYing up Views and Controllers Layouts, partials, helpers, and filters
    2. 2. The Problem
    3. 3. The Problem I said Rails was big on DRY (don’t repeat yourself)
    4. 4. The Problem I said Rails was big on DRY (don’t repeat yourself) But we are duplicating a lot of code so far!
    5. 5. Add an Article Form A trivial page containing a form
    6. 6. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="Content-type" content="text/html; charset=utf-8"> <title>Add an Article</title> </head> <body> <h1>Add an Article</h1> <% form_for @article do |f| %> <%= f.error_messages %> <%= f.label :title %><br><%= f.text_field :title %><br> <%= f.label :body %><br><%= f.text_area :body %><br> <%= f.submit "Post Article" %> <% end %> </body> </html> Add an Article Form A trivial page containing a form
    7. 7. Update Article Form Nearly the exact same page
    8. 8. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="Content-type" content="text/html; charset=utf-8"> <title>Update Article</title> </head> <body> <h1>Update Article</h1> <% form_for @article do |f| %> <%= f.error_messages %> <%= f.label :title %><br><%= f.text_field :title %><br> <%= f.label :body %><br><%= f.text_area :body %><br> <%= f.submit "Save Article" %> <% end %> </body> </html> Update Article Form Nearly the exact same page
    9. 9. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="Content-type" content="text/html; charset=utf-8"> <title>Update Article</title> </head> <body> <h1>Update Article</h1> <% form_for @article do |f| %> <%= f.error_messages %> <%= f.label :title %><br><%= f.text_field :title %><br> <%= f.label :body %><br><%= f.text_area :body %><br> <%= f.submit "Save Article" %> <% end %> </body> </html> Update Article Form Nearly the exact same page
    10. 10. Solutions
    11. 11. Solutions Rails has many different tools to reduce repetition
    12. 12. Solutions Rails has many different tools to reduce repetition Layouts
    13. 13. Solutions Rails has many different tools to reduce repetition Layouts Partials
    14. 14. Solutions Rails has many different tools to reduce repetition Layouts Partials Helpers
    15. 15. Solutions Rails has many different tools to reduce repetition Layouts Partials Helpers Filters
    16. 16. Solutions Rails has many different tools to reduce repetition Layouts Partials Helpers Filters Let’s take a look at what each of these is good for
    17. 17. Layouts A tool for separating page header and footer code
    18. 18. Repetitive HTML
    19. 19. Repetitive HTML Layouts help you to handle header and footer code
    20. 20. Repetitive HTML Layouts help you to handle header and footer code This is handy for HTML <head> … </head> sections and common site design code
    21. 21. Repetitive HTML Layouts help you to handle header and footer code This is handy for HTML <head> … </head> sections and common site design code Rails will render a layout for each page, if available
    22. 22. Layout Selection class ArticlesController < ApplicationController # ... end
    23. 23. Layout Selection class ArticlesController < Each controller can ApplicationController have it’s own layout # ... end
    24. 24. Layout Selection class ArticlesController < Each controller can ApplicationController have it’s own layout # ... end If a controller doesn’t, Rails will check parent controllers
    25. 25. Layout Selection class ArticlesController < Each controller can ApplicationController have it’s own layout # ... end If a controller doesn’t, Rails will check parent controllers application.html.erb is the easiest way to set a global layout
    26. 26. A Basic Layout Just yield where you want to insert the page content
    27. 27. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="Content-type" content="text/html; charset=utf-8"> <title>My Blog</title> </head> <body> <%= yield %> </body> </html> A Basic Layout Just yield where you want to insert the page content
    28. 28. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="Content-type" content="text/html; charset=utf-8"> <title>My Blog</title> </head> <body> <%= yield %> </body> </html> A Basic Layout Just yield where you want to insert the page content
    29. 29. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="Content-type" content="text/html; charset=utf-8"> <title>My Blog</title> </head> <body> <%= yield %> </body> </html> A Basic Layout Just yield where you want to insert the page content
    30. 30. The Revised Add Form This code is inserted into the layout by Rails to create a full page
    31. 31. <h1>Add an Article</h1> <% form_for @article do |f| %> <%= f.error_messages %> <%= f.label :title %><br><%= f.text_field :title %><br> <%= f.label :body %><br><%= f.text_area :body %><br> <%= f.submit "Post Article" %> <% end %> The Revised Add Form This code is inserted into the layout by Rails to create a full page
    32. 32. The Revised Edit Form There’s still some duplication, but things are definitely improving
    33. 33. <h1>Update Article</h1> <% form_for @article do |f| %> <%= f.error_messages %> <%= f.label :title %><br><%= f.text_field :title %><br> <%= f.label :body %><br><%= f.text_area :body %><br> <%= f.submit "Save Article" %> <% end %> The Revised Edit Form There’s still some duplication, but things are definitely improving
    34. 34. Fixing the Title
    35. 35. Fixing the Title <% content_for :name, "Content" %> <% content_for :name do %> <script type="text/javascript" charset="utf-8"> // ... </script> <% end %> <%= yield :name %>
    36. 36. Fixing the Title content_for() can be used to pass content <% content_for :name, "Content" %> between files <% content_for :name do %> <script type="text/javascript" charset="utf-8"> // ... </script> <% end %> <%= yield :name %>
    37. 37. Fixing the Title content_for() can be used to pass content <% content_for :name, "Content" %> between files <% content_for :name do %> <script type="text/javascript" charset="utf-8"> One file sets content, // ... </script> using a Ruby String or <% end %> a block of HTML <%= yield :name %>
    38. 38. Fixing the Title content_for() can be used to pass content <% content_for :name, "Content" %> between files <% content_for :name do %> <script type="text/javascript" charset="utf-8"> One file sets content, // ... </script> using a Ruby String or <% end %> a block of HTML Another file yields to it <%= yield :name %> by name
    39. 39. Set Title Content Each page sets relevant title content
    40. 40. <% content_for :page_title, "Add an Article" %> <h1>Add an Article</h1> <!-- .... --> <% content_for :page_title, "Update Article" %> <h1>Update Article</h1> <!-- ... --> Set Title Content Each page sets relevant title content
    41. 41. Read the Title Content The layout will now make use of the title content if it exists
    42. 42. <title> <%= ["My Blog", yield(:page_title)].compact.join(" : ") %> </title> Read the Title Content The layout will now make use of the title content if it exists
    43. 43. Content Sharing in Action
    44. 44. Content Sharing in Action We now have dynamic titles based on the page you are viewing
    45. 45. Content Sharing in Action We now have dynamic titles based on the page you are viewing
    46. 46. Content Sharing in Action We now have dynamic titles based on the page you are viewing
    47. 47. Content Sharing in Action We now have dynamic titles based on the page you are viewing content_for() is also handy for sidebars and other shared content
    48. 48. Partials A tool for separating repeated chunks of view code
    49. 49. Duplicate Form Fields We need to remove more duplication, but be pragmatic about what to leave
    50. 50. <h1>Update Article</h1> <% form_for @article do |f| %> <%= f.error_messages %> <%= f.label :title %><br><%= f.text_field :title %><br> <%= f.label :body %><br><%= f.text_area :body %><br> <%= f.submit "Save Article" %> <% end %> Duplicate Form Fields We need to remove more duplication, but be pragmatic about what to leave
    51. 51. <h1>Update Article</h1> <% form_for @article do |f| %> <%= f.error_messages %> <%= f.label :title %><br><%= f.text_field :title %><br> <%= f.label :body %><br><%= f.text_area :body %><br> <%= f.submit "Save Article" %> <% end %> Duplicate Form Fields We need to remove more duplication, but be pragmatic about what to leave
    52. 52. <h1>Update Article</h1> <% form_for @article do |f| %> <%= f.error_messages %> <%= f.label :title %><br><%= f.text_field :title %><br> <%= f.label :body %><br><%= f.text_area :body %><br> <%= f.submit "Save Article" %> <% end %> Duplicate Form Fields We need to remove more duplication, but be pragmatic about what to leave
    53. 53. Shared HTML
    54. 54. Shared HTML Any shared HTML can be placed into a “partial”
    55. 55. Shared HTML Any shared HTML can be placed into a “partial” This is often used for form fields, and code that displays the details of an individual model
    56. 56. Shared HTML Any shared HTML can be placed into a “partial” This is often used for form fields, and code that displays the details of an individual model That partial can then be inserted into all needed places
    57. 57. Shared HTML Any shared HTML can be placed into a “partial” This is often used for form fields, and code that displays the details of an individual model That partial can then be inserted into all needed places By convention, partial files begin with an _ in Rails (for example: _article.html.erb)
    58. 58. _form.html.erb I’ve moved the form fields into a separate HTML file, starting with an _ so Rails knows it’s a partial
    59. 59. <%= f.error_messages %> <%= f.label :title %><br><%= f.text_field :title %><br> <%= f.label :body %><br><%= f.text_area :body %><br> _form.html.erb I’ve moved the form fields into a separate HTML file, starting with an _ so Rails knows it’s a partial
    60. 60. <%= f.error_messages %> <%= f.label :title %><br><%= f.text_field :title %><br> <%= f.label :body %><br><%= f.text_area :body %><br> _form.html.erb I’ve moved the form fields into a separate HTML file, starting with an _ so Rails knows it’s a partial
    61. 61. Forms render() the Partial We can render() the partial anywhere we need to reuse it and even pass variables into it
    62. 62. <% content_for :page_title, "Add an Article" %> <h1>Add an Article</h1> <% form_for @article do |f| %> <%= render "form", :f => f %> <%= f.submit "Post Article" %> <% end %> <% content_for :page_title, "Update Article" %> <h1>Update Article</h1> <% form_for @article do |f| %> <%= render "form", :f => f %> <%= f.submit "Save Article" %> <% end %> Forms render() the Partial We can render() the partial anywhere we need to reuse it and even pass variables into it
    63. 63. <% content_for :page_title, "Add an Article" %> <h1>Add an Article</h1> <% form_for @article do |f| %> <%= render "form", :f => f %> <%= f.submit "Post Article" %> <% end %> <% content_for :page_title, "Update Article" %> <h1>Update Article</h1> <% form_for @article do |f| %> <%= render "form", :f => f %> <%= f.submit "Save Article" %> <% end %> Forms render() the Partial We can render() the partial anywhere we need to reuse it and even pass variables into it
    64. 64. Partials for Models
    65. 65. Partials for Models Rails is smart about partials used to show a model
    66. 66. Partials for Models Rails is smart about partials used to show a model It can recognize them by name (more conventions!)
    67. 67. Partials for Models Rails is smart about partials used to show a model It can recognize them by name (more conventions!) It will render() the proper partial for a model or repeatedly render() the same partial for an entire collection of models
    68. 68. Partials for Models Rails is smart about partials used to show a model It can recognize them by name (more conventions!) It will render() the proper partial for a model or repeatedly render() the same partial for an entire collection of models A local variable is set holding the model, again named by the type
    69. 69. Manual Iteration This code works, but Rails is smart enough to help us if we follow some conventions
    70. 70. <h1>Articles</h1> <ul> <% @articles.each do |article| %> <li> <%= link_to h(article.title), article_path(article) %> <%= link_to "edit", edit_article_path(article) %> </li> <% end %> </ul> Manual Iteration This code works, but Rails is smart enough to help us if we follow some conventions
    71. 71. _article.html.erb I moved the Article display code into an _article.html.erb partial
    72. 72. <li> <%= link_to h(article.title), article_path(article) %> <%= link_to "edit", edit_article_path(article) %> </li> _article.html.erb I moved the Article display code into an _article.html.erb partial
    73. 73. <li> <%= link_to h(article.title), article_path(article) %> <%= link_to "edit", edit_article_path(article) %> </li> _article.html.erb I moved the Article display code into an _article.html.erb partial
    74. 74. Partial Found by Name Rails looks for an _article.html.erb to render() the Article (matching the names)
    75. 75. <h1>Articles</h1> <ul> <% @articles.each do |article| %> <%= render article %> <% end %> </ul> Partial Found by Name Rails looks for an _article.html.erb to render() the Article (matching the names)
    76. 76. One Step Further Rails can even recognize a collection (an Array), render()ing the partial once for each member
    77. 77. <h1>Articles</h1> <ul> <%= render @articles %> </ul> One Step Further Rails can even recognize a collection (an Array), render()ing the partial once for each member
    78. 78. Helpers A tool for separating out view logic
    79. 79. Where to Hide View Logic
    80. 80. Where to Hide View Logic Views should be pretty dumb template filling code
    81. 81. Where to Hide View Logic Views should be pretty dumb template filling code Logic in your views is hard to maintain and needs to be moved
    82. 82. Where to Hide View Logic Views should be pretty dumb template filling code Logic in your views is hard to maintain and needs to be moved Move business logic into model methods
    83. 83. Where to Hide View Logic Views should be pretty dumb template filling code Logic in your views is hard to maintain and needs to be moved Move business logic into model methods If it’s really view logic, write a helper method
    84. 84. Where to Hide View Logic Views should be pretty dumb template filling code Logic in your views is hard to maintain and needs to be moved Move business logic into model methods If it’s really view logic, write a helper method A helper is just a Ruby “Mixin” Rails adds to the view
    85. 85. These can be Combined This is some logic though, so it belongs in a helper method
    86. 86. <% content_for :page_title, "Add an Article" %> <h1>Add an Article</h1> <% form_for @article do |f| %> <%= render "form", :f => f %> <%= f.submit "Post Article" %> <% end %> <% content_for :page_title, "Update Article" %> <h1>Update Article</h1> <% form_for @article do |f| %> <%= render "form", :f => f %> <%= f.submit "Save Article" %> <% end %> These can be Combined This is some logic though, so it belongs in a helper method
    87. 87. <% content_for :page_title, "Add an Article" %> <h1>Add an Article</h1> <% form_for @article do |f| %> <%= render "form", :f => f %> <%= f.submit "Post Article" %> <% end %> <% content_for :page_title, "Update Article" %> <h1>Update Article</h1> <% form_for @article do |f| %> <%= render "form", :f => f %> <%= f.submit "Save Article" %> <% end %> These can be Combined This is some logic though, so it belongs in a helper method
    88. 88. Adding a Helper Method I added this method to the Module (“Mixin”) in app/helpers/application_helper.rb
    89. 89. module ApplicationHelper def page_title(title) content_for :page_title, title "<h1>#{title}</h1>" end end Adding a Helper Method I added this method to the Module (“Mixin”) in app/helpers/application_helper.rb
    90. 90. Switch to Using the Helper The views are a little cleaner now with the logic moved to the helper
    91. 91. <%= page_title "Add an Article" %> <% form_for @article do |f| %> <%= render "form", :f => f %> <%= f.submit "Post Article" %> <% end %> <%= page_title "Update Article" %> <% form_for @article do |f| %> <%= render "form", :f => f %> <%= f.submit "Save Article" %> <% end %> Switch to Using the Helper The views are a little cleaner now with the logic moved to the helper
    92. 92. Built-in Helpers
    93. 93. Built-in Helpers Rails comes with a ton of helpers, available in all views
    94. 94. Built-in Helpers Rails comes with a ton of helpers, available in all views Date and time methods
    95. 95. Built-in Helpers Rails comes with a ton of helpers, available in all views Date and time methods Number and currency methods
    96. 96. Built-in Helpers Rails comes with a ton of helpers, available in all views Date and time methods Number and currency methods Link and form builders
    97. 97. Built-in Helpers Rails comes with a ton of helpers, available in all views Date and time methods Number and currency methods Link and form builders Image, CSS, and JavaScript support methods
    98. 98. Built-in Helpers Rails comes with a ton of helpers, available in all views Date and time methods Number and currency methods Link and form builders Image, CSS, and JavaScript support methods …
    99. 99. An Article Show Page Uses helpers to escape HTML, show time, and add simple formatting (like paragraphs) here
    100. 100. <%= page_title h(@article.title) %> <p>posted <%= time_ago_in_words @article.created_at %> ago</p> <%= simple_format @article.body %> An Article Show Page Uses helpers to escape HTML, show time, and add simple formatting (like paragraphs) here
    101. 101. <%= page_title h(@article.title) %> <p>posted <%= time_ago_in_words @article.created_at %> ago</p> <%= simple_format @article.body %> An Article Show Page Uses helpers to escape HTML, show time, and add simple formatting (like paragraphs) here
    102. 102. <%= page_title h(@article.title) %> <p>posted <%= time_ago_in_words @article.created_at %> ago</p> <%= simple_format @article.body %> An Article Show Page Uses helpers to escape HTML, show time, and add simple formatting (like paragraphs) here
    103. 103. <%= page_title h(@article.title) %> <p>posted <%= time_ago_in_words @article.created_at %> ago</p> <%= simple_format @article.body %> An Article Show Page Uses helpers to escape HTML, show time, and add simple formatting (like paragraphs) here
    104. 104. Filters A tool for separating repeated chunks of controller code
    105. 105. Controller Duplication It’s very common for show, edit, update, and destroy to start with the same lookup code
    106. 106. class ArticlesController < ApplicationController # ... def show @article = Article.find(params[:id]) end def edit @article = Article.find(params[:id]) end def update @article = Article.find(params[:id]) # ... end def destroy @article = Article.find(params[:id]) # ... end end Controller Duplication It’s very common for show, edit, update, and destroy to start with the same lookup code
    107. 107. class ArticlesController < ApplicationController # ... def show @article = Article.find(params[:id]) end def edit @article = Article.find(params[:id]) end def update @article = Article.find(params[:id]) # ... end def destroy @article = Article.find(params[:id]) # ... end end Controller Duplication It’s very common for show, edit, update, and destroy to start with the same lookup code
    108. 108. Before or After an Action
    109. 109. Before or After an Action Rails has filters that can be run before or after an action
    110. 110. Before or After an Action Rails has filters that can be run before or after an action before_filter() is often used to lookup model instances or check access control
    111. 111. Before or After an Action Rails has filters that can be run before or after an action before_filter() is often used to lookup model instances or check access control You can choose to skip the action that follows
    112. 112. Before or After an Action Rails has filters that can be run before or after an action before_filter() is often used to lookup model instances or check access control You can choose to skip the action that follows after_filter() isn’t used as much, but it can be handy for statistics tracking
    113. 113. Using a before_filter() You can specify a method to call before certain actions are run
    114. 114. class ArticlesController < ApplicationController before_filter :find_article, :only => %w[show edit update destroy] # ... def show end def edit end def update # ... end def destroy # ... end private def find_article @article = Article.find(params[:id]) end end Using a before_filter() You can specify a method to call before certain actions are run
    115. 115. class ArticlesController < ApplicationController before_filter :find_article, :only => %w[show edit update destroy] # ... def show end def edit end def update # ... end def destroy # ... end private def find_article @article = Article.find(params[:id]) end end Using a before_filter() You can specify a method to call before certain actions are run
    116. 116. class ArticlesController < ApplicationController before_filter :find_article, :only => %w[show edit update destroy] # ... def show end def edit end def update # ... end def destroy # ... end private def find_article @article = Article.find(params[:id]) end end Using a before_filter() You can specify a method to call before certain actions are run
    117. 117. class ArticlesController < ApplicationController before_filter :find_article, :only => %w[show edit update destroy] # ... def show end def edit end def update # ... end def destroy # ... end private def find_article @article = Article.find(params[:id]) end end Using a before_filter() You can specify a method to call before certain actions are run
    118. 118. Questions?
    119. 119. DRY up Your Views Lab Your book has instructions on how to remove the duplication in your code

    ×