Home > Back-end >  Error seeding database in Rails error on attribute I'm not using
Error seeding database in Rails error on attribute I'm not using

Time:12-11

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:in times' /home/stuart/repos/flight-booker/db/seeds.rb:21:in block (2 levels) in ' /home/stuart/repos/flight-booker/db/seeds.rb:15:in block 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:in with_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.

  • Related