Create Join Table for a belongs_to association in Rails 5 - ruby-on-rails

I have a products model and a variations model as a belongs_to association. There are some variations that absolutely belong to a single product, but there are others that can belong to many products. Can I create a join table on a belongs_to association like in a has_and_blongs_to_many association?
My Models Currently
product.rb
class Product < ApplicationRecord
has_many :variations, dependent: :destroy
has_and_belongs_to_many :categories
has_and_belongs_to_many :subcategories
include FriendlyId
friendly_id :name, use: :slugged
def should_generate_new_friendly_id?
name_changed?
end
end
variation.rb
class Variation < ApplicationRecord
has_and_belongs_to_many :categories
has_and_belongs_to_many :subcategories
belongs_to :product
include FriendlyId
friendly_id :name, use: :slugged
def should_generate_new_friendly_id?
name_changed?
end
end

From Rails guides association basics - the belongs_to association:
A belongs_to association sets up a one-to-one connection with another model, such that each instance of the declaring model "belongs to" one instance of the other model.
When you do the belong_to :product association on the Variation model, it expect to have a field named product_id which will point to the associated product.
use example:
variation = Variation.first
product = variation.product # this line will get the product which is associated to the variation by the product_id column.
Since it can hold only one integer (one product id) the best option is to restructure your code. It makes no sense to use a "belong_to" as "has_many" association.
You need to change the association to some king of many to many association.
To chose the best option for you, read and learn the differences in the Rails guides - Association Basics
*** Make sure you won't lose your data when changing the association:
Idea of doing that:
Create the join table
Copy the info from your variations table (the variation.id and the associated product_id)
Start using the new association
(You can probably copy the data in the migration file, just search how to do it)

Related

How to delete exactly one has_and_belongs_to_many association?

"Has and belongs to many" allows having multiple associations between same objects. So, I'm trying to implement shopping carts that may have several of some products in them.
# /app/models/cart.rb
class Cart < ApplicationRecord
has_and_belongs_to_many :products
end
.
# /app/models/product.rb
class Product < ApplicationRecord
has_and_belongs_to_many :carts
end
However, when I try deleting one many-to-many association, it deletes them all:
#cart = Cart.last
#product = Product.last
3.times { #cart.products << #product }
#cart.products.delete(#product)
puts #cart.products.count
# Returns 0; should be 2
Is there a way to delete only one association?
The main problem with your approach is that you are using the product itself directly without actually caring about the association involved.
For example:
When telling rails to delete the (Product 1) it means that it will search in the association table (cart.cart_products.where(product_id: 1)) and apply delete_all to the result.
What you should do instead is:
declare the relationship between the 2 object formally has_many :cart_products
then use it to delete a single instance cart.cart_products.where(product_id: 1).first.delete
NB: the name of the join table (cart_products) may be different for your application.
You can do it like this.
cart = Cart.find(card_id)
product = cart.products.find(product_id)
cart.products.delete(product)
From documentation:
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.
Which is what I am going to do in short while anyway. So I ended up using has_many :through.
# cart.rb
class Cart < ApplicationRecord
has_many :carts_products, class_name: 'CartsProduct'
has_many :products, through: :carts_products
end
.
# product.rb
class Product < ApplicationRecord
has_many :carts_products, class_name: 'CartsProduct'
has_many :carts, through: :carts_products
end
.
# carts_product.rb
class CartsProduct < ApplicationRecord
belongs_to :product
belongs_to :cart
end

Polymorphic relationship vs STI vs Class table inheritance with RoR

There are three types of invoice items with following tables
1) SubscriptionItems,
2) Prorations,
3) UsageItems,
Those have the same attributes below
invoice_id
amount
stripe_invoie_id
However
only SubscriptionItem and Proration
period_start_at
period_end_at
and only Proration and UsageItem has
title
and only UsageItem has
uuid
account_id
description
To achieve this model I've been using polymorphic relation.
class InvoiceItem < ActiveRecord::Base
belongs_to :invoice
belongs_to :itemable, polymorphic: true
end
class SubscriptionItem < ActiveRecord::Base
belongs_to :plan
has_one :invoice_item, as: :itemable
end
class UsageItem < ActiveRecord::Base
belongs_to :account
has_one :invoice_item, as: :itemable
end
class Invoice < ActiveRecord::Base
belongs_to :account
has_many :invoice_items
end
class Account < ActiveRecord::Base
has_many :invoices
has_many :usage_items
end
For now it works.
However As far as I understand polymorphic should have has_many relation.
So this resides in the middle of Polymorphic and STI.
Because those three types of invoice items are always be subscriptionitem, proration, or usageitem.
It's hard decision that I could keep using this models (polymorphic with has_one) or should I use STI instead?
Or class table inheritance should be fit?
EDIT
I'd love to hear the reason why I could use some design.
Maybe those types pros and cons.
As far as I know,
If I apply STI
That leads many NULLable columns, but RoR supports STI. So it's easy
to use.
If I apply polymorphic with has_one
It stills the rails way but the original polymorphic definition is
different. It should have has_many relationship instead of
has_one. Also it's impossible to add foreign key.
Ref: Blog post for STI to polymorphic
If I apply Class table inheritance,
It's more efficient for relational database, but it's not rails way.
Ref: Blog post for STI to class table inheritance
I think STI with a hidden_field passing the appropriate value for each attribute that should determine the invoice type, could be the way to go here. It's simple and efficient.
Let's say you added a field called :invoice_type to your invoice model,
Then just loop through the items in an array like (Rough example):
<% #invoices.where(:invoice_type => "proration").find_each do |invoice| %>
<% #invoices.where(:start_date => "#{#invoice.start_date}").find_each do |invoice| %>
<!--Will only show the start_date of invoices where the invoice_type is propration. -->
<% end %>
<% end %>

