Rails 5 association with different foreign key - ruby-on-rails

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

Related

Error seeding database in Rails error on attribute I'm not using

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.

Single Table Inheritance + Relationships

I'm creating a rails 5 application (a sort of job finder that connects recruiters with applicants).
Here is a part of my model configuration:
class User < ActiveRecord::Base
has_and_belongs_to_many :tag
end
class Applicant < User
has_many :experience
has_many :match
end
class Recruiter < User
has_one :company
has_many :offer
end
class Experience < ActiveRecord::Base
belongs_to :applicant, :foreign_key => "user_id"
has_one :company
end
And these are extracts from my schema file:
create_table "users", force: :cascade do |t|
t.string "type", null: false
t.string "login", limit: 40, null: false
t.string "password", limit: 500, null: false
t.bigint "company_id"
t.index ["company_id"], name: "index_users_on_company_id"
t.index ["login"], name: "index_users_on_login", unique: true
end
create_table "experiences", force: :cascade do |t|
t.string "job_name", limit: 100, null: false
t.bigint "company_id"
t.bigint "user_id", null: false
t.text "description"
t.index ["company_id"], name: "index_experiences_on_company_id"
t.index ["job_name"], name: "index_experiences_on_job_name"
t.index ["user_id"], name: "index_experiences_on_user_id"
end
add_foreign_key "users", "companies"
add_foreign_key "experiences", "companies"
add_foreign_key "experiences", "users"
An Experience is attached to the model Applicant through the table user (which contain a type field for the STI), this is why I specified "foreign_key => 'user_id'" in Experience model.
My problem is when I try to access at the first experience of an applicant, I get this error:
PG::UndefinedColumn: ERROR: column experiences.applicant_id does not exist LINE 1: SELECT "experiences".* FROM "experiences" WHERE "experiences...
I hope you can help me.
Thanks!
As stated in the docs:
By convention, Rails assumes that the column used to hold the foreign key on the other model is the name of this model with the suffix _id added.
Try doing:
class Applicant < User
has_many :experiences, foreign_key: :user_id
has_many :matches
end
Note that it is conventional to use the plural with the has_many association.
BTW, it's no obvious to me why you're using STI, but I'm sure there are good reasons.

Create records with associated tables in Rails

I am new to ruby on rails and don't understand how to create and save records using associated tables. I want the controller to take the data create a product record and then create as many property and product properties associated with that product. The property and product property have a one to one relationship. The product can have many properties and product properties.
Properties and product properties are coming in like this:
{"name"=>"color", "value"=>"red"}
{"name"=>"material", "value"=>"cotton"}
My controller works for the creation of the product but I am unsure how to create a loop that will build as may associated product and product properties that come in the array sent from the client.
My controller now:
class SendDataController < ApplicationController
protect_from_forgery with: :null_session
def hi
product = Product.new
product.name = params[:name]
product.upc = params[:upc].to_i
product.available_on = params[:availableon]
product.save
end
end
Below are my models:
class Product < ApplicationRecord
has_many :propertys, dependent: :destroy
has_many :product_propertys, dependent: :destroy
end
class Property < ApplicationRecord
belongs_to :product
has_one :product_property, dependent: :destroy
end
class ProductProperty < ApplicationRecord
belongs_to :property
belongs_to :product
end
Migration:
class CreateProducts < ActiveRecord::Migration[5.2]
def change
create_table :products do |t|
t.string :name
t.string :upc
t.datetime :available_on
t.timestamps
end
end
end
class CreateProductProperties < ActiveRecord::Migration[5.2]
def change
create_table :product_properties do |t|
t.string :value
t.belongs_to :property
t.belongs_to :product
t.timestamps
end
end
end
class CreateProperties < ActiveRecord::Migration[5.2]
def change
create_table :properties do |t|
t.string :name
t.belongs_to :product
t.timestamps
end
end
end
schema:
ActiveRecord::Schema.define(version: 2018_09_22_140824) do
create_table "product_properties", force: :cascade do |t|
t.string "value"
t.integer "property_id"
t.integer "product_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["product_id"], name: "index_product_properties_on_product_id"
t.index ["property_id"], name: "index_product_properties_on_property_id"
end
create_table "products", force: :cascade do |t|
t.string "name"
t.string "upc"
t.datetime "available_on"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "properties", force: :cascade do |t|
t.string "name"
t.integer "product_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["product_id"], name: "index_properties_on_product_id"
end
end
Thanks for any help you can give a new guy!
Your Product Model plurality required, has_many properties & equally has_many product_properties.
Your Property schema will need product_id as an integer. i would avoid using has_one it can get messy, just use has_many or you may require a has_many through
Your ProductProperty Model You'll also need product_id integer & property_id integer adding them as separate migration.
rails db:create add_product_id_to product_properties, product_id:integer
check the migration file product_id that the attribute is in the file
rails db:migrate
Restart server & test in the console.
Once the Models speak, instantiate a Product object, bring it across into Properties & ProductProperties through the respective controllers by setting & in turn making the SendDataController obsolete unless your logic requires this.

