how to create a 3 table join migration in ruby on rails - ruby-on-rails

I have 3 tables, students messages and coaches.
now i want to create a join migration table with message_id coach_id and student_id
please help to create the migration query
any help is highly appreciated
getting this error when i tried the below code
== 20150924072052 AssociationTable: migrating
=================================
-- create_table(:associations)
rake aborted!
StandardError: An error has occurred, all later migrations canceled:
Mysql2::Error: Key column 'student_id' doesn't exist in table: ALTER TABLE `associations` ADD CONSTRAINT `fk_rails_122f0db022` FOREIGN KEY (`student_id`) REFERENCES `student` (`id`)`

It will be something like this:
$ rails g migration AssociationTable
... which will create a file such as the following:
#db/migrate/association_table_[timestamp].rb
class AssociationTable < ActiveRecord::Migration
def change
create_table :associations do |t|
t.references :student
t.references :message
t.references :coache
t.timestamps null: false
end
end
end
This will create a table with the following columns:
id
student_id
message_id
coach_id
created_at
updated_at
This will be used in a has_many :through relationship, which requires you to have a join model:
#app/models/association.rb
class Association < ActiveRecord::Base
belongs_to :student
belongs_to :message
belongs_to :coach
end
--
To update you regarding the choice between has_many :through and has_and_belongs_to_many, here is what you need to know:
The main difference between the two is the fact that has_many :through utilizes a join model. Join models are basically a model through which ActiveRecord will populate dependent associative data. In short, it "joins" two models together.
Although the join model is the big difference between HABTM and HMT, there is another technical reason why you'd choose between them - HMT permits you to have extra attributes in the join model.
This is important for something like this:
#app/models/doctor.rb
class Doctor < ActiveRecord::Base
has_many :appointments
has_many :patients, through: :appointments
end
#app/models/appointment.rb
class Appointment < ActiveRecord::Base
#columns id | doctor_id | patient_id | time | created_at | updated_at
belongs_to :doctor
belongs_to :patient
end
#app/models/patient.rb
class Patient < ActiveRecord::Base
has_many :appointments
has_many :doctors, through: :appointments
end
The join model (appointment) will therefore be able to have specific data that you'll be able to use with the other models:
#doctor = Doctor.find 1
#appointments = #doctor.appointments.where(time: ____)
#patients = #appointments.patients
Lots of queries. I hope you get the idea.
--
has_and_belongs_to_many is a lot simpler, although I'm not sure if it works for 3 tables (don't see why it shouldn't).
This removes the need for a join model, and in the process prevents you from being able to use extra attributes in the association (notice how the join table has no id or timestamp attributes?). The naming convention for HABTM tables is albabetical_plurals - in your case recipient_messages
Again, I don't know if this will work for a 3-way join, but here's how you'd do it:
#app/models/student.rb
class Student < ActiveRecord::Base
has_and_belongs_to_many :messages, join_table: "recipients_messages", foreign_key: "recipient_id", association_foreign_key: "message_id"
end
#app/models/message.rb
class Message < ActiveRecord::Base
has_and_belongs_to_many :recipients, join_table: "recipients_messages", foreign_key: "message_id", association_foreign_key: "recipient_id"
end
Thinking about your request specifically, I'd say that you'd be better with has_many :through.
Reason being that if you're sending messages, you need a way to know to whom the message was sent, its content and whether it's been read.
I would use messages as the join model:
#app/models/message.rb
class Message < ActiveRecord::Base
#columns id | student_id | coach_id | message | read | created_at | updated_at
belongs_to :student
belongs_to :coach
end
#app/models/coach.rb
class Coach < ActiveRecord::Base
has_many :messages
end
#app/models/student.rb
class Student < ActiveRecord::Base
has_many :messages
end

Related

HABTM join table with different primary keys

I have a two models 1. category primary key is cust_number , 2. product primary key is id . Now i want to make a HABTM relationship between category and product. Here the problem is how to create join table with cust_number and product_id as a keys?
For this use case you want to use has_many through: and not has_and_belongs_to_many. They both acheive the same goal (a m2m association) but has_and_belongs_to_many is very limited.
AFAIK HABTM only takes the foreign_key option which is used to set the foreign key on the join table, there does not seem to be a way to tell it what PK that FK points to.
Run the generator to create the join model:
rails g model category_product product:references
Open up the migration and change the foreign key so that it points to your custom primary key:
create_table :category_products do |t|
t.references :product, foreign_key: true
t.references :category, foreign_key: { primary_key: "cust_number" }
end
Change the foreign key in the assocation:
class CategoryProduct < ApplicationRecord
belongs_to :category, foreign_key: "cust_number"
belongs_to :product
end
Then add the has_many through: assocations to each end:
class Category < ApplicationRecord
has_many :category_products
has_many :products, through: :category_products
end
class Product < ApplicationRecord
has_many :category_products
has_many :categories, through: :category_products
end

