简体   繁体   中英

NoMethodError - undefined method for nil:NilClass

I've followed along with this guide: http://www.tobyh.com/thoughts/4 to make a 2-way friendship system for a social app. After working on this for over a week straight, following that guide is by far the closest I've gotten to making this work, but the end product has a lot of serious problems. Only the first third of it works - sending friend requests. Neither accepting nor declining friend requests works.

To begin explaining this, here are the relevant parts of:

routes.rb

resources :friendships,   only: [:create, :update, :destroy]

friendship.rb

class Friendship < ActiveRecord::Base
  belongs_to :user
  belongs_to :friend, :class_name => 'User'
end

user.rb

class User < ActiveRecord::Base
  has_many :friendships,   dependent: :destroy
  has_many :received_friendships, class_name: "Friendship", foreign_key: "friend_id", dependent: :destroy

  has_many :active_friends, -> { where(friendships: { accepted: true}) }, through: :friendships, source: :friend
  has_many :received_friends, -> { where(friendships: { accepted: true}) }, through: :received_friendships, source: :user
  has_many :pending_friends, -> { where(friendships: { accepted: false}) }, through: :friendships, source: :friend
  has_many :requested_friendships, -> { where(friendships: { accepted: false}) }, through: :received_friendships, source: :user

  attr_accessor :remember_token, :activation_token, :reset_token (I've spent more than 20 hours on slogging through google and
stackoverflow alone, and I have a fuzzy memory of someone saying that attr_accessor might be relevant to this problem, but
I only saw it mentioned in a single question out of more than 20, so I'm not sure if it really is relevant.)

friendships_controller.rb

class FriendshipsController < ApplicationController

  def create
    @friendship = current_user.friendships.build(friend_id: params[:friend_id])
    if @friendship.save
      flash[:notice] = "Friend request sent!"
      redirect_to :back
    else
      flash[:error] = "Unable to send friend request."
      redirect_to :back
    end
  end

  def update
    @friendship = Friendship.find_by(id: params[:id])
    @friendship.update(:accepted, true)
    if @friendship.save
      redirect_to root_url, notice: "Friend request accepted."
    else
      redirect_to root_url, notice: "Unable to accept friend request."
    end
  end

  def destroy
    @friendship = Friendship.find_by(id: params[:id])
    @friendship.destroy
    flash[:notice] = "Friend request declined."
    redirect_to :back
  end

end

_user.html.erb, which are index pages that display every user, with pagination. Here's the only relevant part of that:

<% if logged_in? && !current_user?(user) %>
  <%= link_to "Add Friend", friendships_path(:friend_id => user), :method => :post %>
<% end %>

and show.html.erb, which displays user's profile pages. On this page and the user index page, you can see and use an "Add Friend" button as long as you're logged in as someone other than that user. Also, due to indexing, if you try to send someone 2 friend requests, the 2nd friend request will fail.

        <% if logged_in? && !current_user?(@user) %>
          <%= link_to "Add Friend", friendships_path(:friend_id => @user), :method => :post %>
        <% end %>
...
    <ul class="nav nav-tabs">
      ...
      <% if logged_in? && current_user?(@user) %>
        <li><a data-toggle="tab" href="#friendreqs">Friend Requests</a></li>
      <% end %>
    </ul>
...
        <div id="friendreqs" class="tab-pane fade">
          <h3>Friend Requests</h3>
          <ul>
            <% if current_user?(@user) %>
              <% current_user.requested_friendships.each do |request| %>
              <li>
                <%= request.name %>
                <%= link_to "Accept",  friendship_path(id: request.id), method: "put" %>
                <%= link_to "Decline", friendship_path(id: request.id), method: :delete %>
              </li>
              <% end %>
            <% end %>
          </ul>
        </div>

Right now, sending friend requests seems to work regardless of whether you use the Add Friend link on a user's profile page or the Add Friend link next to their name in the users index. Here's a picture of the console after logging into user #3, adding user #2 as a friend from their profile page, and adding user #1 as a friend through the index page: http://i.imgur.com/MbsBKot.png And here's a picture of an SQL database browser also seeming to confirm that the initial friend requests are successfully being sent: http://i.imgur.com/cX45vm8.png "accepted" is set to false by default after sending a friend request since I'd like for it to be a "facebook style" friend request system, as opposed to a one-way "twitter style" one.

Due to the large amount of time spent working on this problem, I've experienced a wide variety of problems and error messages, most of which no longer matter. Starting from the point of receiving a friend request, here are the problems that currently exist after you click "Decline" on your profile page.

At first, everything seems fine. Your profile page refreshes and the "Friend request declined." message in friendships_controller.rb displays as intended. However, when you click on your Friend Requests tab, you'll see that the friend request you declined is still there. Here's a picture of that, without the flash message above and to the left: http://i.imgur.com/Dg1uTXf.png If you attempt to decline the friend request a 2nd time (or more), you receive the error message in the title of this problem. Here's a picture of that too: http://i.imgur.com/XBhNtkF.png

That seems to suggest that it's failing because the friend request has already successfully been declined, right? As far as I can tell: nope! I believe that's not true because of the contents of this picture: http://i.imgur.com/UOIYnHP.png After downloading a new copy of the database, it claims that there's an even larger number of friendships than there were before! 3 friend requests instead of 2, one of which has no friend_id attached to it. That would also explain why the friend request persists under the Friend Request tab.

On top of that, if you log out, log into the account of the friend request sender, and attempt to send a 2nd friend request to the receiver, you'll receive this error message: http://i.imgur.com/q0WsVCB.png SQLite3::ConstraintException in FriendshipsController#create UNIQUE constraint failed: friendships.user_id, friendships.friend_id I'm pretty sure that's a result of adding indices to friendships, which prevents users from receiving 2 or more friend requests from the same person. That implies once again that clicking Decline doesn't truly delete the friend request, only the "friend_id" portion of it, which has the unintended effect of creating a 3rd friendship entry in the database.

Whew! That was quite a bit of information, but that only covers the "Decline" link. Now for the "Accept" link!

If you log in, see a new friend request, and try to accept it, you'll receive the same error message that you would when attempting to decline a friend request 2 times in a row, but with this one referencing the update method instead of the destroy method: http://i.imgur.com/IHptXDu.png NoMethodError in FriendshipsController#update undefined method `update' for nil:NilClass I have no idea why this is.

As I was writing about and taking pictures of this problem, I noticed this repeated line: "friendship_path(id: request.id)" under show.html.erb, and thought about it compared to this database picture: http://i.imgur.com/UOIYnHP.png What if, when clicking Decline to delete the friendship request, it's only deleting the ID of the user who requested the friend request (which, in that picture, is the user_id, not the friend_id)? Could the problem be the format of the Decline and Accept links under show.html.erb, and everything else is fine..?

I think I've written just about all I can write! I'm sorry for how long this question was, and thank you for taking the time to read it; I truly appreciate it from the bottom of my heart!

I meant to add this over a week ago, but here's what ended up working for me.

friendship.rb:

class Friendship < ActiveRecord::Base
  belongs_to :user
  belongs_to :friend, :class_name => 'User'
  validates :user_id, presence: true
  validates :friend_id, presence: true
end

user.rb:

class User < ActiveRecord::Base
  has_many :friendships,   dependent: :destroy
  has_many :received_friendships, class_name: "Friendship", foreign_key: "friend_id", dependent: :destroy

  has_many :passive_friends, -> { where(friendships: { accepted: true}) }, through: :received_friendships, source: :user
  has_many :active_friends, -> { where(friendships: { accepted: true}) }, :through => :friendships, :source => :friend
  has_many :requested_friendships, -> { where(friendships: { accepted: false}) }, through: :received_friendships, source: :user
  has_many :pending_friends, -> { where(friendships: { accepted: false}) }, :through => :friendships, :source => :friend

friendships_controller.rb:

class FriendshipsController < ApplicationController

  def create
    @friendship = current_user.friendships.build(friend_id: params[:friend_id])
    if @friendship.save
      flash[:notice] = "Friend request sent!"
      redirect_to :back
    else
      errors.add(:friendships, "Unable to send friend request.")
      redirect_to :back
    end
  end

  def update
    @friendship = Friendship.where(friend_id: [current_user, params[:id]], user_id: [current_user, params[:id]]).first
    @friendship.update(accepted: true)
    if @friendship.save
      redirect_to :back, notice: "Friend request accepted."
    else
      redirect_to :back, notice: "Unable to accept friend request."
    end
  end

  def destroy
    @friendship = Friendship.where(friend_id: [current_user, params[:id]]).where(user_id: [current_user, params[:id]]).last
    @friendship.destroy
    flash[:notice] = "Friend request declined."
    redirect_to :back
  end

end

show.html.erb:

  <% current_user.requested_friendships.each do |request| %>
    <% if request.id == @user.id %>
      <%= link_to "Accept Friend Request",  friendship_path(id: request.id), method: "put" %><br>
      <%= link_to "Decline Friend Request", friendship_path(id: request.id), method: :delete %>
    <% end %>
  <% end %>

  <% if logged_in? && !current_user?(@user) && Friendship.where(friend_id: [current_user, params[:id]], user_id: [current_user, params[:id]]).first == nil %>
    <%= link_to "Add Friend", friendships_path(:friend_id => @user), :method => :post %>
  <% end %>

Everything else is the same.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM