I'm following through the Hartl Rails tutorial, and have created 2 models so far: a 'User' model, and a 'Listing' model (which is essentially the 'Microposts' model from the tutorial except customized to be more of a Craigslist-esque type of post/advertisement rather than Twitter-type status update).
I've setup the 'listing.rb' model with "belongs_to :user", and also the 'user.rb' model with "has_many :listings, dependent: destroy", even giving 'listings' a user_id index and and created_at index.
However, whenever I try to create a new Listing and submit it, I get the following error:
"1 error prohibiting this listing from being saved: User can't be blank".
No other errors appear, which makes me assume the other columns of 'listing' which are required (name, description, price, and an optional image) are okay, but for some reason the User_id isn't being attached to the created Listing?
What reason would there be for this?
Here are my user.rb, listing.rb, and schema.rb if that helps, along with the controllers:
schema.rb:
ActiveRecord::Schema.define(version: 20150223175136) do
create_table "categories", force: true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "listings", force: true do |t|
t.string "name"
t.text "description"
t.decimal "price"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "image"
t.integer "user_id"
t.integer "category_id"
t.string "username"
end
add_index "listings", ["user_id", "created_at"], name: "index_listings_on_user_id_and_created_at"
add_index "listings", ["user_id"], name: "index_listings_on_user_id"
add_index "listings", ["username"], name: "index_listings_on_username"
create_table "users", force: true do |t|
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "password_digest"
t.string "remember_digest"
t.boolean "admin", default: false
t.string "activation_digest"
t.boolean "activated", default: false
t.datetime "activated_at"
t.string "reset_digest"
t.datetime "reset_sent_at"
t.string "username"
t.string "first_name"
t.string "last_name"
end
add_index "users", ["email"], name: "index_users_on_email", unique: true
end
user.rb:
class User < ActiveRecord::Base
has_many :listings, dependent: :destroy
attr_accessor :remember_token, :activation_token, :reset_token
before_save :downcase_email
before_create :create_activation_digest
validates :first_name, presence: true, length: { maximum: 25 }
validates :last_name, presence: true, length: { maximum: 50 }
validates :username, presence: true, uniqueness: true, length: {maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, length: { minimum: 6 }, allow_blank: true
class << self
# Returns the hash digest of the given string.
def digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
# Returns a random token.
def new_token
SecureRandom.urlsafe_base64
end
end
# Remembers a user in the database for use in persistent sessions.
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
# Returns true if the given token matches the digest.
def authenticated?(attribute, token)
digest = send("#{attribute}_digest")
return false if digest.nil?
BCrypt::Password.new(digest).is_password?(token)
end
# Forgets a user.
def forget
update_attribute(:remember_digest, nil)
end
# Activates an account.
def activate
update_attribute(:activated, true)
update_attribute(:activated_at, Time.zone.now)
end
# Sends activation email.
def send_activation_email
UserMailer.account_activation(self).deliver_now
end
# Sets the password reset attributes.
def create_reset_digest
self.reset_token = User.new_token
update_attribute(:reset_digest, User.digest(reset_token))
update_attribute(:reset_sent_at, Time.zone.now)
end
# Sends password reset email.
def send_password_reset_email
UserMailer.password_reset(self).deliver_now
end
# Returns true if a password reset has expired.
def password_reset_expired?
reset_sent_at < 2.hours.ago
end
private
# Converts email to all lower-case.
def downcase_email
self.email = email.downcase
end
# Creates and assigns the activation token and digest.
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
end
listing.rb:
class Listing < ActiveRecord::Base
belongs_to :user
default_scope -> { order('created_at DESC') }
validates :name, presence: true
validates :description, presence: true
validates :price, presence: true
validates :user_id, presence: true
mount_uploader :image, ImageUploader
end
Here's the 'Listings' controller:
class ListingsController < ApplicationController
before_action :logged_in_user, only: [:create, :destroy]
before_action :correct_user, only: :destroy
def index
@listings = Listing.all
end
def show
end
def new
@listing = Listing.new
end
def edit
end
def create
@listing = Listing.new(listing_params)
if @listing.save
redirect_to @listing
flash[:success] = "Listing was successfully created."
else
render 'new'
end
end
def update
if @listing.update(listing_params)
flash[:success] = "Listing was successfully updated."
redirect_to @listing
else
render 'edit'
end
end
def destroy
@listing.destroy
flash[:success] = "Listing deleted."
redirect_to request.referrer || root_url
end
private
def listing_params
params.require(:listing).permit(:name, :description, :price, :image)
end
def correct_user
@listing = current_user.listings.find_by(id: params[:id])
redirect_to root_url if @listing.nil?
end
end
and 'Users' controller:
class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
before_action :correct_user, only: [:edit, :update]
before_action :admin_user, only: :destroy
def show
@user = User.find(params[:id])
end
def index
@users = User.paginate(page: params[:page])
end
def new
@user = User.new
end
def create
@user = User.new(user_params)
if @user.save
@user.send_activation_email
flash[:info] = "Please check your email to activate your account."
redirect_to root_url
else
render 'new'
end
end
def edit
@user = User.find(params[:id])
end
def update
@user = User.find(params[:id])
if @user.update_attributes(user_params)
flash[:success] = "Profile updated"
redirect_to @user
else
render 'edit'
end
end
def destroy
User.find(params[:id]).destroy
flash[:success] = "User deleted"
redirect_to users_url
end
private
def user_params
params.require(:user).permit(:first_name, :last_name, :username, :email,
:password, :password_confirmation)
end
# Before filters
# Confirms the correct user.
def correct_user
@user = User.find(params[:id])
redirect_to(root_url) unless current_user?(@user)
end
# Confirms an admin user.
def admin_user
redirect_to(root_url) unless current_user.admin?
end
end
I'm not familiar with the tutorial you're following but the user will not be automatically attached to the newly created listing, you need to do that explicitly. In the create
action of ListingsController
, replace the first line with the following:
@listing = current_user.listings.build(listing_params)
In this case, the Listing
gets build (not saved yet) via the listings
association and the current_user is automatically set as the user
. You could do the same with:
@listing = Listing.new(listing_params)
@listing.user = current_user
but prefer the shorter version.