How can I use two records from the same Rails model as foreign keys in a different Rails model?

I have two models: person.rb and relationship.rb
I need my :relationships table to reference two rows from the :people table as foreign keys.
Here are the migrations for both tables:
class CreatePeople < ActiveRecord::Migration[5.1]
def change
create_table :people do |t|
t.string :first_name
t.string :second_name
t.integer :age
t.string :gender
t.timestamps
end
end
end
class CreateRelationships < ActiveRecord::Migration[5.1]
def change
create_table :relationships do |t|
t.references :person_a
t.references :person_b
t.string :status
t.timestamps
end
end
end
The idea is the :person_a and :person_b fields will both be individual records from the :people table referenced as foreign keys, while the :status field will just be a description of their relationship ("Married", "Friends", "Separated", etc.)
I'm trying to find out:
1) What is the additional code I have to write in the CreateRelationships migration above in order to set :person_a and :person_b up as foreign keys from the :people table?
2) What code do I need to write in the model files (person.rb and relationship.rb) for both tables below to define the relationship structure I'm talking about?
class Person < ApplicationRecord
end
class Relationship < ApplicationRecord
end
I've found one other question on here that deals with this issue, but the answers given were conflicting, some incomplete, and others working with older versions of Rails. None of them have worked for me.
I'm using Rails 5.1.4
You have defined you migration correctly just add the following in your model to define the relationship between the model.
class Person < ApplicationRecord::Base
has_many :relationships, dependent: :destroy
end
class Relationship < ApplicationRecord::Base
belongs_to :person_a, :class_name => 'Person'
belongs_to :person_b, :class_name => 'Person'
end
This allows you to access the Person that a Relationship belongs to like this:
#relationship.person_a #user assigned as the first person.
#relationship.person_b #user assigned as the second person.
Hope this works.
EDIT: Apologies for a rushed and wrong answer.
Initially, I thought that simple has_many/belongs_to association is possible and sufficient.
class Person < ApplicationRecord
has_many :relationships #dependent: :destroy
end
class Relationship < ApplicationRecord
belongs_to :person_a, class_name: "Person"
belongs_to :person_b, class_name: "Person"
enum status: [:married, :friends, :separated]
end
As #engineersmnky pointed out, has_many association can't work here because there is no person_id column in relationships table. Since we can declare only one custom foreign key in has_many association, it's not possible to declare it here this way. belongs_to will work, but I don't think that's enough.
One way is to skip declaring has_many and stick to custom method for querying relationships:
class Person < ApplicationRecord
def relationships
Relationship.where("person_a_id = ? OR person_b_id = ?", id, id)
end
end
It will give you an ActiveRecord::Relation to work with, containing exactly the records you need. The drawbacks of this solution are numerous - depending on your needs, you will probably need more code for inserting data, starting with a setter method to assign relationships to people...
What could be a real solution, is to have a composite primary key in Relationship model - composed of :person_a_id and :person_b_id. ActiveRecord doesn't support composite primary keys, but this gem seems to fill the gap. Apparently it allows to declare such key and use it as a foreign key in a has_many association. The catch is that your person_a/person_b pairs would have to be unique across relationships table.
I had to do the same for a chat module, this is an example to how you can do it:
class Conversations < ApplicationRecord
belongs_to :sender, foreign_key: :sender_id, class_name: 'User'
belongs_to :recipient, foreign_key: :recipient_id, class_name: 'User'
end
class User < ApplicationRecord
...
has_many :conversations
has_many :senders, through: :conversations, dependent: :destroy
has_many :recipients, through: :conversations, dependent: :destroy
...
end
More explanations at complex-has-many-through
Hope it helps,
You can do like this
In relationship model write
belongs_to : xyz, class_name: "Person", foreign_key: "person_a"
belongs_to : abc, class_name: "Person", foreign_key: "person_b"
In Person model write
has_many :relationships, dependent: :destroy
hope it will help

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

Rails Model Relationships for Rugby Players

