I've got model A and model Attach. I'm editing my A form with nested attributes for :attaches. And when I am deleting all attaches from A via accepts_nested_attributes_for how can I get after_update/after_save callbacks for all of my nested models? Problem is that when I am executing callbacks in model A they are executed right AFTER model A is updated and BEFORE model Attach is updated, so I can't, for example, know if there is NO ANY attaches after I delete them all :).
Look for example: my callback after_save :update_status won't work properly after I delete all of my attaches.
model A
after_save :update_status
has_many :attaches
accepts_nested_attributes_for :attaches, :reject_if => proc { |attributes| attributes['file'].blank? }, :allow_destroy => true
def update_status
print "\n\nOUPS! bag is empty!\n\n" if self.attaches.empty?
end
end
model Attach
belongs_to A
end
I am using rails 3 beta
From rubyonrails.org:
IMPORTANT: In order for inheritance to work for the callback queues, you
must specify the callbacks before
specifying the associations.
Otherwise, you might trigger the
loading of a child before the parent
has registered the callbacks and they
won‘t be inherited.
Isn't it your problem? You're specifying the association before the callback.
Ok, I've removed after_save callback from A to nested model Attach (after_destroy callback)
model A
has_many :attaches
accepts_nested_attributes_for :attaches, :reject_if => proc { |attributes| attributes['file'].blank? }, :allow_destroy => true
end
model Attach
after_destroy :update_status
belongs_to :a
def update_status
print "\n\nOUPS! bag is empty!\n\n" if self.a.attaches.empty?
end
end
Related
I'm trying to use an AR callback (after_save) to check, if an association was added on the latest 'update' or 'create'. However, I can't seem to find the correct way to do it.
class Submission < ActiveRecord
has_many :notes, inverse_of: :submission, dependent: :destroy
accepts_nested_attributes_for :notes, :reject_if => proc { |attributes| attributes['message'].blank? }, allow_destroy: true
end
Here is my after_save
after_save :build_conversation
in that method I want to see, if a new note table was added on update or create...
def build_conversation
if self.notes.any?
binding.pry
end
end
This logic does not make sense, because if notes can exist, which is fine. Nevertheless, I only want to enter this block, if there is a new note added on update or create...
Check out this post. Basically, you add include ActiveModel::Dirty in the model, then in the after_change callback you check if_notes_changed. This method is defined using method_missing so for example if you have a name column you can use if_name_changed and so on. If you need to compare the old vs new values, you can use previous_changes.
Alternatively, you can use around_save like so:
around_save :build_conversation
def build_conversation
old_notes = notes.to_a # the self in self.notes is unnecessary
yield # important - runs the save
new_notes = notes.to_a
# binding.pry
end
I'm seeing an issue where destroy callbacks on a child record are not being fired when deleting the child record of a parent model. This issue emerged when updating records from a form, though I don't think that's relevant.
class Job
has_many :assignments, dependent: :destroy
has_many :scheduled_assignments, -> { scheduled }, class_name: 'Assignment'
accepts_nested_attributes_for :scheduled_assignments, dependent_destroy: true
end
class Assignment
belongs_to :job
after_destroy :call_me
after_save :call_me
def call_me
puts "I got called"
end
end
job = Job.create
job.update({ scheduled_assignment_ids: [1] })
# a scheduled assignment is created, and the after_save
# callback is called in assignment
job.update({ scheduled_assignment_ids: [] })
# the scheduled assignment is deleted,
# but the after_destroy callback is not fired
Because the Rails doc mention the child record should be deleted with a 'destroy' action, not 'delete', I'd expect callbacks to be fired on the Assignment object being deleted.
after_destroy is not in the list of callbacks triggered by update:
http://guides.rubyonrails.org/active_record_callbacks.html (Section 3.2)
The docs make it look like this would work:
after_destroy :call_me, on: [ :update ]
I solved this by using after_remove. After searching the Rails source regarding collection associations:
def remove_records(existing_records, records, method)
records.each { |record| callback(:before_remove, record) }
delete_records(existing_records, method) if existing_records.any?
records.each { |record| target.delete(record) }
records.each { |record| callback(:after_remove, record) }
end
You can see that the before_remove and after_remove callbacks are called anytime child record is deleted, regardless of delete or destroy.
So, when adding an after_remove callback to my parent class, I can manually trigger a method on each child record deleted. Which is what I was after.
class Job
has_many :assignments, dependent: :destroy
has_many :scheduled_assignments, -> { scheduled },
class_name: 'Assignment',
after_remove: :teardown_assignment
accepts_nested_attributes_for :scheduled_assignments, dependent_destroy: true
def teardown_assignment(assignment)
# callback here
end
end
I am trying to send a POST request to a rails scaffold controller which contains a nested array representing records that need to be created and associated with the newly created parent.
Here is some example JSON:
{
"plan_id":3,
"weight":60,
"exercise_sets": [
{
"created_at":"2012-06-13T14:55:57Z",
"ended_at":"2012-06-13T14:55:57Z",
"weight":"80.0",
"repetitions":10,
"exercise_id":1
}
]
}
..and my models..
class Session < ActiveRecord::Base
has_many :exercise_sets, :dependent => :destroy
has_many :exercises, :through => :exercise_sets
end
class ExerciseSet < ActiveRecord::Base
belongs_to :exercise
belongs_to :session
end
Is what I am attempting possible?
This is certainly not impossible, though you may have to switch up your parameter naming a bit.
When you pass the JSON above to the controller, it either gets passed as parameters to a constructor:
Session.new(params[:session])
Or gets passed to the #update_attributes method on a persisted Session instance:
#session = Session.find(params[:id])
#session.update_attributes(params[:session])
Both the constructor and #update_attributes methods turn parameters like "plan_id" into assigment method calls. That is,
#session.update_attributes(:plan_id => "1")
Turns into (inside the #update_attributes method):
#session.plan_id = "1"
So, this works for your plan_id and weight attributes, because you have both #plan_id= and #weight= setter methods. You also have an #exercise_sets= method given to you by has_many :exercise_sets. However, the #exercise_sets= method expects ExerciseSet objects, not ExerciseSet attributes.
Rails is capable of doing what you are trying to do via the #accepts_nested_attributes_for class method. Try this:
class Session < ActiveRecord::Base
has_many :exercise_sets, :dependent => :destroy
has_many :exercises, :through => :exercise_sets
accepts_nested_attributes_for :exercise_sets
end
This sets up (metaprograms) an #exercise_sets_attributes= method for you. So just modify your JSON to:
{
"plan_id":3,
"weight":60,
"exercise_sets_attributes": [
{
"created_at":"2012-06-13T14:55:57Z",
"ended_at":"2012-06-13T14:55:57Z",
"weight":"80.0",
"repetitions":10,
"exercise_id":1
}
]
}
More info: http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
It's perfectly possible, you just have to add the following line to your Session model:
accepts_nested_attributes_for :exercise_sets, :reject_if => lambda { |a| a[:exercise_id].blank? }, :allow_destroy => true
Now, when your controller does Session.new(params[:session]), Rails will build (or update) the Session and the related ExerciceSet(s).
Please review the :reject_if clause. That's where you define which records will be created and which will not.
This are the basics, but Ryan Bates explains the nested model forms perfectly (as always) in this screencast.
I have two models, one is the parent of the other, and the parent accepts_nested_attributes_for and validates_associated the children.
However, some of my validations have an :if that needs to check one of the properties of the parent.
I was thinking that I could do something like this:
validates_presence_of :blah, :if => Proc.new{|thing| thing.parent.some_value.present?}
However, the 'parent' relationship doesn't appear to be setup at the time of validation (I would assume the children get instantiated and validated first.
Therefore is there any way of doing what I'm thinking of? Is it possible?
You can use before_update or before_create callbacks as per your need like this..
def before_update
self.errors.add("Error Message") if self.parent.some_value.present?
return false if self.errors.count > 0
end
def before_create
self.errors.add("Error Message") if self.parent.some_value.present?
return false if self.errors.count > 0
end
This kind of validation should work:
validates_associated :children
But it won't
The reason is, as far as I understand, beause using acceptes_nested_attributes_for is creating nested objects straight to database via one transaction without passing any children validations.
What you can do here: write your own validation in parent model and validate creating children objects.
Use the :inverse_of option for the association on the parent, so the children will have a reference to the parent when they are built.
class Parent < ActiveRecord::Base
has_many :children, :inverse_of => :parent
accepts_nested_attributes_for :children
end
class Child < ActiveRecord::Base
belongs_to :parent
end
p = Parent.new :children_attributes => { 0 => { :child_attribute => 'value' } }
p.children.first.parent #=> shouldn't be nil anymore
Now I hava a problem,how can I make the callback#after_add receive a reference to the join model in a has_many :through association?
my code like this:
class Emergency
has_many :departments, :through => :eme_references, :after_add => Proc.new { |eme_reference| eme_reference.eme_flag = 1}
end
the attribute eme_flag is the model EmeReference's attribute! but in the block ,i get the eme_reference.class is Emergency.
I want to set the attribute eme_flag of the model EmeReference.
That is my question!
cheers!
Presumably Emergency also has_many :eme_references in order for the :through association to work?
In that case, you should be able to attach the callback there:
has_many :eme_references,
:after_add => Proc.new { |emergency, eme_ref| # code here }
The block accepts 2 parameters, the first will be the Emergency, the 2nd will be the EmeReference being added.
Perhaps a before_save callback on EmeReference can also do what you want in this instance?
I think what you want to do can't be done there.
You could create an after_create hook on departments (I'm assuming Emergency has_many eme_references has_many departments):
class Emergency
has_many :departments, :through => :eme_references
def flag!
eme_flag=1
save
end
end
class Department
after_create :check_emergency
# this allows you to call department.emergency. Will return nil if anything is nil
delegate :emergency, :to =>:eme_reference, :allow_nil => true
def check_emergency
self.emergency.flag! if self.emergency.present?
end
end