I am working on the flight-booker project in The Odin Project. I am trying to seed a database with 60 days worth of flights to and from 10 cities. When I seed the database, I get this error:
Created database 'flight_booker_development' Created database 'flight_booker_test' #Airport:0x00007f003f0c6f20 rails aborted! ActiveModel::MissingAttributeError: can't write unknown attribute
flight_id
raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`" ^^^^^ /home/stuart/repos/flight-booker/db/seeds.rb:23:in `block (4 levels) in <main>'
/home/stuart/repos/flight-booker/db/seeds.rb:22:in
times' /home/stuart/repos/flight-booker/db/seeds.rb:22:in
block (3 levels) in ' /home/stuart/repos/flight-booker/db/seeds.rb:21:intimes' /home/stuart/repos/flight-booker/db/seeds.rb:21:in
block (2 levels) in ' /home/stuart/repos/flight-booker/db/seeds.rb:15:inblock in <main>' /home/stuart/repos/flight-booker/db/seeds.rb:14:in
' Tasks: TOP => db:reset => db:setup => db:seed (See full trace by running task with --trace)
The problem seems to be that it is trying to write an attribute that I am not using. Here is my seeds.rb file:
require "faker"
airports = %w[LAX DFW NYC DEN BOS MIA HOU PIT POR MIN]
airports.each { |city_code| Airport.create!(city_code: city_code) }
Airport.all.each do |departure|
Airport.all.each do |arrival|
next if departure == arrival
puts departure
duration = rand(100..300)
flight_number = rand(1000..1999)
frequency = rand(3..5)
60.times do
frequency.times do
Flight.create!(
origin_id: departure,
destination_id: arrival,
duration: duration,
flight_number: flight_number,
departure_time: Faker::Time.forward(days: 60, period: :all)
)
end
end
end
end
I was trying to use flight_id to hold the flight number, but have changed that because I realized I had a flight_id column in my bookings table. I am not doing anything with the bookings table at this time though. I was getting this same error before and then I did a migration to remove flight_id and add_flight number to the flights table. Here is the current schema.rb
ActiveRecord::Schema[7.0].define(version: 2022_12_09_210422) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "airports", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "city_code"
end
create_table "bookings", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "user_id", null: false
t.bigint "flight_id", null: false
t.index ["flight_id"], name: "index_bookings_on_flight_id"
t.index ["user_id"], name: "index_bookings_on_user_id"
end
create_table "flights", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "destination"
t.string "origin"
t.datetime "departure_time"
t.integer "duration"
t.bigint "destination_id", null: false
t.bigint "origin_id", null: false
t.integer "flight_number"
t.index ["destination_id"], name: "index_flights_on_destination_id"
t.index ["origin_id"], name: "index_flights_on_origin_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.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
add_foreign_key "bookings", "flights"
add_foreign_key "bookings", "users"
add_foreign_key "flights", "airports", column: "destination_id"
add_foreign_key "flights", "airports", column: "origin_id"
end
Here the models/flight.rb file:
class Flight < ApplicationRecord
belongs_to :booking, foreign_key: "flight_id"
has_one :origin_id, class_name: "Airport"
has_one :destination_id, class_name: "Airport"
validates :departure_time, presence: true
validates :duration, presence: true
validates :flight_number, presence: true
end
That is the only place that flight_id appears anywhere, but I still get the same error if I remove that line of code. In the error it is referencing the seeds file line 23, which is the start of the Flight.create action. It did previously try to create flight_id, but that has been changed to flight_number, and I have saved the file, and restarted the computer to be thorough.
Here is the flights_controller.rb file:
class FlightsController < ApplicationController
before_action :set_flight
def index
@flight = Flight.all
end
def new
@flight = Flight.new
end
def create
@flight = Flight.new(flight_params)
end
private
def set_flight
@flight = Flight.find(params[:id])
end
def flight_params
params.require(:flight).permit(
:airport,
:flight_number,
:origin,
:origin_id,
:destination_id,
:destination,
:duration,
:departure_time,
:arrival_time
)
end
end
I previously had flight_id as a permitted param, but that has been changed to flight_number.
So, I'm at a bit of a loss as to what to try next. Any help would be greatly appreciated. I would be happy to provide any additional information you might think is relevant. Thank you.
Edit to add that I tried creating a table entry from the rails console and got the same error in irb.
irb(main):001:0> Flight.create!(departure_time: "2022-12-25 11:11:11 -0700", duration: 200, flight_number: 1599, origin_id: Airport.first, destination_id: Airport.last) Airport Load (0.2ms) SELECT "airports".* FROM "airports" ORDER BY "airports"."id" ASC LIMIT $1 [["LIMIT", 1]] Airport Load (0.1ms) SELECT "airports".* FROM "airports" ORDER BY "airports"."id" DESC LIMIT $1 [["LIMIT", 1]]
/home/stuart/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/activemodel-7.0.4/lib/active_model/attribute.rb:211:inwith_value_from_database': can't write unknown attribute
flight_id` (ActiveModel::MissingAttributeError)raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`" ^^^^^ irb(main):002:0>
CodePudding user response:
The problem is that you have defined your assocation with foreign_key: "flight_id"
class Flight < ApplicationRecord
belongs_to :booking, foreign_key: "flight_id"
end
This option is documented as:
Specify the column used to store the associated object's type
Needless to say having a flights.flight_id
column that references bookings
is just wrong.
Lets scrub this broken attempt and try again. Stash you work in GIT and roll back and lets setup those models correctly.
To setup the assocation just between flights, airports and airlines you should use three tables and associate them like so:
# rails g model airline name
class Airline < ApplicationRecord
has_many :flights
end
# rails g model airport name iata_code
class Airport < ApplicationRecord
has_many :flights_as_origin,
class_name: 'Flight',
foreign_key: :origin_id
has_many :flights_as_destination,
class_name: 'Flight',
foreign_key: :destination_id
end
# rails g model flight flight_number:string airline:references origin:references destination:references
class Flight < ApplicationRecord
belongs_to :airline
# These should be belongs_to assocations
belongs_to :origin, class_name: 'Airport'
belongs_to :destination, class_name: 'Airport'
end
This is the data thats common to all passengers. You don't alter anything here when a passenger books a ticket. When creating the flights
table you need to explicitly tell Rails that destination_id
and origin_id
should point to the airports
table:
class CreateFlights < ActiveRecord::Migration[7.0]
def change
create_table :flights do |t|
# ...
t.references :origin, null: false, foreign_key: { to_table: :airports }
t.references :destination, null: false, foreign_key: { to_table: :airports }
# ...
end
end
end
To model the assocation between passengers, flights and bookings you actually want a many to many assocation with a join table:
# rails g model ticket passenger_name:string flight:references seat:string
class Ticket < ApplicationRecord
belongs_to :flight
end
class Booking < ApplicationRecord
belongs_to :user
has_many :tickets
has_many :flights, through: :tickets
end
class Flight < ApplicationRecord
# ...
has_many :tickets
has_many :bookings, through: :tickets
end
Here you use the tickets table to store each item in an itenary so that you can actually model stuff like multi-leg trips and multiple passengers per booking.