rails 3 habtm delete only association - ruby-on-rails

class Company
has_and_belongs_to_many :users
end
class User
has_and_belongs_to_many :companies
end
when i delete a company, what's the best (recommended) way to delete ONLY the associations of the users from that company? (i mean not the actual users, only the associations)

I prefer the following since it keeps model logic in the model. I don't understand why ActiveRecord doesn't just do it. Anyway, in both joined models, I add the following callback.
before_destroy {|object| object.collection.clear}
So in your example:
class Company
has_and_belongs_to_many :users
before_destroy {|company| company.users.clear}
end
class User
has_and_belongs_to_many :companies
before_destroy {|user| user.companies.clear}
end
In a lot of discussions around doing a cascade delete on a collection association, many people declare the HABTM association dead and recommend has_many :through instead. I disagree. Use whatever makes sense. If the association has no intrinsic attributes, then use HABTM.

http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_and_belongs_to_many
collection.delete will do the trick.

If you call destroy instead of delete, associations will be deleted automatically.

Related

Build method is not associating in Rails

I'm still newbie in Rails, but got confused with the initialization of a HABTM association. Reading its documentation, it says
When initializing a new has_one or belongs_to association you must use the build_ prefix to build the association, rather than the association.build method that would be used for has_many or has_and_belongs_to_many associations.
So, basically, let's suppose we have two models:
class User < ApplicationRecord
has_and_belongs_to_many :organizations
end
class Organization < ApplicationRecord
has_and_belongs_to_many :users
end
Inside organization_controller, since I'm using Devise, my create method should have something like this:
#organization = current_user.organizations.build(organization_params)
#organization.save
However, it is not working. With byebug, I checked that for the current_user.organizations, the new organization was there, but, if I call #organization.users, there's an empty array. Looks like it's required to run current_user.save as well, is it correct? I was able to associate both models with this code:
#organization = Organization.new(organization_params)
#organization.users << current_user
#organization.save
You should highly consider using has_many, :through as that's the preferred way to do these kinds of relationships now in Rails.
having said that if you want to use has_and_belongs_to_many associations yes its get stored in join table on main object save.

Custom scope on has_many, :through association (Rails 4)

CONTEXT:
In my setup Users have many Communities through CommunityUser, and Communities have many Posts through CommunityPost. If follows then, that Users have many Posts through Communities.
User.rb
has_many :community_users
has_many :communities, through: :community_users
has_many :posts, through: :communities
Given the above User.rb, Calling "current_user.posts" returns posts with one or more communities in common with current_user.
QUESTION:
I'm looking to refine that association so that calling "current_user.posts" returns only those posts whose communities are a complete subset of the current_user's communities.
So, given a current_user with community_ids of [1,2,3], calling "current_user.posts" would yield only those posts whose "community_ids" array is either 1, [2], [3], [1,2], [1,3], [2,3], or [1,2,3].
I've been researching scopes here, but can't seem to pinpoint how to accomplish this successfully...
Nice question...
My immediate thoughts:
--
ActiveRecord Association Extension
These basically allow you to create a series of methods for associations, allowing you to determine specific criteria, like this:
#app/models/user.rb
has_many :communities, through: :community_users
has_many :posts, through: :communities do
def in_community
where("community_ids IN (?)", user.community_ids)
end
end
--
Conditional Association
You could use conditions in your association, like so:
#app/models/user.rb
has_many :posts, -> { where("id IN (?)", user.community_ids) }, through: :communities #-> I believe the model object (I.E user) is available in the association
--
Source
I originally thought this would be your best bet, but looking at it more deeply, I think it's only if you want to use a different association name
Specifies the source association name used by has_many :through
queries. Only use it if the name cannot be inferred from the
association. has_many :subscribers, through: :subscriptions will look
for either :subscribers or :subscriber on Subscription, unless a
:source is given.
Being honest, this is one of those questions which needs some thought
Firstly, how are you storing / calling the community_ids array? Is it stored in the db directly, or is it accessed through the ActiveRecord method Model.association_ids?
Looking forward to helping you further!
You don't show the model and relationship definitions for for Community or CommunityPost so make sure you have a has_many :community_posts and a has_many :posts, through: :community_posts on your Community model and a belongs_to :community and a belongs_to :post on CommunityPost. If you don't need to track anything on ComunityPost you could just use a has_and_belongs_to_many :posts on Community and a join table communities_posts containing just the foreign keys community_id and post_id.
Assuming you have the relationships setup as I describe you should be able to just use current_user.posts to get a relation that can be further chained and which returns all posts for all communities the user is associated with when you call .all on it. Any class methods (such as scope methods) defined your Post model will also be callable from that relation so that is where you should put your refinements unless those refinements pertain to the Community or CommunityPost in which case you would put them on the Community or CommunityPost models respectively. Its actually rare to need an AR relationship extension for scopes since usually you also want to be able to refine the model independently of whatever related model you may use to get to it.

destroy_all not calling destroy on each object for has_many through

