NoMethodError in Posts#index

Hello, I’m making an instagram like website using Ruby on Rails and I’m very new to Ruby on Rails. I have my code in index.html.erb and it’s like this

<div class="posts-wrapper row">
  <div class="post">
    <div class="post-head">
      <div class="name">
        Ben Walker
      </div>
    </div>
    <div class="image center-block">
      <%= image_tag @post.image.url(:medium) %>
    </div>
    <p class="caption">
      <%= @post.caption %>    </p>
    <div class="text-center">
      <%= link_to "Cancel", posts_path %>
    </div>
  </div>
</div>

But I get an error: “NoMethodError in Posts#index” and it’s directing to the lines <%= image_tag @post.image.url(:medium) %> and <%= @post.caption %> Please tell me if that is enough code. Thank you

EDITS:

Here is my posts_controller.rb

class PostsController < ApplicationController

  def index
    @posts = Post.all
  end
  
  def new
    @post = Post.new
  end
  
  def create
    @post = Post.create(post_params)
    redirect_to posts_path
  end
  
  def show
    @post = Post.find(params[:id])
  end
  
  private
  
  def post_params
    params.require(:post).permit(:image, :caption)
  end

end

Need to see the page controller, not just the view

1 Like

Ok I’ve added my posts_controller.rb code to my question

1 Like

The index page uses @posts - ie it’s for rendering a list of all posts (so you want to loop through them in the view). But you’re trying to use @post, which is used on singular pages/routes instead. This is why you’re getting a no method error - it can’t render @post.caption. You would normally do something like (been a while since I worked on RoR code so apologies if this is slightly off):

<% @posts.each do |post| %>
  <div class="posts-wrapper row">
    <div class="post">
      # snip
        <%= image_tag post.image.url(:medium) %>
      </div>
      <p class="caption">
         <%= post.caption %>
         # snip
<% end %>
1 Like

