How to delete exactly one has_and_belongs_to_many association? - ruby-on-rails

"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

Related

How to use ActiveRecord to create a has_many relationship without a bridge table

Sorry, this one is hard to phrase in the title. So here's what I'm trying to do. A workshop has many districts. Each district has exactly one district_contact (actually a district_contact_id). How can I use ActiveRecord to model the relationship between workshop and district_contact? I want to be able to do this:
Workshop.district_contacts
And get a collection of the actual user objects. Right now, I've done it using a short function:
def district_contacts
district_ids = []
self.districts.each do |district|
if district.contact_id
district_ids << district.contact_id
end
end
User.find(district_ids)
end
Define associations in the Workshop model:
has_many :districts
has_many :district_contacts, through: disctricts
Your model associations should look something like this.
class Workshop < ActiveRecord::Base
has_many :districts
has_many :district_contacts, through: disctricts
end
class District < ActiveRecord::Base
belongs_to :workshop
has_one :district_contract
end

Creating a many-to-many relation in Rails

So I'm Rails n00b and I want to create a "favorites" relationship such that a User can have many favorite Item. I'm not entirely sure how to do this, this is how I'm going to try but I'm not sure if this is a good practice at all:
class User < ActiveRecord::Base
has_many :favorites
//other code
end
class Favorite < ActiveRecord::Base
belong_to :user
has_one :item
end
class Item < ActiveRecord::Base
belongs_to :item
end
Is this a good way to do it? Should I be using has_and_belongs_to_many ?
I'm specially concerned in the following scenario: Say a user has 100 favorite items.
When I do a User.find(id) will I also be retrieving the 100 favorites and the 100 Items?
In case it's important: ruby version 1.9.3, rails version 3.2.11
Can you try has_many => :through?
class User < ActiveRecord::Base
has_many :favorites
has_many :items, :through => :favorites
//other code
end
In your case has_many :through is definitely the way to go. I would recommend reading: http://guides.rubyonrails.org/association_basics.html
Of particular interest with regard to your question:
2.8 Choosing Between has_many :through and has_and_belongs_to_many
Rails offers two different ways to declare a many-to-many relationship between models. The simpler way is to use has_and_belongs_to_many, which allows you to make the association directly:
class Assembly < ActiveRecord::Base
has_and_belongs_to_many :parts
end
class Part < ActiveRecord::Base
has_and_belongs_to_many :assemblies
end
The second way to declare a many-to-many relationship is to use has_many :through. This makes the association indirectly, through a join model:
class Assembly < ActiveRecord::Base
has_many :manifests
has_many :parts, :through => :manifests
end
class Manifest < ActiveRecord::Base
belongs_to :assembly
belongs_to :part
end
class Part < ActiveRecord::Base
has_many :manifests
has_many :assemblies, :through => :manifests
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.
It is better than using has_and_belongs_to_many.
When I do a User.find(id) will I also be retrieving the 100 favorites
and the 100 Items?
No. You'll just get the user object.
Update:
Calling User.include(:favourites, :items).find(id) will get you joined tables in case you want to make many calls to items table from user object.

Create and destroy HABTM associations - not actual records?

This is similar to a previous question, but our approach has changed a little since.
I need to be able to destroy the associations between Releases & Tracks + Products & Tracks without destroying the track itself. I need to then be able to re-associate a Track with any Release or Product.
My models are as follows:
class Release < ActiveRecord::Base
has_many :products, :dependent => :destroy
has_and_belongs_to_many :tracks
end
class Product < ActiveRecord::Base
belongs_to :release
has_many :releases_tracks, :through => :release, :source => :tracks
has_and_belongs_to_many :tracks
before_save do
self.track_ids = self.releases_track_ids
end
end
class Track < ActiveRecord::Base
has_and_belongs_to_many :releases
has_and_belongs_to_many :products
end
class ReleaseTracks < ActiveRecord::Base
belongs_to :release
belongs_to :track
end
class ProductsTracks < ActiveRecord::Base
belongs_to :product
belongs_to :track
end
Can anyone help with the appropriate controller methods please?
If I use the following on a Product Track for example, it destroys the track and association:
#product = Product.find(params[:product_id])
#track = #product.tracks.find(params[:id])
#track.destroy
I've tried something like this but it gives me a notmethoderror:
#product = Product.find(params[:product_id])
#track = #product.products_tracks.find(params[:track_id])
#track.destroy
Can anyone point me in the right direction? As I say, I need this primarily on destroy, but i also need to then be able to re-create associations.
has_and_belongs_to_many (HABTM) is one way of declaring a many-to-many relationship between two models. Under the covers, rails uses a simple join table (a table with two columns only, each a foreign key to the primary key of the main tables). The association exists when a record in the join table exists. I am not sure how destroy works by default in this case, but it may decide to delete all of the associated records (not just the association). You may be able to control this by using the dependent <action> clause on the HABTM declaration. The relationship is created when you assign one or more "children" to a parent and save.
In this case, maybe what you want is a pair of has_many :through relations -- the "through" is a relation, but can be treated as a first class model, and extended and operated on with ActiveRecord, meaning you can get at a specific release track (for example) and decide to delete it without affecting either association.
There's a good section on when to use one vs. the other in this Rails Guide: http://guides.rubyonrails.org/association_basics.html#choosing-between-has_many-through-and-has_and_belongs_to_many

