Coder Social home page Coder Social logo

partial-locals-reading's Introduction

Partials with Locals

Objectives

  1. Use the locals keyword
  2. Understand why using instance variables in partials is non-optimal
  3. Use a partial while rendering a collection
  4. Use a partial from another controller with a local

Introduction

Partials help us break our code up into reusable chunks. They also often have implicit dependencies that can lead to bugs. For example, what if a partial assumes that a @user variable is present. If the point is to reuse partials, if you put it inside of an action that didn't set a @user variable, you're going to have a bug. Using "locals" in partials is how we can make these implicit assumptions explicit. In the following example, we'll unpack exactly what locals are and how they're used.

Lesson

Take a look at the included repo. You should notice the same piece of view code in a few places.

<ul>
  <li> <%= @author.name %></li>
  <li> <%= @author.hometown %></li>
</ul>

You'll find that code (or very similar code) in the following pages:

  • app/views/authors/show.html.erb
  • app/views/authors/index.html.erb
  • app/views/posts/show.html.erb.
  • app/views/posts/index.html.erb.

Let's see how we might be vulnerable to bugs. In this <ul> we assume that there will be a controller-set variable, @author. But what if that person-like entity makes more sense to be called @admin or @guest or @owner. We want the same bit of UI, but don't want to have to re-name our variables to make it work. We know what we want in the partial (the <ul>), what we want to be flexible is the "thing" that we invoke .name and hometown on.

ASIDE: This should recall the "why do methods have arguments and parameters" discussion from when you were learning to write methods.

Let's start with the author show page. Watch our process here as we're going to apply it to all views that reference this name and hometown information.

Let's remove the code from our app/views/authors/show.html.erb page. Now our file should be empty:

<!-- app/views/authors/show.html.erb -->

We can move the removed code into a partial, app/views/authors/_author.html.erb, that now has the following code:

<!-- app/views/authors/_author.html.erb -->

<ul>
  <li> <%= @author.name %></li>
  <li> <%= @author.hometown %></li>
</ul>

To keep our code in the show page rendering out the same content, we call the partial from the app/views/authors/show.html.erb file. Doing this, the app/views/authors/show.html.erb file now looks like the following:

<%= render 'author' %>

Great!

Now let's take a look at the app/views/posts/show.html.erb file. It currently looks like the following:

Information About the Post
<ul>
  <li> <%= @author.name %></li>
  <li> <%= @author.hometown %></li>
</ul>
<%= @post.title %>
<%= @post.content %>

You can see that lines 2-5 are exactly the same as the code in our authors/author partial. Let's remove the repetition in our codebase by using that partial instead. By using the partial, our code will look like the following:

Information About the Post
<%= render 'authors/author' %>
<%= @post.title %>
<%= @post.content %>

NOTE: Because we are calling a partial from outside the current app/views/posts folder, we must specify the folder that our author partial is coming from by calling render 'authors/author'.

The Problem

In app/views/authors/show.html.erb our source of information about .name and .hometown is @author; in app/views/posts/show.html.erb the source of information about .name and .hometown is @post.author. If we could tell the partial "use as your source" @author or @post.author, we could share the partial across these two different views.

The locals parameter to render provides this flexibility.

Let's see how local variables make our code more explicit.

This is what the entire show view, app/views/posts/show.html.erb, looks like when locals are used:

Information About the Post
<%= render partial: "authors/author", locals: {post_author: @author} %>
<%= @post.title %>
<%= @post.content %>

Notice a few things:

  1. We are no longer passing the render method a String; we're passing key-value pairs
  2. The first key-value pair tells Rails the name of the partial to render ("authors/author")
  3. The second key-value pair specifies the locals as a Hash. That Hash's keys (post_author here) will be created as local variables within the partial.

When we use locals, we need to make sure that the variables we refer to in our partial have the same names as the keys in our locals hash.

In our example partial, app/views/author/_author.html.erb, we need to change our code from:

<ul>
  <li> <%= @author.name %></li>
  <li> <%= @author.hometown %></li>
</ul>

to:

<ul>
  <li> <%= post_author.name %></li>
  <li> <%= post_author.hometown %></li>
</ul>