I have two models in my rails 4.2.4 project that I'd like to join together and I'm struggling with creating the correct relationship between the two.
The domain in question is rugby and the code is all available at https://github.com/rgbycch/rgbycch-rest/tree/rails. The models are:
Player (https://github.com/rgbycch/rgbycch-rest/blob/rails/app/models/player.rb)
PlayerPosition (https://github.com/rgbycch/rgbycch-rest/blob/rails/app/models/player_position.rb)
I'd like to be able to create a relationship whereby a Player can have multiple favoured PlayingPositions. I believe my db structure would need to look something like this:
create_table :favoured_positions do |t|
t.references :player, index: true, foreign_key: true # fk to player table
t.references :player_position, index: true, foreign_key: true # fk to player_position table
t.integer :num_favs # additional data for a favored playing position
t.timestamps null: false
end
I can generate a new FavoredPosition model like this I guess:
bundle exec bin/rails generate model FavouredPosition player:references player_position:references num_favs:integer
which generates a class that looks like:
class FavouredPosition < ActiveRecord::Base
belongs_to :player
belongs_to :player_position
end
I'm not sure how to alter my Player and PlayerPosition models to reflect this new relationship. Is the following correct:
class Player < ActiveRecord::Base
has_many :favored_positions, :through => :favoured_positions
end
class PlayerPositions < ActiveRecord::Base
has_many :favored_playing_positions, :through => :favoured_positions
end
Would you advise me to add indexes to either of these tables too?
Thanks,
Sean
You would need to set up the associations like this:
class Player < ActiveRecord::Base
has_many :favoured_positions
has_many :player_positions, through: :favoured_positions
end
class PlayerPosition < ActiveRecord::Base
has_many :favoured_positions
has_many :players, through: :favoured_positions
end
See: http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
Firstly, change PlayerPosition to Position, it's far simpler and works with the DRY (Don't Repeat Yourself) principle.
Secondly, what you're referring to is an ActiveRecord association. These are basically how the ActiveRecord ORM allows you to associate objects in Rails.
--
has_many :through
The association I feel you'd best using is either going to be has_many :through or has_and_belongs_to_many. Since you've comitted to has_many :through, here's what you need to do:
#app/models/player.rb
class Player < ActiveRecord::Base
#columns id | etc | etc | created_at | updated_at
has_many :favoured_positions
has_many :positions, through: :favoured_positions
end
#app/models/position.rb
class Position < ActiveRecord::Base
#columns id | etc | etc | created_at | updated_at
has_many :favoured_positions
has_many :players, through: :favoured_positions
end
#app/models/favoured_position.rb
class FavouredPosition < ActiveRecord::Base
#columns id | position_id | player_id | any | other | column | created_at | updated_at
belongs_to :position
belongs_to :player
end
--
has_and_belongs_to_many
An important caveat about the difference between has_many :through and has_and_belongs_to_many. If you didn't have any extra columns in your favoured_positions join model, I would recommend you use has_and_belongs_to_many. It's far simpler for what you need:
#app/models/player.rb
class Player < ActiveRecord::Base
has_and_belongs_to_many :positions
end
#app/models/position.rb
class Position < ActiveRecord::Base
has_and_belongs_to_many :players
end
You'd then make a join table as follows:
#players_positions
player_id | position_id

Has many :through association not found

I have two models that can have tags added to them.
Player
Ticket
and I have a Tag model which belongs to both so I have two join models
tag_ticket
tag_player
I am getting a Could not find the association :tag_tickets in model Ticket error but my association is in there.
class Ticket < ActiveRecord::Base
has_many :tag_tickets
has_many :tags, :through => :tag_tickets
end
I'm just focusing on the Ticket model but the player model should look similar.
this is my migration for TagTicket
class CreateTagTickets < ActiveRecord::Migration
def change
create_table :tag_tickets do |t|
t.integer :ticket_id
t.integer :tag_id
t.timestamps
end
end
end
You need to specify the :tag_tickets join first like this:
class Ticket < ActiveRecord::Base
has_many :tag_tickets
has_many :tags, :through => :tag_tickets
end
You would also need to specify the joins in your TagTicket model:
class TagTicket < ActiveRecored::Base
belongs_to :ticket
belongs_to :tag
end
Alternatively, you can skip all this and use a habtm join (only recommended if the tag_tickets join is truly only used as a join and has no primary key for itself). In this case you would have no TagTicket model (just a tag_tickets table) and the Ticket model would look like this:
class Ticket < ActiveRecord::Base
has_and_belongs_to_many :tags
end

Resources