0

I'm trying to create a simple Like/unlike button with Rails 4. I tried to do this with socialization gem, but after one day of struggling I gave up and decided to modify M. Hartl's 'foollow' mechanism from Rails Tutorial. Here is what i got so far:

User.rb

class User < ActiveRecord::Base
devise :database_authenticatable, :registerable, :validatable

has_many :questions
has_many :answers
has_many :likes, foreign_key: "liker_id", dependent: :destroy
has_many :liked_answers, through: :likes, source: :liked, source_type: "Answer"

def like?(answer)
 likes.find_by(liked_id: answer.id)
end

def like!(answer)
 likes.create!(liked_id: answer.id)
end

def unlike!(answer)
 likes.find_by(liked_id: answer.id).destroy
end
end

Answer.rb:

class Answer < ActiveRecord::Base
belongs_to :question
belongs_to :user

has_many :likes, foreign_key: "liked_id",
                 class_name:  "Like",
                 dependent:   :destroy
has_many :likers, through: :likes, source: :liker
end

Like.rb:

class Like < ActiveRecord::Base
belongs_to :liker, class_name: "User"
belongs_to :liked, class_name: "Answer"
validates :liker_id, presence: true
validates :liked_id, presence: true
end

likes_controller.rb:

class LikesController < ApplicationController
skip_before_action :verify_authenticity_token
before_action :authenticate_user!

respond_to :html, :js

def create
  @answer = Answer.find(params[:like][:liked_id])
  current_user.like!(@answer)
  respond_with @answer.question
end

def destroy
  @answer = Like.find(params[:id]).liked
  current_user.unlike!(@answer)
  respond_with @answer.question
end
end

_like.html.erb:

<%= form_for(current_user.likes.build(liked_id: @answer.id), remote: true) do |f| %>
<div><%= f.hidden_field :liked_id %></div>
<%= f.submit "Like!", class: "btn btn-large btn-primary" %>
<% end %>

_unlike.html.erb:

<%= form_for(current_user.likes.find_by(liked_id: @answer.id),
         html: { method: :delete }, remote: true) do |f| %>
<%= f.submit "Unlike.", class: "btn btn-large" %>
<% end %>

routes.rb:

Rails.application.routes.draw do
devise_for :users
scope "/admin" do
  resources :users
end
resources :questions do
resources :answers do
    get :likes
  end
end
resources :likes, only: [:create, :destroy]
root to: 'questions#index'
end

I also have jQuery and jQuery_ujs required in application.js, and relevant js.erb files ("create" and "destroy") in views.

The 'like/unlike' mechanism itself seems to work pretty well - in the console, with my 'like!' and 'unlike!' methods, I'm able to create and destroy "Like" objets with id, liker_id and liked_id.

The problem begins with the button itself.

I can see the "Like!" button next to each answer, but when I click it - I get this error:

ActiveRecord::RecordNotFound in LikesController#create
Couldn't find Answer with 'id'=

The error points on this line in LikesController:

@answer = Answer.find(params[:like][:liked_id])

so I suppose my @answer.id results to be 'nil', but I have no idea where did I make mistake. My first guess would be routes file - I'm still not sure if everything is correct there.

I've spent whole day looking for solution, I also found some similar questions on SO, but none of the answers could help me.

Any ideas would be greatly appreciated.

EDIT: Params from the error

Parameters:

{"utf8"=>"✓",
"like"=>{"liked_id"=>""},
"commit"=>"Like!"}
Cœur
  • 37,241
  • 25
  • 195
  • 267

1 Answers1

0

You're using the hidden field tag wrong. http://api.rubyonrails.org/v4.1.1/classes/ActionView/Helpers/FormHelper.html#method-i-hidden_field shows you need to supply two values into your tag. Change your like ERB file to this:

_like.html.erb:

<%= form_for(current_user.likes.build(liked_id: @answer.id), remote: true) do |f| %>
  <div><%= f.hidden_field :liked_id, :value => @answer.id %></div>
  <%= f.submit "Like!", class: "btn btn-large btn-primary" %>
<% end %>

and that should get you what you want.

MCBama
  • 1,432
  • 10
  • 18
  • Ok, I'm one step further - big thanks for that. But now I'm getting: [undefined method `merge' for nil:NilClass] in
    <%= f.hidden_field :liked_id, @answer.id %>
    . (Params: "id"=>"1") when i try to see the page with button.
    – qwerty1234 Jul 29 '14 at 16:09
  • http://stackoverflow.com/questions/9279313/rails3-form-for-hidden-field-undefined-method-merge provides the reason. Apparently it's looking for a symbol so you have to supply one not just a value. – MCBama Jul 29 '14 at 18:12
  • Yes, I've already tried with ':value', but that gets me back to the first error ("Couldn't find Answer with 'id'=" etc.). I also noticed that when I click "Like", rails tries to redirect me to localhost:3000/likes. Is this what should hapen? – qwerty1234 Jul 29 '14 at 18:27
  • I'm afraid I don't know. I'm not a fan of building objects in a view (especially in a form) but without diving into it I don't see anything wrong with it but it could be the problem. I would suggest pulling your `current_user.likes.build...` out of your view and into your controller if at all possible. – MCBama Jul 29 '14 at 18:56
  • Day 2 Update for all who may read this. I noticed that answers are nested into questions, so I suppose that's why `Answer.find(params[:like][:liked_id]` gives 'nil'. In my LikesController#create, I've put `@question = Question.find(params[:id])` and then `@answer = @question.answers.find(params[:like][:liked_id]`. Sadly, that hardly makes any difference. Now i get similar error, but for question: `Couldn't find Question without an ID`. I'm still trying to modify that @question params or routes, but for now - without success. – qwerty1234 Jul 30 '14 at 21:22
  • In the params list you show there is no params[:id] being passed in so there's no where `Question.find(params[:id])` would ever work unless you're calling it from somewhere else. You issue is going to be in your view where you're building the params list from your form. – MCBama Jul 31 '14 at 14:04