Rails 5.0: Add self join table that references existing table - ruby-on-rails

We're creating a flight scheduling program that schedules employees and planes for flights.
Here are the models that currently exist
Airport
Pilot
FlightAttendant
Aircraft
We are starting with only four airports. We want to fill in the four airports in the Airports table as soon as the app launches, and then make a self-join table from the Airports that lists all possible origin-destination combinations and their duration.
How would we do that?
I've seen some stuff online about it but it looks like its done when we create the models, but our models are already made, so I can't figure out the migration and how to fill in that table automatically. We know the durations and just need to feed them in.
EDIT:
In response to the flight times: we plan to store them as an integer which is the number of minutes it takes to complete a flight. The airports are in Midwestern cities chosen at random in the Central Time Zone.
Lincoln and Iowa City: 32 mins
Lincoln and Evanston: 57 mins
Lincoln and West Lafayette: 62 mins
Iowa City and Evanston: 24 mins
Iowa City and West Lafayette: 31 mins
Evanston and West Lafayette: 13 mins
For further detail
This is the specific migration which created the Airports table
class CreateAirports < ActiveRecord::Migration[5.2]
def change
create_table :airports do |t|
t.string :full_name
t.string :flight_code
t.timestamps
end
end
end
full_name is simply the name like Evanston. flight_code is the the three letter code to represent it, like EVA.
The model is currently empty. Do I need to add something in it first before I add the association columns, or do I need to generate the migration to create the join table and then alter the Airport model?

You can run ruby code after the migration, Rails code in this case:
class CreateAirports < ActiveRecord::Migration[5.2]
def change
create_table :airports do |t|
t.string :full_name
t.string :flight_code
t.timestamps
end
Airport.create(params_for_airport1)
Airport.create(params_for_airport2)
Airport.create(params_for_airport3)
Airport.create(params_for_airport4)
end
end
Then, for the durations table, I imagine you'll have something like a "FlightDuration" using a table with 3 columns: airport_from_id, airport_to_id, duration. You can do the same as before, you create the table and right after the create_table just create the objects with the data.
You'll have to add the relationships to Airport and to FlightDuration, something like:
class Airport < ApplicationRecord
has_many :flight_durations_from, class_name: 'FlightDuration', inverse_of: :airport_from
has_many :flight_durations_to, class_name: 'FlightDuration', inverse_of: :airport_to
end
class FlightDuration < ApplicationRecord
belongs_to :airport_from, class_name: 'Airport', foreign_key: :airport_from_id
belongs_to :airport_to, class_name: 'Airport', foreign_key: :airport_to_id
end

Related

Referencing a column on a table to a column on another table Ruby on Rails

