has_many :through and join table in Rails - ruby-on-rails

I would like to implement properly database and associated models for my application.
I have two models: user and location. I would like them to be joined by a table due to the fact that I need to keep historic data about the association between these two entities.
Therefore, I created a join table called user_locations in addition to the foreign keys of location_id and user_id I have two additional fields which I need.
So far so good.
A new requirement has emerged and I need my location to be polymorphic.
I don't know how to set up my models properly to store to be able to have that joined table and a polymorphic association.
Here is what I came up with so far:
user.rb
class User < ApplicationRecord
has_many :locations, through: :user_locations, as: :locationable, source_type: 'User'
has_many :user_locations
end
location.rb
class Location < ApplicationRecord
belongs_to :locationable, polymorphic: true
has_many :users
end
user_location.rb
class UserLocation < ApplicationRecord
belongs_to :user
belongs_to :location
validates_presence_of :user
validates_presence_of :location
end

I found a very good tutorial/article that I think can help you here.
It would entail you doing something like:
class User < ApplicationRecord
has_many :locations
has_many :model_ones, through: :locations, source: :locationable, source_type: 'ModelOne'
has_many :model_twos, through: :locations, source: :locationable, source_type: 'ModelTwo'
And:
class Location < ApplicationRecord
belongs_to :locationable, polymorphic: true
belongs_to :user
end
Where ModelOne and ModelTwo are filled in with the models you need obviously.

Related

Rails Association returns join model not actual model

So the join table is setup like this
User model.
Book model.
read_books is associating those two.
want_to_read_books is association those two again for a different reason.
Two join tables.
read_books
belongs_to :user
belongs_to :book
want_to_read_books
belongs_to :user
belongs_to :book
In the user table i have
has_many :books, through: :read_books
has_many :books, through: :want_to_read_books
in the book table I have
has_and_belongs_to_many :read_books
has_and_belongs_to_many :want_to_read_books
To this point it all works fine BUT i am forced to reference them as:
user.read_books.first.book.title
instead of
user.read_books.first.title.
What do I need to add to make it return the model not the association?
I have also tried the following thinking it was what i was missing but resulted in the same thing.
has_many :books, through: :read_books
has_many :read_books
has_many :books, through: :want_to_read_books
has_many :want_to_read_books
If you define several associations with the same name the methods defined by the later will just overwrite the previous association. Instead you want to just name your associations appropriately:
class User
has_many :book_readings # for lack of a better name
has_many :read_books,
through: :book_readings,
source: :book
has_many :wishlist_books
has_many :want_to_read_books,
through: :wishlist_books,
source: :book
end
class BookReading
belongs_to :user
belongs_to :book
end
class WishlistBook
belongs_to :user
belongs_to :book
end

Is this database relation schema correct?

I made my first project and got into problem that I couldn't get the right category_information values for specific competitions category through relations. So I started thinking that this could be the wrong schema for this task, so my question - is it actually wrong?
Current Scheme:
Assuming the following relationships between models from your image.
class Competition < ApplicationRecord
has_many :categories
has_many :informations
has_many :category_informations, through: :categories
end
class Category < ApplicationRecord
belongs_to :competetion
has_many :category_informations
has_many :information, through: :category_informations
end
class CategoryInformation
belongs_to :catagory
belongs_to :information
end
class Information < ApplicationRecord
belongs_to :competetion
has_many :category_informations
has_many :catagory, through: :category_information
end
Model can relates with one_to_many_to_many using :through option
It explains a association used to set up a many-to-many connection with another model.
you can get the category_informations from competition like
Competition.first.category_informations
It is all for doing! Pretty good, right?
And you could do get information from category too
Category.first.informations
Actually wrong schema doesn't exist, just there exists some wrong association description.
You can get more usage to use association from docs at 2.3 section and 4.3 section
Assuming the following relationships between tables,
A competition has_many categories,
A competition has_many information,
A category has_many information,
A category has_many competition,
An information has_many category
You can use has_many_through relationships
class Category < ApplicationRecord
has_many :category_competitions
has_many :competitions, through: :category_competition
has_many :category_informations
has_many :informations, through: :category_informations
end
class Information < ApplicationRecord
has_many :category_informations
has_many :categories, through: :category_informations
end
class Competition < ApplicationRecord
has_many :category_competition
has_many :categories, through: :category_competitions
end
class CategoryCompetition < ApplicationRecord
belongs_to :category
belongs_to :information
end
class CategoryInformation < ApplicationRecord
belongs_to :category
belongs_to :information
end
By this way you can access,Categories of a particular competition by #competition.categories
This article might be helpful for you to understand associations better
https://www.sitepoint.com/master-many-to-many-associations-with-activerecord/

