NoMethodError in Posts#index undefined method `username' for nil:NilClass - ruby-on-rails

I am new to rails and am trying to build a blog app where only signed in users can see the username of the person who created a post, however I keep getting this error NoMethodError in Posts#index undefined method `username' for nil:NilClass
screenshot of error in localhost:3000
Here is my routes.rb
```
Rails.application.routes.draw do
devise_for :users
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
# Defines the root path route ("/")
# root "articles#index"
root "posts#index"
resources :posts, only: [:new, :create, :index]
get "/posts/new.html.erb", to: "posts#create", as: "create"
get "/posts/new.html.erb", to: "posts#new", as: "new"
end
```
here is my posts_controller.rb
class PostsController < ApplicationController
before_action :authenticate_user!, except: [:index]
def new
#post = Post.new
end
def create
#post = current_user.posts.build(post_params)
#post.user = current_user
respond_to do |format|
if #post.save
format.html { redirect_to user_post_path(current_user, #post), notice: 'Post was successfully created.' }
format.json { render :show, status: :created, location: #post }
else
format.html { render :new }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
def index
#posts = Post.all.order(created_at: :desc)
end
private
def post_params
params.require(:post).permit(:title, :description)
end
end
```
here is my post.rb model
```
class Post < ApplicationRecord
belongs_to :user
end
```
here is my user.rb model
```
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :posts
validates :username, presence: true
validates :email, presence: true
validates :password, presence: true
end
``
here is my schema
```
ActiveRecord::Schema[7.0].define(version: 2022_11_14_173843) do
create_table "posts", force: :cascade do |t|
t.string "title"
t.text "description"
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.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "username"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
end
```
here is my AddNameToUsers migration
```
class AddNameToUsers < ActiveRecord::Migration[7.0]
def change
add_column :users, :username, :string
end
end
```
Here is my AddUserIdToPosts migration
```
class AddUserIdToPosts < ActiveRecord::Migration[7.0]
def change
add_column :posts, :user_id, :integer
end
end
```
Here is my CreatePosts Migration
```
class CreatePosts < ActiveRecord::Migration[7.0]
def change
create_table :posts do |t|
t.string :title
t.text :description
t.timestamps
end
end
end
```

I finally got it working, for the link_to error, I simply needed to remove the only: function in my routes.rb to only say resources :posts and for the username error I referenced a user in my createPosts migration and then cleared the Posts and Users in the rails console and started over from signing up to making a post to viewing all posts to seeing a single post

Try debugging the post object which is raising the Exception.
It looks like there is a Post object which doesnt have a user associated with it. So post.user returns nil and when username method is called on the nil object an Exception is raised as it is not defined on nil.
You may check in the rails console which post doesnt have a user associated with it and based on your requirements either correct such objects or remove the objects from being displayed in index or just do not show a username for posts if it is not present.

Related

How to setup basic Rails models associations?

hey guys im working on a application where a devise user sign ups and logs in, Once the user logs in they can 'create a team' or 'join a team'. I have my associations set up like this
user.rb
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :confirmable
validates_presence_of :phone, :city, :state, :street, :zip, presence: true, on: :create
belongs_to :team
end
team.rb
class Team < ApplicationRecord
has_many :users
end
and my tables are set up
schema.rb
create_table "teams", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "team_name"
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.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "confirmation_token"
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.string "firstname"
t.integer "team_id"
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
team_controller.rb
class TeamController < ApplicationController
before_action :authenticate_user!
def index
#team = current_user.team
end
def new_team
end
def create_team
#team = current_user.create_team(sanitize_team)
if #team.save
redirect_to team_root_path
else
render json: #team.errors.full_messages
end
end
def join_team
#teams = Team.all
end
def team
end
private
def sanitize_team
params.require(:team).permit(:team_name, :team_statement)
end
end
I want the users 'team_id' attribute to update with the teams id when they create a team. or when they join a team. Are my associations correct? how would i make this happen in the controller ?
Yes, associations are correct. You can do it better only by adding foreign key to your database schema. It can be done by generator rails g migration AddTeamToUsers team:references
More information about associations can be found here: https://guides.rubyonrails.org/association_basics.html
In controller you have to change only the whitelisting params to allow team_id. And you probably need to add to your form in view something like this:
<%= f.select :team_id, Team.all.map { |t| [t.team_name, t.id] } %>
Let's strip your code example down to the minimum required:
# app/models/team.rb
class Team < ApplicationRecord
has_many :users
end
# app/models/user.rb
class User < ApplicationRecord
belongs_to :team
end
# db/migrate/20181124230131_create_teams.rb
class CreateTeams < ActiveRecord::Migration[5.2]
def change
create_table :teams do |t|
t.string :team_name
t.timestamps
end
end
end
# db/migrate/20181124230136_create_users.rb
class CreateUsers < ActiveRecord::Migration[5.2]
def change
create_table :users do |t|
t.belongs_to :team
t.timestamps
end
end
end
Then in your controller:
team = Team.where(team_name: 'foo').first_or_create!
team.users << current_user
Start by setting the association up as optional:
class User < ApplicationController
belongs_to :team, optional: true
end
Otherwise the validations on the user model will not let the user be saved without a team.
Then setup the teams resource:
# config/routes.rb
resources :teams do
post :join
end
post :join creates an additional POST /teams/:team_id/join route.
Then setup the controller:
class TeamsController
# ...
# GET /teams/new
def new
#team = Team.find
end
# POST /teams
def create
#team = Team.new(team_params)
if #team.save
unless current_user.team
current_user.update(team: #team)
end
redirect_to 'somewhere'
else
render :new
end
end
# ...
def join
#team = Team.find(params[:team_id])
if current_user.update(team: #team)
redirect_to #team, notice: 'Team joined'
else
redirect_to #team, error: 'Could not join team'
end
end
#
private
def team_params
params.require(:team).permit(:team_name, :team_statement)
end
end
Note that prefixing your action names is neither needed nor compatible with the "Rails way". Prefixing column names is also largely superfluous.

Curl request on Rails 5 API results in ActionController::ParameterMissing

I'm creating a to-do list API in Rails 5, but I'm running into an error that I cannot seem to solve. Basically, I have users, lists and items. Items belong to lists, and lists belong to users.
Using curl in the command line, I have successfully created a user. However, when I try to follow the same steps to create a list, I get this error as seen from my rails server:
ActionController::ParameterMissing (param is missing or the value is empty: list):
app/controllers/api/lists_controller.rb:15:in 'list_params'
app/controllers/api/lists_controller.rb:5:in 'create'
After researching for hours here and elsewhere, my thoughts are:
1. My curl request isn't formatted correctly for the belongs_to relationship between lists and users
2. My strong parameters aren't formatted correctly for the belongs_to relationship between lists and users
3. I'm following a curriculum for Rails 4, but since I'm using Rails 5, something is different
Since I was able to create a user successfully, I'll include the user code as well as the list code. I've been racking my brain for way, way too long on this, so I sincerely hope someone can help me out.
Curl requests in the command line
To create a new user:
curl -d "user[username]=thisisatest" -d "user[password]=thisisatest" http://localhost:3000/api/users/
This created a user with the user ID of 10, which I'm using in the list example
To create a new list:
curl --digest -u thisisatest:thisisatest -d "list[title]=test list" -d "list[private]=true" http://localhost:3000/api/users/10/lists
I'm using bcrypt, so my database uses password_digest, which is why I've included the --digest flag
Rails/API setup
config/routes.rb
Rails.application.routes.draw do
root 'welcome#index'
namespace :api, defaults: { format: :json } do
resources :users do
resources :lists
end
resources :lists, only: [] do
resources :items, only: [:create]
end
resources :items, only: [:destroy]
end
end
db/schema.rb
ActiveRecord::Schema.define(version: 20171005175122) do
create_table "items", force: :cascade do |t|
t.text "body"
t.integer "list_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["list_id"], name: "index_items_on_list_id"
end
create_table "lists", force: :cascade do |t|
t.string "title"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
t.boolean "private"
t.index ["user_id"], name: "index_lists_on_user_id"
end
create_table "users", force: :cascade do |t|
t.string "username"
t.string "password_digest"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
app/contollers/api_controller.rb
class ApiController < ApplicationController
skip_before_action :verify_authenticity_token
private
def authenticated?
authenticate_with_http_basic {|username, password| User.where( username: username, password: password).present? }
end
end
User
app/controllers/api/users_controller.rb
class Api::UsersController < ApiController
before_action :authenticated?
def index
users = User.all
render json: users, each_serializer: UserSerializer
end
def create
user = User.new(user_params)
if user.save
render json: user
else
render json: {errors: user.errors.full_messages }, status: :unprocessable_entity
end
end
private
def user_params
params.require(:user).permit(:username, :password)
end
end
app/models/user.rb
class User < ApplicationRecord
has_many :lists, dependent: :destroy
validates :username,
presence: true,
length: { minimum: 1, maximum: 100 },
uniqueness: { case_sensitive: false }
validates :password,
presence: true,
length: { minimum: 6 }
has_secure_password
end
app/serializers/user_serializer.rb
class UserSerializer < ActiveModel::Serializer
attributes :id, :created_at, :username
end
List
app/controllers/api/lists_controller.rb
class Api::ListsController < ApiController
before_action :authenticated?
def create
list = List.new(list_params)
if list.save
render json: list
else
render json: {errors: list.errors.full_messages }, status: :unprocessable_entity
end
end
private
def list_params
params.require(:list).permit(:title, :private)
end
end
app/models/list.rb
class List < ApplicationRecord
belongs_to :user
has_many :items, dependent: :destroy
validates :title, length: { minimum: 1 }, presence: true
validates :user, presence: true
end
app/serializers/list_serializer.rb
class ListSerializer < ActiveModel::Serializer
attributes :id, :title, :user_id, :private
end

How to fill multiple form fields using attribute from another model?

I have 3 models - User, Shipment and Friendship. User can be friends with another user via Friendship-model. User also can create Shipments and can add a Friend-User to it. There is address-attribute in User and Shipment models. I need to give User a possibility to fill that address field in 2 ways at the same form:
By filling the address field manually.
By choosing from select-list a Friend of that User - so the Friends
address-attribute transfers and fills the Shipments adress-attribute
(like ctrl-c/ctrl-v) and User can Submit the form.
I can guess, that AJAX is needed to refresh the content without refreshing the page.
Shipment model:
class Shipment < ActiveRecord::Base
belongs_to :user
belongs_to :friendship
validates :image, presence: true
validates :user_id, presence: true
end
Shipments controller:
class ShipmentsController < ApplicationController
helper_method :shipment, :user
before_action :set_shipment, only: [:show]
before_action :authenticate_user!
before_action :require_same_user, only: [:show]
def index
#shipments = Shipment.all
end
def new
#shipment = Shipment.new
end
def create
#shipment = Shipment.new(shipment_params)
#shipment.user = current_user
if #shipment.save
flash[:success] = "Shipment etc."
redirect_to shipment_path(#shipment)
else
render 'new'
end
end
def show
#shipment = Shipment.find(params[:id])
end
private
def user
#user = current_user
end
def shipment
#shipment = user.shipments.new
end
def shipment_params
params.require(:shipment).permit(:name, :kg, :length, :width, :height,
:adress, :image, :user_id, :friend_id)
end
def set_shipment
#shipment = Shipment.find(params[:id])
end
def require_same_user
if current_user != #shipment.user
flash[:alert] = "Restricted/"
redirect_to root_path
end
end
end
User model:
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_many :shipments, dependent: :destroy
has_many :friendships
has_many :friends, through: :friendships
has_many :inverse_friendships, :class_name => 'Friendship',
:foreign_key => 'friend_id'
has_many :inverse_friends, :through => :inverse_friendships, :source => :user
end
Users controller (the User itself is created by Devise)
class UsersController < ApplicationController
before_action :authenticate_user!
def show
#user = User.find(params[:id])
end
def my_friends
#friendships = current_user.friends
end
def search
#users = User.search(params[:search_param])
if #users
#users = current_user.except_current_user(#users)
render partial: 'friends/lookup'
else
render status: :not_found, nothing: true
end
end
private
def require_same_user
if current_user != set_user
flash[:alert] = "Restricted."
redirect_to root_path
end
end
def set_user
#user = User.find(params[:id])
end
end
Friendship model:
class Friendship < ActiveRecord::Base
belongs_to :user
belongs_to :friend, class_name: 'User'
has_many :shipments
end
Friendships controller:
class FriendshipsController < ApplicationController
def index
#friendships = Friendship.all
end
def create
#friendship = current_user.friendships.build(:friend_id => params[:friend_id])
if #friendship.save
flash[:success] = "Added to friends."
redirect_to my_friends_path
else
flash[:alert] = "Impossible to add as a friend."
redirect_to my_friends_path
end
end
def destroy
#friendship = current_user.friendships.find_by(friend_id: params[:id])
#friendship.destroy
flash[:notice] = "Unfriended."
redirect_to my_friends_path
end
private
def name
#name = friend_id.name
end
end
Schema:
create_table "friendships", force: :cascade do |t|
t.integer "user_id"
t.integer "friend_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "shipments", force: :cascade do |t|
t.string "name"
t.integer "length"
t.integer "width"
t.text "adress"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "image_file_name"
t.string "image_content_type"
t.integer "image_file_size"
t.datetime "image_updated_at"
t.integer "height"
t.integer "kg"
end
add_index "shipments", ["user_id"], name: "index_shipments_on_user_id"
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
t.string "name"
t.integer "phone", limit: 30
t.string "username"
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
Shipment form view (new):
<%= form_for(shipment, html: { multipart: true }) do |f| %>
<p>Choose a friend from your friendlist or fill the address field manually:</p>
<%= f.select :friend_id, user.friendships.map{ |friendship|
[friendship.friend.name, friendship.id] } %>
<%= f.text_field :adress, placeholder: "Address and index" %>
<%= f.submit "Submit", class: "button" %>
<% end %>
With ActiveRecord::Base, you could use eager loading and nested form to solve your problem.
Eager load the object related to the main object and use nested form to display the related object.

ROR - nil class on index but not on show

I've been trying to get the answer to this problem but without any luck. I guess it's an association problem and probably a rookie mistake (I'm one).
This is the functionality:
I need to create packs of beers for specific profiles (I know everything sounds fun with beers but it's killing me)
I have 3 models:
A beer model:
class Beer < ActiveRecord::Base
include PermissionsConcern
validates :name, presence: true
has_many :ratings
has_many :users, through: :ratings
has_many :packs
end
A profile model:
class Profile < ActiveRecord::Base
has_many :packs
end
A Pack model:
class Pack < ActiveRecord::Base
belongs_to :beer
belongs_to :profile
end
This is the packs_controller
class PacksController < ApplicationController
before_action :set_pack, only: [:show, :edit, :update, :destroy]
def index
#packs = Pack.all
end
def show
end
def edit
#beers = Beer.all #Implementación incompleta. Revisar Filtros
#profiles = Profile.all
end
def create
#pack = Pack.new(pack_params)
respond_to do |format|
if #pack.save
format.html { redirect_to #pack, notice: 'Pack was successfully created.' }
else
format.html { render :new }
end
end
end
def update
respond_to do |format|
if #pack.update(pack_params)
format.html { redirect_to #pack, notice: 'Pack was successfully updated.' }
else
format.html { render :edit }
end
end
end
...
private
def set_pack
#pack = Pack.find(params[:id])
end
def pack_params
params.require(:pack).permit(:delivery_date, :profile_id, :beer_id, :status)
end
end
With this configuration I have the following situation:
in the Index view I do
#packs.each do |p|
p.beer.name #works fine
p.profile.name #brings an "undefined method `name' for nil:NilClass" message
end
In the show view I do:
#pack.beer.name #works fine.
#pack.profile.name #WORKS FINE ALSO
I tried to do it in the console and the results are the same:
Pack.last.profile.name # works fine
Pack.all # works and shows the profile_id correctly.
packs = Pack.all
packs.each do |p|
print p.beer.name #works fine
print p.profile.name #nil class again
end
Just in case I'm including the Schema:
create_table "beers", force: :cascade do |t|
t.string "name", limit: 255
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "beer_type", limit: 255
end
create_table "packs", force: :cascade do |t|
t.date "delivery_date"
t.integer "profile_id", limit: 4
t.integer "beer_id", limit: 4
t.integer "status", limit: 4
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "packs", ["beer_id"], name: "index_packs_on_beer_id", using: :btree
add_index "packs", ["profile_id"], name: "index_packs_on_profile_id", using: :btree
create_table "profiles", force: :cascade do |t|
t.string "ibu_range", limit: 255
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "name", limit: 255
end
add_foreign_key "packs", "beers"
add_foreign_key "packs", "profiles"
end
I tried to explain the situation as detailed as possible. Can anyone help me understand what I'm doing wrong? Thanks!!!
Some of the packs may not have profile?
Since you are using console, try this:
Pack.all.select{|item| item.profile.nil?}.size
If size>0 and you don't want this, then please add validates :profile, presence: true.

Rails (One Month Rails) NoMethodError in PinsController#create - can't add new pin

When I try to add a new pin, I can't. Instead, I get this error:
NoMethodError in PinsController#create
undefined method `name' for #<pin:0x000001011b9638>
Application Trace | Framework Trace | Full Trace
app/controllers/pins_controller.rb:48:in `block in create'
app/controllers/pins_controller.rb:47:in `create'
Request
Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"n9E2nob/KBzu20PEzYoQWXnibAUR5TH6iPWNd66383k=",
"pin"=>{"description"=>"stea"},
"commit"=>"Create Pin"}
pins_controller.rb
def create
#pin = current_user.pins.new(params[:pin])
respond_to do |format|
if #pin.save
format.html { redirect_to #pin, notice: 'Pin was successfully created.' }
format.json { render json: #pin, status: :created, location: #pin }
else
format.html { render action: "new" }
format.json { render json: #pin.errors, status: :unprocessable_entity }
end
end
end
app/model/user.rb
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:rememberable, :trackable, :validatable
attr_accessible :email, :password, :password_confirmation, :remember_me, :name
has_many :pins, :dependent => :destroy
end
routes.rb
Omrails::Application.routes.draw do
resources :pins
devise_for :users
root :to => 'pages#home'
get 'about' => 'pages#about'
app/models/pin.rb
class Pin < ActiveRecord::Base
attr_accessible :description
validates :description, presence: true
validates :name, presence: true
belongs_to :user
validates :user_id, presence: true
end
db/migrate/create_pins
class CreatePins < ActiveRecord::Migration
def change
create_table :pins do |t|
t.string :description
t.timestamps
end
end
end
db/migrate/add_user_id_to_pins.rb
class AddUserIdToPins < ActiveRecord::Migration
def change
add_column :pins, :user_id, :integer
add_index :pins, :user_id
end
end
db/migrate/add_name_to_users.rb
class AddNameToUsers < ActiveRecord::Migration
def change
add_column :users, :name, :string
end
end
Any ideas about what has gone wrong?
Not sure if it's relevant, but this used to work. I was able to follow along Mattan Griffel's One Month Rails course -- Add Assoc bt Pins and Users video until 29m50s but then I realized that I had to skip back to Customizing Devise bec I forgot to add simple forms.
Now that simple forms have been added, I am trying to go forward - and getting stuck here :(
UPDATE: I ran migrate redo for creating pins and adding user id to pins. Then I removed the validate name line. Now I get the following error when I create pin
ActiveRecord::UnknownAttributeError in PinsController#new
unknown attribute: user_id
app/controllers/pins_controller.rb:29:in `new'
pins_controller.rb
def new
#pin = current_user.pins.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #pin }
end
end
Many thanks for helping
db/schema.rb
ActiveRecord::Schema.define(:version => 20130828163738) do
create_table "pins", :force => true do |t|
t.string "description"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "users", :force => true 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
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
You are validating the presence of :name in the Pin model but it does not have a :name field. Your User has.
Just remove the validates :name, presence: true from you Pin model (line 5).
What happened is that Rails, when trying to save your Pin model, will run all the validations. When it encounters the presence validation on :name, it will check to see if #pin.name isn't blank. But the thing is that your Pin model does not have a name method. So it raises this error.
If you actually want your Pin model to have a name, add it the to pins table:
$ rails g migration add_name_to_pins name:string
$ rake db:migrate

Resources