Rails ActiveRecord belongs_to association not loading - ruby-on-rails

I am trying to render a list of Active Records as follows:
<% #workout_sets.each do |workout_set| %>
<tr>
<td><%= workout_set.reps %></td>
<td><%= workout_set.exercise.name %></td>
<td><%= link_to 'Show', workout_set %></td>
<td><%= link_to 'Edit', edit_workout_set_path(workout_set) %></td>
<td><%= link_to 'Destroy', workout_set, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
My AR setup looks like:
class WorkoutSet < ActiveRecord::Base
belongs_to :workout
belongs_to :exercise, class_name: 'Exercise', foreign_key: 'exercises_id'
end
class Exercise < ActiveRecord::Base
end
class Workout < ActiveRecord::Base
has_many :workout_set
end
and my schema is
create_table "exercises", force: :cascade do |t|
t.string "name", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "exercises", ["name"], name: "index_exercises_on_name", unique: true
create_table "workout_sets", force: :cascade do |t|
t.integer "reps", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "exercises_id"
t.integer "workouts_id"
end
add_index "workout_sets", ["exercises_id"], name: "index_workout_sets_on_exercises_id"
add_index "workout_sets", ["workouts_id"], name: "index_workout_sets_on_workouts_id"
create_table "workouts", force: :cascade do |t|
t.string "location", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
In attempting to render the page I get the following error
undefined method `name' for nil:NilClass
When I change the path in my template to <%= workout_set.exercise %> it renders each row like 444 #<Exercise:0x007fbde9dde998> Show Edit Destroy which is what I expect.
Why is the the attempted access of the name property causing this error?

One of your WorkoutSet does not have an associated Exercise. You can enforce that a WorkoutSet has an exercise Exercise in your WorkoutSet model but there are implications to that. Mainly, you could not create a WorkoutSet without first creating the Exercise. If that's what you want then add the following to the WorkoutSet model.
validates_presence_of :exercise_id
More likely though, you just want to handle the page crashing when there is no associated Exercise.
<td><%= workout_set.exercise.name unless workout_set.exercise.blank? %></td>
That will give you a blank cell but you can do something like this to have a placeholder.
<td><%= workout_set.exercise.blank? ? "No exercise for this set" : workout_set.exercise.name %></td>

You haven't set up the relationship in the Exercise model
class Exercise < ActiveRecord::Base
has_many :workout_sets
has_many :workouts, through: :workouts_sets #not needed but good to setup
end
or if you're trying to do a 1-to-1 relationship between Exercise and WorkoutSet
class Exercise < ActiveRecord::Base
has_one :workout_set
end
Also having an 's' at the end of the foreign keys in your workout_sets table (i.e. 'workouts_id') is somewhat bad form. I'm pretty sure Rails will be smart enough to make it work but if you run into more bugs I'd try changing those to 'workout_id' and 'exercise_id'.

Related

Not able to display the user's name

I am working on a rails application, where I have two models, user and team. team should have a captain_id which is actually a user_id, implemented through a has one association. Now my problem is that when I am creating a team, I am not able to display the captain's name in the show page of the team.
This is my model definition:
class Team < ApplicationRecord
has_one :captain, class_name: 'User' , foreign_key: 'captain_id'
has_many :users, dependent: :destroy
validates :ground_name, presence:true
validates :team_name, presence:true
validates :captain_id, presence:true ,uniqueness: true
end
class User < ApplicationRecord
belongs_to :team,optional: true
end
This is my show view:
<table class="table table-striped">
<tr>
<th>Id</th>
<th>Team Name</th>
<th>Ground Name</th>
<th>Captain Name</th>
<th>Manage</th>
</tr>
<% #team.each do |f| %>
<tr>
<td><%= f.id %></td>
<td><%= f.team_name %></td>
<td><%= f.ground_name %></td>
<td><%= f.captain_id&.first_name %></td>
</tr>
<%end%>
</table>
This generates the error:
undefined method `first_name' for 2:Integer
This is my database schema:
ActiveRecord::Schema.define(version: 2019_02_19_090032) do
create_table "teams", force: :cascade do |t|
t.string "team_id"
t.string "team_name"
t.string "ground_name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "captain_id"
end
create_table "users", force: :cascade do |t|
t.string "first_name"
t.string "last_name"
t.string "email"
t.string "photo"
t.datetime "created_at", null: false
t.datetime "updated_at", 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 "team_id"
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
t.index ["team_id"], name: "index_users_on_team_id"
end
end
The associated user should be available under the attribute captain. So your markup should look like this:
<td><%= f.captain&.first_name %></td>
Instead of :has_to, you should define a :belongs_to relation, so that the foreign key is defined in the team table:
belongs_to :captain, class_name: 'User', foreign_key: :captain_id
If you do not have the captain_id column defined in your teams table, you have to create a migration and run it.
To create the migration:
rails g migration add_captain_id_to_teams
Now you have a new migration file in db/migrate, something like db/migrate/<timestamp>_add_captain_id_to_teams.rb
Open this file and edit it to look like this:
class AddCaptainIdToTeams < ActiveRecord::Migration[5.0]
def change
add_column :teams, :captain_id, :integer
end
end
Now run this command to create the column:
bundle exec rake db:migrate
Now you can assign the captain id and save it in the database.
Following can solve issue,
<td><%= f.captain&.first_name %></td>
but following can also you can use,
<td><%= f.captain.first_name %></td>
No need to add optional: true, remove it & remove following from team.rb
validates :captain_id, presence:true ,uniqueness: true
because we removed optional: true
In user.rb, correction is needed,
belongs_to :team, optional: true, foreign_key: :captain_id

