I have an event model. Each event can have multiple sessions.
I want to ensure that no model can exist without it having at least 1 session associated with it.
validates :sessions, :length => { :minimum => 1 }
The problem is - when I go to try to create sessions on a particular event by calling my model method:
create_sessions()
Which does something like :
sessions.create(event_id: id,date: x,day_of_the_week:x.strftime("%A"),classPin: pin)
for each of the dates the event will run.
It fails to save with the error:
ActiveRecord::RecordNotSaved in EventsController#create
You cannot call create unless the parent is saved
Of course - by this point the new event record has not yet been saved - so this association cannot yet be created due to create on an association not available until the parent is saved!
Therefore how does any validation between this kind of relationship work - because the validation occurs at save time....but I want to validate the count of sessions will be greater than 0 before we save the event!
I think a simpler way would be to create your events and sessions with new
instead of create
it will only create the objects but not store them to the database. And after you have created your sessions do a save
on your event.
@event = event.new(event_args)
@event.sessions.new(session_args)
@event.save
Addition
The @event.save
will store the @event
object and all associated session
objects. Without further configuration only new objects will be safed see Active Record Autosave Association for more information.
The main point is that your validation conflicts with the way ActiveRecord works. You are creating a paradox.
There are a number of solutions, but you must relax the validation. That is, it should not run when the record is new.
You can either define it like this:
class Event < ActiveRecord::Base
validates :sessions, length: { minimum: 1 },
unless: :new_record?
end
Or, to have even more flexibility, you can use a custom validation:
class Event < ActiveRecord::Base
validate :session_count_validation
private
def session_count_validation
if !new_record? && sessions.count < 1
errors.add(:base, "Not enough sessions!")
end
end
end
This second style also has the advantage of being more performant: sessions.count
will simply count how many associated Session
records exist, while your original validation will load all of them in memory, and check the length of the relation/array.
Then, to ensure that your creation logic is sound, you should use a transaction:
begin
ActiveRecord::Base.transaction do
event = Event.create!(args)
sessions = dates.map do |date|
event.sessions.build(event_id: id,
date: date,
day_of_the_week: date.strftime("%A"),
classPin: pin)
end
sessions.each(&:save!)
end
rescue
# your rescue logic
# e.g. display an error to the User
end
So that everything will be safely rolled back if any save!
operation fails.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With