I have the associations setup in this manner.
class Program
has_many :program_activities, dependent: :destroy
has_many :recent_activities, through: :program_activities, source: :recent_activity
end
class RecentActivity
has_many :program_activities, dependent: :destroy
end
I wanted to delete the recent_activities associated with the program object.
program.recent_activities.destroy_all
But the above query actually just deletes ( please note that deletes and not destroy ) the program_activities and leaves the recent_activities objects alone.
I found this by inspecting the rails console queries, Is there something wrong with the destroy_all method or Have I actually setup the associations incorrectly.
to me there's nothing wrong, it's normal in a many-to-many relationship.
if a recent_activity was related to one and only one program, you shouldn't have a program_activities relationship model. you just should have RecentActivity with a belongs_to :program and the deletion would work.
in your case, you consider that a recent_activity could have many programs. so the deletion of a program can't delete a recent_activity entity because by definition, it can belong to several programs. it will just delete the relationship.
I think there is nothing wrong. It's common that you can just delete the association table when you destroy a model row.
Assume this situation:
Group has_many group_members
Member has_many group_members
Group has_many members through group_members
Member has_many groups through group_members
Is that meaning when we destroy a group, and the relative members should be destroyed too? They are independent model rows, even there is not any groups. And maybe they are of other groups. If they are cascading destroyed, data table will be messy.
Hope this example can explain the logic.
Came across this recently, and this is what ended up working for me:
program.program_activities.each { |activity| activity.recent_activity.destroy }
Not exactly concise, but since the destroy_all method doesn't do what you would expect, this may be the best there is. It does indeed trigger destroy, so in your case, it will take the association with it. If you don't have dependent: :destroy set, you'll get a foreign key violation.

ActiveRecord has_many association doesn't call after_destroy when setting a collection

3 models: User, Movie, Like
User has_many :likes
User has_many :movies, through: :likes
This code:
user.movies = [ m1, m2, m3 ]
calls Like#after_create for the new Like records that relate user and m1/m2/m3. Then, this code:
user.movies = [ m3, m4 ]
doesn't call Like#after_destroy for the Like records that relate user to m1/m2, but does call Like#after_create for the new relationship with m4.
The movies collection could be set manually or with a form that has user[movie_ids] checkboxes, and then
user.update_attributes(params[:user])
What is the right Rails approach for setting a collection?
How do I force it to call after_destroy?
Update:
As #jdoe cited from the docs, it's not achievable when assigning new collection or when deleting from a collection (user.movies.delete(m1)). The only way is to use before_remove/after_remove callbacks on the user model (and in case of a polymorphic relationship -- any other model), with the has_many definition:
has_many :movies, through: :likes, before_remove: :before_like_destroy, after_remove: after_like_destroy
def before_like_destroy(movie)
like = self.likes.where(movie_id: movie)
# trigger the after_destroy on like
like.trigger_before_destroy # to be implemented on like, should call what the original callbacks contained
end
def after_like_destroy(movie)
# no way to get Like object here because it was already removed, need to remember it on the before_destroy somehow
end
Can't understand the logic behind it. It makes callbacks on relationship models totally useless. If something happened on after_create it can't be undone in after_destroy, and since it's better practice to have after and before logic together and not separately, it makes all callbacks useless.
Think I'm gonna write a gem that does that automagically.
According to docs:
collection=objects
Replaces the collections content by deleting and adding objects as
appropriate. If the :through option is true callbacks in the join
models are triggered except destroy callbacks, since deletion is
direct.
If you add dependent: :destroy to both the like and movie associations, it calls the after_destroy callback

Rails: Finding children of children in habtm activerecord relationships

I have 3 classes related by habtm associations...Users, Locations, Employees.
Users is related to Locations via a habtm relationship, and Locations is related to Employees via a habtm relationship.
What I would like to be able to do is say:
current_user.locations.employees
Does anyone know the "Rails Way" to do this? I can do it in SQL, but I am wondering if there is a cleaner way.
You can extend associations in ActiveRecord:
class User
has_many :locations do
def employees
# Use whatever logic you'd like here.
locations.find(:all, :include => [:employees]).collect {|l| l.employees }
end
end
end
u = User.find 1
u.locations.employees #=> calls our method defined above
And see this:
http://ryandaigle.com/articles/2006/12/3/extend-your-activerecord-association-methods
You may also try has_many :through:
class User
has_many :user_locations
has_many :locations, :through => :user_locations
# Not sure if you can nest this far, this guy has problems with it:
# http://tim.theenchanter.com/2008/10/how-to-hasmany-through-hasmany-through.html
#
# Maybe if locations was a habtm instead of :through? experiment with it!
#
has_many :employees, :through => :locations
end
u = User.find 1
u.employees #=> Uses associations to figure out the SQL
In general, Lee, I'm concerned about your data model. HABTM relationships are not really recommended now. Using has_many :through allows you to name the join table, and then you can store attributes on a relationship with better business meaning. I would say the "Railsy" thing is to add some through relationships to expose more domain modelling.
Also, some example models would be helpful to really understand your question.
Good luck!
I assume that the following is true and that your ActiveRecords will reflect this.
user belongs_to location
location has_many users
location has_many employees
employee belongs_to location
then you can say in your User ActiveRecord
has_many :employees :through => :locations
I think that should work.
UPDATE:
Just realized that your user has many locations as well. Not quite sure if the above will work or not. Worth a try though.
You could define method in User sort of like the following
def get_all_related_employees
employees = []
self.locations.each do |location|
employees << location.employees
end
employees
end
or you could do it right inline
current_user.locations.map {|location| location.employees }.each do |employee|
puts employee.name
end

Resources