Create and destroy HABTM associations - not actual records? - ruby-on-rails

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

Related

Question about controllers, dependent models, and controller functions

I'm new to ruby on rails and I'm thoroughly confused from all the documentation online. I'm creating a database program that keeps track of orders. I've already setup the has_one, belongs_to, and has_many associations in the model and I can verify their relationship with my sqlbrowswer.
Here's an rough illustration of the models.
Models
Each order has only one product, one service, one bill, and each bill can have many charges. The same product may appear in different orders, but each order would be unique.
I also have an order_observer model to create dependent models of the product
class OrderObserver < ActiveRecord::Observer
observe :order
def after_create(order)
Product.create! :order_id => order.id
Service.create! :order_id => order.id
Bill.create! :order_id => order.id
end
end
Here's what my product class and order class look like:
class Product < ApplicationRecord
belongs_to :order
end
class Order < ApplicationRecord
has_one :product, dependent: :destroy
has_one :bill, dependent: :destroy
has_one :service, dependent: :destroy
accepts_nested_attributes_for :product
accepts_nested_attributes_for :bill
accepts_nested_attributes_for :service
end
Now, what I'm confused on is how to update the dependent models. Should I implement multiple controllers for each model or just one controller to handle the whole thing?
Right now all I have in my main controller is:
def create
#order = Order.new(order_params)
end
def edit
#order = Order.find(params[:id])
end
def update
end
I would appreciate any tips or guidance.
Thanks!
Now, what I'm confused on is how to update the dependent models. Should I implement multiple controllers for each model or just one controller to handle the whole thing?
You can do this whatever you want, but you should always remember the idea 'resources', In this case, each model represents a resource, so you should do it separately.
The Rails program is based on resources, as you can see that in 'routes.rb'.

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

ActiveRecord how to add existing record to association in has_many :through relationship in rails?

In my rails project I have three models:
class Recipe < ActiveRecord::Base
has_many :recipe_categorizations
has_many :category, :through => :recipe_categorizations
accepts_nested_attributes_for :recipe_categories, allow_destroy: :true
end
class Category < ActiveRecord::Base
has_many :recipe_categorizations
has_many :recipes, :through => :recipe_categorizations
end
class RecipeCategorization < ActiveRecord::Base
belongs_to :recipe
belongs_to :category
end
With this simple has_many :through setup, how can I take a given recipe like so:
#recipe = Recipe.first
and add a category to this recipe based off an existing category, and have it be updated on the respective category as well.
So:
#category = #Existing category here
#recipe.categories.build(#category)
and then
#category.recipes
will contain #recipe?
The reason why I ask this is because I'm trying to achieve this behavior through the gem rails_admin, and every time I create a new recipe object, the form to specify it's categories is the form to create a new category, rather than attach an existing one to this recipe.
So it would be helpful to understand how ActiveRecord associates existing records to newly created records in a many_to_many relationship.
Thanks.
The build method is close enough to the new method, used to create new records.
if you need to add a current category to #recipe.categories, you just need to:
#recipe.categories << #category
This will add a record in the RecipeCategorization table (automatically saving it).
now #category.recipes will include #recipe

.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.

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