I have three relevant models:
class InventoryItem < ActiveRecord::Base
belongs_to :item, :foreign_key => :item_id
belongs_to :vendor
has_many :shopping_list_items
class ShoppingList < ActiveRecord::Base
has_many :shopping_list_items
belongs_to :user
end
class ShoppingListItem < ActiveRecord::Base
belongs_to :shopping_list
belongs_to :inventory_item
end
What I am trying to do is create a sidebar shopping list that will autoupdate ShoppingListItem attributes (specifically price) when a respective attribute is changed in the InventoryItem table (again, price). My thinking was to have these three classes and map ShoppingListItems directly to InventoryItems, but I'm unsure of how to proceed with that. Alternatively, is it possible to do away with the ShoppingListItem class entirely and make ShoppingList be a collection of InventoryItems specified by the user? Any input is much appreciated. Thanks in advance!
To redo my comments as a real answer, yes, it is possible to forego the ShoppingListItem model in this case, as long as you don't need to attach any data to that model itself (e.g. the time the item was added to the list). You could link your models as follows with a has_and_belongs_to_many association:
class InventoryItem < ActiveRecord::Base
belongs_to :item
belongs_to :vendor
has_and_belongs_to_many :shopping_lists
end
class ShoppingList < ActiveRecord::Base
belongs_to :user
has_and_belongs_to_many :inventory_items
end
This will allow you to assign an array of inventory items to the inventory_items attribute of a shopping list, and Rails will create or delete the necessary join records automatically. More information from the Rails guides. Note that you'll still need a join table in your schema -- there just isn't a model associated with it. In your case, the migration might look like this:
create_table :inventory_items_shopping_lists, id: false do |t|
t.references :inventory_item
t.references :shopping_list
end
add_index :inventory_items_shopping_lists, :inventory_item_id
add_index :inventory_items_shopping_lists, :shopping_list_id
add_index :inventory_items_shopping_lists, [:inventory_item_id, :shopping_list_id], unique: true
Note that in order for Rails to auto-detect the table, its name should be the combined plural forms of both models in alphabetical order. Otherwise you need to specify the table name using the join_table option when defining the association.
Related
I'm currently creating a Rails app using rails 6. I have model named user_teams and a model named players. What I want to do is have multiple fields in the user_teams model such as player_one, player_two...player_six that are each an individual reference to a player.
I have implemented this but in a slightly janky way. I have the six fields in my user_team model and each is an integer of the id associated with the chosen player. I feel like there is a much better way to do this but I can't figure out how. Any help would be greatly appreciated!
Also once I have the references set up properly, how can I validate each player reference so that duplicate players can't be in the same user_team?
Example
UserTeam
player_one: 1
player_two: 1
Would not be allowed but
UserTeam
player_one: 1
player_two: 2
Would be allowed?
This is a standard many to many association and what you want is a join table that joins players and teams:
class Player < ApplicationRecord
has_many :positions
has_many :user_teams, through: :positions
end
class UserTeam < ApplicationRecord
has_many :positions
has_many :players, through: :positions
end
# generate the model and migration with:
# rails g model position player:belongs_to user_team:belongs_to
class Position < ApplicationRecord
belongs_to :player
belongs_to :user_team
end
Another way to set this up would be with has_and_belongs_to_many but as there is no join model there is no way to access any additional columns on the join table and you cannot join it directly.
You can validate the uniqueness of a player on a team by using validates_uniqueness with the scope option:
class Position < ApplicationRecord
belongs_to :player
belongs_to :user_team
validates :player_id, uniqueness: { scope: :user_team_id }
end
This will prevent most duplicates but is prone to race conditions which could lead to duplicate data being inserted so it should be complimented with a unique index that enforces the constraint on the database level:
class CreatePositions < ActiveRecord::Migration[6.0]
def change
create_table :positions do |t|
t.belongs_to :user_team, null: false, foreign_key: true
t.belongs_to :player, null: false, foreign_key: true
t.timestamps
end
add_index :positions, [:user_team_id, :player_id], unique: true
end
end
UserTeam model should have field name &
Player model should have field user_team_id
model/UserTeam.rb
class UserTeam < ApplicationRecord
has_many :players
end
model/Player.rb
class Player < ApplicationRecord
belongs_to :user_team
end
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
I was following an online tutorial on ActiveRecord where the instructor writes the following code when defining a table and its relationship:
#Setup of the database table
class CreateTimeEntries < ActiveRecord::Migration
def change
create_table :time_entries do |t|
t.float :time
t.belongs_to :customer
t.belongs_to :employee
t.timestamps
end
end
end
#Relationship definition in the relevant model
class TimeEntry < ActiveRecord::Base
belongs_to :customer
belongs_to :employee
end
Aren't those lines redundant ?
#in table setup
t.belongs_to :customer
t.belongs_to :employee
#in the relevant model
belongs_to :customer
belongs_to :employee
I understood that the lines in the db table setup are here to define foreign keys, how come then that we need to define the relationship in the model as well?
I thought foreign keys were by themselves defining such relationship.
What am I missing here? Can't find any clear answer on the web. Thanks a lot.
Those are two completely different methods:
Within migration belongs_to :parent only creates parent_id column - it is only executed when you run migration. No foreign key is defined here - rails do not believe those are needed. This is just a syntatic sugar for:
t.integer :parent_id
After migrations are run, all rails know is that your TimeEntry model has parent_id column - it has absolutely no other meaning. This is why you have to give it the meaning with belongs_to - this method executed in context of ActiveRecord objects will create the association - it will create a method parent with all the arel power behind it to fetch referenced object as well as provide some validation and save hooks to make working with the object easier. It is not always necessary to have this defined.
In short, without belongs_to you would only be able to call my_model.parent_id (provided by belongs_to in migration), but not my_model.parent
They have different goals. The migartion define de db table and allow create or modify the table. The model define to the Rails framework how to work with db tables.
You must define your models like this:
class Customer < ActiveRecord::Base
has_many :time_entries
end
class Employe < ActiveRecord::Base
has_many :time_entries
end
class TimeEntry < ActiveRecord::Base
belongs_to :customer
belongs_to :employe
end
At the migration level, belongs_to :customer add a field customer_id that relate models.
I have a User model and a product model.
User has_many :products, :dependent => :destroy
Product belongs_to :user, :foreign_key => "user_id", touch: true
I want to create a wishlist for every user.
So i have to create a wishlist model with proper association.
But i don't know how to start.
I presume that the wishlist model contain an id, user_id and product_id field
Do i have to use has_many through association or a has_and_belongs_to_many ?
I also want that if a user is destroyed to destroy his wishlist.
What is the best way to do?
Many thanks!
As #JZ11 pointed out, you shouldn't be linking a Product directly to a User (unless a User actually 'owns' a product for some reason). However, what was missed is the model that makes up a Wishlist item:
class User < ActiveRecord::Base
has_many :wishlists # or has_one, depending on how many lists a User can have...
end
class Product < ActiveRecord::Base
has_many :wishlist_items
end
class Wishlist < ActiveRecord::Base
belongs_to :user
has_many :wishlist_items
has_many :products, :through => :wishlist_items
end
class WishlistItem < ActiveRecord::Base
belongs_to :product
belongs_to :wishlist
end
Naturally, you should be adding :dependent => :destroy where necessary.
You don't need the has_many :products relationship on User.
I don't think it makes sense for User and Product to be linked outside of a Wishlist.
class Wishlist < ActiveRecord::Base
has_many :products
belongs_to :user
end
class User < ActiveRecord::Base
has_one :wishlist, dependent: :destroy
end
class Product < ActiveRecord::Base
belongs_to :wishlist
end
To create your join table, do:
rails g migration create_products_users_table
Once you've done that, you need to add some code, below, to create the fields in the join table. Notice the :id => false, because you do not need an id in the join table:
class CreateProductsUsersTable < ActiveRecord::Migration
def change
create_table :products_users, :id => false do |t|
t.references :product
t.references :user
end
add_index :products_users, [:product_id, :user_id]
add_index :products_users, :user_id
end
end
The code above also creates some indexes and ensures that you don't have duplicates even at the database level.
Your models would then have to look like this:
class Product < ActiveRecord::Base
has_and_belongs_to_many :users
end
class User < ActiveRecord::Base
has_and_belongs_to_many :products
end
When you destroy a user correctly, like user.destroy and not just delete it (there is a difference), then the related rows in the join table will be deleted as well. This is built in to ActiveRecord.
Notice though, that doing this will not really let you use the join table. It will accept code like user.products = [product1, product2] etc, and other goodies, but no real use of a wish list.
If you do want to use a wish list, you will have to create and use the middle join table differently, using has_many :through (I didn't check PinnyM's answer but that might be the way to do it).
I'm trying to understand rails associations
I've the following tables and I've to define their relations, can any one please help me understand.
The tables are Products, ProductDistributors, and Distributors.
Every product has a distributor, a distributor carries multiple products
I defined these as
class Product < ActiveRecord::Base
has_one :product_distributor
has_one :distributor, through: :product_distributor
end
class ProductDistributor < ActiveRecord::Base
belongs_to :products
belongs_to :distributors
end
class Distributor < ActiveRecord::Base
has_many :product_distributors
has_many :products, through: :product_distributors
end
Is this correct? If not, how can I correct it?
I feel the problem lies in the Distributors class name because it's plural. when you say something like has_many :distributors, Rails by default will link to the Distributor class, but in your case the class name is Distributors.
Adding class_name option to your relationship declarations should work:
class Product < ActiveRecord::Base
has_one :product_distributor
has_one :distributor, through: :product_distributor, class_name: 'Distributors'
end
class ProductDistributor < ActiveRecord::Base
belongs_to :product
belongs_to :distributor, class_name: 'Distributors'
end
class Distributors < ActiveRecord::Base
has_many :product_distributors
has_many :products, through: :product_distributors
end
Also note that your belongs_to should be singular not plural. Please go through the Association guide for details: http://guides.rubyonrails.org/association_basics.html.
As I see, Product has one (belongs to) Distributor, and Distributor has many Products. So you not need to use ProductDistributor
class Product < ActiveRecord::Base
belongs_to :distributor
end
class Distributors < ActiveRecord::Base
has_many :products
end
Just add column *distributor_id* to products table
I recomend you to read Active Record Associations Guid to understand associations
The reason to use has_many through: is that you need to use a join table that isn't named in the standard Rails naming convention. In this case, ProductDistributor has such a table associated (product_distributors) because the Rails convention puts the tables in the name in lexicographic order and pluralizes them. The Rails table name would be distributors_products. If you created such a table with foreign keys to the ids of the distributors and products tables you wouldn't need to specify the join table and you could just say has_and_belongs_to_many :distributors on Products and has_and_belongs_to_many :products on Distributors.
What you have isn't quite right for what you are trying accomplish, frankly. You have a many_to_many relationship between Product and Distributors. If you really want a many_to_one relationship between Product and Distributor you should make the following changes:
schema changes: Drop the product_distributors table and add a foreign key from products to distributors.
drop_table :product_distributors
change_table :products do |t|
t.references :distributors
end
model changes: Delete the model for ProductDistributor and change Product and Distributor to reference each other directly. Also note that Distributors is renamed to Distributor.
class Product < ActiveRecord::Base
belongs_to :distributor
end
class Distributor < ActiveRecord::Base
has_many :products
end