The situation:
class Cellar < ActiveRecord::Base
belongs_to :house, dependent: :destroy
accepts_nested_attributes_for :house, allow_destroy: true
attr_accessible :house_id, :house_attributes, [...]
end
.
class House < ActiveRecord::Base
has_one: cellar
end
The problem:
When I send the Cellar form and include the key-value pair "_destroy" => "true" inside the house_attributes, the House gets destroyed as it should, but the Cellar.house_id is not updated to NULL.
Is this normal behavior? How should I best fix this?
This may be normal depending on the version of Rails... I think up until Rails 3.2 it was normal for the foreign key to remain untouched when destroying object (and vice versa when updating a foreign key to nil). What version of Rails are you using?
But, regardless, if you want to keep going as is and just clean up the foreign key on #cellar after saving then you can always just call #cellar.reload after the successful #cellar.save. This would refresh the state of your #cellar object from the database and remove the house_id attribute since it's no longer present.
For the sake of completeness, here's what I ended up doing in order to correct the issue:
class Cellar < ActiveRecord::Base
belongs_to :house, dependent: :destroy
accepts_nested_attributes_for :house, allow_destroy: true
attr_accessible :house_id, :house_attributes, [...]
# I ADDED THIS:
before_save :drop_invalid_id
def drop_invalid_id
self.house_id = nil if house.marked_for_destruction?
end
end
Related
Application
I am working on a college admissions system where a student can make an application to up to 5 courses. The way I have designed this is to have an Application model and a CourseApplication model. An application can consist of many course_applications:
class Application < ActiveRecord::Base
# Assosciations
belongs_to :user
has_many :course_applications, dependent: :destroy
has_many :courses, through: :course_applications
has_one :reference
# Validations
validates :course_applications, presence: true
end
Course Application
class CourseApplication < ActiveRecord::Base
# Intersection entity between course and application.
# Represents an application to a particular course, has an optional offer
# Associations
belongs_to :application
belongs_to :course
has_one :offer, dependent: :destroy
end
I want to make sure that a student cannot apply to the same course twice. I have already done some research but have had no success. This is the UI for a student making an application:
Screenshot of application form
When a course is selected, the course id is added to an array of course ids:
def application_params
params.require(:application).permit(:user, course_ids: [])
end
Right now a student can select the same course twice, I want to prevent them from doing this. Any help is much appreciated.
For the rails side, I would do on the CourseApplication
validates :course, uniqueness: { scope: :application }
For your reference this can be found at: http://guides.rubyonrails.org/active_record_validations.html#uniqueness
Also suggest on the database side to make a migration
add_index :course_applications, [:course, :application], :unique => true
For the validating on the form you will have to write javascript to make sure two things are selected, this will just return an error when someone tries to do it.
I have two models:
class Shift < ActiveRecord::Base
attr_accessible :ranges_attributes
has_many :ranges
accepts_nested_attributes_for :ranges, allow_destroy: true
end
class Range < ActiveRecord::Base
belongs_to :shift
validates :shift, presence: true
end
When, in my controller, I want to create a shift with ranges I'm getting:
Shift.create! params[:shift]
#ActiveRecord::RecordInvalid Exception: Validation failed: Shift ranges shift can't be blank
If I remove validates :shift, presence: true from Range model this works beautifully. I'm able to create a new shift with his children. ActiveRecord does that for me.
The question is: why do I need to remove that validation to make this work?
The thing with validating presence of parent like this is timing !! actually the Shift is not yet saved so when trying to create nested ranges it won't find parent Shift in database.
I found this workaround here
class Shift < ActiveRecord::Base
attr_accessible :ranges_attributes
has_many :ranges, :inverse_of => :shift
accepts_nested_attributes_for :ranges, allow_destroy: true
end
and i quote (with minor modifications) from the same source:
With this option rails won't try to get parent from database when
child is validated. The parent will be got from memory. If you don't
familiar with this option I strongly recommend you to read an official
rails guide
I have the following setup:
class Round < ActiveRecord::Base
has_many :cards_rounds
has_many :cards, through: :cards_rounds
accepts_nested_attributes_for :cards_rounds
validate :round_validations
private
def round_validations
// pry
unless cards.map(&:id).uniq.size == 3
errors.add(:round, "Must have 3 unique cards")
end
unless cards.map(&:quality).uniq.size == 1
errors.add(:round, "Cards must be of the same rarity")
end
end
end
class CardsRound < ActiveRecord::Base
belongs_to :card
belongs_to :round
end
class Card < ActiveRecord::Base
has_many :cards_rounds
has_many :rounds, through: :cards_rounds
end
Round always fails to validate on creation. When I step in using pry, I can see that cards is nil, but cards_rounds is populated and I can call cards_rounds[0].card (for example).
Is this the expected behaviour? It seems odd to me that I can reference the cards through cards_rounds but not directly as a collection.
Rails version is 4.0.1
Yeah I feel your pain. I'm not sure if this is the expected behavior but I've came into this same problem some times.
The best think you can do is to open an issue on https://github.com/rails/rails
Not sure where the problem is, I did the following gist which the tests pass: https://gist.github.com/arthurnn/9607180. See that I changed the validation. The problem is that the validation cannot rely on mapping the ids from cards, as they dont have an id yet.
I have a model Purchase with:
class Purchase < ActiveRecord::Base
has_many :purchase_items, dependent: :destroy
accepts_nested_attributes_for :purchase_items, reject_if: :all_blank, allow_destroy: true
validates_length_of :purchase_items, minimum: 1
end
And PurchaseItem with:
class PurchaseItem < ActiveRecord::Base
belongs_to :purchase
end
Say I have a purchase with only one item. If I mark the item for destruction by doing:
purchase.purchase_items.first.mark_for_destruction
purchase.save!
The purchase is saved fine, leaving it without any referenced items in the DB.
Inspecting the validate_each method inside ActiveModel::Validations::LengthValidator, we can see that it does not verify if the value being validated has objects marked for destruction.
https://github.com/rails/rails/blob/master/activemodel/lib/active_model/validations/length.rb
Is that the normal behavior or it is in fact an issue? If it is normal, what is the correct way to validate the length of a relation along with marked_for_destruction objects?
(without custom validators of course...)
Your only choice is to write a custom validator.
This is an open bug, and there is a PR to fix it, but they've just kinda been sitting there for months:
https://github.com/rails/rails/issues/7247
https://github.com/rails/rails/pull/9917
I have a customer model that has_many phones like so;
class Customer < ActiveRecord::Base
has_many :phones, as: :phoneable, dependent: :destroy
accepts_nested_attributes_for :phones, allow_destroy: true
end
class Phone < ActiveRecord::Base
belongs_to :phoneable, polymorphic: true
end
I want to make sure that the customer always has at least one nested phone model. At the moment I'm managing this in the browser but I want to provide a backup on the server (I've encountered a couple of customer records without phones and don't quite know how they got like that so I need a backstop).
I figure this must be achievable in the model but I'm not quite sure how to do it. All of my attempts so far have failed. I figure a before_destroy callback in the phone model is what I want but I don't know how to write this to prevent the destruction of the model. I also need it to allow the destruction of the model if the parent model has been destroyed. Any suggestion?
You can do it like this:
before_destory :check_phone_count
def check_phone_count
errors.add :base, "At least one phone must be present" unless phonable.phones.count > 1
end