The way we use locals with a partial is similar to how we pass arguments into a method. In the locals Hash, the post_author: key is the argument name, and the value of that argument, @author, is the value stored as post_author and passed into the method. We can name the keys whatever we want.

Now notice that, if we choose to add the line <%= render {partial: "authors/author", locals: {post_author: @author}} %> from the posts/show view, calling the partial requires us to pass in data about the author. The @author = @post.author line in our PostsController may no longer be needed.

In fact, with locals, we can completely eliminate the @author = @post.author line in the posts#show controller action, instead only accessing that data where we need it, in the partial.

Let's remove that line of code in our controller and in the view pass through the author information by changing our code to the following:

  # app/controllers/posts_controller.rb
  def show
    @post = Post.find(params[:id])
  end
<!-- app/views/posts/show.html.erb -->
Information About the Post
<%= render partial: "authors/author", locals: {post_author: @post.author} %>
<%= @post.title %>
<%= @post.content %>

This code is much better. We are being more explicit about our dependencies, reducing lines of code in our codebase, and reducing the scope of the author variable.

Let's update the rest of our views to use the our partial with locals as well:

<!-- app/views/authors/show.html.erb -->
<%= render partial: "authors/author", locals: {post_author: @author} %>
<!-- app/views/authors/index.html.erb -->
<% @authors.each do |author| %>
  <%= render partial: "authors/author", locals: {post_author: author} %>
<% end %>
<!-- app/views/posts/index.html.erb -->
<% @posts.each do |post| %>
  <%= render partial: "authors/author", locals: {post_author: post.author} %>
  <%= post.title %>
  <%= post.content %>
<% end %>

Don't worry if you find the syntax for rendering a partial hard to remember โ€“โ€“ it is. You can always reference this guide or the Rails Guides.

Conclusion

In this lab we've learned how partials help us DRY out our views and how the locals Hash can be used to create flexibility in our calls to the partials.

Resources

partial-locals-reading's People

Contributors

annjohn avatar blake41 avatar danielseehausen avatar dependabot[bot] avatar hoffm386 avatar ihollander avatar jeffkatzy avatar lawrend avatar lizbur10 avatar maxwellbenton avatar mikeappell avatar mnrd-brian avatar ngevan avatar perpepajn25 avatar pletcher avatar rrcobb avatar sdcrouse avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

partial-locals-reading's Issues

ReadMe is incorrect

  • <%= post.author.name %>
  • <%= post.author.hometown %>

was replaced with

<%= render partial: "authors/author", locals: {post_author: @author} %>

which I believe wouldn't work...unless in the Controller you did:

def show
@post = Post.find(params[:id])
@author = @post.author
end

Bad Practice to Query DB from the View

The README instructs students to remove the @author instance variable from the controller and only pass @post to the view because in the view you can say @post.author. The #author method when called on @post actually makes a call to the database, it runs some SQL. It is generally bad practice to make db queries from the view, exactly what the README says is better practice here.

In fact, with locals, we can eliminate @author = @post.author line in the posts#show action in the controller completely, by instead only accessing that data in where we need it, in the partial.

Let's remove that line of code in our controller, and in the view pass through the author information by changing our code to the following:

app/controllers/posts_controller

def show
    @post = Post.find(params[:id])
  end
app/views/posts/show.html.erb

Information About the Post
<%= render partial: "authors/author", locals: {post_author: @post.author} %>
<%= @post.title %>
<%= @post.content %>

This code is much better. We are being more explicit about our dependencies, reducing lines of code in our codebase, and reducing the scope of the author variable

typo

in readme under "The Problem", @about is plaintext (without snippet formatting)

Learn IDE is broken for this lab

For some reason, this lab won't fork or clone in Learn IDE. I was able to pass the lab on my local, but wanted to make you aware of this issue. Here is the error when opening Learn IDE:

learn open partial-locals-reading-online-web-ft-031119 Looking for lesson... Traceback (most recent call last): 11: from /usr/local/rvm/gems/ruby-2.6.1/bin/ruby_executable_hooks:24:in

