ActiveRecord transitive association deletion - ruby-on-rails

I have the following model:
class Organization < ActiveRecord::Base
has_many :providers, :dependent => :destroy
has_many :products, :through => :providers
end
class Provider < ActiveRecord::Base
belongs_to :organization
has_many :products, :inverse_of => :provider
end
class Product < ActiveRecord::Base
belongs_to :provider, :inverse_of => :products
end
When I create an organization with a provider (and no products) and then delete it with destroy:
Organization.find(1).destroy
Rails 3.0.x does NOT delete the associated provider leaving non-existing organization_id. This is weird behavior, I'd expect either nil there or the provider to be deleted (that's what I want to do).
I see there is transitive association has_many :products :through => :providers - I wonder if this is the reason why provider is not deleted.
Thanks for any help
Edit:
Ok this has nothing to do with Rails, we have the following check in the Provider class
def prevent_redhat_deletion
if redhat_provider?
errors.add(:base, _("Red Hat provider can not be deleted"))
return false
end
true
end
and obviously I was deleting a redhat_provider one. For some reason, Rails won't exit with an error.

What you can also try, is changing the :dependent = :destroy, to :dependent => :delete. See if the providers get deleted. If they do, its probably something with your code thats preventing the deletion.

Related

Validate presence of associated model

I don't get it I have the following models:
class Seller < ActiveRecord::Base
has_many :cars, :dependent => :destroy
end
class Car < ActiveRecord::Base
belongs_to :seller
# I have tried both with the validates existence gem:
validates :existence => {:allow_nil => false}
# And normally...
validates_presence_of :seller
end
But nothing works if I do the following:
seller = Seller.new()
seller.cars.build()
seller.save # I get => false #messages={:seller=>["does not exist"], :seller_id=>["does not exist"]}
I should be able to do this right?
It's like - it's validating the associated model before the mother-object has been saved - and i have NOT defined a validates_associated or something like that.
Any clue? Or am I getting the order of saving and validating all wrong?
I have run into this in the past, and have used "inverse_of" to solve it. You also need "accepts_nested_attributes_for". So in the seller, you would want to change your code to the following:
class Seller < ActiveRecord::Base
has_many :cars, :dependent => :destroy, :inverse_of => :seller
accepts_nested_attributes_for :cars
end
Seller does not exist because it has not been saved in the database, it's just in the memory, and so Car does not know Seller's id which it needs to know - it has to add it to the seller_id column. So you first have to save Seller and you don't need the validates_presence_of :seller call in Car.

Determine what ids were removed from array in Rails after_save callback?

I am trying to figure out how i can tell what has changed in an array in the after save callback. Here is an example of code i am using:
class User < ActiveRecord::Base
has_many :user_maps, :dependent => :destroy
has_many :subusers, :through => :user_maps, :dependent => :destroy
has_many :inverse_user_maps, :class_name => "UserMap", :foreign_key => "subuser_id"
has_one :parent, :through => :inverse_user_maps, :source => :user
after_save :remove_subusers
def remove_subusers
if self.subuser_ids_were != self.subuser_ids
leftover = self.subuser_ids_were - self.subuser_ids
leftover.each do |subuser|
subuser.destroy
end
end
end
end
class UserMap < ActiveRecord::Base
belongs_to :user
belongs_to :subuser, :class_name => "User"
end
I am removing the subusers with the after_save callback because i could not get the dependent destroy feature to work through user_maps. Does anyone have any ideas on a way to do this?
Thanks!
You can use the Dirty module accessors http://ar.rubyonrails.org/classes/ActiveRecord/Dirty.html as suggested in Determine what attributes were changed in Rails after_save callback?
In your case the handler you have for after_save will have access to subusers_change which is an array of two elements, first being the previous value and second being the new value.
although not strictly the answer to your question, I think you maybe able to get :dependent => :destroy working if you try the following...
class User < ActiveRecord::Base
has_many :user_maps, :dependent => :destroy
has_many :subusers, :through => :user_maps # removing the :dependent => :destroy option
end
class UserMap < ActiveRecord::Base
belongs_to :user
belongs_to :subuser, :class_name => "User", :dependent => :destroy # add it here
end
By moving the :dependent => :destroy option to the belongs_to association in the UserMap model you set up a cascading delete via the UserMap#destroy method. In other words, calling User#destroy will call UserMap#destroy for each UserMap record, which will in turn call sub_user.destroy for its sub_user record.
EDIT
Since the solution above didn't work, my next suggestion would be to add a callback to the user_maps association, however this comes with a warning that I will add after
class User < ActiveRecord::Base
has_many :user_maps, :dependent => :destroy, :before_remove => :remove_associated_subuser
def remove_associated_subuser(user_map)
user_map.subuser.destroy
end
end
WARNINGS
1) Using a before_remove callback will mean that the user_map.destroy function won't be called if there is an error with the callback
2) You will have to destroy your UserMap record using the method on the User class for example...
# this will fire the callback
u = User.first
u.user_maps.destroy(u.user_maps.first)
# this WONT fire the callback
UserMap.first.destroy
All things considered, this would make me nervous. I would first try modifying your code to make the associations a little less coupled to the same tables, so the :dependent => :destroy option can work, and if you can't do that, add a cascade delete constraint on to the database, at least then your associations will always be removed regardless of where / how you destroy it in your rails app.

How to define allow_destroy and :dependent => :destroy in Rails?