Get data from different table using a foreign key in a rails view file

I'm creating a new .html.erb file where I'm attempting to list all of the teams and their opponents. I have 2 tables: a users (team) table and a records table. The records table has 2 columns (user_id, opponent_user_id) referencing the same users table.
My view file:
<% #record.each do |record| %>
<tr>
<td><%= record.user.name %></td>
<td><%= record.opponent_user_id %></td>
</tr>
<% end %>
Rather than using record.user_id, I was able to replace the id with the name of the user by using record.user.name. I am unable to do the same with the opponent team however. I tried using
<td><%= record.opponent_user_id.user.name %></td>
but that didn't work. How can I replace the opponent_user_id with the actual name?
Additional info below
Controller
def prof
#user = User.find(params[:id])
#record = Record.where(user_id: #user.id)
end
Table schema
create_table "users", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
t.string "name"
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["email"], name: "index_users_on_email", unique: true
end
create_table "records", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
t.bigint "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "opponent_user_id"
t.index ["user_id"], name: "index_records_on_user_id"
end
Thanks for the help. I'm still new to rails. Any additional feedback for what I shouldn't be doing this way would likewise be appreciated.
class Record < ActiveRecord::Base
belongs_to :user, class_name: "User", foreign_key: "user_id"
belongs_to :opponent_user, class_name: "User", foreign_key: "opponent_user_id"
end
In User model:-
class User < ActiveRecord::Base
has_many :records, class_name: "Record", foreign_key: "user_id"
has_many :opponent_user_records, class_name: "Record", foreign_key: "opponent_user_id"
end
Here by:-
def prof
#user = User.find(params[:id])
#records = #user.records
end
And
<% #records.each do |record| %>
<tr>
<td><%= record.user.name %></td>
<td><%= record.opponent_user.name %></td>
</tr>
<% end %>

Rails: NoMethodError in Applications#index

