Joined Table Confusion - Ruby on Rails - ruby-on-rails

I'm a little befuddled on how to create a joined table in ROR.
Let's say there are 2 tables:
User
Book
In order to create a joined table user_book, my initial impression was that you did this:
rails g migration user_book
But I've been told that to create an actual joined table, I generate an actual model:
rails g model user_book
Is that correct?
And if the second one is correct, then once I've created it, I open it and add both the book_id, and user_id to the migrated file, and then run rake db:migrate command. Is this correct?
EDIT:
Just wanted to added that
User model would have this: has_many :books, through: :user_book
Book model would have this: has_many :users, through: :user_book

If you want to use has_many through:, you need a table to handle that. As well as a model object to represent that table.
How you go about creating it (rails g model or rails g migration) doesn't ultimately matter. All those commands do is create either a model AND a migration file, or just a migration file. Personally, I would do the rails g model, since you need both.
You'd just add a user_books table with 2 IDs, user_id and book_id.
Migration would look something like this:
class CreateUserBooks < ActiveRecord::Migration
def change
create_table :user_books do |t|
t.references :user, index: true, foreign_key: true, null: false
t.references :book, index: true, foreign_key: true, null: false
t.timestamps null: false
end
end
end
Models would look like this:
class User < ActiveRecord::Base
has_many :user_books
has_many :books, through: :user_books
end
class Book < ActiveRecord::Base
has_many :user_books
has_many :users, through: :user_books
end
class UserBook < ActiveRecord::Base
belongs_to :user
belongs_to :book
end

Related

how to create a record for a join table?

I have the following associations set up:
class Book < ActiveRecord::Base
belongs_to :author
belongs_to :category
has_many :users_books
has_many :users, through: :user_books
end
and
class User < ActiveRecord::Base
has_many :users_books
has_many :books, through: :users_books
end
I created a join table migration as I ought to
class CreateUsersBooks < ActiveRecord::Migration[4.2]
def change
create_table :users_books do |t|
t.integer :user_id
t.integer :book_id
end
end
end
Now I need to create a method called check_out_book, that takes in a book and a due_date as arguments. When a user checks out a book, it should create a new UserBook record (or Checkout record or whatever you want to call you join table/model). That new UserBook record should have a attribute (and therefore table column) of returned? which should default to false. How would I go about creating this method and the migrations?
Your tablenames and your associations in Rails should always be singular_plural with the exception of the odd duckling "headless" join tables used by the (pretty useless) has_and_belongs_to_many association.
class CreateUserBooks < ActiveRecord::Migration[4.2]
def change
create_table :user_books do |t|
t.references :user
t.references :book
t.boolean :returned, default: false
end
end
end
class UserBook < ActiveRecord::Base
belongs_to :user
belongs_to :book
end
class Book < ActiveRecord::Base
belongs_to :author
belongs_to :category
has_many :user_books
has_many :users, through: :user_books
end
class User < ActiveRecord::Base
has_many :user_books
has_many :books, through: :user_books
end
But you should really use a better more descriptive name that tells other programmers what this represents in the domain and not just a amalgamation of the two models it joins such as Loan or Checkout.
I would also use t.datetime :returned_at to create a datetime column that can record when the book is actually returned instead of just a boolean.
If you want to create a join record with any additional data except the foreign keys you need to create it explicitly instead of implicitly (such as by user.books.create()).
#book_user = Book.find(params[:id]).book_users.create(user: user, returned: true)
# or
#book_user = current_user.book_users.create(book: user, returned: true)
# or
#book_user = BookUser.new(user: current_user, book: book, returned: true)

Ruby on Rails - Polymorphic associations + joint table + reference the same model twice?

I am having a hard time using polymorphic associations in a joint table where one option could lead to the association of two instances of the same table. I am relatively new to programming so I hope my question makes sense.
Basically, I have 3 models where my aim is to make associations between different products:
Product
ExtrenalProduct
Integration (polymorphic)/joint table
The Integration table will link 2 Products or 1 Product and 1 ExternalProduct
Here is my migration file:
class CreateIntegrations < ActiveRecord::Migration[5.1]
def change
create_table :integrations do |t|
t.references :main_product
t.belongs_to :integratable, polymorphic: true
t.timestamps
end
add_index :integrations, [:integratable_id, :integratable_type]
add_foreign_key :integrations, :products, column: :main_product_id, primary_key: :id
end
end
Here is my Integration Model:
class Integration < ApplicationRecord
belongs_to :integratable, polymorphic: true
belongs_to :main_product, class_name: 'Product'
end
Here is my ExternalProduct Model:
class ExternalProduct < ApplicationRecord
has_many :integrations, as: :integratable
end
Here is my Product Model:
has_many :integrations, class_name: 'Integration', foreign_key: 'main_product_id', dependent: :destroy
has_many :integrations, as: :integratable
My question is regarding the way I can query all Integrations from #product. As for now, I have to build a custom method:
def all_integrations
Integration.where(main_product_id: id).or(Integration.where(integratable_type: 'Product', integratable_id: id))
end
I would like to be able to do a: #product.integrations (which currently retrieves an empty array)
Any clue on what I am doing wrong or how I could make this code DRY? I feel I am missing something.
Thanks for reading!

Naming convention for Rails models based on existing tables

