0
votes

I have this issue with test my CommentsController:

Failure/Error: redirect_to user_path(@comment.user), notice: 'Your comment was successfully added!' ActionController::UrlGenerationError: No route matches {:action=>"show", :controller=>"users", :id=>nil} missing required keys: [:id]

This is my method in my controller:

  def create 
    if params[:parent_id].to_i > 0
      parent = Comment.find_by_id(params[:comment].delete(:parent_id))
      @comment = parent.children.build(comment_params)
    else
      @comment = Comment.new(comment_params)
    end
    @comment.author_id = current_user.id
    if @comment.save
      redirect_to user_path(@comment.user), notice: 'Your comment was successfully added!'
    else
      redirect_to user_path(@comment.user), notice: @comment.errors.full_messages.join
    end
  end

This is my RSpec:

  context "User logged in" do 
    before :each do 
      @user = create(:user)
      sign_in @user
    end 

    let(:comment) { create(:comment, user: @user, author_id: @user.id) }
    let(:comment_child) { create(:comment_child, user: @user, author_id: @user.id, parent_id: comment.id) }

    describe "POST #create" do 
      context "with valid attributes" do 
        it "saves the new comment object" do
          expect{ post :create, comment: attributes_for(:comment), id: @user.id}.to change(Comment, :count).by(1)
        end

        it "redirect to :show view " do 
          post :create, comment: attributes_for(:comment), user: @user
          expect(response).to redirect_to user_path(comment.user)
        end
      end

      ...
    end
  end

My Comment model:

class Comment < ActiveRecord::Base
    belongs_to :user
    acts_as_tree order: 'created_at DESC'

    VALID_REGEX = /\A^[\w \.\-@:),.!?"']*$\Z/
    validates :body, presence: true, length: { in: 2..240}, format: { with: VALID_REGEX }
end

How Can I add user_id to that request? When I change code in my controller redirect_to user_path(@comment.user) to redirect_to user_path(current_user) - test pass. May I redirect_to user in comments controller? Is any posibility to do it right? Thanks for your time.

1

1 Answers

2
votes

Basically the error is caused by the fact that the @comment.user is nil.

Lets start fixing it by cleaning up the spec:

context "User logged in" do 

  # declare lets first.
  let(:user) { create(:user) }
  let(:comment) { create(:comment, user: user, author: user) }
  # use do instead of braces when it does not fit on one line.
  let(:comment_child) do 
    # use `user: user` instead of `user_id: user.id`.
    # the latter defeats the whole purpose of the abstraction.
    create(:comment_child, user: user, author: user, parent: comment)
  end

  before { sign_in(user) }

  describe "POST #create" do 
    context "with valid attributes" do 
      it "saves the new comment object" do
        expect do 
          post :create, comment: attributes_for(:comment) 
        end.to change(Comment, :count).by(1)
      end

      it "redirects to the user" do 
        post :create, comment: attributes_for(:comment)
        expect(response).to redirect_to user
      end
    end
  end
end

You should generally avoid using instance vars and instead use lets in most cases. Using a mix just adds to the confusion since its hard to see what is lazy loaded or even instantiated where.

Then we can take care of the implementation:

def create
  @comment = current_user.comments.new(comment_params)

  if @comment.save
    redirect_to @comment.user, notice: 'Your comment was successfully added!'
  else
    # ...
  end
end

private 
  def comment_params
    # note that we don't permit the user_id to be mass assigned
    params.require(:comment).permit(:foo, :bar, :parent_id)
  end

Basically you can cut a lot of the overcomplication:

  • Raise an error if there is no authenticated user. With Devise you would do before_action :authenticate_user!.
  • Get the user from the session - not the params. Your not going to want or need users to comment on the behalf of others.
  • Wrap params in the comments key.
  • Use redirect_to @some_model_instance and let rails do its polymorpic routing magic.
  • Let ActiveRecord throw an error if the user tries to pass a bad parent_id.

Also does your Comment model really need both a user and author relationship? Surely one of them will suffice.