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
Related
I have a joined table but looking for a way to input the information from a form into both tables, or have it work in general:
My schema:
create_table "categories", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "categories_listings", id: false, force: :cascade do |t|
t.integer "category_id", null: false
t.integer "listing_id", null: false
end
create_table "listings", force: :cascade 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"
end
Models:
class Category < ApplicationRecord
has_and_belongs_to_many :listings
end
Listing < ApplicationRecord
belongs_to :category, required: false
belongs_to :categories_listings, required: false
end
Views
<%= form_with(model: listing, local: true) do |form| %>
...
<div class="space">
<%= form.select :category_ids, options_from_collection_for_select(Category.all, :id, :name), :prompt => "Select a Category", :multiple => true %>
</div>
...
Before i joined the tables, I had it working with a categories element (i believe thats the right term) within the listing tables that was attached to a categories table... You can see my previous post on SO where I was suggested to do this: Allowing multiple records in category to submit to listing
When i click submit, nothing enters into the categories_listings tables. Suggestions on how I make this happen?
The associations in your Listing model are wrong. It should be just
Listing < ApplicationRecord
has_and_belongs_to_many :categories
end
I suggest you to read has_and_belongs_to_many
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 %>
I'm trying to build an app for online single page quizzes, so i'm using nested forms. My problem comes when i try to access data from nested classes, I'm confused on how to handle this as a best practice.
The app has a Quiz class, which has Questions, and each question has multiple Alternatives.
quizzes_controller.rb :
def take
#quiz = Quiz.find(params[:id])
#user_quiz = #quiz
#SAVE ANSWERS TO CURRENT USER
end
take.html.erb :
<%= simple_form_for #user_quiz do |quiz| %>
<%= quiz.simple_fields_for :questions do |question| %>
**<!-- this should show question.statement -->**
<div class="custom-controls-stacked">
<%= question.input :answer, collection: **#this should be question.alternatives**
,as: :radio_buttons, class:"custom-control-input" %>
</div>
<% end %>
<%= quiz.button :submit %>
<% end %>
quiz.rb :
class Quiz < ApplicationRecord
belongs_to :session
has_many :questions
accepts_nested_attributes_for :questions
end
question.rb
class Question < ApplicationRecord
belongs_to :quiz
has_many :alternatives
end
alternative.rb
class Alternative < ApplicationRecord
belongs_to :question
end
schema.rb
ActiveRecord::Schema.define(version: 20171008213618) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "alternatives", force: :cascade do |t|
t.text "answer"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "question_id"
t.index ["question_id"], name: "index_alternatives_on_question_id"
end
create_table "questions", force: :cascade do |t|
t.text "statement"
t.integer "correct_answer"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "quiz_id"
t.integer "answer", default: 0
t.index ["quiz_id"], name: "index_questions_on_quiz_id"
end
create_table "quizzes", force: :cascade do |t|
t.integer "minutes", default: 20
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "session_id"
t.index ["session_id"], name: "index_quizzes_on_session_id"
end
end
How can I access said data (question.statement is a string, and question has_many alternatives), when questions is a nested class?
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
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'.