Given the following database model, how and where would you define the deletion relationships between the models? I figured out the basic table association setup but when I want to add dependencies to enable the deletion of nested objects I get lost.
Here is the relationship model I created.
class User < ActiveRecord::Base
has_many :studies
end
class Study < ActiveRecord::Base
has_many :internships
belongs_to :student, :class_name => "User", :foreign_key => "user_id"
belongs_to :subject
belongs_to :university, :class_name => "Facility", :foreign_key => "facility_id"
accepts_nested_attributes_for :subject, :university, :locations
end
class Subject < ActiveRecord::Base
has_many :studies
end
class Internship < ActiveRecord::Base
belongs_to :study
belongs_to :company, :class_name => "Facility", :foreign_key => 'facility_id'
accepts_nested_attributes_for :company, :study
end
class Facility < ActiveRecord::Base
has_many :internships
has_many :locations
has_many :studies
accepts_nested_attributes_for :locations
end
class Location < ActiveRecord::Base
belongs_to :facility
end
Where would you put :dependent => :destroy and :allow_destroy => true to enable the following scenarios? I do not want to confuse you. Therefore, I leave out my tryings.
Internship scenario: A user wants to delete an internship.
Its associated company (facility) can be deleted if the company is not related to another internship.
If so, the locations of the associated company can be deleted.
The related study will not be affected.
Study scenario: A user wants to delete a study.
Its associated subject can be deleted if no other study refers to this subject.
Its associated university (facility) can be deleted if no other study refers to this university.
Its associated internships can be deleted. The company can only be deleted if no other internship refers to it.
I am totally unsure whether I can add :dependent => :destroy only after has_one and has_many or also after belongs_to.
Edit: To simplify the problem please stick to the following (reduced) example implementation.
class Study < ActiveRecord::Base
belongs_to :subject
accepts_nested_attributes_for :subject, :allow_destroy => true
end
class Subject < ActiveRecord::Base
has_many :studies, :dependent => :destroy
end
In my view I provide the following link.
<%= link_to "Destroy", study, :method => :delete, :confirm => "Are you sure?" %>
The path is based on the named routes given by a restful configuration in routes.rb.
resources :studies
resources :subjects
The study will be deleted when I click the link - the subjects stays untouched. Why?
I think your relations are the wrong way around here...
The accepts_nested_attributes_for should be declared on the model that has_many for the model that it has_many of. Also, in your example, destroying the subject would enforce dependent_destroy on the many studies, not the other way around.
You can add :dependent => :destroy to all three but I'm not sure if that'll give you enough power to do the checks required before determining whether an associated object should be destroyed.
You have a few options.
Add a before_destroy callback on each model that raises an exception or stops the delete from occurring.
class Facility < ActiveRecord::Base
has_many :internships
has_many :locations
has_many :studies
def before_destroy
raise SomethingException if internships.any? || ...
# or
errors.add(...
end
end
or do it silently by overriding destroy
class Facility < ActiveRecord::Base
has_many :internships
has_many :locations
has_many :studies
def destroy
return false if internships.any || ...
super
end
end
Note: this is basically meant for guidance only and may not be the correct way of overriding destroy etc...

declarative_authorization and user context

Am planning on using declarative authorization in a Rails 3 app.
I have the following model relationships:
class Role < ActiveRecord::Base
has_many :permissions, :dependent => :destroy
has_many :users, :through => :permissions, :uniq => true
end
class Permission < ActiveRecord::Base
belongs_to :user
belongs_to :role
belongs_to :context
end
class User < ActiveRecord::Base
has_many :permissions, :dependent => :destroy
has_many :roles, :through => :permissions
roles.map do |role|
role.name.underscore.to_sym
end
end
class Context < ActiveRecord::Base
has_many :contexts, :dependent => :destroy
end
The concept here is that I am going to segment various datasets into different contexts. However, a given user may have different roles for each context -- maybe an admin in one context and a basic user in another. I have implemented current_user and current_context for use in controllers and views.
I plan on using the if_attribute to reference the right permission on the right dataset.
However, the question is how do I make the def role_symbols only return the roles associated with the user in a particular context when I cannot/should not reference current_context in a model (where role_symbols is defined).
Any ideas?
Was luck enough to find the answer here:
http://blog.drivingthevortex.nl/2010/01/24/using-declarative_authorization-with-subdomains/

Rails : uninitialized constant error on Active Record destroy

I am having an issue when trying to destroy an active record instance.
It involves the following AR
class Client < ActiveRecord::Base
has_many :phone_numbers, :dependent => :destroy
has_many :email_addresses, :dependent => :destroy
has_many :user_clients , :dependent => :destroy
has_many :users, :through => :user_clients
end
class UserClient < ActiveRecord::Base
belongs_to :user
belongs_to :client , :dependent => :destroy
has_many :instructions, :dependent => :destroy
end
When performing a destroy on a Client instance I am given the following error
#dead_man = Client.find(params[:id])
#dead_man.destroy => uninitialized constant UserClient::Instruction
I am really not sure where this error is coming from. Any help is greatly appreciated!
It's not finding your Instruction model. Make sure it's in the models directory, appropriately named, extends ActiveRecord::Base, etc.
Also, you should remove the :dependent => :destroy from the belongs_to :client line in the UserClient model, unless you really want deletion of a user_client to result in deletion of the client. It sounds like it should be the other way around, and that's already set up in the Client model.
Also check that the file name corresponds with the class name. In my case I had
Class NameSpace::MyStats
in
namespace/old_stats.rb
and Rails kept on throwing the "uninitialized constant error" until I changed it to
namespace/my_stats.rb

Resources