Update another column if specific column exist on updation - ruby-on-rails

I have a model which have two columns admin_approved and approval_date. Admin update admin_approved by using activeadmin. I want when admin update this column approval_date also update by current_time.
I cant understand how I do this.Which call_back I use.

#app/models/model.rb
class Model < ActiveRecord::Base
before_update 'self.approval_date = Time.now', if: "admin_approved?"
end
This assumes you have admin_approved (bool) and approval_date (datetime) in your table.
The way it works is to use a string to evaluate whether the admin_approved attribute is "true" before update. If it is, it sets the approval_date to the current time.

Use after_save callback inside your model.
It would be something like this:
after_save do
if admin_approved_changed?
self.approval_date = Time.now
save!
end
end
Or change the condition as you like!

You could set the approval_date before your model instance will be saved. So you save a database write process instead of usage of after_save where you save your instance and in the after_save callback you would save it again.
class MyModel < ActiveRecord::Base
before_save :set_approval_date
# ... your model code ...
private
def set_approval_date
if admin_approved_changed?
self.approval_date = Time.now
end
end
end
May be in your controller:
my_instance = MyModel.find(params[:id])
my_instance.admin_approved = true
my_instance.save

Related

How do I invoke a method only when my object (model) is first created in Rails 5?

I'm using Rails 5. I want a method invoked on my model only when the model is first created. I have tried this ...
class UserSubscription < ApplicationRecord
belongs_to :user
belongs_to :scenario
def self.find_active_subscriptions_by_user(user)
UserSubscription.joins(:scenario)
.where(["user_id = ? and start_date < NOW() and end_date > NOW()", user.id])
end
after_initialize do |user_subscription|
self.consumer_key = SecureRandom.urlsafe_base64(10)
self.consumer_secret = SecureRandom.urlsafe_base64(25)
end
end
but I noticed this gets called every tiem I retrieve a model from a finder method in addition to its begin created. How can I create such functionality in my model?
You want to use after_create (from active record docs) or after_create_commit which was introduced in Rails 5 as a shortcut for after_commit :hook, on: :create.
after_create always executes after the transactions block whereas after_create_commit does so after the commit but within the same transactions block. These details likely don't matter here, but it's a new capability if you need that extra control for ensuring the model state is correct before you execute the after call.
Pyrce's answer is good. Another way is to keep the after_initialize method but only run if it's a new record:
after_initialize :set_defaults
def set_defaults
if self.new_record?
self.consumer_key = SecureRandom.urlsafe_base64(10)
self.consumer_secret = SecureRandom.urlsafe_base64(25)
end
end
(It's generally considered better to not override the after_initialize method. Instead provide the name of a method to run, as I did above.

rails 4 attr_accessor and new records

I have a model Booking with attr_acessor :date and time:
class Booking < ActiveRecord::Base
# ...
before_save :convert_date_and_time
attr_accessor :date, :time
def convert_date_and_time
self.date_and_time = DateTime.parse("#{date.to_s} #{time.to_s}")
end
end
I am trying to define getter methods for date and time:
def date
date_and_time.to_date if self.id.present?
end
def time
date_and_time.to_time if self.id.present?
end
but I think this is not quite the way to do it. I need self.id.present? because when I am trying to create a new record, obviously date_and_time still has no value and the getters will yield errors.
If that is the case, how should the getters look like so that I can handle new records that are not yet saved? Should I leave them like how they are now?
Thanks!
To detect new record you can use new_record?, but in your case you can use try :
date_and_time.try(:to_date)
date_and_time.try(:to_time)

stack level too deep after_save callback in Rails4

My model is like this,
class Slot
include Mongoid::Document
after_save :calculate_period
field :slot, type: Array
def calculate_period
if condition
do something
end
self.slot = true
save
end
end
After submit button it will show this error,
SystemStackError in SlotsController#create
stack level too deep
and also consuming more time. If i remove the save from def calculate_period then the values are not storing after_save callback.
Any solution...!!!
You should change this to before_save, that way you can change the model's attributes, and then they will be saved to the database as normal.
class Slot
include Mongoid::Document
before_save :calculate_period
def calculate_period
if condition
#do something
end
end
end
You have infinite loop - calling save in calculate_period method invokes callbacks, including your calculate_period callback. The first solution that came into my mind is to add virtual attribute and check it before calling your callback method:
class Slot
include Mongoid::Document
after_save :calculate_period, unless: :period_calculated # I'm not sure if Mongoid allows this
attr_accessor :period_calculated
def calculate_period
if condition
# do something
end
self.period_calculated = true
save
end
end

how to validate a record in table before saving in ruby on rails

I am new to Ruby on Rails
I have a scenario in which I have a form which has some fields. One of the field values I need to validate against a table which has the data .
I want to restrict the user from saving any data unless the field is validated with the table records.
Initially I added the code in controller to validate that but I have other fields which I need to validate as empty so it did not work .
Also I want the the validation error to be part of other errors.
I tried the below code in the model file
before_create :validate_company_id
def validate_company_id
cp = Company.find_by_company_id(self.company)
if #cp != nil
return
else
self.status ||= "Invalid"
end
end
But its not validating , could you help me how I can validate it .
regards
Surjan
The guys answered correctly, but provided the other way for solution. You could ask yourself: "Why doesn't my code get executed?"
First of all, you have errors in your code - #cp is undefined. Also, I don't know what are you trying to achieve with self.status ||= "Invalid".
You also don't have to use self when you're calling an attribute, but you do have to call it when you're assignig a new attribute value. So self.company is unnecessary, you can just use company.
I've also noticed you have the company_id attribute in your companies table. That's not neccessary, common convention is using just an id instead. If you don't want to alter your table you can set the id field on your model like so:
class Company < ActiveRecord::Base
set_primary_key :company_id
# ... the rest of your model code ...
end
After that you can use Company.find instead of Company.find_by_company_id.
Okay, let's say you have the following code after the fixes:
before_create :validate_company_id
def validate_company_id
cp = Company.find(company)
if cp != nil
return
else
self.status ||= "Invalid"
end
end
First of all I would like to use ternary operator here
before_create :validate_company_id
def validate_company_id
Company.find(company) ? return : self.status ||= "Invalid"
end
Isn't this cleaner? It does the exact same thing.
Now about that self.status of yours. If you would like to invalidate the object in ActiveModel you have to set some values in errors hash. You're in misconception if you think that a model with the status attribute of "Invalid" is invalid. It's still perfectly valid model in Rails.
So how do you invalidate?
You put some values into errors hash. You can also specify a message and the attribute you're validation error refers to.
So let's do it on your model
before_create :validate_company_id
def validate_company_id
Company.find(company) ? return : errors.add(:company,"Invalid Company ID")
end
Now if you try to save your model with invalid company_id it will still pass and get saved to the DB. Why is that?
It's because of the ActiveModels lifecycle. Your method gets called too late.
Here are all the callback methods you can use
Creating
before_validation
after_validation
before_save
around_save
before_create
around_create
after_create
after_save
Updating
before_validation
after_validation
before_save
around_save
before_update
around_update
after_update
after_save
Destroying
before_destroy
around_destroy
after_destroy
Notice how your method gets called long after the validation cycle. So you should not use before_create, but after_validation or before_validation callbacks instead.
And here we are with the working validation method of your model.
after_validation :validate_company_id
def validate_company_id
Company.find(company) ? return : errors.add(:company,"Invalid Company ID")
end
instead of using before_create. You can tell the model to use a custom method for validation as follows
validate :validate_company_id
def validate_company_id
cp = Company.find_by_company_id(self.company)
if cp.nil?
errors.add(:company, 'Invalid Company ID')
end
end
Inside you model, you can add custom validations as below:
validate :validate_company_id
def validate_company_id
Your validations
Add error messages can be added as below
errors.add(:company, "is invalid")
end

before_update syntax

post.rb Model
after_update :assign_owner
def assign_owner
self.owner = "test"
end
The above method works in terminal but does not change the value of Post.new.owner in Rails. What am I missing?
This is an after update (object needs to be saved) so
post = Post.new.save
Then
post.owner # will be test
If you wanna do this you may want to use after_initialize
for e.g in post.rb
class Post < ActiveRecord::Base
protected
def after_initialize
self.owner = "test
end
end
after_update only fires when you update your object. after_update will not call when you create.
You can use after_create callback when you want to call method on creating new object.
after_create :assign_owner
after_update :assign_owner
def assign_owner
self.owner = "test"
end
after_update and after_create are called after the object is saved. You do set the value of the owner but you don't save it.
Two possible options: use before_update instead --> your object is not yet saved and your change will be saved correctly.
Or use after_update and write it as follows:
def assign_owner
self.update_attribute :owner, "test"
end
Note: any callback will only be called right before or right after saving, so Post.new.owner will still be wrong. But Post.create(:context => 'blabla') should trigger it correctly (or Post.new.save).
Hope this helps.

Resources