.build method not creating association in the join table

I have three Models setup with the following associations
class User < ActiveRecord::Base
has_many :faculties
has_many :schools, :through => :faculties
end
class School < ActiveRecord::Base
has_many :faculties
has_many :users, :through => :faculties
end
class Faculty < ActiveRecord::Base
belongs_to :user
belongs_to :school
end
and in my controller i go to create a school and assign the user
class SchoolsController < ApplicationController
def create
#school = current_user.schools.build(params[:school])
...
end
end
When I login and submit the form the flash displays success, but the association doesn't build on the join table.
I tried it inside the apps console and it builds the association just fine.
I've been stuck on this for a couple days now and I just cannot figure out what I am missing. Thank in advance for any and all advice
The build method does not save the object. You need to explicitly call #school.save.
Two things: If the schools association is :through a has_many association, you will have to select which parent the School exists through.
So, for instance, if you were to nest School resources under users as in /users/:id/faculties/:id you could create a school via current_user.faculties.find(params[:faculty_id]).schools.build(params[:school]).save
Based on the example code, it looks like the fundamental problem is that the has_many xxx, :through syntax is being used without specifying the id of the faculties record. Remember two things: 1) ActiveRecord doesn't natively support composite primary keys, and 2) you must call #save on associated records created using #build. If you remember these, you should be fine.

What is the best way to setup my tables and relationships for this use case?

1)A user can have many causes and a cause can belong to many users.
2)A user can have many campaigns and campaigns can belong to many users. Campaigns belong to one cause.
I want to be able to assign causes or campaigns to a given user, individually. So a user can be assigned a specific campaign. OR a user could be assigned a cause and all of the campaigns of that cause should then be associated with a user.
Is that possible? And could I set it up so that the relationships could be simplified like so:
User.causes = all causes that belong to a user
User.campaigns = all campaigns that belong to user whether through a cause association or campaign association
This should work.
class User < ActiveRecord::Base
has_many :causes, :through => :cause_users
has_many :campaigns, :through => :campaign_users
# other model stuff
class Cause < ActiveRecord::Base
has_many :users, :through => :cause_users
has-many :campaigns
# other model stuff
class Campaign < ActiveRecord::Base
belongs_to :cause
has_many :users, :through => :campaign_users
# other model stuff
class CampaignUser < ActiveRecord::Base
belongs_to :campaign
belongs_to :user
# other model stuff
class CauseUser < ActiveRecord::Base
belongs_to :cause
belongs_to :user
# other model stuff
has_many :through requires that you create a new model for each of these joins: campaign_users and cause_users, as is shown but it provides more functionality later on than has_and_belongs_to_many.
I would also suggest using better names than :campaign_users and :cause_users so the relationship is more meaningful.
I believe you should use the following:
class User < ActiveRecord::Base
has_and_belongs_to_many :causes
has_and_belongs_to_many :campaigns
end
class Cause < ActiveRecord::Base
has_and_belongs_to_many :users
has_many :campaigns
end
class Campaign < ActiveRecord::Base
has_and_belongs_to_many :users
belongs_to :cause
end
This way you can use
User.causes
User.campaigns
Cause.campaing
Cause.users
Campaign.users
Campaign.cause
You can read here about has_and_belongs_to_many relationship, here about has_one and here about belongs_to.
Let me know if this is what you want :]
Edit:
"I would still need User.campaigns to
be campaigns from a user's causes or
individual campaigns associated with a
user"
You can have a method on users model that returns all campaigns. Something like this:
def all_campaigns
self.campaigns + self.causes.collect{ |c| c.campaigns }
end
You can make :has_many :through associations between users and campaigns using a join model, and also between users and causes using the another join model. The you can make a :has_many :campaigns association in the causes model, putting a :belongs_to :cause in the campaign model.
But you won't be able to fetch all the users campaigns or causes by User.campaigns.orders or User.order.campaigns. You should make an iteration over the User.campaigns collection or User.causes, fetching Campaign.cause or Cause.capaigns. Or even making a custom SQL query, using joins and conditions to filter information in the joins.

Resources