Search code examples
ruby-on-railsrubyrails-for-zombies

Rails 4, Foreign Key column is not populated when creating new recrod


New to Rails, please forgive me for a simple question but I'm stumped.

Rails 4.1.1 and Ruby 2.1.2

I am creating a small Rails app (Zombie Twitter) based on the videos I have been following from Code School.

I have four controllers: Application, Welcome, Zombie and Tweets.

I have three models: User, Zombie, Tweet.

User (from Devise), has_one :zombie.

Zombie, belongs_to :user and has_many :tweets.

Tweet, belongs_to :zombie.

A new zombie record is created when a user is registered. This works.

However, the zombie_id field in the tweets table is not populated when creating a new tweet. This I cannot figure out and is my problem.

Also: Can someone explain the build and build_model methods and when to use them? I get a method missing error when I call something along the lines of @zombie.tweets.build. Is it necessary to call these methods in Rails 4.1.1 or is this a holdover from an earlier version of Rails?

User.rb

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  has_one :zombie
  accepts_nested_attributes_for :zombie, allow_destroy: true
end

Zombie.rb

class Zombie < ActiveRecord::Base
  belongs_to :user
  has_many :tweets

  validates_uniqueness_of :zombie_name
end

Tweet.rb

class Tweet < ActiveRecord::Base
  belongs_to :zombie

  attr_accessor :zombie_id

  accepts_nested_attributes_for :zombie
end

ApplicationController.rb

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  before_action :set_zombie
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.for(:sign_up) << [zombie_attributes: :zombie_name]
  end

  def set_zombie
    if user_signed_in?
      @zombie = Zombie.includes(:tweets).find_by user_id: current_user.id
    end
  end
end

TweetController.rb

class TweetsController < ApplicationController
  before_action :set_tweet, only: [:show, :edit, :update, :destroy]

  # GET /tweets/new
  def new
    @tweet = current_user.zombie.tweets.new
  end

  # POST /tweets
  # POST /tweets.json
  def create
    @tweet = current_user.zombie.tweets.new(tweet_params)

    logger.debug params

    respond_to do |format|
      if @tweet.save
        format.html { redirect_to :root, notice: 'Tweet was successfully created.' }
        format.json { render :show, status: :created, location: @tweet }
      else
        format.html { render :new }
        format.json { render json: @tweet.errors, status: :unprocessable_entity }
      end
    end
  end

  def set_tweet
    @tweet = Tweet.find(params[:id])
  end

  def tweet_params
    params.require(:tweet).permit(:status, :is_public)
  end
end

/tweets/_form.html.rb

<%= form_for([@zombie, @tweet]) do |f| %>
  <% if @tweet.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@tweet.errors.count, "error") %> prohibited this tweet from being saved:</h2>

      <ul>
      <% @tweet.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label 'Public' %>
    <%= f.check_box :is_public, checked: true %>
  </div>
  <div class="field">
    <%= f.label :status %><br>
    <%= f.text_area :status %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

create_zombies_migration

class CreateZombies < ActiveRecord::Migration
  def change
    create_table :zombies do |t|
      t.string :zombie_name

      t.timestamps
    end

    add_reference :zombies, :user, index: true
    add_index :zombies, [:user_id, :id]
    add_index :zombies, :zombie_name, unique: true
  end
end

create_tweets_migration

class CreateTweets < ActiveRecord::Migration
  def change
    create_table :tweets do |t|
      t.text :status, :limit => 140
      t.boolean :is_public, default: true
      t.timestamps
    end

    add_reference :tweets, :zombie, index: true
    add_index :tweets, [:zombie_id, :id], unique: true
  end
end

Last, here is the record from development.log that details the POST request to create a tweet.

enter image description here


Solution

  • You need to remove attr_accessor :zombie_id from tweet.rb.

    And add t.references :zombie in create_tweets_migration

    zombie_id should be column in database, not virtual attribute.

    PS. accepts_nested_attributes_for :zombie also has no sense in tweet.rb.