PageRenderTime 50ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/README.md

https://gitlab.com/learn-co-curriculum/partials-lecture
Markdown | 258 lines | 184 code | 74 blank | 0 comment | 0 complexity | 071c1cc29e72d937299c434ec24bf56f MD5 | raw file
  1. ## Intro
  2. * What is a partial? Code re-use for HTML. Think-keeping our code DRY.
  3. * General example: Our app displays a user profile page, and an edit user profile page, a new user profile page and an index page of all of the users. Each of these four HTML templates repeat the same, or very similar, code for displaying a user's details--their name, age, and location. But, our project manager has just let us know that our client has decided a user's details should also include their favorite cat. While we commend our client for their excellent taste, we are annoyed that we have to make the *same change* to all four files that display a user's details. Using partials avoids this headache. With a partial, we can extract a portion of our html code, store it in some other file, and simply call, or *render*, the code in that file in any other template. Think of it as making your html code into a method that you can call again and again.
  4. ## The Domain Model
  5. Today, we'll be working with a music management app that has a song, artist and genre model. An artist has many songs and a song belongs to an artist. A song has many genres and a genre has many songs. We'll be taking a closer look at a few views in particular: the application.html.erb layout, the song show page, the genre show page and the new and edit pages for the song model.
  6. ## Rendering a Simple Partial
  7. Let's take a look at the simple navigation menu that we've provided our music management app. Right now our menu (which has no styling, don't judge), is placed directly in our `views/layouts/application.html` view:
  8. ```html
  9. <!DOCTYPE html>
  10. <html>
  11. <head>
  12. <title>PlaylisterApp</title>
  13. <%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %>
  14. <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
  15. <%= csrf_meta_tags %>
  16. </head>
  17. <body>
  18. <p>Menu</p>
  19. <ul>
  20. <li>
  21. <%= link_to 'Songs', songs_path %>
  22. </li>
  23. <li>
  24. <%= link_to 'Genres', genres_path %>
  25. </li>
  26. <li>
  27. <%= link_to 'Artists', artists_path %>
  28. </li>
  29. </ul>
  30. <%= yield %>
  31. </body>
  32. </html>
  33. ```
  34. Right now, our view is a little disorganized. Its crowded and difficult to read. Imagine an even more complex navigation menu, along with some other content that you might find on this view, like a footer and a sidebar that contains some widgets. Our view would get increasingly difficult to read, understand and edit. Wouldn't it be great if we could organize discrete units of html code, like our menu, by extracting them into their very own files? That's exactly what a partial will do for us. Let's do it.
  35. * Step 1: Make the partial file: `app/views/layouts/_menu.html.erb`. *Always name partial files with an underscore*.
  36. * Step 2: Remove the menu code from the application view and place it in the partial:
  37. ```html
  38. # app/views/layouts/_menu.html.erb
  39. <p>Menu</p>
  40. <ul>
  41. <li>
  42. <%= link_to 'Songs', songs_path %>
  43. </li>
  44. <li>
  45. <%= link_to 'Genres', genres_path %>
  46. </li>
  47. <li>
  48. <%= link_to 'Artists', artists_path %>
  49. </li>
  50. </ul>
  51. ```
  52. * Step 3: Call, or *render*, the partial on the application view.
  53. ```html
  54. # app/views/layouts/application.html.erb
  55. <!DOCTYPE html>
  56. <html>
  57. <head>
  58. <title>PlaylisterApp</title>
  59. <%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %>
  60. <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
  61. <%= csrf_meta_tags %>
  62. </head>
  63. <body>
  64. <%= render 'menu' %>
  65. <%= yield %>
  66. </body>
  67. </html>
  68. ```
  69. * Notice that we call the render method with an argument of the name of the partial we want to render, *minus the underscore*.
  70. Now that we have a basic understanding of how to render a partial to keep our view code organized and DRY, let's take a look at using more complicated partials.
  71. ## Rending Partials with Locals
  72. Notice that the genre show page displays the genre's name, the songs associated with that genre, and some of the details of each of those songs:
  73. ```html
  74. <p id="notice"><%= notice %></p>
  75. <p>
  76. <strong>Genre Name:</strong>
  77. <%= @genre.name %>
  78. </p>
  79. <br>
  80. <h3>Songs:</h3>
  81. <ul>
  82. <% @genre.songs.each do |song| %>
  83. <p>
  84. <strong>Name:</strong>
  85. <%= song.name %>
  86. </p>
  87. <p>
  88. <strong>Artist Name:</strong>
  89. <%= link_to song.artist.name, song.artist if song.artist %>
  90. </p>
  91. <%end %>
  92. </ul>
  93. <%= link_to 'Edit', edit_genre_path(@genre) %> |
  94. <%= link_to 'Back', genres_path %>
  95. ```
  96. Notice that the song show page also displays the song name and the name of that song's artist:
  97. ```html
  98. <p id="notice"><%= notice %></p>
  99. <p>
  100. <strong>Name:</strong>
  101. <%= @song.name %>
  102. </p>
  103. <p>
  104. <strong>Artist Name:</strong>
  105. <%= link_to @song.artist.name, @song.artist if @song.artist %>
  106. </p>
  107. <p>
  108. <strong>Genres:</strong>
  109. <%= @song.genres.collect{|g| g.name}.to_sentence %>
  110. </p>
  111. <%= link_to 'Edit', edit_song_path(@song) %> |
  112. <%= link_to 'Back', songs_path %>
  113. ```
  114. This code isn't very dry. We repeat the same code in two locations, and any changes we make to what attributes a song has and how we display them will need to be made in both locations.
  115. Let's extract this code for displaying a song's name and artist into a partial.
  116. * Step 1: Create a partial in `views/songs/_song_details.html.erb`.
  117. * Step 2: Place the HTML that displays a song name and it's artist's name in that file:
  118. ```html
  119. <p>
  120. <strong>Name:</strong>
  121. <%= @song.name %>
  122. </p>
  123. <p>
  124. <strong>Artist Name:</strong>
  125. <%= link_to @song.artist.name, song.artist if @song.artist %>
  126. </p>
  127. ```
  128. Now, let's render our partial in place of those lines of code on our song show page:
  129. ```html
  130. <%= render 'song_details'%>
  131. ```
  132. It works! Notice that our partial relies on an instance variable, `@song`, which it implicitly has access to since we set it in the controller action that brought us to this view page.
  133. **Using Convention**
  134. let's try rendering our partial like this
  135. ```html
  136. <%#= render @song %>
  137. ```
  138. We get error message missing template songs/song. Rails convention is trying to find a partial that matches the name of the object we are passing in to render. Would this work if we create a song/_song.html.erb that looked just like `_song_details`? Let's try it!
  139. Yes it works, explain a bit about that rails convention but then go back to using song_detail.
  140. Now Let's try rendering our `_song_detail` partial from our genre show page:
  141. ```html
  142. <ul>
  143. <% @genre.songs.each do |song| %>
  144. <%= render "songs/song_details" %>
  145. <%end %>
  146. </ul>
  147. ```
  148. Notice that this time, we have to specify which subdirectory of views our partial is found in `/songs`. If a partial is in the same directory as the file on which it is rendered, we can leave this part off.
  149. Let's visit a genre's show page and see our partial in action. Oh no! We have an error, `undefined method 'name' for nil class`. This error is getting thrown by this line of our partial: `<%= @song.name %>`. Which controller action brought us to the genre show page? `genres#show`, not only did that controller action *not* set a variable, `@song`, for us, but we are actually calling this partial inside our `#each` iteration as we iterate over the collection of the given genre's song. All of this is to say that we do *not* in fact have access to any variable called `@song` on our partial when it is rendered from the genre show page. For this reason, we never want to use instance variables in a partial.
  150. So, how can we solve this?
  151. * First, we'll replace the instance variable `@song` with a local variable, `song`, on that partial.
  152. * Then, we'll pass in the value of the local variable when we call `render` on both the song and genre show page, using the following syntax:
  153. ```html
  154. # song view
  155. <%= render partial: 'song_details', locals: {song: @song} %>
  156. ```
  157. ```html
  158. <%= render partial: 'song_details', locals: {song: song} %>
  159. ```
  160. In this way, we can dynamically pass in the appropriate song object whenever and wherever we render the partial. Think of it as calling the `render` method with keyword arguments. Each time, we are setting the local variable, `song`, inside of the partial, equal to whatever object gets set to `locals: {song: some_song_object}`.
  161. `render` has a polymorphic interface. the way we use it changes. if we pass it a hash, it expects the full thing above. if first arg is a string, it reacts appropriate and expects second arg to be hash of locals:
  162. ```erb
  163. <%=render 'songs/song_details', song: song%>
  164. ```
  165. ## Rendering A Collection via a Partial
  166. Let's take a moment and refactor our rendering of the `song_details` partial on the genre show page. Here, we are using the partial inside of our iteration over a genre's collection of songs. This is such a common practice, that Rails provides us with an even easier way to do it:
  167. ```html
  168. <%= render partial: "songs/song_details", collection: @genre.songs, as: :song %>
  169. ```
  170. In fact, there is an even more concise way to render a partial with a collection of objects:
  171. ```html
  172. <%= render @genre.songs %>
  173. ```
  174. However, if we try this approach, we'll see the following error: (missing template songs/song). Rails is trying to locate a partial using a singular version of the collection we are passing the render method (songs/song). Since we named our partial song_details, this level of abstraction won't work for us. We'll have to use the more explicit version. Let's take a closer look at that now:
  175. ```html
  176. <%= render partial: "songs/song_details", collection: @genre.songs, as: :song %>
  177. ```
  178. Here, we assign `collection` equal to the collection of songs we want to render and we tell our partial to set the `song` local variable equal to each member of the `@genre.songs` collection. The render method will the work for us of iterating over the given collection and setting each member of that collection equal to the local variable, `song`.
  179. ## Rendering Partials with Local Variables, Form Example
  180. Another common usage of partials is in rendering forms. Let's take a look at the page to edit a song and the view for creating a new song. Notice that the forms are exactly the same! Further, notice that the number of form fields can be a lot to take in. The more form fields we have, the harder it gets to read, understand and edit the form, from the developers point of view. Let's refactor these forms into using partials to render the form fields:
  181. * Step 1: Make a partial, `songs/_form_fields.html.erb`.
  182. * Step 2: Paste our form fields in the partial
  183. * Step 3: Pass the local variable, `f`, into the partial when we render it on the pages.
  184. <p data-visibility='hidden'>View <a href='https://learn.co/lessons/partials-lecture' title='Intro'>Intro</a> on Learn.co and start learning to code for free.</p>