ActiveAdmin No method error

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.

Choosing the right Model Associations in Rails 4

I am having trouble deciding which type of association fits best for my application. I have the following models: Firm, Client, and Case.
A Firm handles many Cases. And each Case can have one or more Clients assigned to it. Therefore my initial approach was:
class Firm < ActiveRecord::Base
has_many :cases
has_many :clients
end
class Case < ActiveRecord::Base
belongs_to :firm
has_many :clients
end
class Client < ActiveRecord::Base
belongs_to :firm
has_many :cases
end
But I think something is not right with this. I was thinking a has_many :through Association would be better but I am not sure.
Rails Guides says that “You should use has_many :through if you need validations, callbacks, or extra attributes on the join model”. As the great coding philosopher Avi Flombaum once eluded, how can you possibly know that your join model will not serve an additional purpose this early in the application process. No matter where you are in the development stage, you can never see so far in the future to know you will not need to extend the join table.
Why You Don’t Need Has_and_belongs_to_many Relationships
With that out of the way:
# models/firm.rb
class Firm < ActiveRecord::Base
has_many :clients
has_many :cases, through: :clients
end
# models/client.rb
class Client < ActiveRecord::Base
belongs_to :firm
has_many :client_cases
has_many :cases, through: :client_cases
end
# models/case.rb
class Case < ActiveRecord::Base
has_many :client_cases
has_many :clients, through: :client_cases
end
# models/client_case.rb
class ClientCase < ActiveRecord::Base
belongs_to :client
belongs_to :case
end
UPDATE
Your issue must lie somewhere else; I was able to create the following in a terminal without any errors.
f = Firm.create name: 'Magical Group' # => #<Firm id: 1...
b = f.clients.create name: 'Billy' # => #<Client id: 1...
m = f.clients.create name: 'Megan' # => #<Client id: 2...
c = Case.create name: 'Billy & Megan are getting married!' # => #<Case id: 1...
b.cases << c # => #<ActiveRecord::Associations::CollectionProxy
m.cases << c # => #<ActiveRecord::Associations::CollectionProxy
f.cases.count # => 2
f.cases.uniq.count # => 1
I've provide the database schema, make sure yours resembles:
ActiveRecord::Schema.define(version: 20150813173900) do
create_table "cases", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "client_cases", force: :cascade do |t|
t.integer "client_id"
t.integer "case_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "client_cases", ["case_id"], name: "index_client_cases_on_case_id"
add_index "client_cases", ["client_id"], name: "index_client_cases_on_client_id"
create_table "clients", force: :cascade do |t|
t.string "name"
t.integer "firm_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "clients", ["firm_id"], name: "index_clients_on_firm_id"
create_table "firms", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
Assuming that a case will never belong to a different firm:
class Firm < ActiveRecord::Base
has_many :clients
has_many :cases, through: :clients
end
class Client < ActiveRecord::Base
belongs_to :firm
has_and_belongs_to_many :cases
end
class Case < ActiveRecord::Base
has_and_belongs_to_many :clients
end
Migrate join table
rails g migration CreateJoinTableCaseClient case client

Resources