Ok, but when I add <% @posts.each do |post| %> and <% end %> I’m not able to click on the image. (I’m supposed to be able to click on the image so it directs me to a page with just the image and description.

BTW this is the tutorial I am following: https://www.devwalks.com/lets-build-instagram-in-rails-part-1/

Aha, it’s a little bit further down where the author links everything up - the relevant bit of code is this:

=link_to (image_tag post.image.url(:medium), class:'img-responsive'), post_path(post)

Basically, what it’s doing is using the post_path helper that get automagically created by Rails, and that is going going to refer to the route for the show action in the controller.

At the minute, all you’ve got is the URL to the image, whereas what it needs to be to get that linking up is the image wrapped in a link tag, and the href for the link will match the route you want (if they makes sense). I think you just need to go a bit further in the tutorial and you should be good :+1:

1 Like

Aaah thank you so much!

Oof the <%= @post.caption %> part gives me the error “NameError in Posts#index”

Careful with your @s - if you look in the controller, commonly you should have a view that corresponds to each of those actions. The @post, @posts etc - they are what you are going to be able to reference directly in the view that corresponds to that action. On the index page, you have access to (or you could look at it as exposing) the instance variable @posts. That’s why you do the @posts.every |post| - in that loop, each individual post you’ll reference with the variable post, and therefore post.caption. But that’s just a variable in a loop - it could be @posts.every |foo|, then it would be foo.caption or whatever.

  • The instance variable/s available to you in that particular view you’ll find in the controller. They are used mainly to refer to either a thing or a collection of things you pull out of the database and that you want to do something with. So in the index action, the @posts refers to all the posts in the database. In the show action, @post will refer to a single post, located by ID.
  • In a view, if the instance variable refers to a collection of things, normally you’ll want to loop over them, so whatever variable you use to represent the current thing in the loop, that’s what you use.

I’m not sure if I’m doing it right, I still get the error. Here is my index.html.erb:

<div class="posts-wrapper row">
  <div class="post">
    <div class="post-head">
      <div class="name">
        Ben Walker
      </div>
    </div>
    <div class="image center-block">
      <%= link_to (image_tag post.image.url(:medium), class:'img-responsive'), post_path(post) %>
    </div>
    <p class="caption">
      <%= @post.caption %>
    </p>
    <div class="text-center">
      <%= link_to "Cancel", posts_path %>
    </div>
  </div>
</div>

post.caption.

It’s just a loop, so in pseudocode:

for post in @posts:
  print post.img.url
  print post.caption

Anything prefixed with @ in Rails is special, and it is made available in the view that relates to the specific method in the controller that you define it in (variables prefixed with @ are called instance variables and have special purpose in Ruby). So in the index method in the controller, you have @posts. You do not have anything called @post, and you cannot use it in index.erb unless you actually specify it (but you do not want to do this).

1 Like

I get “undefined method image” for nil:NilClass" for <%= image_tag @post.image.url(:medium) %> I’m not so sure what I am doing wrong because I am using erb and the tutorial was using haml so I used a converter and put the code in.

<% @posts.each do |post| %>
<div class="posts-wrapper row">
  <div class="post">
    <div class="post-head">
      <div class="name">
        Ben Walker
      </div>
    </div>
    <div class="image center-block">
      <%= image_tag @post.image.url(:medium) %>    
    </div>
    <p class="caption">
      <%= @posts.caption %>
    </p>
    <div class="text-center">
      <%= link_to "Cancel", posts_path %>
    </div>
  </div>
</div>
<% end %>

Ok, the web/haml conversion isn’t the issue here.

So the way the framework works is

  1. It gets a request against a URL and the router decides what to do with it, using what is defined in routes.rb
  2. The router decides which controller it should use to handle the request,
  3. and then which method in the controller.
  4. Then that method in the controller will render a view.

When you want to go to the posts index page, you

  1. hit the router, which
  2. recognises it needs to use the Posts controller,
  3. then the index method in that controller,
  4. which will render index.erb.html.

Look at the controller. Whatever is in that index method: that is what is available in that view.

class PostsController < ApplicationController
  def index
    @posts = Post.all
  end

  ...

There is no instance variable called @post that you have access to.

@ has special meaning.

What you do have access to is a variable called @posts which you have to loop through:

@posts.every |post|
  # do stuff with`post`

This is a loop. So if you’ve used JavaScript, it could be

for (let post of posts) {
  // Do stuff with each post here

Or

posts.forEach(post => {
  // do stuff with post here

Or

for (var i = 0; i < posts.length; i++) {
  // do stuff with posts[i] here

post in your view is the variable in the loop that refers to an individual post. You could call it container_ship instead, it doesn’t matter:

@posts.each |container_ship|
  container_ship.image.url
  container_ship.caption

That will still loop through every post and print out the image url and the caption.

What you cannot do is use the instance variable @post where it is not available to you.

So like this?

<% @posts.each do |post| %>
<div class="posts-wrapper row">
  <div class="post">
    <div class="post-head">
      <div class="name">
        Ben Walker
      </div>
    </div>
    <div class="image center-block">
      <%= image_tag post.image.url(:medium) %>    
    </div>
    <p class="caption">
      <%= post.caption %>
    </p>
    <div class="text-center">
      <%= link_to "Cancel", posts_path %>
    </div>
  </div>
</div>
<% end %>

The only problem is that I think when I add the loop, I am no longer able to click on the image. After I click on the image it’s supposed to direct me to a page with the post by itself. When I remove the <% @posts.each do |post| %> and <% end %> it gives me an error

undefined local variable or method `post' for #<#<Class:0x00000004f9bc90>:0x00000005606008>
Did you mean?  post_url
               posts_url
               post_path
               @posts

Yup, it works like that. The reason it doesn’t link is that you haven’t added a link to individual posts. There is a link there, but it links to posts_path, which goes to the index of all posts, which is the current view.

The reason it doesn’t work if you remove the loop is that the variable post only exists inside the loop.

Can I ask how much programming experience you have, not with Rails, just in general? Because re that last point, you can’t remove the loop from a loop in any programming language, the code inside will just not work

I’ve done HTML and CSS for a couple of years and a bit of JS and Ruby. It’s hard for me to learn by myself and easier if I do tutorials. I did a bit of coding before and stopped for a while and now I’m trying to catch up.

I’ll try and explain it a bit better once I’ve had a think about it, but what I would do is go back up the tutorial just a little bit, clear the code you have and work back again. Converting it to erb is probably a really good thing here, because you’re forcing yourself to write out the code for the views, but you’re not quite converting it as is, and there are a few errors creeping in - that’s why I say go back up the tutorial a bit. One key thing is that the controller actions (the methods defined in the controller) map directly to views most of the time, so whatever is defined with instance variables in the corresponding action is what you have available in that particular view.

1 Like

I’ve got all my code in this github link:

I know what I did wrong, I think I was supposed to create a new file and put that code in there. Thank you for your help.