I'm trying to figure out what the appropriate ActiveRecord associations would be for my models. I want to create a very barebones ecommerce site. I want to have a system for upgrading and repairing products, so having access to a user's order history is important.
My current setup is as follows:
User
has_many :products, :through => :orders
has_many :orders, :dependent => :destroy
Orders
belongs_to :user
has_many :products
Products
belongs_to :orders
My first question is does this make sense? I'm concerned with the belongs_to :orders part of Products because I want to make sure a product can be part of many different orders (for obvious reasons). If this is wrong/right, what would be the neccessary migrations needed for the correct relationship?
Thanks in advance.
I realize the post is quite old but since I was facing the same issue, it might be interesting to add some clarity for the followers. As #ManojMonga said in the comments, here you need to use a has_and_belongs_to_many association. So models will look like :
User
has_many :orders, :dependent => :destroy
has_many :products, through: :orders
Order
belongs_to :user
has_and_belongs_to_many :products
Product
has_and_belongs_to_many :orders
Then, as explained in the Active Record Association Rails Guide , you will need to create a join table that will hold the orders and products foreign key. Migration looks like this :
class OrdersProducts < ActiveRecord::Migration
def change
create_table :orders_products, id: false do |t|
t.belongs_to :order, index: true
t.belongs_to :product, index: true
t.timestamps
end
end
end
Then, after you've run the migration, you'll be able to use has_and_belongs_to_many methods.
Remember when you create your join table that in a has_an_belongs_to_many association, Active Record will look for a table named from the 2 tables name concatenated by alphabetical order. If you wish to name it otherwise, you will have to specify it in models as following :
Order
has_and_belongs_to_many :products, :join_table => "new_tables_name"
Products
has_and_belongs_to_many :orders, :join_table => "new_tables_name"
Finally, the has_many :products, through: :orders in the User's model isn't mandatory. It will just allow to access products through users.
That's it, I'll be happy if it can be of any help. I'm using Rails 4.1 by the way.
Products has_one :order. This will allow you to use things like #product.order, but it won't actually create the foreign key for order in the products database table.
Related
My :users table is successfully self-joined with all the necessary confusing (to this newbie) code and tables necessary to do that. :users's two groups are :teachers and :students.
I need to make the group :teachers join one-to-many with the :bands table (a band may have only one teacher) while at the same time joining :students many-to-many with the :bands table (a band may have many students and vice versa).
What's tripping me up is :students and :teachers are both :users. Therefore, if for a moment I pretend that there's only one kind of user and go for a one-to-many (teacher) relationship, then the Band model belongs_to :user and the User model has_many :bands
But if I go for the many-to-many (student) relationship instead, the Band model has_many :users, through :user_bands join table and the User model has_many :bands, through :user_bands join table. (UserBands model has belongs_to :user and belongs_to :band in this case)
But I need both relationships at the same time. I haven't actually tried putting has_many :bands in the User model while simultaneously having has_many :users (through join table) and belongs_to :users in the Bands model because, unless Rails is more magic than I give it credit for, it won't differentiate that teachers get the single-to-many while the students get the many-to-many.
I have not attempted my best guess (below) because I'm admittedly skittish: my database already has a sprawling number of many-to-many relations that are intact and functioning properly. The one time I attempted to make a complicated alteration to it earlier in the process, it thoroughly messed things up so badly that rolling back and undoing model alterations didn't get me back to where I'd started somehow, so I had to go back to rebuilding everything from scratch after pulling out enough hair for a tonsure. I do have github this time, so I should be able to revert the project if it blows up like before, but git is its own fussy minefield.
So if some folks could take a look at my guess first, I'd deeply appreciate it. Does it look right? Do I need to make changes before updating the database schema?:
In User model, add has_many :bands.
In Band model, add has_many :students, through :user_bands ; add belongs_to :teacher
In the create_bands migration, add t.belongs_to :teacher
In UserBands model, add belongs_to :teacher and add t.belongs_to :teacher in the create_user_bands migration.
The needed associations are not self-joining. Self-joins are primarily used to build tree like hierarchies from a single table - see the guides for a good example.
You just need multiple associations between two tables - the key thing here to remember is that you must use unique names for each association. If you declare multiple associations with the same name the later just overwrites the former without error.
Teachers
class AddTeacherIdToBands < ActiveRecord::Migration[5.2]
def change
add_reference :bands, :teacher, foreign_key: { to_table: :users }
end
end
class User < ApplicationRecord
has_many :bands_as_teacher,
class_name: 'Band',
foreign_key: 'teacher_id'
end
class Band < ApplicationRecord
belongs_to :teacher,
class_name: 'User'
end
We name the association bands_as_teacher to avoid conflict and confusion. This requires us to explicitly set the class_name and foreign_key options as they cannot be deduced from the name.
This is kind of where you tripped up and overcomplicated your solution. If the association is one to many you don't need to involve a join table.
Students
To create the association between a band and its members you need a m2m association through a join table:
class CreateBandMemberships < ActiveRecord::Migration[5.2]
def change
create_table :band_memberships do |t|
t.references :band, foreign_key: true
t.references :user, foreign_key: true
t.timestamps
end
end
end
class Band < ApplicationRecord
# ...
has_many :band_memberships
has_many :users, through: :band_memberships
end
class BandMembership < ApplicationRecord
belongs_to :band
belongs_to :user
end
class User < ApplicationRecord
# ...
has_many :band_memberships
has_many :bands, through: :band_memberships
end
You can improve the naming by providing the source option which tells Rails which association to use on the model its joining through.
class Band < ApplicationRecord
# ...
has_many :band_memberships
has_many :members, through: :band_memberships, source: :user
end
class User < ApplicationRecord
# ...
has_many :band_memberships
has_many :bands_as_member, through: :band_memberships, source: :band
end
I have the following relations:
reservation.rb
has_many :room_requests, dependent: :destroy, inverse_of: :reservation
accepts_nested_attributes_for :room_requests, allow_destroy: true
has_many :rooms, through: :room_requests
room_request.rb
belongs_to :reservation
belongs_to :room
room.rb
has_many :room_requests
has_many :reservations, through: :room_requests
And I'm trying update the attribute 'status' from rooms that belong to certain reservations. Something like:
Reservation.joins(:rooms).update_all('rooms.status': 'to_clean')
But evidently it doesn't work like this. I want to do it in a single query but I can't quite grasp it. What am I missing?
In the end I couldn't do the query that way because what I needed to update was on the Room model, and the query was being done in the Reservation model. I needed to reverse it, and came up with this:
Room.where(id: RoomRequest.where(reservation: Reservation.checked_in).select(:room_id)).update_all(status: Room.statuses[:to_clean])
You should link your Reservations model also with Rooms via a has_many, through: room_requests..
Once done you can easily go with:
Reservations.rooms.status.update_all('rooms.status': 'to_clean')
However, make sure that your room_requests are available and that reservations are thus assigned to rooms via the room_requests.
Cheers,
T
I am building an event app that lets users post events, and other users can 'register' themselves as attending that event. I currently have two models, 'User' and 'Article', where article is the event that a user can post. An article belongs_to :user and a user has_many :articles, dependent: :destroy.
I am trying to set up a new database to store which users plan to attend an event, in the form of a 'Registration', where it stores a user and the event he plans to attend. This means i need a many to many association between Users and Articles (as a user can attend multiple events, and an event can have multiple attendees). But will this not conflict with the original setting that states an article belongs to a single user?
How do i set this up so my associations dont interfere?
You could try either a has_many through: or a has_and_belongs_to_many relationship. Personally, I think I would use a HABTM for this, but the advantage of a HM Through is that there is an intermediate model, which can be used for additional information (such as whether an "attendee" is going or merely interested, etc): http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association
As for having multiple different associations between the same two models, you can name the association anything you like but specify the class_name of the model you are pointing to: http://guides.rubyonrails.org/association_basics.html#has-and-belongs-to-many-association-reference
For example:
class Article < ActiveRecord::Base
belongs_to :user
has_and_belongs_to_many :attendees, class_name: "User", join_table: "articles_attendees", foreign_key: "attended_event_id", association_foreign_key: "attendee_id"
...
end
And for your User model:
class User < ActiveRecord::Base
has_many :articles
has_and_belongs_to_many :attended_events, class_name: "Article", join_table: "articles_attendees", foreign_key: "attendee_id", association_foreign_key: "attended_event_id"
...
end
That way you're able to name your association whatever you like, just be sure to keep your singulars singular and your plurals plural, and generally everything readable. class_name should be the name of the model to which you are defining the relationship. foreign_key is the database column name containing the ID of the models in which the relationship is defined. For example, in your User model, foreign_key should be the user ID. The association_foreign_key is the column containing the ID of the model to which you are linking.
Also don't forget to create your migration. Something like this example:
class CreateArticlesAttendees < ActiveRecord::Migration
def self.up
create_table :articles_attendees, :id => false do |t|
t.references :attended_event
t.references :attendee
end
add_index :articles_attendees, [:attended_event_id, :attendee_id]
add_index :articles_attendees, :attendee_id
end
def self.down
drop_table :articles_attendees
end
end
I have a model A,
Class A < ActiveRecord::Base
has_many: names, class_name: 'B'
and a model B
class B < ActiveRecord::Base
belongs to :A
and there are already a bunch of data in database.
How do I write a migration to migrate them from one-to-many to many-to-many relationship? I prefer to use
has_many: through
if possible.
It's not hard to write the db migration, but what do I do to migrate the data in it?
This scenario comes up quite often in Rails projects and I'm surprised there still aren't a lot of how-tos out there as its a straightforward data evolution but requires some delicacy when dealing with already deployed systems.
I'm not sure if you're interested in polymorphic behavior for the many-to-many but I'm throwing that in as I find it useful for many many-to-many scenarios (pun intended! :-).
I had this before I started:
class Tag < ActiveRecord::Base
has_many :posts, inverse_of: :tag
class Post < ActiveRecord::Base
belongs_to :tag, inverse_of: :posts
I know, I know, why only one Tag for a Post? Turns out, I wanted my Posts to have multiple Tags after all. And then I thought, wait a minute, I want other things to have Tags as well, such as some kind of Thing.
You could use :has_and_belongs_to_many for each of Posts-Tags and Things-Tags but then that makes for 2 join tables and we'll probably want to Tag more entities as they get added right? The has_many :through is a great option here for one side of our associations and avoids having multiple join tables.
We are going to do this in 2 STEPS involving 2 deploys:
Step 1 - No change to existing associations. A new Taggable model/migration that will be polymorphic with respect to Posts and Things. DEPLOY.
Step 2 - Update associations. New migration to drop the old :tag_id foreign_key from Posts. DEPLOY.
The two steps are necessary to be able to execute your migration in Step 1 using your previous association definitions, otherwise your new associations will not work.
I think two steps is the simplest approach, recommended if your traffic is low enough that the risk of additional Tags being created on Posts/Things in between the two steps is low enough. If your traffic is very high, you can combine these two steps into one but you'll need to use different association names and then go back to delete the old unused ones after a working rollout. I'll leave the 1 step approach as an exercise for the reader :-)
Step 1
Create a model migration for a new polymorphic join table.
rails g model Taggable tag_id:integer tagged_id:integer tagged_type:string --timestamps=false
Edit the resulting migration to revert to using #up and #down (instead of #change) and add the data migration:
class CreateTaggables < ActiveRecord::Migration
def up
create_table :taggables do |t|
t.integer :tag_id
t.integer :tagged_id
t.string :tagged_type
end
# we pull Posts here as they have the foreign_key to tags...
Posts.all.each do |p|
Taggable.create(tag_id: p.tag_id, tagged_id: p.id, tagged_type: "Post")
end
end
def down
drop_table :taggables
end
end
Edit your new model:
class Taggable < ActiveRecord::Base
belongs_to :tag
belongs_to :tagged, polymorphic: true
end
At this point, DEPLOY your new model and migration. Great.
Step 2
Now we're going to update our class definitions:
class Tag < ActiveRecord::Base
has_many :taggables
has_many :posts, through: :taggables, source: :tagged, source_type: "Post"
has_many :things, through: :taggables, source: :tagged, source_type: "Thing"
class Post < ActiveRecord::Base
has_and_belongs_to_many :tags, join_table: 'taggables', foreign_key: :tagged_id
class Thing < ActiveRecord::Base
has_and_belongs_to_many :tags, join_table: 'taggables', foreign_key: :tagged_id
You should be able to add dependent: :destroy on has_many :posts and has_many :things as :tag is a belongs_to on Taggable.
Don't forget to drop your old foreign_key:
class RemoveTagIdFromPosts < ActiveRecord::Migration
def up
remove_column :posts, :tag_id
end
def down
add_column :posts, :tag_id, :integer
end
end
Update your specs!
DEPLOY!
I'm beginning a project with rails where there are products, clients and sellers. Each seller has_many products. Each Client has_many products. (And in my case, each client only buys one product at a time).
I want to know who are my clients' seller and my Seller's clients, knowing that, they'll be linked by, the purchase, of one product.
Should I use a has_and_belongs_to_many association between clients and sellers ? Or a double has_many through :products, like :
Seller :
has_many :clients through :products
Belongs_to :products
Client :
has_many :sellers through :products
Belongs_to :products
In order to avoid two belongs_to in the product class, could this work ?
class Client < ActiveRecord::Base
has_many :products, as: :productable
has_many :sellers, through: :products
end
class Seller < ActiveRecord::Base
has_many :products, as: :productable
has_many :clients, through: :products
end
class Product < ActiveRecord::Base
belongs_to :productable, polymorphic: true
end
Thanks in advance for your answer.
I would go with has_many :through here.
class Client < ActiveRecord::Base
has_many :products
has_many :sellers, through: :products
end
class Seller < ActiveRecord::Base
has_many :prodcuts
has_many :clients, through: :products
end
class Product < ActiveRecord::Base
belongs_to :client
belongs_to :seller
end
The simplest rule of thumb is that you should set up a has_many
:through relationship if you need to work with the relationship model
as an independent entity. If you don't need to do anything with the
relationship model, it may be simpler to set up a
has_and_belongs_to_many relationship (though you'll need to remember
to create the joining table in the database).
You should use has_many :through if you need validations, callbacks,
or extra attributes on the join model.
And also see these Guides for choosing between HABTM and a has_many :through
I want to approach your question from the other end: let us start from the product. I think this will clarify a lot of things.
So you have three models: Seller, Client and Product.
A Product has a seller and client. In your model that would like this:
class Product
belongs_to :seller
belongs_to :client
end
This means that in the products table we have a column seller_id and client_id.
Afaik a product needs to have both, always. So this also means you cannot use a polymorphic association here. At least not the way you proposed it. If you write
belongs_to :productable, polymorphic: true
you will add the fields productable_id and productable_typeto yourProduct` model. But that is only 1 link (so either a seller or a client, but never both). You could introduce a link table here, so a product could be linked to many "productables" but in your case i think it is besides the point. You know a product has one seller and one client.
Secondly, now this is established, your Product is exactly the link-table between clients and sellers. So you do not have to introduce a new link-table, just use the one already there.
class Seller
has_many :products
has_many :clients, through: :products
end
class Client
has_many :products
has_many :sellers, through: :products
end
So in conclusion:
use the has_many :through because you already have the link table as a model. Only use a habtm if you do not care about the join-table (link-table).
you can't use a polymorphic association here, as you need two links (without introducing a link-table, which seems overkill imho). I like the explicitness, clarity, readability of having an explicit seller_id and client_id, and it is also easier to manage.