I am trying to get my followers to display on the pages/friends however, I keep getting multiple errors like an undefined method `each' for nil:NilClass in my Pages#friends error. I am following Michael Harlts tutorials on follow/unfollow however, I needed to tweak a couple of things because I did not follow the entire thing.
When a user goes to their Friends link (pages/friends) I want it to display everyone who is following them. Like how my users/index displays everyone. Please see my code below. Any help of suggestions would be great.
Pages/friends
<h1>Friends</h1>
<% #user ||= current_user %>
<% #users.each do |user| %>
<center><%= link_to image_tag(user.avatar.url(:thumb)), user %></center>
<strong><center><br><%= link_to user.name, user %></br></center></strong>
<% if current_user.admin %>
<% end %>
<% end %>
Pages/controller
def home
end
def about
end
def friends
end
end
**Users/index*
<% provide(:title, 'All users') %>
<h1>All users</h1>
<div class="col-md-offset-4 col-med-8">
<div class="panel panel-default">
<div class="panel-heading center">
<% #users.each do |user| %>
<center><%= link_to image_tag(user.avatar.url(:thumb)), user %></center>
<strong><center><br><%= link_to user.name, user %></br></center></strong>
<% if current_user.admin %>
<% #user ||= current_user %>
<div class="stats">
<a href="<%= friends_path(#user) %>">
<strong id="following" class="stat">
<%= #user.followed_users.count %>
</strong>
following
</a>
<a href="<%= friends_path(#user) %>">
<strong id="followers" class="stat">
<%= #user.followers.count %>
</strong>
followers
</a>
</div>
<center><%= link_to "Delete", user, method: :delete, data: { confirm: "Are you sure?" } %></center>
<% end %>
<% end %>
<div class="center">
<%= will_paginate #users, renderer: BootstrapPagination::Rails %>
</div>
</div>
Routes
devise_for :admins
devise_for :users
resources :posts
resources :users do
member do
get :following, :followers
end
end
resources :relationships, only: [:create, :destroy]
resources :user_friendships do
member do
put :accept
end
end
get "users/show"
root "pages#home"
get 'feed', to: 'posts#index', as: :feed
get "about" => "pages#about"
get "friends" => 'pages#friends'
match 'users/:id' => 'users#show', via: :get
match 'users/:id' => 'users#index', via: :destroy
match 'users/:id' => 'users#destroy', via: :get
match 'users/:id' => 'users#destroy', via: :delete
get '/:id', to: 'users#show'
Users/controller
class UsersController < ApplicationController
before_action :correct_user, only: [:edit, :update, :destroy, :following, :followers]
before_action :authenticate_user!, except: [:index, :show]
before_action :admin_user, only: :destroy
def index
#users = User.paginate(page: params[:page], :per_page => 5)
end
def show
#user = User.find(params[:id])
if #user
#posts = #user.posts.all
render actions: :show
else
render file: 'public/404', status: 404, formats: [:html]
end
def destroy
User.find(params[:id]).destroy
flash[:success] = "Your account has been deleted."
redirect_to root_path
end
def correct_user
#user = User.find(params[:id])
redirect_to root_path
end
def admin_user
redirect_to root_path unless current_user.admin?
end
def following
#title = "Following"
#user = User.find(params[:id])
#users = #user.followed_users.paginate(page: params[:page])
render 'show_follow'
end
def followers
#title = "Followers"
#user = User.find(params[:id])
#users = #user.followers.paginate(page: params[:page])
render 'show_follow'
end
end
end
Relationships Controller
class RelationshipsController < ApplicationController
before_action :authenticate_user!, except: [:index, :show]
def create
#user = User.find(params[:relationship][:followed_id])
current_user.follow!(#user)
respond_to do |format|
format.html { redirect_to #user }
format.js
end
end
def destroy
#user = Relationship.find(params[:id]).followed
current_user.unfollow!(#user)
respond_to do |format|
format.html { redirect_to #user }
format.js
end
end
end
The error undefined method 'each' for nil:NilClass means that you're calling .each on an object that is nil.
For example, here, #users could be nil:
def index
#users = User.paginate(page: params[:page], :per_page => 5)
end
If you then call <% #users.each ... %>, you'll get the undefined method error. You've posted quite a lot of code above, so it's difficult to know exactly where that error is being thrown. Try and work out exactly where the error is being thrown (posting a stack trace would help), and work out which object is nil. Then work out why it's nil - is there no data? Or is your ActiveRecord query returning no results?
Hopefully that gives you some pointers as to where to start looking next.
Edit: As mmichael points out, the error is being caused due to the fact that #users was undeclared in pages#friends. Thanks!
As you're new, I'll give you another opinion. What I'm going to write is similar to Sam's answer; I intend to help you appreciate how Rails works, to further your experience
Objects
You must remember that Rails is built on top of Ruby - an object-orientated language. This means every time you declare / use a variable inside Ruby, you're actually accessing an object, which Ruby treats as having a series of methods:
Ruby is pure object-oriented language and everything appears to Ruby
as an object. Every value in Ruby is an object, even the most
primitive things: strings, numbers and even true and false. Even a
class itself is an object that is an instance of the Class class. This
chapter will take you through all the major functionalities related to
Object Oriented Ruby.
I write this because Ruby's object-orientated structure is much different than handling variables in the "traditional" sense -- in the simplest description, it means that Ruby presumes that objects are ALWAYS present (it just builds them from the NilClass), allowing you call "empty" data sets without running into "undeclared" issues
The problems occur when you try and run methods like .each on empty / nil objects. If you do this, errors like the one you alluded to occur; preventing you from being able to perform the functionality which you intended
--
Fix
Your error basically means you haven't declared your variable.
After seeing your code, the most obvious problem will be here:
#app/controllers/pages_controller.rb
Class PagesController < ApplicationController
def friends
# normally, you'd define your #instance_variables here
end
end
I see that your friends action does not declare your #users variable, which is required to perform the #users.each method. As recommended by Sam, the first step is to ensure you're able to declare this value, allowing you to loop through it as required:
#app/controllers/pages_controller.rb
Class PagesController < ApplicationController
def friends
#users = User.all
end
end
Related
I'm trying to display all albums in the albums#index page but I'm getting an error in my Albums controller "cannot find album without ID". I understand the issue is that there are no params, but I've used the find method with params[:id] a bunch of times in my app and haven't had this issue thus far.
For reference, Albums have many Reviews and have many Users through Reviews.
Users have many Reviews and have many Albums through Reviews.
I haven't built out my Reviews controller yet so that's unrelated.
Here is the error:
ActiveRecord::RecordNotFound in AlbumsController#index
Couldn't find Album without an ID
Extracted source (around line #40):
38
39
40
41
42
43
def set_album
#album = Album.find(params[:id])
end
def album_params
Rails.root: /Users/melc/review_project
Application Trace | Framework Trace | Full Trace
app/controllers/albums_controller.rb:40:in `set_album'
Request
Parameters:
None
Here is my Albums controller:
class AlbumsController < ApplicationController
before_action :set_album, only: [:index, :show, :edit, :update]
def index
#albums = Album.all
#current_user
end
def show
end
def new
#album = Album.new
end
def create
#album = Album.new(album_params)
if #album.save
redirect_to album_path(#album)
else
render :new
end
end
def edit
end
def update
if #album.update(album_params)
redirect_to album_path(#album), notice: "Your album has been updated."
else
render 'edit'
end
end
private
def set_album
#album = Album.find(params[:id])
end
def album_params
params.require(:album).permit(:artist, :title, :avatar)
end
end
Here is my albums#index view:
<h2>All Albums</h2>
<br>
<br>
<% if #album.avatar.attached? %>
<image src="<%=(url_for(#album.avatar))%>%" style="width:350px;height:350px;">
<% end %>
<br>
<%= #album.artist %> -
<%= #album.title %>
<br>
<%= link_to "Edit Album", edit_album_path %><br><br>
<%= link_to "Upload a New Album", new_album_path %>
Here is the routes.rb file:
Rails.application.routes.draw do
get '/signup' => 'users#new', as: 'signup'
post '/signup' => 'users#create'
get '/signin' => 'sessions#new'
post '/signin' => 'sessions#create'
get '/signout' => 'sessions#destroy'
resources :albums do
resources :reviews
end
resources :users
root to: "albums#index"
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end
you need to change a couple things here:
on AlbumsController, you need to remove index from actions that "preload" an album
before_action :set_album, only: [:show, :edit, :update]
You need to pass the album object to the route in the view:
<%= link_to "Edit Album", edit_album_path(#album) %><br><br>
hope that helps
EDIT: about the avatar issue, looks like you're displaying the albums in the index, but you aren't iterating through them, something like:
<h2>All Albums</h2>
<% #albums.each do |album| %>
<br>
<br>
<% if album.avatar&.attached? %>
<image src="<%=(url_for(album.avatar))%>%" style="width:350px; height:350px;">
<% end %>
<br>
<%= album.artist %> - <%= album.title %>
<%= link_to "Edit Album", edit_album_path(album) %><br><br>
<br>
<% end %>
<%= link_to "Upload a New Album", new_album_path %>`
I've been searching for hours but I can't find the answer anywhere. I'm new to ruby on rails and I can't figure out how to fix this. What caused the problem is that I moved an instance variable from one file to another and now no links are working and the error always displays: undefined method `each' for nil:NilClass
here is my code:
Application.html.erb:
<% number = 1 %>
<% #projects.each do |project| %>
<%= link_to project, id: "a-#{number}" do %>
<div class="flex">
<div class="each--project" id="project-<%= number %>">
<h3><%= project.title %></h3>
<p class="date"><%= project.created_at.strftime("%A, %b %d")%></p>
<p class="content"><%= project.description %></p>
</div>
</div>
<% number = number + 1 %>
<% end %>
<% end %>
application_controller.rb
class ApplicationController < ActionController::Base
def index
#projects = Project.all
end
protect_from_forgery with: :exception
end
projects_controller
class ProjectsController < ApplicationController
before_action :find_project, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!, except: [:index, :show]
def index
#projects = Project.all.order("created_at desc")
end
def new
#project = Project.new
end
def create
#project = Project.new project_params
if #project.save
redirect_to #project, notice: "Yay Mia! That project was saved!"
else
render 'new'
end
end
def show
end
def edit
end
def update
if #project.update project_params
redirect_to #project, notice: "Yay Mia! That project was updated!"
else
render 'edit'
end
end
def destroy
#project.destroy
redirect_to projects_path
end
private
def find_project
#project = Project.friendly.find(params[:id])
end
def project_params
params.require(:project).permit(:title, :description, :link, :slug)
end
end
routes rb
Rails.application.routes.draw do
devise_for :users
resources :posts
resources :projects
resources :contacts, only: [:new, :create]
get 'welcome/index'
root 'welcome#index'
get '*path' => redirect('/')
end
You don't have an index action on the ApplicationController
You can however achieve the same thing with a before_action if you want it loaded for all actions in all controllers. This is not something I would recommend though.
class ApplicationController < ActionController::Base
before_action :load_projects
def load_projects
#projects = Project.all
end
end
Hint:
<% number = 1 %>
<% #projects.each do |project| %>
can be much better written as
<% #projects.each_with_index do |project, number| %>
If you're referencing something in your main application layout then it must be loaded for every controller. Putting it in one specific controller only engages it when that controller is in use, which it will only be on very specific routes.
If #projects needs to be populated on every request, move that to a load_projects method and add a before_action filter in your main ApplicationController. Putting it in a default index method doesn't work, that will never get run.
You can always stub in Rails.logger.debug('...') type calls to see if your code is actually being exercised. Watch log/development.log constantly to see what's happening. Most problems can be quickly resolved by examining what's been logged.
I've got a User and Post models, which are related to each other in a classical way -- User has_many :posts and Post belongs_to :user. In my users#show, where I display a user's profile, I also have a list of all posts he has made. Also, I wanted to have links to edit and delete each post respectfully. So, I made up with this:
<% #user.posts.each do |post| %>
<h1><%= link_to post.title, post_path(post) %></h1>
<% if #user == current_user %>
<%= link_to 'Edit', edit_post_path(post) %>
<%= link_to 'Delete', post_path(post), method: :delete %>
<% end %>
<% end %>
But surely placing this logic into view results in a mess, so I decided to use Draper and write decorators for that. As we are going to check rights for posts#edit and posts#delete methods, I came up with a decorator for Post model and tried to use it in PostsController. Here it goes:
class PostDecorator << Draper::Decorator
delegate_all
def link_to_edit
if object.user == current_user
h.link_to 'Edit', h.edit_post_path(object)
end
end
def link_to_delete
if object.user == current.user
h.link_to 'Delete', h.post_path(object), method: :delete
end
end
end
Then, in my PostsController:
# ... class definition
before_action :set_post, only: [:show, :edit, :update, :destroy]
# ... other controller methods
def edit; end
def update
if #post.update(post_params)
#post.save
redirect_to post_path(#post)
else
render 'edit'
end
end
def destroy
#post.destroy
redirect_to feed_path
end
private
# Using FriendlyId gem to have neat slugs
def set_post
#post = Post.friendly.find(params[:id]).decorate
end
But every time I try to render my User profile with list of his posts, with the use of my new helpers <%= post.link_to_delete %> and <%= post.link_to_edit %> instead of that conditional mess, it just returns me the following error:
What am I doing wrong?
You probably figured this out in the meantime but here's an answer for others: You were calling #post = ....decorate in your controller but you are using #user.posts.each { |post| ... } in your view. The objects fed to that block are not decorated. Only #post is.
In your view you should have done something like #user.posts.each { |raw_post| post = raw_post.decorate } and so on. Obviously, with ERB syntax. Or #user.decorated_posts.each ... where
class User < ActiveRecord::Base
...
def decorated_posts
# this will load every post associated with the user.
# if there are a lot of them you might want to only load a limited scope of them
posts.map(&:decorate)
end
...
end
I'm currently trying to implement likes and unlikes rails app using thumbs_up. I followed the instructions on this page: Clarification on how to use "thumbs_up" voting gem with Rails 3
Users can like and unlike books. I have 2 buttons, like and unlike and I would like to hide one or the other from the user, depending on the users current like status. So I figured an if else would be appropriate like this:
<% if #user.voted_on?(#book) %>
<div class="unlike_button"><%= link_to("Unlike", unvote_book_path(book), method: :post) %></div>
<% else %>
<div class="like_button"><%= link_to("Like", vote_up_book_path(book), method: :post) %></div>
<% end %>
and in my route.rb file:
resources :books do
member do
post :vote_up
post :unvote
end
end
But when I run this I get the error message:
undefined method `voted_on?' for nil:NilClass
Is there anything I might be doing wrong?
Update
As Mischa suggested, I changed it to current_user.voted_on. Now I get this error message:
Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id
Below is a snippet of my Books controller
include UsersHelper
include SessionsHelper
before_filter :signed_in_user, only: [:index]
before_filter :admin_user, only: :destroy
def index
array = Book.search(params[:search])
#books = Kaminari.paginate_array(array).page(params[:page]).per(5)
end
def show
#book = Book.find(params[:id])
#respond_to do |format|
#format.js
#end
end
def destroy
Book.find(params[:id]).destroy
flash[:success] = "Book deleted."
redirect_to books_url
end
def vote_up
begin
current_user.vote_for(#book = Book.find(params[:id]))
flash[:success] = "Liked!."
redirect_to books_url
end
end
def unvote
begin
current_user.unvote_for(#book = Book.find(params[:id]))
flash[:success] = "Unliked!."
redirect_to books_url
end
end
You should change your method of link from post to put and
change your routes.rb to
resources :books
You can also update a data using a link by using this code:
<%= link_to "Like", book, :method => :put %>
or
<%= link_to "Unlike", book, :method => :put %>
and this goes with your controller in
assuming that you have a is_liked boolean field in your Post model
def update
#post = Post.find(params[:id])
if #post.is_liked?
#post.attributes = {
:is_liked => "f"
}
redirect_to posts_path, :flash => "Unliked"
else
#post.attributes = {
:is_liked => "t"
}
redirect_to posts_path, :flash => "Liked"
end
end
I'm building an application that has a Keynote model and a Story model (as well as a User model that I implemented with Devise).
Keynotes have many Stories and a Story belongs to a Keynote.
I'm having problems creating new stories and I get the following error:
ActiveRecord::RecordNotFound in StoriesController#create
Couldn't find Keynote without an ID
The error happens on line 17 of stories_controller.rb which is
#keynote = Keynote.find(params[:keynote_id])
in the create method.
This is part of my stories_controller.rb
class StoriesController < ApplicationController
before_filter :authenticate_user!, :except => [:show, :index]
def index
#keynote = Keynote.find(params[:keynote_id])
#stories = #keynote.stories
end
def new
#keynote = Keynote.find(params[:keynote_id])
#story = #keynote.stories.build
end
def create
if user_signed_in?
#keynote = Keynote.find(params[:keynote_id])
#story = #keynote.current_user.stories.build(params[:story])
if #story.save
flash[:notice] = 'Question submission succeeded'
redirect_to keynote_stories_path
else
render :action => 'new'
end
end
end
This is my keynotes_controller.rb
class KeynotesController < ApplicationController
def index
#keynotes = Keynote.find :all, :order => 'id ASC'
end
def new
#keynote = Keynote.new
end
def show
#keynote = Keynote.find(params[:id])
end
def create
#keynote = Keynote.new(params[:keynote])
if #keynote.save
flash[:notice] = 'Keynote submission succeeded'
redirect_to keynotes_path
else
render :action => 'new'
end
end
end
Any help would be really appreciated.
Edit:
These are the Parameters when I try to create a new Story.
{"utf8"=>"✓",
"authenticity_token"=>"76odSpcfpTlnePxr+WBt36fVdiLD2z+Gnkxt/Eu1/TU=",
"story"=>{"name"=>"as"},
"commit"=>"Send"}
It looks like the ID for the Keynote is not being passed.
This is the view for StoriesController#new
<%= error_messages_for 'story' %>
<%= form_for #story do |f| %>
<p>
Question:<br />
<%= f.text_field :name %>
</p>
<p>
<%= submit_tag "Ask" %>
</p>
<% end %>
This is what I have in my routes.rb file:
get "keynotes/index"
get "users/show"
devise_for :users
get "votes/create"
get "stories/index"
resources :keynotes do
resources :stories
end
resources :stories do
get 'bin', :on => :collection
resources :votes
end
resources :users
root :to => 'keynotes#index'
I think this should do the trick:
<%= form_for #story do |f| %>
<%= hidden_field_tag 'keynote_id', #keynote.id %>
.
rest of the form stuff here
.
<% end %>
PS. Not sure if you will get the keynote_id in params[:keynote_id] or params[:story][:keynote_id] .. check out both.
NB: I think there would be a easier way to do it too, using fields_for or something similar, but I don't have access to a Rails setup at the moment to test that out.