Please note that "applications" was a poor naming convention. This is because it is a scholarship application portal.
I get the following error when I try to open the index for applications.
NoMethodError in Applications#index
Showing /home/zane/scholarship-application/app/views/applications/_current_user_application.html.erb where line #22 raised:
undefined method `name' for nil:NilClass
Extracted source (around line #22):
20
21
22
23
24
25
<tbody>
<tr>
<td><%= current_user.application.name %></td>
<td><%= current_user.application.name %></td>
<td><%= current_user.application.gender %></td>
<td><%= current_user.application.date_of_birth %></td>
Here's what the code for that page looks like:
<% if user_signed_in? && current_user.role == "User" %>
<h1 class="center">Your Application</h1>
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Gender</th>
<th>Date of birth</th>
<th>GPA</th>
<th>Address</th>
<th>State</th>
<th>University</th>
<th>Essay</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<tr>
<td><%= current_user.application.name %></td>
<td><%= current_user.application.gender %></td>
<td><%= current_user.application.date_of_birth %></td>
<td><%= current_user.application.gpa %></td>
<td><%= current_user.application.address %></td>
<td><%= current_user.application.state %></td>
<td><%= current_user.application.university %></td>
<td><%= current_user.application.essay %></td>
<td><%= link_to 'Show', current_user.application %></td>
<td><%= link_to 'Edit', current_user.edit_application_path(application) %></td>
<td><%= link_to 'Destroy', current_user.application, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
</tbody>
</table>
<% end %>
User Model:
class User < ActiveRecord::Base
#Defining different roles
enum role: [:Guest, :User, :Admin]
#Users can only have one scholarship application
has_one :applications
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
Ability Model:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.role == "Admin"
can :manage, :all
elsif user.role == "User"
can :manage, Application
can :manage, User
else
can :read, Static_Page
end
end
end
Application Model:
class Application < ActiveRecord::Base
belongs_to :user
#Users may only submit one application
validate :limit_applications, :on => :create
#User must fully fill out all forms application
validates :name, presence: true
validates :gender, presence: true
validates :date_of_birth, presence: true
validates :gpa, presence: true
validates :university, presence: true
validates :address, presence: true
validates :state, presence: true
private
def limit_applications
limit = 1
if self.user.applications.(:reload).count >= limit
errors.add(:base, "You can only create #{limit} application.")
end
end
end
Schema.rb
create_table "applications", force: :cascade do |t|
t.string "name"
t.string "gender"
t.date "date_of_birth"
t.string "gpa"
t.text "essay"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "university"
t.string "address"
t.string "state"
t.integer "user_id"
end
create_table "entries", force: :cascade do |t|
t.string "name"
t.boolean "winner"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "nasa_apis", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "users", force: :cascade do |t|
t.string "first_name"
t.string "last_name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
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.inet "current_sign_in_ip"
t.inet "last_sign_in_ip"
t.integer "role", default: 1
t.boolean "winner", default: false
t.integer "application_id"
end
I'm going to assume you made a model called application.rb. When you type current_user.application, Rails searches for a method called application. This will only work if you specified a database association in your model to connect it to a table called applications. I don't know what the association is, but if you're calling .application (singular), I'd imagine you need to add one of these to your User model: has_one :application or belongs_to :application. Keep in mind you'll also need to add the corresponding database association to the Application Model. There's also a chance that Rails doesn't like the word Application and that could just be screwing up everything.
Also, the attributes you named sound an awful lot like attributes that should belong to a user, not an application. Is there any chance you don't need the .application at all and these are attributes of your user model? That would mean writing code like current_user.email, not current_user.application.email.
Now that you've included more code the answer is pretty clear: change has_one :applications to has_one :application

Display Unique Results from Two Models

I have two models: games and pickems.
Here is my schema for these models:
create_table "games", force: :cascade do |t|
t.integer "week_id", limit: 4
t.integer "home_team_id", limit: 4
t.integer "away_team_id", limit: 4
t.integer "home_score", limit: 4
t.integer "away_score", limit: 4
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "season_id", limit: 4
end
create_table "pickems", force: :cascade do |t|
t.integer "user_id", limit: 4
t.integer "game_id", limit: 4
t.integer "winner_id", limit: 4
t.integer "score", limit: 4
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Game model:
class Game < ActiveRecord::Base
belongs_to :home_team, class_name: 'Team'
belongs_to :away_team, class_name: 'Team'
belongs_to :week
belongs_to :season
end
Pickem model:
class Pickem < ActiveRecord::Base
has_one :user
has_one :game
has_one :winner, class_name: 'Team'
end
In my view, I want to display all Games that do not have a Pickem associated that is referencing it. I also want to display below all the Pickems and the game attributes associated. What do I need to be calling from the controller and/or add to the model(s) to display this information?
You will need to correct the Games/Pickem association, by adding this to the Game model:
has_one :pickem
and this to the Pickem model:
belongs_to :game
This query will retrieve all of the Games that do not have a Pickem associated:
#games_without_pickems = Game.joins(:pickem).group("games.id").having("COUNT('pickems.id') = 0").order("games.id")
change table references
This will retrieve all of the Pickems and associated Games information:
#pickems = Pickem.includes(:games).all
In your view, simply loop over both #games_without_pickems and #pickems, like this:
<table>
<th>
<td>Week</td>
<td>Home Team</td>
<td>Away Team</td>
<td>Home Score</td>
<td>Away Score</td>
<td>Season</td>
</th>
<% #games_without_pickems.each do |game| %>
<tr>
<td><%= game.week.name %></td>
<td><%= game.home_team.name %></td>
<td><%= game.away_team.name %></td>
<td><%= game.home_score %></td>
<td><%= game.away_score %></td>
<td><%= game.season.name %></td>
</tr>
<% end %>
</table>
<table>
<th>
<td>User</td>
<td>Winner</td>
<td>Score</td>
<td>Week</td>
<td>Home Team</td>
<td>Away Team</td>
<td>Home Score</td>
<td>Away Score</td>
<td>Season</td>
</th>
<% #pickems.each do |pickem| %>
<tr>
<td><%= pickem.user.name %></td>
<td><%= pickem.winner.name %></td>
<td><%= pickem.score %></td>
<td><%= pickem.game.week.name %></td>
<td><%= pickem.game.home_team.name %></td>
<td><%= pickem.game.away_team.name %></td>
<td><%= pickem.game.home_score %></td>
<td><%= pickem.game.away_score %></td>
<td><%= pickem.game.season.name %></td>
</tr>
<% end %>
</table>
That should do it.
First of all, you have
# in `pickems` table
t.integer "game_id", limit: 4
and
# in Pickem Model
class Pickem < ActiveRecord::Base
...
has_one :game
...
end
I think for has_one belongs_to, you need to put foreign_key in games table rather than pickems table and it will start making sense.
i.e.
create_table "games", force: :cascade do |t|
...
t.integer "pickem_id", limit: 4
...
end
class Game < ActiveRecord::Base
...
belongs_to :pickem
...
end
Now
I want to display all Games that do not have a Pickem associated that
is referencing it
Game.where(pickem_id: nil)
One Suggestion
Since your Game model belongs to multiple models like Pickem, Team, etc. so you can use Polymorphic Association instead.
Reason: If one of your game record belongs to pickem then other fields will be empty.

Adding Tags in Rails

Adding Tags and I Get this ERROR:
SQLite3::SQLException: no such column: taggings.available_work_id:
SELECT "tags".* FROM "tags" INNER JOIN "taggings" ON "tags"."id" =
"taggings"."tag_id" WHERE "taggings"."available_work_id" = ?
Available_work.index.html
<tbody>
<% #available_works.each do |available_work| %>
<tr>
<td><%= available_work.title %></td>
<td><%= available_work.description.html_safe %></td>
<td>Tags: <%= raw available_work.tags.map(&:name).map { |t| link_to t, tag_path(t) }.join(', ') %></td>
<td><%= image_tag available_work.image_url(:thumb) %></td>
<td><%= link_to 'Show', available_work %></td>
<td><%= link_to 'Edit', edit_available_work_path(available_work) %></td>
<td><%= link_to 'Destroy', available_work, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
Available_work.rb
class AvailableWork < ActiveRecord::Base
attr_accessor :image, :remote_image_url, :tag_list
mount_uploader :image, ImageUploader
has_many :taggings
has_many :tags, through: :taggings
def self.tagged_with(name)
Tag.find_by_name!(name).available_work
end
def self.tag_counts
Tag.select("tags.*, count(taggings.tag_id) as count").
joins(:taggings).group("taggings.tag_id")
end
def tag_list
tags.map(&:name).join(", ")
end
def tag_list=(names)
self.tags = names.split(",").map do |n|
Tag.where(name: n.strip).first_or_create!
end
end
end
Tag.rb
class Tag < ActiveRecord::Base
has_many :taggings
has_many :available_work, through: :taggings
end
Taggings.rb
class Tagging < ActiveRecord::Base
belongs_to :tag
belongs_to :availble_work
end
Schema
create_table "available_works", force: :cascade do |t|
t.string "title"
t.string "description"
t.string "tags"
t.string "image"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "taggings"
end
You would have to add an available_work_id column the to taggings table. It might be better however to create a polymorphic relation to the tagged object though so that you can use tags for more than the AvailableWork model.
EDITED. There where several mistakes in my example. Here is a complete running version:
class Tag < ActiveRecord::Base
has_many :taggings
def self.tag_counts
select("tags.*, count(taggings.tag_id) as count")
.joins(:taggings)
.group("taggings.tag_id")
end
end
class Tagging < ActiveRecord::Base
belongs_to :tag
belongs_to :tagged,
polymorphic: :true,
inverse_of: :taggings
end
class AvailableWork < ActiveRecord::Base
has_many :taggings, as: :tagged
has_many :tags, through: :taggings
def self.tagged_with(name)
# We have to do this in two queries since Rails does not
# do joins on polymorphic relations.
ids = Tagging.where(tagged_type: self.name)
.joins(:tag)
.where(tags: { name: name }).pluck(:tagged_id)
find(ids)
end
def self.tag_counts
Tag.tag_counts.where(taggings: { tagged_type: self.name })
end
def tag_list
tags.map(&:name).join(", ")
end
def tag_list=(names)
self.tags = names.split(",").map do |n|
Tag.where(name: n.strip).first_or_create!
end
end
end
ActiveRecord::Schema.define(version: 20150621234032) do
create_table "available_works", force: :cascade do |t|
t.string "title"
t.string "description"
t.string "image"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "taggings", force: :cascade do |t|
t.integer "tag_id"
t.integer "tagged_id"
t.string "tagged_type"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "taggings", ["tag_id"], name: "index_taggings_on_tag_id"
add_index "taggings", ["tagged_id"], name: "index_taggings_on_tagged_id"
create_table "tags", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
<table>
<tbody>
<% #available_works.each do |available_work| %>
<tr>
<td><%= available_work.title %></td>
<td><%= available_work.description.try(:html_safe) %></td>
<td>Tags: <%= available_work.tag_list %></td>
<td><%#= image_tag available_work.image_url(:thumb) %></td>
<td><%= link_to 'Show', available_work %></td>
<td><%= link_to 'Edit', edit_available_work_path(available_work) %></td>
<td><%= link_to 'Destroy', available_work, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
Note that I commented out the images part to save time since it is not directly relevant to the question.
The Rails app I created to answer the question is available at https://github.com/maxcal/playground/tree/adding-tags-in-rails.

Resources