Rails maintain order of before_validation callback - ruby-on-rails

I have several before_validation callbacks that are added to my ApplicationRecord class. For example:
class ApplicationRecord < ActiveRecord::Base
before_validation :validation_one
before_validation :validation_two
end
This validations should always be called in this particular order only.
In one of the child class of the application record I have to add the validation_one again in order to add an if condition.
class User < ApplicationRecord
before_validation :validation_one, if: :some_condition?
end
Is there a way I can maintain the order of validation as was in the base class, without editing anything in the child class?
I know I can add prepend: true to User class to achieve this but I want to keep it generic so that I don't have to add prepend: true to all the child class.

Related

How to generate Child objects each time a parent object is created automatically on rails 5.1

In my application i have the following models:
class Bus < ApplicationRecord
belongs_to :user
has_many :seats, dependent: :destroy
end
class Seat < ApplicationRecord
belongs_to :bus
end
Is there a way to have a specific number of "Seats" created each time a user adds a bus? I don't want users to be creating seats for the buses.
You can hook creation of child object to after_create callback
https://guides.rubyonrails.org/active_record_callbacks.html
class Parent < ApplicationRecord
# register callback
after_create :createChilds
private
def createChilds
# create required amount of childs
end
end
You can use callbacks for this purpose, specifically after_create one.
class Bus
DEFAULT_SEATS_COUNT = 50.freeze
after_create :add_seats
private
def add_seats
DEFAULT_SEATS_COUNT.times do
# This logic can be more complicated if you need different attribute values for different seats.
self.seats.create!
end
end
end

Create dependant record on Clearance user creation

I simply want to create another record when the user signs up. I think the following are the only places where Clearance touches my app, not counting the views.
class ApplicationController < ActionController::Base
include Clearance::Controller
before_action :require_login
.
.
.
end
class User < ActiveRecord::Base
include Clearance::User
has_many :received_messages, class_name: 'Message', foreign_key: :receiver_id
has_one :privilege
end
You want after_create (or perhaps before_create, or some other hook, depending on your semantics), which is provided by Rails independent of Clearance. It lets you declare a method to run after your User record is created, and the method can create other objects that you want to exist.
class User < ActiveRecord::Base
after_create :create_other_thing
private
def create_other_thing
OtherThing.create(other_thing_attributes)
end
end
Be aware than after_create runs in the same transaction as your User creation, so if there's an exception during OtherThing.create, both it and the User will be rolled back.
Check out Active Record Callbacks for full details on how ActiveRecord lifecycle hooks work.

Decrease credit for every Create and Update action in controller. Rails 4

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

Rails don't save if duplicate

I have a somewhat complex Rails model setup that I'll try to simplify as much as possible. The goal of this setup is to be able to have objects (Person, Pet) that are long-lived, but with relationships between them changing each year via TemporalLink. Basically, I have these models:
class Person < ActiveRecord::Base
include TemporalObj
has_many :pet_links, class_name: "PetOwnerLink"
has_many :pets, through: :pet_links
end
class Pet < ActiveRecord::Base
include TemporalObj
has_many :owner_links, class_name: "PetOwnerLink"
has_many :owners, through: :owner_links
end
class PetOwnerLink < ActiveRecord::Base
include TemporalLink
belongs_to :owner
belongs_to :pet
end
and these concerns:
module TemporalLink
extend ActiveSupport::Concern
# Everything that extends TemporalLink must have a `year` attribute.
end
module TemporalObj
extend ActiveSupport::Concern
# Everything that extends TemporalObj must have a find_existing() method.
####################
# Here be dragons! #
####################
end
The desired behavior is:
When creating a TemporalObj (Pet, Person):
1) Check to see if there is an existing one, based on certain conditions, with find_existing().
2) If an existing duplicate is found, don't perform the create but still perform necessary creations to associated objects. (This seems to be the tricky part.)
3) If no duplicate is found, perform the create.
4) [Existing magic already auto-creates the necessary TemporalLink objects.]
When destroying a TemporalObj:
1) Check to see if the object exists in more than one year. (This is simpler in actuality than in this example.)
2) If the object exists in only one year, destroy it and associated TemporalLinks.
3) If the object exists in more than one year, just destroy one of the TemporalLinks.
My problem is I have uniqueness validations on many TemporalObjs, so when I try to create a new duplicate, the validation fails before I can perform any around_create magic. Any thoughts on how I can wrangle this to work?
You can (and should) use Rails' built-in validations here. What you've described is validates_uniqueness_of, which you can scope to include multiple columns.
For example:
class TeacherSchedule < ActiveRecord::Base
validates_uniqueness_of :teacher_id, scope: [:semester_id, :class_id]
end
http://apidock.com/rails/ActiveRecord/Validations/ClassMethods/validates_uniqueness_of
In response to JacobEvelyn's comment, this is what I did.
Created a custom validate like so
def maintain_uniqueness
matching_thing = Thing.find_by(criteria1: self.criteria1, criteria2: self.criteria2)
if !!matching_thing
self.created_at = matching_thing.created_at
matching_thing.delete
end
true
end
Added it to my validations
validate :maintain_event_uniqueness
It worked.

Ruby on Rails - Update Parent Field upon Child Creation

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

Resources