Lucas Hiago
Posted on June 14, 2020
It’s quite easy to create tables with Ruby On Rails. However, some cases require personalizations to reproduce real world situations and the relationships between them. Here, we have two models: User
and Meeting
. They have a 1..N
relation, so one User
is part of a meeting and a Meeting
has two users (yes, specifically two - a two-person meeting). The problem doesn’t rely on this relationship but in the generated migration because we cannot have two t.references :user_id and also we want to identify which user is available for the meeting and which user requested the meeting.
How can I generate a model on Rails?
Before we start with the solution, let’s understand the command rails generate
or the shorthand code rails g
.
This command is one of the many options that follows the rails
command. If you open your terminal and run rails --help
, you’ll see a list of options:
$ rails --help
The most common rails commands are:
generate Generate new code (short-cut alias: "g")
console Start the Rails console (short-cut alias: "c")
server Start the Rails server (short-cut alias: "s")
test Run tests except system tests (short-cut alias: "t")
test:system Run system tests
dbconsole Start a console for the database specified in config/database.yml (short-cut alias: "db")
new Create a new Rails application. "rails new my_app" creates a new application called MyApp in "./my_app"
And if we run rails generate --help
, another list of options will be shown:
$ rails g --help
Spring preloader in process 70748
Usage: rails generate GENERATOR [args] [options]
...
Please choose a generator below.
Rails:
application_record
assets
channel
controller
generator
helper
integration_test
jbuilder
job
mailbox
mailer
migration
model
resource
responders_controller
scaffold
scaffold_controller
system_test
task
...
In this article, we will use the rails db:migrate
and rails generate model
options to create our models and save them to db/schema.rb
, the file that represents our database.
The solution
First, we will create the User
and Meeting
models.
$ rails generate model User name:string email:string
$ rails generate model Meeting starts_at:datetime ends_at:datetime available_user:references requester_user:references
This will generate the migration under db/migrate
, the model files under app/models
, and the test files. The migration for Meeting
should look like this:
class CreateMeetings < ActiveRecord::Migration[6.0]
def change
create_table :meetings do |t|
t.datetime :starts_at
t.datetime :ends_at
t.references :available_user, null: false, foreign_key: true
t.references :requester_user, null: false, foreign_key: true
t.timestamps
end
end
end
But if you try to run rails db:migrate
to create the tables in the database, an error will be raised on your terminal since there aren't any tables called available_user
and requester_user
to make reference as Foreign Key. With that in mind, our next step is to remove the foreign_key: true
, create the foreign key reference with the correct columns, and then the file should look like this:
class CreateMeetings < ActiveRecord::Migration[6.0]
def change
create_table :meetings do |t|
t.datetime :starts_at, null: false
t.datetime :ends_at, null: false
t.references :available_user, null: false # Remove foreign_key: true
t.references :requester_user, null: false # Remove foreign_key: true
t.timestamps
end
add_foreign_key :meetings, :users, column: :available_user_id
add_foreign_key :meetings, :users, column: :requester_user_id
end
end
Now we can safely run rails db:migrate
and see the changes on db/schema.rb
ActiveRecord::Schema.define(version: 2020_06_13_202030) do
create_table "meetings", force: :cascade do |t|
t.datetime "starts_at"
t.datetime "ends_at"
t.integer "available_user_id", null: false
t.integer "requester_user_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["available_user_id"], name: "index_meetings_on_available_user_id"
t.index ["requester_user_id"], name: "index_meetings_on_requester_user_id"
end
create_table "users", force: :cascade do |t|
t.string "name"
t.string "email"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
add_foreign_key "meetings", "users", column: "available_user_id"
add_foreign_key "meetings", "users", column: "requester_user_id"
end
In order to tell Rails what table will be referenced by the Meeting
model, we should make changes to our model files: app/models/meetings.rb
and app/models/users.rb
. They should look like this:
class Meeting < ApplicationRecord
belongs_to :available_user, class_name: 'User'
belongs_to :requester_user, class_name: 'User'
end
class User < ApplicationRecord
has_many :available_user_meetings, class_name: 'Meeting', foreign_key: 'available_user_id'
has_many :requester_user_meetings, class_name: 'Meeting', foreign_key: 'requester_user_id'
end
Now the relationship is done and we can create meetings and users in this way:
irb(main):001:0> user1 = User.create!(name: 'John Snow', email: 'night_watcher@thegreatwall.com')
=> #<User id: 1, name: "John Snow", email: "night_watcher@thegreatwall.com", created_at: "2020-06-12 20:34:41", updated_at: "2020-06-12 20:34:41">
irb(main):002:0> user2 = User.create!(name: 'Daenerys Targaryen', email: 'stormborn@dragonstone.com')
=> #<User id: 2, name: "Daenerys Targaryen", email: "stormborn@dragonstone.com", created_at: "2020-06-12 20:36:10", updated_at: "2020-06-12 20:36:10">
irb(main):003:0> starting_time = Time.zone.now
irb(main):004:0> ending_time = Time.zone.now + 2.hour
irb(main):005:0> meeting_about_family = Meeting.create!(starts_at: starting_time, ends_at: ending_time, available_user_id: user2.id, requester_user_id: user1.id)
=> #<Meeting id: 1, starts_at: "2020-06-12 20:40:45", ends_at: "2020-06-12 22:41:04", available_user_id: 2, requester_user_id: 1, created_at: "2020-06-12 20:41:41", updated_at: "2020-06-12 20:41:41">
irb(main):006:0> meeting_about_family.available_user
=> #<User id: 2, name: "Daenerys Targaryen", email: "stormborn@dragonstone.com", created_at: "2020-06-12 20:36:10", updated_at: "2020-06-12 20:36:10">
irb(main):007:0> meeting_about_family.requester_user
=> #<User id: 1, name: "John Snow", email: "night_watcher@thegreatwall.com", created_at: "2020-06-12 20:34:41", updated_at: "2020-06-12 20:34:41">
There's a better way
Instead of writing a two new lines in our migration, in order to replace foreign_key: true
, we could simply add foreign_key: { to_table: :users }
. The file should look like this:
class CreateMeetings < ActiveRecord::Migration[6.0]
def change
create_table :meetings do |t|
t.datetime :starts_at, null: false
t.datetime :ends_at, null: false
t.references :available_user, null: false, foreign_key: { to_table: :users }
t.references :requester_user, null: false, foreign_key: { to_table: :users }
t.timestamps
end
end
end
Easier, isn’t it?
Conclusion
I hope this post was useful to you, and remember: always look up the docs!
Posted on June 14, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.