Dynamic has_many class_name using polymorphic reference

I am trying to associate a polymorphic model (in this case Product) to a dynamic class name (either StoreOnePurchase or StoreTwoPurchase) based on the store_type polymorphic reference column on the products table.
class Product < ActiveRecord::Base
belongs_to :store, polymorphic: true
has_many :purchases, class_name: (StoreOnePurchase|StoreTwoPurchase)
end
class StoreOne < ActiveRecord::Base
has_many :products, as: :store
has_many :purchases, through: :products
end
class StoreOnePurchase < ActiveRecord::Base
belongs_to :product
end
class StoreTwo < ActiveRecord::Base
has_many :products, as: :store
has_many :purchases, through: :products
end
class StoreTwoPurchase < ActiveRecord::Base
belongs_to :product
end
StoreOnePurchase and StoreTwoPurchase have to be separate models because they contain very different table structure, as does StoreOne and StoreTwo.
I am aware that introducing a HABTM relationship could solve this like this:
class ProductPurchase < ActiveRecord::Base
belongs_to :product
belongs_to :purchase, polymorphic: true
end
class Product < ActiveRecord::Base
belongs_to :store, polymorphic: true
has_many :product_purchases
end
class StoreOnePurchase < ActiveRecord::Base
has_one :product_purchase, as: :purchase
delegate :product, to: :product_purchase
end
However I am interested to see if it is possible without an extra table?
Very interesting question. But, unfortunately, it is impossible without an extra table, because there is no polymorphic has_many association. Rails won't be able to determine type of the Product.purchases (has_many) dynamically the same way it does it for Product.store (belongs_to). Because there's no purchases_type column in Product and no support of any dynamically-resolved association types in has_many. You can do some trick like the following:
class Product < ActiveRecord::Base
class DynamicStoreClass
def to_s
#return 'StoreOnePurchase' or 'StoreTwoPurchase'
end
end
belongs_to :store, polymorphic: true
has_many :purchases, class_name: DynamicStoreClass
end
It will not throw an error, but it is useless, since it will call DynamicStoreClass.to_s only once, before instantiating the products.
You can also override ActiveRecord::Associations::association to support polymorphic types in your class, but it is reinventing the Rails.
I would rather change the database schema.

Rails belongs_to_many