I have two models. Users and Jobs. I have a join table to indicate a User's interest in a Job. I was using a has_and_belongs_to_many relationship for Users and Jobs. I now realize I need to be using a has_many :through relationship. Because of that, I need to have a model for my join table or I get undefined Constant errors. My join table is called jobs_users. What should I name the .rb file for the model and what should the class name be in order to get Rails magic to work? I have found SO questions that imply the class should be JobUser, but nothing on what the .rb file should be named.
Naming convention for Rails models based on existing tables
Model class names use CamelCase. These are singular, and will map automatically to the plural database table name.
> 'JobsUser'.underscore
=> "jobs_user"
This means your file should be named jobs_user.rb. And class will look like below
class JobsUser < ApplicationRecord
...........
end
For fresh migration, you can rollback and destroy current table migration e.g jobs_users and then run this command
rails g model UserJob user:references job:references
db/migrate/TIMESTAMP_create_user_jobs.rb file looks like below
class CreateUserJobs < ActiveRecord::Migration[5.1]
def change
create_table :user_jobs do |t|
t.references :user, foreign_key: true
t.references :job, foreign_key: true
t.timestamps
end
end
end
then migrate this and created model file name is user_job.rb
has_many :through relationship will looks like this
#user.rb
class User < ApplicationRecord
has_many :user_jobs, dependent: :destroy
has_many :jobs, through: :user_jobs
end
#job.rb
class Job < ApplicationRecord
has_many :user_jobs, dependent: :destroy
has_many :users, through: :user_jobs
end
#user_job.rb
class UserJob < ApplicationRecord
belongs_to :user
belongs_to :job
end
That's it

What is the behaviour of create_join_table of ActiveRecord::Migration?

I've discovered a nice way to generate join tables for my HABTM relationships in my Rails app.
rails g migration CreateJoinTable table1 table2
This generates an ActiveRecord::Migration that employs the method create_join_table
I'm wondering what this wonderful mysterious method does. I guess it makes a table (probably without an id field) that has a column for table1 foreign key and a column for table2 foreign key, but does the table have any other features?. My habit for join tables has always been to add a unique index across both those columns so that a relationship between a record in table1 and a record in table2 cannot be entered twice.
My question boils down to: If I use create_join_table do I need to keep adding that unique index, or does this method do that for me (I think it should)?
The documentation I usually look at doesn't go into this sort of detail.
Called without any block, create_join_table just creates a table with two foreign keys referring to the two joined tables.
However, you can actually pass a block when you call the method to do any additional operations (say, adding indexes for example). From the Rails doc:
create_join_table :products, :categories do |t|
t.index :product_id
t.index :category_id
end
Have a look at create_join_table documentation.
You can check the create_join_table code at the bottom (click on Source: show).
SchemaStatements#create_join_table() only creates join table without any fancy indexes etc,... So if you wish to use uniqueness constraint on two fields you have to do something like this:
class CreateJoinTable < ActiveRecord::Migration
def change
create_join_table :posts, :users do |t|
t.integer :post_id, index: true
t.integer :user_id, index: true
t.index [:post_id, :user_id], name: 'post_user_un', unique: true
end
end
end
Please also note that create_join_table by default does NOT create id field.
It turns out it doesn't do any more than the basics I described in the question. I found this out simply by running the migration and seeing what ends up in db/schema.rb
For those interested, to get the unique index do this:
class CreateJoinTable < ActiveRecord::Migration
def change
create_join_table :posts, :users
add_index :posts_users, [:post_id, :user_id], unique: true, name: 'index_posts_users'
end
end
Also be aware of how you define the dependent destroy for this join table.
If you later move away from HABTM and define the relationships using through: and get it wrong you might run into the 'to_sym' error I reported here.
Make sure you have defined the destroy like this:
class Proposal < ActiveRecord::Base
has_many :assignments
has_many :products, through: :assignments, dependent: :destroy # <- HERE
end
class Product < ActiveRecord::Base
has_many :assignments
has_many :proposals, through: :assignments, dependent: :destroy # <- HERE
end
class Assignment < ActiveRecord::Base
belongs_to :product
belongs_to :proposal
end
not this:
class Proposal < ActiveRecord::Base
has_many :assignments, dependent: :destroy
has_many :products, through: :assignments
end
class Product < ActiveRecord::Base
has_many :assignments, dependent: :destroy
has_many :proposals, through: :assignments
end
class Assignment < ActiveRecord::Base
belongs_to :product
belongs_to :proposal
end

Ruby On Rails scaffold need to include foreign keys?

I'm learning the basics of ruby on rails and I want to make some simple queries but I have a doubt:
I will have these models:
class Client < ActiveRecord::Base
has_one :address
has_many :orders
has_and_belongs_to_many :roles
end
class Address < ActiveRecord::Base
belongs_to :client
end
class Order < ActiveRecord::Base
belongs_to :client, counter_cache: true
end
class Role < ActiveRecord::Base
has_and_belongs_to_many :clients
end
Now, I will use scaffold to generate all the things, and I want to know if I have to directly put the foreign keys in the scaffols, like:
rails generate scaffold Adress street:string number:integer client_id:integer
Or when I make those associations and then migrate my db they will be implicit?
I don't know if I explain myself in the best way.
Thanks
Yep, there is no reference. You need to either pass the client_id or a reference to Client model, e.g:
rails generate scaffold Address street:string number:integer client_id:integer:index
or
rails generate scaffold Address street:string number:integer client:references
Either, in rails 4 you can use belongs_to this way:
Assume that you have a user model in your application
rails g scaffold comment belongs_to:user text:string
it generates that class in your migrate folder:
class CreateComments < ActiveRecord::Migration
def change
create_table :comments do |t|
t.string :text
t.belongs_to :user, index: true, foreign_key: true
t.timestamps null: false
end
end
end
you should execute rake db:migrate then, this command create user_id properties as a index column in your database table.

Resources