Validate presence of associated model - ruby-on-rails

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.

Related

ActiveRecord transitive association deletion

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.

validates_presence_of and unsaved associations

Take the following code (Rails 3.0.10):
User < AR
has_many :providers
Provider < AR
belongs_to :user
validates_presence_of :user
user = User.new
user.providers.build
# so both models not yet saved but associated with each other
user.valid?
=> false
user.errors
=> {:providers=>["is invalid"]}
user.providers.first.errors
=> {:user_id=>["can't be blank"]}
Why can't Provider see that it has a not yet saved associated user model available? Or in other words - how can I deal with that so that the validation is still present? Or maybe I'm doing something wrong?
Note, that I'm looking for a clean solution, so suggesting a before validation callback in Provider model saving the User model to the database is a no-go.
Use :inverse_of
class User < ActiveRecord::Base
has_many :providers, :inverse_of => :user
end
class Provider < ActiveRecord::Base
belongs_to :user, :inverse_of => :providers
validates :user, :presence => true
end

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

Rails: How do I check a polymorphic association?

I have the follow model setup:
class Favorite < ActiveRecord::Base
belongs_to :favoritable, :polymorphic => true
belongs_to :user
end
class Photo < ActiveRecord::Base
belongs_to :user
has_many :favorites, :as => :favoritable
end
class User < ActiveRecord::Base
has_many :photos
end
And that Favorite model has favoritable_id and favoritable_type` fields.
What I ultimately want to do is check and see if a User has already marked a photo as a favorite.
I'm able to create the database record without issue...it's the checking to see if that user already has favorited the photo (or other data type) that I'm having issues with.
I could obviously do some sort of raw SQL query to get it, but seems like there's gotta be a more "standard" way of doing it.
I'm running Rails 3.0.3.
you can add two other associations in the User model and a method that checks if the photo is favorite :
has_many :favorites
has_many :favorites_photos, :through => :favorites, :source => :favoritable, :source_type => 'Photo'
def photo_favorite?(photo)
favorites_photos.exists?(photo.id)
end
or more simply just by adding this method to the Photo model :
def favorite_for?(user)
favorites.exists?(:user_id => user.id)
end
I did not tested it, but I think it should work.

Validating associated objects before object.save, or rolling back object.save on associated object vaidation failure

Right down to business....
There are tasks, which have assigned users
class Task < ActiveRecord::Base
has_many :task_assignments, :dependent => :destroy
has_many :assigned_users, :through => :task_assignments, :source => :user
validates_associated :task_assignments
end
And users have assigned tasks
class User < ActiveRecord::Base
has_many :task_assignments, :dependent => :destroy
has_many :assigned_tasks, :through => :task_assignments, :source => :task
end
The task_assignments table looks like this
class TaskAssignment < ActiveRecord::Base
validates_presence_of :user, :message => 'You must add some USERS fool!'
belongs_to :user
belongs_to :task
end
Those associations seem to be working well :0)
Here's the rub - when I add a new task through /tasks/new, I also want to specify a list of users assigned to that task, which the form is returning in "params[:users_list][:id]".
I can get this to work, but I don't want the form to validate unless there is at least one user selected.
I can't for the life of me figure out how to get this validation to take place in the models rather than in the create method.
As you can see, I've thrown "validates _associated :task _assignments" in the tasks method, but to no avail. I'm clearly in over my head.
Thanks for your help.
I think you have to name the parameter user_ids...
f.e.:
params[:users_ids][:id]

Resources