I have three models. Customer, Address, and CreditMemo.
Originally, I had a non polymorphic model Address and I changed it to polymorphic when I created CreditMemo so they could both share the same model. Since then, when I try to create a record with a different parent type than Customer, I get a validation error saying the Customer parent doesn't exist.
Before I created CreditMemo I simply had a 1-to-many association between Customer and Address. Here is the migration I made to change Address to polymorphic.
class MakeAddressPolymorphic < ActiveRecord::Migration[5.1]
def up
rename_column :addresses, :customer_id, :addressable_id
add_column :addresses, :addressable_type, :string
add_index :addresses, [:addressable_id, :addressable_type]
Address.reset_column_information
Address.update_all(:addressable_type => "Customer")
end
def down
rename_column :addresses, :addressable_id, :customer_id
remove_column :addresses, :addressable_type
end
end
My schema after the migration:
create_table "addresses", force: :cascade do |t|
t.text "line_1"
t.text "line_2"
t.string "city"
t.string "state"
t.string "zip_code"
t.string "address_type"
t.bigint "addressable_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "same_as_billing"
t.string "addressable_type"
t.index ["addressable_id", "addressable_type"], name: "index_addresses_on_addressable_id_and_addressable_type"
t.index ["addressable_id"], name: "index_addresses_on_addressable_id"
end
I can create a customer with the nested attributes just fine. However, when I try to create a CreditMemo with the nested address atrributes, I get this error:
PG::ForeignKeyViolation: ERROR: insert or update on table "addresses" violates foreign key constraint "fk_rails_d5f9efddd3" DETAIL: Key (addressable_id)=(36) is not present in table "customers". : INSERT INTO "addresses" ("line_1", "line_2", "city", "state", "zip_code", "address_type", "addressable_id", "created_at", "updated_at", "addressable_type") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING "id"
Here is my Customer model:
has_many :addresses, :as => :addressable, inverse_of: :addressable
accepts_nested_attributes_for :addresses
My CreditMemo model:
has_many :addresses, :as => :addressable, inverse_of: :addressable
accepts_nested_attributes_for :addresses
My Address model:
belongs_to :addressable, :polymorphic => true
I'm pretty sure the issue is in this line which is at the bottom of my schema:
add_foreign_key "addresses", "customers", column: "addressable_id"
I'm not sure how to fix this or what it should be .
You can't use foreign keys with polymorphic associations, so you'd have to remove the foreign key from your schema, you can use remove_foreign_key for this.
Just write a new migration with this:
remove_foreign_key :addresses, :customers
Related
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>
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.
I'm currently working on a small school project that utilizes Ruby on Rails and I'm having some trouble getting my self-referential associations working correctly.
Context
The intended functionality of my web app is for users to post houses/apartments for other users to search through and rent. Since I'm having issues with a specific association, I'm working with a completely stripped down version that only has two models, User and Lease.
What I'm Trying to Accomplish
Ideally, when a person first registers on the site, a User object is created to hold their information such as email and password. A User can then either post a listing or search through listings.
Once a post has been created and another user decides to rent the posted house, a Lease object is created, which holds the ID of the posting User as well as the ID of the renting user, aliased as "landlord_id" and "tenant_id" respectively.
A User should now be identified as either a User, Landlord or a Tenant (or both Landlord and Tenant) based on whether there are any Lease objects with their ID as either a Landlord or a Tenant. This identification will be used to determine whether the User can access other areas of the site.
userFoo.leases
This should give me a list of all Lease objects with which the User's ID is associated, regardless of whether it's as a Landlord or Tenant.
userFoo.tenants
This should give me a list of any User object whose ID is associated with the ID of userFoo as a Tenant through Lease, and the inverse if I ask for landlords.
The Code
User Class
class User < ApplicationRecord
has_many :tenants, class_name: "Lease", foreign_key: "landlord_id"
has_many :landlords, class_name: "Lease", foreign_key: "tenant_id"
end
Lease Class
class Lease < ApplicationRecord
belongs_to :landlord, class_name: "User"
belongs_to :tenant, class_name: "User"
end
Users Table Migration
class CreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users do |t|
t.string :name
t.string :email
t.string :password_digest
t.timestamps
end
end
end
Leases Table Migration
class CreateLeases < ActiveRecord::Migration[6.0]
def change
create_table :leases do |t|
t.references :landlord, null: false, foreign_key: {to_table: :users}
t.references :tenant, null: false, foreign_key: {to_table: :users}
t.timestamps
end
end
end
Database Schema
ActiveRecord::Schema.define(version: 2020_10_18_005954) do
create_table "leases", force: :cascade do |t|
t.integer "landlord_id", null: false
t.integer "tenant_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["landlord_id"], name: "index_leases_on_landlord_id"
t.index ["tenant_id"], name: "index_leases_on_tenant_id"
end
create_table "users", force: :cascade do |t|
t.string "name"
t.string "email"
t.string "password_digest"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
add_foreign_key "leases", "users", column: "landlord_id"
add_foreign_key "leases", "users", column: "tenant_id"
end
What's Wrong?
userFoo.leases
Normally a User would have_many leases by having their ID associated with a lease as "user_id." However, since I'm using "tenant_id" and "landlord_id", this command fails because it can't find "user_id" in the Leases table.
userFoo.tenants
This command gives me a list of all Lease objects where userFoo's ID is associated as "landlord_id" instead of all User objects associated with userFoo's ID as tenants. To retrieve a tenant as is, I have to use the command:
userFoo.tenants.first.tenant
Conclusion
I am having a bit of a hard time understanding these deeper, more complex associations, and I've spent some time trying to find a detailed reference on has_many that covers all the arguments, but all I can really find are small blog posts that reference the "Employees" and "Managers" example on guides.rubyonrails.com . I think one problem is that I'm not sure I'm correctly reflecting my model associations in my table schema.
I'm more than happy to teach myself if someone can point me in the right direction. I'm also open to alternative solutions but only if I can't get the functionality I want out of this setup, because my instructor specifically asked me to try it this way
Thanks in advance for any help! It's much appreciated.
as per your requirement you can try like this:
# app/models/user.rb
class User < ApplicationRecord
has_many :owned_properties, class_name: "Property", foreign_key: "landlord_id"
has_many :rented_properties, class_name: "Property", foreign_key: "tenant_id"
end
Here I have declared two associations with same table but different foreign keys.
# app/models/property.rb
class Property < ApplicationRecord
belongs_to :landlord, class_name: "User"
belongs_to :tenant, class_name: "User"
end
Here I have taken one table by using this user can post one property where landlord is the owner of a house and later you can add tenant who is taking rent to one property.
# db/migrations/20201018054951_create_users.rb
class CreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users do |t|
t.string :name, null: false
t.string :email, null: false, index: true
t.string :password_digest, null: false
t.timestamps
end
end
end
Above is your users table migration.
# db/migrations/20201018055351_create_properties.rb
class CreateProperties < ActiveRecord::Migration[6.0]
def change
create_table :properties do |t|
t.references :landlord, foreign_key: {to_table: :users}, null: false
t.references :tenant, foreign_key: {to_table: :users}
t.timestamps
end
end
end
Above is your properties table migration.
# db/schema.rb
ActiveRecord::Schema.define(version: 2020_10_18_055351) do
create_table "properties", force: :cascade do |t|
t.bigint "landlord_id", null: false
t.bigint "tenant_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["landlord_id"], name: "index_properties_on_landlord_id"
t.index ["tenant_id"], name: "index_properties_on_tenant_id"
end
create_table "users", force: :cascade do |t|
t.string "name", null: false
t.string "email", null: false
t.string "password_digest", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["email"], name: "index_users_on_email"
end
add_foreign_key "properties", "users", column: "landlord_id"
add_foreign_key "properties", "users", column: "tenant_id"
end
If you want to fetch all the owned properties of a user, use user.owned_properties.
If you want to fetch all rented properties of a user, use user.rented_properties.
^^ Here both the cases you'll get objects of Property class.
If you want to get landlord of a property, use property.landlord.
If you want to get tenant of a property, use property.tenant.
^^ Here both the cases you'll get objects of User class.
If you want you can add other attributes like: name, price, etc to properties table.
I think, this will help you. Thanks :) Happy Coding :)
I have 2 models (Books and Authors) and a third table joining them (has_many through association).
I am trying to implement search in my app and run a query on both tables. My query looks like this and I cannot figure out the problem:
Book.includes(:authors, :author_books).where("books.title LIKE ? OR authors.name = LIKE ?", "%#{book}%", "%#{book}%")
This is the error that I get running it:
PG::UndefinedTable: ERROR: missing FROM-clause entry for table "authors"
SELECT "books".* FROM "books" WHERE (books.title LIKE '%Harry%' OR authors.name = LIKE '%Harry%')
Here is my schema of the three tables:
create_table "author_books", force: :cascade do |t|
t.bigint "author_id"
t.bigint "book_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["author_id"], name: "index_author_books_on_author_id"
t.index ["book_id"], name: "index_author_books_on_book_id"
end
create_table "authors", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "books", force: :cascade do |t|
t.string "title"
t.text "description"
t.string "image"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "rating"
t.string "critics"
t.float "price"
end
author_book.rb
class AuthorBook < ApplicationRecord
validates_presence_of :author, :book
belongs_to :author
belongs_to :book
end
author.rb
class Author < ApplicationRecord
validates :name, uniqueness: true
has_many :author_book
has_many :books, through: :author_book
end
book.rb
class Book < ApplicationRecord
validates :title, uniqueness: true, :case_sensitive => false
has_many :author_book
has_many :authors, through: :author_book
has_many :categories, through: :category_book
def self.search_book(book)
if book
Book.joins(:authors, :author_books).includes(:authors, :author_books).where("books.title LIKE ? OR authors.name = LIKE ?", "%#{book}%", "%#{book}%")
end
end
end
I call this search_book method in my book controller like so:
def search
#books = Book.search_book(params[:book])
end
Some help, please?
Thanks!
From the docs
If you want to add conditions to your included models you’ll have to
explicitly reference them.
That said, you need to add references(:authors) to your query like below to resolve the error
Book.includes(:authors, :author_books).where("books.title LIKE ? OR authors.name = LIKE ?", "%#{book}%", "%#{book}%").references(:authors)
Update:
Can't join 'Book' to association named 'author_books'; perhaps you
misspelled it?
You should replace has_many :author_book with has_many :author_books and through: :author_book with through: :author_books
You forgot to join authors and author_books to your relation. includes loads both :author and :author_books but in separate queries.
Try this:
Book.joins(:authors, :author_books).includes(:authors, :author_books).where("books.title LIKE ? OR authors.name = LIKE ?", "%#{book}%", "%#{book}%")
I think I have missed something when creating an association with a foreign_key in Rails.
I have 2 models: Company and Employee. Company has_many employees and Employee belongs_to a company. Company has an attribute called company_code and I should be able to figure out which company the employee works for using the company_code instead of company_id.
At first, I created the models:
rails g model Company company_code:integer:index name
rails g model Employee joined:date:index salary:integer
Then, I generated a migration to add the company_code column to the employees table.
class AddReferenceToEmployee < ActiveRecord::Migration[5.1]
def change
add_column :employees, :company_code, :integer, index: true
add_foreign_key :employees, :companies, column: :company_code
end
end
And, finally I added the foreign key at the model level.
class Company < ApplicationRecord
has_many :employees, foreign_key: :company_code
end
class Employee < ApplicationRecord
belongs_to :company, foreign_key: :company_code
end
However, I'm still not able to create proper association.
company = Company.create(name: 'test', company_code: 123)
company.employees.create(joined: Date.today, salary: 1000)
It creates employee record with company_code = 1 instead of 123.
When I try to create a new instance of employee
company.employees.new
It will generate
#<Employee id: nil, joined: nil, salary: nil, created_at: nil, updated_at: nil, company_code: 1>
What am I missing? Is this the right way to do it?
Bellow is my schema.rb
ActiveRecord::Schema.define(version: 20180828052633) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "companies", force: :cascade do |t|
t.integer "company_code"
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["company_code"], name: "index_company_on_company_code"
end
create_table "employees", force: :cascade do |t|
t.date "joined"
t.integer "salary"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "company_code"
end
add_foreign_key "employees", "companies", column: "company_code"
end
class Company < ApplicationRecord
has_many :employees, primary_key: :company_code, foreign_key: :company_code
end
class Employee < ApplicationRecord
belongs_to :company, foreign_key: :company_code, primary_key: :company_code
end
I added a couple of foreign keys to my models and to my tables and it has since broken my use of Active Admin. I'm wondering if anyone knows a work around or a fix to this issue.
schmea.rb
create_table "students", primary_key: "student_id", id: :string, force:
:cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
t.string "last_name"
t.string "first_name"
t.string "home_address"
t.string "home_city"
t.string "home_state"
t.string "home_zip"
t.string "school_year_address"
t.string "school_year_city"
t.string "school_year_zip"
t.string "room_number"
t.string "home_phone"
t.string "cell_phone"
t.boolean "new_student"
t.boolean "returning_student"
t.string "athletic_team"
t.bigint "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["user_id"], name: "fk_rails_148c9e88f4"
end
add_foreign_key "emergency_contacts", "students", primary_key: "student_id"
add_foreign_key "students", "users"
add_foreign_key "vehicles", "students", primary_key: "student_id"
student.rb Students Model
class Student < ApplicationRecord
self.primary_key = :student_id
belongs_to :user
has_one :emergency_contact
has_one :vehicle
end
I'm getting the error, has anyone found a fix for this?
undefined method `emergency_contact_id_eq' for Ransack::Search<class: Student, base: Grouping <combinator: and>>:Ransack::Search
You have defined the assocation wrong.
class Student < ApplicationRecord
self.primary_key = :student_id
belongs_to :user
belongs_to :emergency_contact, class_name: 'User'
has_one :vehicle
end
belongs_to places the foreign key on this table and is exactly what you want. When joining you want to have the id on this table instead of having to look for records where student_id matches this record.
You also need to make sure to add a foreign key column and the correct foreign key constraint:
class AddEmergencyContactIdToStudents < ActiveRecord::Migration[5.0]
def change
add_reference :students, :emergency_contact, foreign_key: false
add_foreign_key :students, :users, column: :emergency_contact_id,
end
end
I would also strongly advise against using non standard primary keys. Prefixing the PK with student_ gives you nothing but headaches and will confuse other developers.