I was reading another question on here regarding referencing columns from two separate tables but was a little confused if it addressed my issue. What's going on is I have two tables, Destination and Booking. The Destination table has a column for location_id, and the Booking has a column for location, and I am trying to reference location in Booking table from location_id column in Destination table.
Here is my table for Booking(migration)
class CreateBookings < ActiveRecord::Migration[6.1]
def change
create_table :bookings do |t|
t.string :name
t.string :start_date
t.string :end_date
t.string :email
t.integer :location
t.timestamps
end
end
end
and here is my table(Migration) for Destination
class CreateDestinations < ActiveRecord::Migration[6.1]
def change
create_table :destinations do |t|
t.string :address
t.string :city
t.string :state
t.string :zip
t.integer :location_id
t.timestamps
end
end
end
My Models are setup currently as
class Booking < ApplicationRecord
# belongs_to :reservation, optional: true
has_many :destinations, :class_name => 'Destination', :foreign_key=> 'location_id'
validates :name, :start_date, :end_date, :email, presence: true
end
and
class Destination < ApplicationRecord
has_many :bookings, :class_name => 'Booking', :foreign_key=> 'location'
end
Am I currently referencing the columns correctly, or is there something else I should be doing?
How you should write your migrations depends on the association between your models. Foreign keys go onto tables that have a belongs_to association.
Can a single Booking have multiple Destinations? If the answer is no, you need to change the association in your Booking model to belongs_to :destination and then put a :destination_id on your bookings table (you can give it a custom name like :location_id if you want but the convention is to use the model name).
If a single Booking can have multiple Destinations, and surely a single Destination can have multiple Bookings, then you have a many-to-many relationship. In that case you will not put foreign keys on the destinations table, nor the bookings table. Instead you will need a join table between them and that's where the foreign keys go.
Rails gives 2 different ways to declare many-to-many relationships. See https://guides.rubyonrails.org/association_basics.html#choosing-between-has-many-through-and-has-and-belongs-to-many.
If you want to use has_and_belongs_to_many, your models would look like this:
class Booking < ApplicationRecord
has_and_belongs_to_many :destinations
end
class Destination < ApplicationRecord
has_and_belongs_to_many :bookings
end
And the migration would look like this:
class CreateBookingsAndDestinations < ActiveRecord::Migration[6.0]
def change
create_table :bookings do |t|
# ...
end
create_table :destinations do |t|
# ...
end
create_table :bookings_destinations, id: false do |t|
t.belongs_to :booking
t.belongs_to :destination
end
end
end
Caveat: Based on your question I'm assuming you want a booking to have a destination. If you want a destination to many bookings and vise-versa, Sean's answer is great.
I think you're misunderstanding how foreign keys / associations work in databases.
It sounds like you want a column in the bookings table to "reference" a value column in the destinations table (or maybe the opposite), as in:
bookings.location -> destinations.location_id or maybe destinations.location_id -> bookings.location.
That's not typically what we mean by "reference" in a relational database. Instead, when you say that a table (for example, a 'comments' table) references another table (for example, a comments table references a user table), what we typically mean is that we're storing the primary key column of the referenced table (e.g. the user's id) in a column in the first table (e.g. comments.user_id --> users.id).
From an english language standpoint I expect that you want a booking to refer to a destination, so I'm going to assuming we want a the booking table to reference/refer to the destinations table, like this:
booking.location -> destinations.id
In Ruby on Rails, the convention is to name a column that stores an association with the same as the table it references, plus _id, like so the convention would be this:
booking.destination_id -> destinations.id
A common way to create this in a migration would be with:
add_reference :bookings, :destination
When adding a reference in a database you almost always want to index by that value (so that you can do Bookings.where(destination_id: #destination.id) and not kill your database). I am also a strong advocate for letting your database enforce referential integrity for you, so (if your database supports it) i'd recommend the following:
add_reference :destinations, :booking, index: true, foreign_key: true
This would prevent someone from deleting a destination that has a booking associated with it.

Search objects which have multiple description objects?

I am new with Rails and I would need some advice :) I have this:
class Club < ActiveRecord::Base
has_many :players, :through => :club_players
has_many :club_players
end
class Player < ActiveRecord::Base
has_many :clubs, :through => :club_players
has_many :club_players
end
class ClubPlayer < ActiveRecord::Base
belongs_to :club
end
create_table "players", force: true do |t|
t.string "name"
t.string "age"
t.string "nationality"
t.string "sex"
end
Now I would like to model search engine where users can search players by age and have that defined like this in drop down:
Team Seniors
Team Kids
Age [14-18]
Age [19-25]
Age [26-30]
Age [31-35]
Age [36-40]
Retired
Or by nationality:
American
Indian
French
Mixed
How can my search engine look like if user clicks on Age [19-25]? Where would be a good idea to define what certain string in drop down represent? The same thing with nationality. For example I need to define somewhere what category A Team Seniors represent. I would define that team as a team which has more than 50% of players older than 25 years. So, I need somehow to define categories which are defined from some columns of Player.
What would be the best practice in doing this? Thanks!
I would create Tags for each of these categories. Then you could apply as many/little of the tags you want to apply to them. Or you create some look up tables/lists for the different sections like nationality.
For age, you probably need birthday and you could then create an age method to give you their current age.

Database modeling this relationship?

Rails app:
A user has_many positions.
Each position has one company (company name and company id) per the following schema:
create_table "positions", :force => true do |t|
t.integer "user_id"
...
t.string "company"
t.integer "company_id"
end
I would like users to be able to "follow" as many individual companies as they would like (i.e. a user can follow many different companies a company can be followed by many different users). It would seem that this calls for a has_and_belongs_to_many relationship between users and positions, but I want users to be able to follow the company attribute of a position row and not the position itself.
Should I create a new "following" table altogether that would pull companies from the positions table to be matched to user_id's? Or is there a way I can set up a has_many :through relationship and map user_id's to company_id's?
Thank you!
What I think you could have
A User table:
integer User_Id
....
A Company Table:
string company
integer company_id
...
A Positions table:
integer user_id foreign_key -> User table
integer company_id foreign_key -> company table
A Following table (If the user can follow any comapny regarding of whether he has a position in it):
integer user_id foreign_key -> User table
integer company_id foreign_key -> company table
OR if the user can only follow a company that he has position in then you can add a new column to position table. This would be a boolean flag telling if the user if 'following' the company identified by the position. Alternatively the Following table can also map user to position in this case.
I broadly agree with MickJ, although having created the Company and User models/tables (which obviously have an id column in each) I'd do it as:
create_table "companies" do |t|
t.string "name"
...
end
create_table "positions" do |t|
t.references "user"
t.references "company"
...
end
create_table "followings" do |t|
t.references "user"
t.references "company"
...
end
Models:
class User
has_many :positions
has_many :followings
end
class Company
has_many :positions
has_many :followings
end
class Position
belongs_to :user
belongs_to :company
end
class Following
belongs_to :user
belongs_to :company
end
You could reference the company from the position by doing:
position = Position.first
puts position.company.name
or by user with something like
user = User.first
user.positions.each do |position|
puts position.company.name
end
-- EDIT1:
To extract the company name from positions into a separate table you'd be best off writing a little rake task - something like:
Position.all.each do |position|
company = Company.find_or_initialize_by_name(position.company_name)
position.company_id = company.id
position.save
end
Then you might want to write a migration to remove the company name column from the positions table ... just to keep things tidy.

Ruby on Rails: How to model recurring times (weekly activities)?

In my Ruby on Rails application, I have a "Group" model that has weekly recurring "activities". Some activities occur only one day a week (Friday at 6:00pm) while some occur multiple times a week (Mon-Fri at 8:00am, or Tues/Thurs at 10:00am).
I am having trouble trying to figure out how to model this data, and how to use Rails to create a form to create/update the data. Do I create an "Activities" table that has a datetime field? Or do I separate the day of the week from the time of day into two separate fields? What about the activities that occur multiple times a week?
Any ideas or advice would be appreciated. Also, I would appreciate knowing if you know of a Gem that helps with this so I don't have to re-invent the wheel.
Update:
For Example, if I needed to display something like this:
Special Group A's Activities
Monday at 10pm - Football
Tues/Thurs at 8am - Tennis
Special Group B's Activities
Monday-Friday at 12pm - Lunch
Saturday at 8am - Breakfast
Sunday at 6pm - Dinner
What steps would I need to take in order to model and display this data, using Ruby on Rails?
Models
group.rb
class Group < ActiveRecord::Base
validates_presence_of :name
has_many :activities, :through => :group_activity
end
activity.rb
class Activity < ActiveRecord::Base
validates_presence_of :name
belongs_to :group
has_many :occurances, :through => :activity_occurance
end
occurance.rb
class Activity < ActiveRecord::Base
validates_presence_of :date
belongs_to :activity
end
Migrations (separate or all together)
add_everything.rb
class AddEverything < ActiveRecord::Migration
def self.up
create_table :groups, :force => true do |t|
t.string :name
t.timestamps
end
create_table :group_activity, :force => true do |t|
t.integer :group_id, :activity_id
t.timestamps
end
create_table :activities, :force => true do |t|
t.string :name
t.timestamps
end
create_table :activity_occurance, :force => true do |t|
t.integer :activity_id, :occurance_id
t.timestamps
end
create_table :occurance, :force => true do |t|
t.datetime :date
t.timestamps
end
end
def self.down
drop_table :groups
drop_table :activities
drop_table :occurances
drop_table :group_activity
drop_table :activity_occurance
end
end
That take's care of your model work. In your groups _form view I would add your associated group name, and fields_for for your activity name and fields_for occurance. In your occurance, use this handy jQuery datetime picker that is an extension off of the jQuery date picker, to populate your occurance field:
http://puna.net.nz/timepicker.htm
You should also have separate views to manage activities separately with it's own respective form. In your show page displaying other fields is pretty standard, but for the occurances you can have something like (haml syntax):
= #group.name
- for activity in #group.activities
= activity.name
- for occurance in activity.occurances
= occurance.date.strftime("%A at %r")
Hope this at least gets you started. You can add additional logic for checking activity.occurances.size to format accordingly if you want to display something day1/day2/day3
https://github.com/jimweirich/texp Jim Weirich's Temporal expressions library is an excellent resource for querying these sorts of things in ruby.
If you don't need to query this set other than looking at all of them in batch, then just serializing that datastructure would probably work for you.
But in the end you'll probably just use a has_many :occurances where occurances start off as date_time homebaked-recurrence-pattern pairs and iterate from there depending on what feature set you need.
If you think it straight, there is a great number of possibilites for you to represent and persist diverse date and time formats and intervals on a database, also you probably are going to change it to add some options to users or to remove options accordingly to the growth of your website.
I would go with creating two fields like "date_start" and "date_end", and one string field containing a code that represents the frequency. Something like 3 chars per code, first can be M for monthly, W weekly; second can be F for first, L for last; third char can be a number for a specific number of the week, F for friday.
The point here is that you can (encode and) decode that programatically so if you add features you won't have to recreate your database relations.
In the same way a group can have multiple activities, I think an activity can have multiple occurances. I would suggest trying to model your database that way, with a occurance table.
Regarding the form, what about a "master - detail" form with activity as the master, and occurance as the detail?

Display column name on view if value present in the column -- Ruby on Rails

I have column LTD in my Company model. After retrieving value from model using
Company.find
If any value is present in LTD column, then I have to display the text "Limited" on the view. I have many columns in the model which are in the abbreviated form and when value is present their long form are displayed on the view. Therefore writing conditions on the view is not feasible.
I was thinking whether writing a custom rails config file containing application constants will do. But I don't have quantitative and qualitative information on this.
Please help. Thanks in advance.
You could create a separate Abbreviation model that your Company model could be associated with through a join model CompanyAbbreviation. Then there would be one join table record for each column in a specific company record. Rather than having each abbreviation as a column in your companies table you would have secondary keys in your company_abbreviations table referring to the associated company and abbreviation records.
Something like the following:
class Company < ActiveRecord::Base
has_many :company_abbreviations
has_many :abbreviations, :through => :company_abbreviations
end
class Abbreviation < ActiveRecord::Base
has_many :company_abbreviations
end
class CompanyAbbreviation < ActiveRecord::Base
belongs_to :company
belongs_to :abbreviation
end
class CreateAbbreviations < ActiveRecord::Migration
def self.up
create_table :abbreviations do |t|
t.string :abbr
t.string :description
end
add_index :abbreviations, :abbr
end
end
class CreateCompanyAbbreviations < ActiveRecord::Migration
def self.up
create_table :company_abbreviations do |t|
t.references :company
t.references :abbreviation
end
add_index :company_abbreviations, :company_id
add_index :company_abbreviations, :abbreviation_id
end
end
In db/seeds.db you could pre-populate your abbreviations table.
You add new associations like this:
#company.company_abbreviations.create(:abbreviation => Abbreviation.find_by_abbr("LTD"))
In your view you can reference the expanded abbreviation columns cleanly like this:
<% #company.abbreviations.each do |abbr| %>
<%= abbr.description %>
<% end %>
You may also want to control the display order in some fashion, say by a sort column in the join table,
This works for me perfectly.
I have declared a global hash in config/environment.rb which maintains the list of all the column name short-forms and long-forms and on the view I just check if value is present in the column I search for the corresponding key value pair from the global hash and display the long-form.
Thanks guyz for giving your time to help me.

Resources