Seeding an ActiveRecord Database with objects from another table - ruby-on-rails

I'm new to Rails, I'm making my first app and I have (another) question:
I'm adding a "decks of cards" feature to my flashcard app, but having trouble with the modeling to generate a "deck" that is populated by card objects from my card table.
Here are my associations:
User has_many :decks
Deck belongs_to :user
Deck has_many :cards
Card belongs_to :deck
class Card < ActiveRecord::Base
belongs_to :deck
validates :front, presence: true
validates :back, presence: true
validates :deck_id, presence: true
end
And here's my decks migration/table:
class Decks < ActiveRecord::Migration
def change
create_table :decks do |t|
t.string :name, null: false
t.string :card
t.integer :user_id, null: false
end
end
end
My issue is that I would like for the "card" column in the decks table to consist of Card objects, so that I can access/manipulate their methods, but I'm not sure how exactly to do this. I tried populating the table with t.string of ":card" in the hopes that this would work but it only comes up blank. I am wondering if this is even possible or advisable or is there a better way?
If anyone can point me to resources/offer advice on this, thank you. I checked the docs/SO and can't seem to find anything.

What you're describing can be easily accomplished.
Provided that User, Deck and Card are ActiveRecord models, you can associate them by setting foreign keys to connect the tables. A foreign key is an integer column that contains the id of the associated model (its table's primary key)
The Rails convention is to use belongs_to and has_many to declare "one to many" associations (docs). These methods will add to your model objects the required methods to represent and interact with the association.
On the DB schema side of things, you'll need to set the foreign keys on the models' tables that declare the belongs_to associations.
So, if you have these models:
class User < ActiveRecord::Base
has_many :decks
end
class Deck < ActiveRecord::Base
belongs_to :user
has_many :cards
end
class Card < ActiveRecord::Base
belongs_to :deck
end
You'll need these migrations:
class CreateDecks < ActiveRecord::Migration
def change
create_table :decks do |t|
# your other columns...
t.integer :user_id
end
end
end
class CreateCards < ActiveRecord::Migration
def change
create_table :cards do |t|
# your other columns...
t.integer :deck_id
end
end
end

With a has_many relation you don't store the foreign key on the owning table. Instead you have a deck_id column in the cards table.
An example of using an association:
# Load a deck and include the cards in the query
#deck = Deck.joins(:cards).last
#deck.cards.each do |card|
puts card.front
end
# create a new card
#deck.cards.new(front: 'foo', back: 'bar')
#deck.save # will save the card as well.
http://guides.rubyonrails.org/association_basics.html

You don't want the card column in your decks table. The has_many :cards and belongs_to :deck lines in your models provide that functionality for you to be able to do things like #deck.cards.
You just have to be sure to assign a deck_id when you create a new Card object.
You should read through the Rails Guides on associations, and also on database seeding.

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.

How to use one foreign key for two models in Ruby on Rails?

Here is my migration file
class CreateTestRevisions < ActiveRecord::Migration
def change
create_table :test_revisions do |t|
t.integer :user_id
t.integer :test_id
t.integer :question_id
t.integer :test_type
t.timestamps null: false
end
end
end
I have two models: OnlineTest & AllindiaTest.
How can I relate the t.integer :test_id to these two models?
You can set up a polymorphic association like this:
class OnlineTest < ActiveRecord::Base
has_many :test_revisions, as: :testable
end
class AllindiaTest < ActiveRecord::Base
has_many :test_revisions, as: :testable
end
class TestRevision < ActiveRecord::Base
belongs_to :testable, polymorphic: true
end
And make sure you have two columns in test_revisions table:
testable_id: integer
testable_type: string
testable_id will store id of OnlineTest or AllindiaTest.
testable_type will store OnlineTest or AllindiaTest string.
So if you have a test_devision, you can use test_devision.testable to get OnlineTest or AllindiaTest, depend on testable_type.
More information about Polymorphich assocication.
You can use foreign_key option to specify the same key for two models like:
class TestRevision < ApplicationRecord
belongs_to :online_test, foreign_key: :test_id
belongs_to :allindia_test, foreign_key: :test_id
end
Or the best way I think is to treat both of these objects as one through delegate:
delegate :allindia_test, to: :online_test
This way, you only need to have one association: online_test, and when you call, allindia_test, the call will be delegated to online_test.
Edit:
Well, if you would like to have two different relations, you would need two different ids in that case: online_test_id & all_india_test_id.
class TestRevision < ApplicationRecord
belongs_to :online_test
belongs_to :all_india_test
end

Is it advisable to use :foreign_key in my migrations rather than just adding user_id?

I am using rails 4.2, I just want to know if there would be any difference if I use the :foreign_key keyword in my migrations rather than just adding a user_id column to add relationship to my models ?
YES
The key difference is not on the application layer but on the database layer - foreign keys are used to make the database enforce referential integrity.
Lets look at an example:
class User < ActiveRecord::Base
has_many :things
end
class Thing < ActiveRecord::Base
belongs_to :user
end
If we declare things.user_id without a foreign key:
class CreateThings < ActiveRecord::Migration
def change
create_table :things do |t|
t.integer :user_id
t.timestamps null: false
end
end
end
ActiveRecord will happily allow us to orphan rows on the things table:
user = User.create(name: 'Max')
thing = user.things.create
user.destroy
thing.user.name # Boom! - 'undefined method :name for NilClass'
While if we had a foreign key the database would not allow us to destroy user since it leaves an orphaned record.
class CreateThings < ActiveRecord::Migration
def change
create_table :things do |t|
t.belongs_to :user, index: true, foreign_key: true
t.timestamps null: false
end
end
end
user = User.create(name: 'Max')
thing = user.things.create
user.destroy # DB says hell no
While you can simply regulate this with callbacks having the DB enforce referential integrity is usually a good idea.
# using a callback to remove associated records first
class User < ActiveRecord::Base
has_many :things, dependent: :destroy
end

Confusion about ActiveRecord associations and foreign keys

In the below example, do I have to create employee_id in the Office model, or is it created automatically by db:migrate?
class Employee < ActiveRecord::Base
has_one :office
end
class Office < ActiveRecord::Base
belongs_to :employee # foreign key - employee_id
end
Feels like I'm missing something fundamental. I'm trying to get a basic one to many relationship working, where I can use a drop-down select of objects from the one side. Are there any good basic tuts explaining how this works?
I had to create _ids in all the models where I wanted this to work, but it doesn't seem right from examples I've looked at.
two steps.
firstly, you have to create an employee_id field in the office table in the migration file. you will have something like that :
class CreateOffices < ActiveRecord::Migration
def change
create_table :offices do |t|
t.string :name
t.integer :employee_id
t.timestamps
end
end
end
secondly, you have to define the association in the model. by convention, if you name the foreign_key field employee_id, you don't have to specify the name of it in the model.
class Office < ActiveRecord::Base
belongs_to :employee
end
should be enough.
Associations in ActiveRecord comprise two parts. Hooking together the model objects (like you've done) and setting up the database. So you'll need to define the association in your migration like so:
def change
create_table :offices do |t|
# Other migrations
t.references :employee
end
end
Alternatively you can do t.integer :employee_id which will achieve the same end too.

Scaffolding ActiveRecord: two columns of the same data type

Another basic Rails question:
I have a database table that needs to contain references to exactly two different records of a specific data type.
Hypothetical example: I'm making a video game database. I have a table for "Companies." I want to have exactly one developer and exactly one publisher for each "Videogame" entry.
I know that if I want to have one company, I can just do something like:
script/generate Videogame company:references
But I need to have both companies. I'd rather not use a join table, as there can only be exactly two of the given data type, and I need them to be distinct.
It seems like the answer should be pretty obvious, but I can't find it anywhere on the Internet.
Just to tidy things up a bit, in your migration you can now also do:
create_table :videogames do |t|
t.belongs_to :developer
t.belongs_to :publisher
end
And since you're calling the keys developer_id and publisher_id, the model should probably be:
belongs_to :developer, :class_name => "Company"
belongs_to :publisher, :class_name => "Company"
It's not a major problem, but I find that as the number of associations with extra arguments get added, the less clear things become, so it's best to stick to the defaults whenever possible.
I have no idea how to do this with script/generate.
The underlying idea is easier to show without using script/generate anyway. You want two fields in your videogames table/model that hold the foreign keys to the companies table/model.
I'll show you what I think the code would look like, but I haven't tested it, so I could be wrong.
Your migration file has:
create_table :videogames do |t|
# all your other fields
t.int :developer_id
t.int :publisher_id
end
Then in your model:
belongs_to :developer, class_name: "Company", foreign_key: "developer_id"
belongs_to :publisher, class_name: "Company", foreign_key: "publisher_id"
You also mention wanting the two companies to be distinct, which you could handle in a validation in the model that checks that developer_id != publisher_id.
If there are any methods or validation you want specific to a certain company type, you could sub class the company model. This employs a technique called single table inheritance. For more information check out this article: http://wiki.rubyonrails.org/rails/pages/singletableinheritance
You would then have:
#db/migrate/###_create_companies
class CreateCompanies < ActiveRecord::Migration
def self.up
create_table :companies do |t|
t.string :type # required so rails know what type of company a record is
t.timestamps
end
end
def self.down
drop_table :companies
end
end
#db/migrate/###_create_videogames
class CreateVideogames < ActiveRecord::Migration
create_table :videogames do |t|
t.belongs_to :developer
t.belongs_to :publisher
end
def self.down
drop_table :videogames
end
end
#app/models/company.rb
class Company < ActiveRecord::Base
has_many :videogames
common validations and methods
end
#app/models/developer.rb
class Developer < Company
developer specific code
end
#app/models/publisher.rb
class Publisher < Company
publisher specific code
end
#app/models/videogame.rb
class Videogame < ActiveRecord::Base
belongs_to :developer, :publisher
end
As a result, you would have Company, Developer and Publisher models to use.
Company.find(:all)
Developer.find(:all)
Publisher.find(:all)

Resources