I have a two models, Submission (the parent) and SubmissionDetail (the child). Submission has a field called status, which can either be Incomplete, Pending, Approved, or Disapproved. When you create a new Submission, status is automatically set to Incomplete. The child has a foreign key of :submission_id.
What I would like to happen is when a new SubmissionDetail is created, its parent's status will automatically be changed to "Pending." Not sure how to go about this. For example, I've read a little about touch in the model, but I don't think that applies here.
You can probably make use of ActiveRecord's callbacks to update the parent whenever a new child is created.
class Submission < ActiveRecord::Base
has_many :submission_details
end
class SubmissionDetail < ActiveRecord::Base
belongs_to :submission
after_create :set_parent_to_pending
def set_parent_to_pending
# child should always have a parent, but we need to check just in case
submission.update(status: 'pending') unless submission.nil?
end
end
The after_create callback will be run after the child record is created.
You can certainly handle this by hooking create, but then you're not hooking update and certain actions won't propagate back to Submission. You're also littering your persistence layer with business logic and coupling models.
My preference, avoiding the creation of service objects, would be to use after_touch:
class SubmissionDetail < ActiveRecord::Base
belongs_to :submission, touch: true
end
class Submission < ActiveRecord::Base
has_many :submission_details
after_touch :pending!
protected
def pending!
self.status = 'pending'
save!
end
end
This keeps the logic out of SubmissionDetail and keeps Submission responsible for keeping its own status up to date.
Note that if you end up having to manipulate status based on flow and conditions like this a lot, you really want to look into integrating a state machine.
Touch just updates the updated_at column to the current time. You can just add an after_create hook:
class SubmissionDetail < AR
belongs_to :submission
after_create :set_pending
private
def set_pending
submission.update_attributes(state: "Pending") # or whatever random method
end
end
Related
I have these 2 models as follow
class Application < ActiveRecord::Base
has_many :commitments, class_name: "Commitment", \
:source => :application, dependent: :destroy
accepts_nested_attributes_for :commitments
after_create: update_case_code
end
class Commitment < ActiveRecord::Base
belongs_to :application
after_create: send_notification
def send_notification
ap self.application.case_code
end
end
class ApplicationsController < ApplicationController
def create
#application = Application.new(params)
#application.save
end
end
In my application_controller whenever i create a new Application record,a new record is also created in the Commitment and it tries to get the case_code from the application record but the after_create method of the application model hasnt been executed yet.
Is there any way to optimize this code so that it works properly?
Probably there is. Probably you can also use another callback on the application model which happens before, there are plenty of them. See Active Record Callbacks
However this is exactly the case, which other people call rails callback hell
The best practice here would be just creating a form object, which creates the data in the order you need and remove the callbacks
class ApplicationCommitmentForm
include ActiveModel::Model
attr_accessor ...
def submit
a = Application.create ..
a.update_case_code
a.commitments.create ...
end
end
See ActiveModel Form Objects
Btw you could also wrap the submit code into a transactions ensuring that either all records are created or in case of any errors nothing at all.
How can I decrease #user.credit every time User makes changes or updates in Entity Controller. I would have to make changes in User table from Entity controller to do this.
Purpose of this is that app charges money/credits for making actions in database.
You will need and after_create callback in the Entity model.
class Entity < ActiveRecord::Base
belongs_to :user
after_save :update_user_credits
def update_user_credits
User.update_counters user_id, credits: -1
end
end
To add to bcd's answer, you'd be better changing the user associative object rather than invoking a totally new SQL call:
#app/models/entity.rb
class Entity < ActiveRecord::Base
belongs_to :user
after_save :update_credits
private
def update_credits
self.user.decrement! :credit
end
end
Refs for the decrement! method
I have a package model which has_many sales.
I'd like to sum up all the sales revenue and update the package model's total_revenue after each new sale.
How do I do that?
You want to use an active record callback. I would probably use after_create. You can add code like this:
class Sale < ActiveRecord::Base
belongs_to :package
after_create :update_package_revenue
def update_package_revenue
package.update(total_revenue: package.sales.sum(:revenue)) # substitute the correct code here
end
end
This allows you to run code every time you create a new sale.
A simplistic way to accomplish this would be with a setup like:
class Package < ActiveRecord::Base
has_many :sales
def calculate_and_update_total_revenue
update_attributes(:total_revenue, sales.sum(:revenue))
end
end
class Sales < ActiveRecord::Base
belongs_to :package
after_create :update_parent_package
private
def update_parent_package
package.calculate_and_update_total_revenue
end
end
It gives you a race-condition resistan method to bump the total (you could just add a +self.revenue for each new sale, but then you'd be modifying a global state from multiple execution contexts.
Still, if you write logic like this you'll end up with fat models, really hard to manage in a big application.
What about using mediator objects instead?
I have:
class Parent < ActiveRecord::Base
has_many :things
before_save :update_something
private
def update_something
self.update_column(:something, "something")
end
end
And
class Thing < ActiveRecord::Base
belongs_to :parent, autosave: true
end
I expect that when I save an instance of Thing that it's Parent should also be saved. I also expect that instance of Parent to have it's before_save callback called. This doesn't seem to be the case.
Any idea why this doesn't work and how I might remedy it?
Referring to the docs
If you set the :autosave option to true, Rails will save any loaded
members and destroy members that are marked for destruction whenever
you save the parent object.
I suggest you creating new after_save callback for Thing to update parent if you want to go Rails way.
But the OO way would be to create class that handles saving the object, such as:
class ThingUpdater
def initialize(thing)
#thing = thing
end
def call(params)
#thing.update_attributes(params)
#thing.parent.update_something
end
end
Thanks to this you will avoid callback hell - take also a look here
In Rails 3 is there a clean way to check if parent object is a new record but from the before_create callback of the child
class Parent
has_many :children
accepts_nested_attributes_for :children
end
class Child
before_create :on_before_create
def on_before_create
logger.debug self.parent.new_record?
logger.debug self.parent.id
logger.debug self.parent.id_was
end
end
I only want to execute code in on_before_create if the parent is not a new record
but at the point the child is created the parent record has been inserted into the db and so it has an id, id and id_was both return the same value,
new_record? is actually returning nothing, usually it returns a boolean
any ideas?
Use before_save rather than before_create. You'll need to check for create manually, but the callback is fired before any associated objects are saved.
class Parent < ActiveRecord::Base
has_many :children
accepts_nested_attributes_for :children
end
class Child < ActiveRecord::Base
before_save :on_before_save
belongs_to :parent
def on_before_save
# Don't run if this is not create.
return unless self.new_record?
self.parent.new_record? #=> boolean
end
end
Also, make sure you define the callbacks before the associations, as per the documentation:
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.