rails - why polymorphic associations

An example from this blog
class Tag < ActiveRecord::Base
attr_accessible :name, :taggable_id, :taggable_type
belongs_to :taggable, :polymorphic => true
end
class Car < ActiveRecord::Base
attr_accessible :name
has_many :tags, :as => :taggable
end
class Bike < ActiveRecord::Base
attr_accessible :name
has_many :tags, :as => :taggable
end
It looks to me we could do thing like this without polymorphic associations
class Tag < ActiveRecord::Base
attr_accessible :name,
belongs_to :cars,
belongs_to :bikes,
end
class Car < ActiveRecord::Base
attr_accessible :name
has_many :tags
end
class Bike < ActiveRecord::Base
attr_accessible :name
has_many :tags
end
What is the difference of with polymorphic associations and without?
THanks
In your last example, Car has many tags, so conventionally the "tags" table will have a field car_id. Now Bike has many tags, so one more field bike_id.
Without Polymorphic, how many fields are you going to create for the tags table? :)
Even you guarantee that there will only be two models have tag ultimately, there will also lots of null data in the table, say a bike doesn't have car_id, and that is not nice.
Polymorphism solved this problem by defining a common interface so that Car and Bike can share same operations with different sub type. http://www.princeton.edu/~achaney/tmve/wiki100k/docs/Polymorphism_in_object-oriented_programming.html
A polymorphic association allows you to create a table with relationships between multiple models.
Consider your example. Both Car and Bike can have many tags, so instead of creating two different tables, say car_tags and bike_tags, you can use a single polymorphic table named Tag which not only stores the foreign key (in a column named resource_id), but also the type of resource it's associated with (in a column named resource_type), which, in this case would be Car or Bike.
In summary, polymorphic relationships are between many different models whereas normal relationships are, generally speaking, only between two.
You can find more information in the RoR Guides; http://guides.rubyonrails.org/association_basics.html#polymorphic-associations
Hope that helps clarify things a bit.
Polymorphic associations allow for a single model to belong to multiple models on a single association[1]. Taking your example above, simply adding has_many :tags to the Car and Bike models and belongs_to :car and belongs_to :bike to the Tag model has two major shortcomings:
It introduces a glut of foreign keys of which a large quantity will have no value
It still doesn't allow for a tag to belong to more than one model
Some great resources for learning more about polymorphic associations are listed below.
RailsGuides on Polymorphic
Associations
RailsCasts #154 Polymorphic Associations
(revised)
What's the Deal with Rails' Polymorphic
Associations?

HABTM Lifecycle hook

I have two models: team and season associated so that a team can belong to many seasons and each season can also have many teams. So far I have used an simple HABTM relationship between the models using a join table seasons_teams without an ID attribute.
Now I would like to add a hook for when an association is deleted, to be executed when a team drops out from a season. Is it correct that the best way to do this is to transform the HABTM association into a has_many / :trough, adding an ID attribute to what was the join table and creating the corresponding model file that will contain the new before_destroy hook? If so, how do I write the migration to add an auto-incremented index to my join table? (Or would it be better to create a new join table/model with index and to copy all the entries in the existing table)
Following the Rails Style Guide:
Prefer has_many :through to has_and_belongs_to_many. Using has_many :through allows additional attributes and validations on the join model
In your case:
class SeasonTeam < ActiveRecord::Base # couldn't find a better name...
belongs_to :team
belongs_to :season
# the validates are not mandatory but with it you make sure this model is always a link between a Season and a Team
validates :team_id, :presence => true
validates :season_id, :presence => true
before_destroy :do_some_magic
#...
end
class Season < ActiveRecord::Base
has_many :teams, :through => :season_teams
end
class Team < ActiveRecord::Base
has_many seasons, :through => :season_teams
end
You could also look at Rails' Association Callbacks. It provides before_remove and after_remove callback methods which you can use to customize behaviour.

rails, model naming question

I'm creating a model called Chats. And I want to assign users to a discussion. They are either a part of the Chats or they aren't...
So I create one model Chats.
What's the standard Rails naming convention for the other table?
ChatUsers?
While has_and_belongs_to_many is an ok option here, I recommend going with has_many :through instead.
In essence you will have an explicit join model, which you can call something like ChatSession.
class Chat < ActiveRecord::Base
has_many :chat_sessions
has_many :users, :through => :chat_sessions
end
class User < ActiveRecord::Base
has_many :chat_sessions
has_many :chats, :through => :chat_sessions
end
class ChatSession < ActiveRecord::Base
belongs_to :user
belongs_to :chat
end
Now you will need a table called chat_sessions with columns :user_id, and :chat_id in it. This is your join table.
Advantage
You get a model which is fully under your control, and isn't just a dumb join table managed by rails. So for example, if you want to track number of messages particular user left in particular chat, it could be a column in chat_sessions table. Presence of :through renders habtm unneeded in most cases. There is no complexity overhead either.
If it is a join table, it would be both table names joined by '_' and in alphabetical order of table names:
chats_users
This is called a has_and_belongs_to_many association in rails. You basically have two models that call has_and_belongs_to_many and create a linking table that uses the two models in the name (alphabetical and plural).
models:
class Chat < ActiveRecord::Base
has_and_belongs_to_many :users
end
class user < ActiveRecord::Base
has_and_belongs_to_many :chats
end
Then your tables would be
chats
users
chats_users

Resources