Lotix
Lotix

Reputation: 309

has_one association and disappearing user id

I'm having an error which most likely is caused by has_one associations in my model.

Issue

After declaring those associations i started having weird interaction where i'd suddenly get "undefined methodemail' for nil:NilClass"` with the following snippet from the error console:

<div class="row">

<div class="column-md-6 column-md-offset-3">

<% @posts.each do |post| %>

  email: <%= post.user.email %> <br> # <= source of the issue!

  Level: <%= post.level %> <br>

  Region: <%= post.region %> <br>

  Info: <%= post.description %> <br>

  <% if post.user == current_user %>

  <%= link_to "Edit", edit_post_path(post) %>

Source of the issue

I had hard time tracking down the bug and trying to reproduce it. I took a quick look at my database in rails console and found out that user_id becomes nil even though the creation of the post from user is successful. I finally managed to reproduce the error by following these steps:

1) Login

2) Create the post

3) Go to "All posts". Post index action renders here all the Posts from every user correctly.

4) While still logged in as the same user, create another post.

5) Go back to "All posts" and I get the aforementioned error.

Post model

class Post < ActiveRecord::Base
  validates_presence_of :level, :region, :description
  validates :level, length: { maximum: 50 }

  belongs_to :user
end

User model

class User < ActiveRecord::Base  
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  has_one :post
end

Posts controller

class PostsController < ApplicationController
  before_action :set_post, only: [:edit, :update]
  before_action :authenticate_user!, except: [:index, :show]

  def index
    @posts = Post.all
  end

  def show
    @post = Post.find(params[:id])
  end

  def new
    @post = current_user.build_post
  end

  def create
    @post = current_user.build_post(post_params)
    if @post.save
      flash[:success] = "Post successfully created!"
      redirect_to posts_path
    else
      render 'new'
    end
  end

  def edit
  end

  def update
   #@post = Post.find(post_params)
    if @post.update(post_params)
      flash[:success] = "Profile updated"
      redirect_to posts_path
    else
      render 'edit'
    end
  end

  def destroy
    Post.find(params[:id]).destroy
    flash[:success] = "Post deleted"
    redirect_to posts_path
  end

private

 def post_params
   params.require(:post).permit(:description, :level, :region)
 end

 def set_post
   @post = Post.find(params[:id])
 end

end

schema.rb

ActiveRecord::Schema.define(version: 20151209193950) do

  create_table "posts", force: :cascade do |t|
    t.text     "description"
    t.string   "level"
    t.string   "region"
    t.datetime "created_at",  null: false
    t.datetime "updated_at",  null: false
    t.integer  "user_id"
  end

  create_table "users", force: :cascade do |t|
    t.string   "email",                  default: "", null: false
    t.string   "encrypted_password",     default: "", null: false
    t.string   "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.integer  "sign_in_count",          default: 0,  null: false
    t.datetime "current_sign_in_at"
    t.datetime "last_sign_in_at"
    t.string   "current_sign_in_ip"
    t.string   "last_sign_in_ip"
    t.datetime "created_at",                          null: false
    t.datetime "updated_at",                          null: false
  end

  add_index "users", ["email"], name: "index_users_on_email", unique: true
  add_index "users", ["reset_password_token"], name:      "index_users_on_reset_password_token", unique: true

end

Question

My goal was to have a one post limit for each user. I'm almost certain the has_one association is part of the issue. My question is whether it was a correct way of achieving my goal or should i use has_many association and enforce the limit in some other way?

If has_one is the correct way, how do i fix this issue?

Upvotes: 2

Views: 391

Answers (3)

Emerson
Emerson

Reputation: 86

In your new and create actions, you call current_user.build_post, which has a somewhat surprising side-effect in Rails.

Immediately after assigning to or building a has_one association, a query is fired which sets the foreign key on the associated model to nil. There is no way to stop this query from being executed on has_one associations. If you change the relationship on User to a has_many, it will work as expected.

http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_one#737-build-association-deletes-existing-dependent-record

Upvotes: 1

Padhu
Padhu

Reputation: 1580

Since you mentioned that a user can only have one post you have to check if a user don't have any post before creating. Try like below

def create
    @post = current_user.build_post(post_params) unless current_user.post
    if @post.save
      flash[:success] = "Post successfully created!"
      redirect_to posts_path
    else
      flash[:success] = "You exceeded your limit!"
      redirect_to root_path
    end
  end

and also you should add @benjamin suggestion as well for edit action.

Upvotes: 1

benjamin
benjamin

Reputation: 2185

You have to find the correct user again in your edit action of the controller and make it available to the view, otherwise the instance is nil, leading to your error.

Edit_0:
In general, right before the code you have posted, add:

<%= debug params %>

to see which variables you are missing/available in your view.

Upvotes: 2

Related Questions