'
10: from /usr/local/rvm/gems/ruby-2.6.1/bin/ruby_executable_hooks:24:ineval' 9: from /usr/local/rvm/gems/ruby-2.6.1/bin/learn-open:23:in '
8: from /usr/local/rvm/gems/ruby-2.6.1/bin/learn-open:23:in load' 7: from /usr/local/rvm/gems/ruby-2.6.1/gems/learn-open-1.2.28/bin/learn-open:7:in <top (required)>'
6: from /usr/local/rvm/gems/ruby-2.6.1/gems/learn-open-1.2.28/lib/learn_open/opener.rb:12:in run' 5: from /usr/local/rvm/gems/ruby-2.6.1/gems/learn-open-1.2.28/lib/learn_open/opener.rb:38:in run'
4: from /usr/local/rvm/gems/ruby-2.6.1/gems/learn-open-1.2.28/lib/learn_open/lessons.rb:19:in classify' 3: from /usr/local/rvm/gems/ruby-2.6.1/gems/learn-open-1.2.28/lib/learn_open/lessons.rb:19:in find'
2: from /usr/local/rvm/gems/ruby-2.6.1/gems/learn-open-1.2.28/lib/learn_open/lessons.rb:19:in each' 1: from /usr/local/rvm/gems/ruby-2.6.1/gems/learn-open-1.2.28/lib/learn_open/lessons.rb:20:in block in classify'
/usr/local/rvm/gems/ruby-2.6.1/gems/learn-open-1.2.28/lib/learn_open/lessons/ios_lesson.rb:6:in detect': undefined method any?' for false:FalseClass (NoMethodError)`

brackets differ in 3 examples in this lesson

The less shows 'conflicting' syntax, or perhaps both work?, when depicting brackets in the hash passed to a partial.

This reference seems to agree with examples 1 and 3:
http://guides.rubyonrails.org/layouts_and_rendering.html#using-partials
Example: <%= render partial: "form", locals: {zone: @zone} %>

Lesson example 1:
<%= render partial: "authors/author", locals: {post_author: @author} %>

Lesson example 2:
<%= render {partial: "authors/author", locals: {post_author: @author}} %>

are these {} brackets a mistake? (not consistent with the examples 1 and 3 above and below)

Lesson example 3:
Information About the Post
<%= render partial: "authors/author", locals: {post_author: @post.author} %>
<%= @post.title %>
<%= @post.content %>

Tests missing

I know it's a readme lab, usually there are some tests though, and a way to submit it. Here, there's nothing.. and I can't mark it ad 'Read' because it behaves like a lab. How can I run tests and mark it as done & submitted?

Screen Shot 2019-12-22 at 20 10 54

Was not able to open this lab with learn gem

I would also like to add i was unable to use the learn function on my local without going to the repo and forking/cloning directly.

learn open partial-locals-reading-online-web-ft-031119
Looking for lesson...
/home/jon/.rvm/gems/ruby-2.3.7/gems/learn-open-1.2.28/lib/learn_open/lessons/ios_lesson.rb:6:in detect': undefined method any?' for false:FalseClass (NoMethodError)
from /home/jon/.rvm/gems/ruby-2.3.7/gems/learn-open-1.2.28/lib/learn_open/lessons.rb:20:in block in classify' from /home/jon/.rvm/gems/ruby-2.3.7/gems/learn-open-1.2.28/lib/learn_open/lessons.rb:19:in each'
from /home/jon/.rvm/gems/ruby-2.3.7/gems/learn-open-1.2.28/lib/learn_open/lessons.rb:19:in find' from /home/jon/.rvm/gems/ruby-2.3.7/gems/learn-open-1.2.28/lib/learn_open/lessons.rb:19:in classify'
from /home/jon/.rvm/gems/ruby-2.3.7/gems/learn-open-1.2.28/lib/learn_open/opener.rb:38:in run' from /home/jon/.rvm/gems/ruby-2.3.7/gems/learn-open-1.2.28/lib/learn_open/opener.rb:12:in run'
from /home/jon/.rvm/gems/ruby-2.3.7/gems/learn-open-1.2.28/bin/learn-open:7:in <top (required)>' from /home/jon/.rvm/gems/ruby-2.3.7/bin/learn-open:23:in load'
from /home/jon/.rvm/gems/ruby-2.3.7/bin/learn-open:23:in <main>' from /home/jon/.rvm/gems/ruby-2.3.7/bin/ruby_executable_hooks:24:in eval'
from /home/jon/.rvm/gems/ruby-2.3.7/bin/ruby_executable_hooks:24:in `