I'm a beginner in Rails and I have a problem with ActiveRecords associations.
I'm creating simple car rental service and I made the following associations:
class Client < ActiveRecord::Base
has_many :rentals
has_many :bookings
has_many :cars, :through => :rentals
has_many :cars, :through => :bookings
end
class Rental < ActiveRecord::Base
belongs_to :client, dependent: :destroy
has_one :car
end
class Booking < ActiveRecord::Base
belongs_to :client, dependent: :destroy
has_one :car
end
What I need is to have a car belonging to many bookings and rentals while every booking and rental can have only one car assigned.
class Car < ActiveRecord::Base
# belongs_to_many :bookings
# belongs_to_many :rentals
end
How should I do that?
If a car can have many bookings/rentals, but a booking/rental can only have one car, you're looking at a classic belongs_to/has_many situation. It looks like you're being tripped up by the distinction between belongs_to and has_one -- it's not a grammatical one, but a matter of where the foreign key column is located in your database.
belongs_to: "I am related to exactly one of these, and I have the foreign key."
has_one: "I am related to exactly one of these, and it has the foreign key."
has_many: "I am related to many of these, and they have the foreign key."
Note that has_one and has_many both imply there's a belongs_to on the other model, since that's the only option where "this" model has the foreign key. Note also that this means has_one should only be used when you have a one-to-one relationship, not a one-to-many.
Taking this into consideration, I would replace the has_one :car with belongs_to :car in both your Rental and Booking models, and place has_many :bookings and has_many :rentals in your Car model. Also ensure that your rentals and bookings tables have a car_id column; there should be no rental- or booking-related columns in your cars table.
Yes, there is a "belongs_to_many" in Rails, sort of. It's a little more work and you can't use generators with it. It's called a polymorphic association.
Even though you could make a car have many bookings & rentals, you could associate the car by making it belong to a polymorph such as rentable_vehicle. Your code would look like this
class Car < ActiveRecord::Base
belongs_to :rentable_vehicle, polymorphic: true
end
class Rental < ActiveRecord::Base
belongs_to :client, dependent: :destroy
has_many :cars, as: :rentable_vehicle
end
class Booking < ActiveRecord::Base
belongs_to :client, dependent: :destroy
has_many :cars, as: :rentable_vehicle
end
You can't do belongs_to_many. The closest you can really get is has_and_belongs_to_many, but I'm not sure that's what you want here - unless you can have multiple cars per rental/booking. Check out the guide for a full explanation.
I'd change it up like this:
class Rental < ActiveRecord::Base
belongs_to :client, dependent: :destroy
belongs_to :car
end
class Booking < ActiveRecord::Base
belongs_to :client, dependent: :destroy
belongs_to :car
end
class Car < ActiveRecord::Base
has_many :bookings
has_many :rentals
end
Also, I don't know how your rentals relate to bookings, but my immediate thought is that there should be some relationship between the two, because you probably can't have a rental without booking it, right?

has_many :through and has_many relationship between same 2 models

A list has one owner (a user). A list also has a number of panelists (also users). I have tried defining the relationships between the three models: User, List, and Panelist. But I'm getting nowhere.
user.rb
class User < ActiveRecord::Base
has_many :lists
has_many :panelMemberships, :through => :panelists, :source => :lists
end
list.rb
class List < ActiveRecord::Base
belongs_to :user
has_many :panelMembers, :through => :panelists, :source => :user
end
panelist.rb
class Panelist < ActiveRecord::Base
belongs_to :list
belongs_to :user
end
I've tried all different combinations but nothing seems to work. Thanks in advance for any help you can provide.
The model also has to have a has_many relationship for whatever the through model is, so wherever you have has_many :x, through: :y, you also need to say has_many :y. You also shouldn't have a panelist model separate from your user model if panelists are users (unless you're doing STI, which you're not). From what I understand, you're trying to do something like this:
class User < ActiveRecord::Base
has_many :owned_lists, class_name: "List", foreign_key: :owner_id # this is for the owner/list relationship
has_and_belongs_to_many :lists # for the normal panelist / list relationship
end
class List < ActiveRecord::Base
belongs_to :owner, class_name: "User"
has_and_belongs_to_many :users
end
Then you'll need to make a migration for a users_lists (with user id and list id) table which will be your join table but won't need its own model. But if you really want to keep the through relationship (good for if you do other stuff with the join model), then you'd do:
class User < ActiveRecord::Base
has_many :owned_lists, class_name: "List", foreign_key: :owner_id # this is for the owner/list relationship
has_many :panel_memberships
has_many :lists, through: :panel_memberships
end
class List < ActiveRecord::Base
belongs_to :owner, class_name: "User"
has_many :panel_memberships
has_many :users, through: :panel_memberships
end
class PanelMembership < ActiveRecord::Base
belongs_to :user
belongs_to :list

Resources