'

The code in the README is inaccurate, starting in the "The Problem" section

The second section of the README (The Problem) contradicts the first. The first section is OK, and it matches the code we are given. It says that the authors/show page and the posts/show page both contain this HTML:

<ul>
  <li> <%= @author.name %></li>
  <li> <%= @author.hometown %></li>
</ul>

The lesson then has us refactor them into an authors/_author.html.erb partial. That's all OK. But then the next section ("The Problem") says that the authors/show and posts/show pages have completely different code. Instead of both of them using @author, it now says that only the authors/show page uses @author, while posts/show uses @post.author. But as the README and our code demonstrated earlier, this is a contradiction - they BOTH use @author.

The rest of that section shows how we can refactor our code by using locals. One thing I noticed is that the lesson came with (and showed) not only show files for authors and posts, but index files as well. My guess is that locals were supposed to be used with the index files, not the show files, but I can't say for sure.

Thanks a lot for looking into this, and Merry Christmas!

---Sdcrouse

learn is not worked

Hi! I run
gem install bundler:2.0.1
and follow the instructions, but I can't run learn at all.

This can be fixed by doing the following:

Add gem 'rspec-rails', ">= 2.0.0.beta" to the gemfile
Run bundle install
Run rails generate rspec:install
Run learn test
Run learn submit

This adds spec files to the lab so that it can be properly submitted
Thanks,

I was advised to run this : "gem install learn-co" and it fixed the learn issue.

Missing spec files

The lab is telling me I need to run local tests, but when I ran learn I got "This directory doesn't appear to have any specs in it." I then looked at the code and couldn't find any spec files. I looked at the GitHub repo and didn't see any there either. I'm not sure if the error is the missing spec files or the "run local tests" part of the lab.

Incorrect code

Hi, I noticed a few inaccuracies in the code for this lab. Specifically, the post index page uses iterates through all the posts with this line <%= @posts.each do |post| %> - rendering the @posts array. Also, in many of the views, a local variable author which does not exist is used instead of the instance variable @author. This causes an error if you add routes and run the browser. Thanks!

Incomplete information

Does not provide any information regarding what happens to the app/views/authors/show.html.erb and app/views/authors/index.html.erb.

These views also contain @author reference. Upon changing the _author partial to refer to post_author, it coincides with post_author usage in app/views/posts/show.html.erb, but what happens to the dependencies of the above two author views?

Not opening the lesson

After forking the lesson and cloning the lesson is in my computer but it does not go through learn, getting these errors:

Looking for lesson...
Forking lesson...
Cloning lesson...
/Users/ziv/.rvm/gems/ruby-2.2.3/gems/learn-open-1.1.57/lib/learn_open/opener.rb:231:in read': No such file or directory @ rb_sysopen - /Users/ziv/Development/code/partial-locals-reading-v-000/.learn (Errno::ENOENT) from /Users/ziv/.rvm/gems/ruby-2.2.3/gems/learn-open-1.1.57/lib/learn_open/opener.rb:231:inios_lesson?'
from /Users/ziv/.rvm/gems/ruby-2.2.3/gems/learn-open-1.1.57/lib/learn_open/opener.rb:296:in bundle_install' from /Users/ziv/.rvm/gems/ruby-2.2.3/gems/learn-open-1.1.57/lib/learn_open/opener.rb:33:inrun'
from /Users/ziv/.rvm/gems/ruby-2.2.3/gems/learn-open-1.1.57/lib/learn_open/opener.rb:7:in run' from /Users/ziv/.rvm/gems/ruby-2.2.3/gems/learn-open-1.1.57/bin/learn-open:7:in<top (required)>'
from /Users/ziv/.rvm/gems/ruby-2.2.3/bin/learn-open:23:in load' from /Users/ziv/.rvm/gems/ruby-2.2.3/bin/learn-open:23:in

'
from /Users/ziv/.rvm/gems/ruby-2.2.3/bin/ruby_executable_hooks:15:in eval' from /Users/ziv/.rvm/gems/ruby-2.2.3/bin/ruby_executable_hooks:15:in'
logout

[Process completed]

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.