changed? call on properties not working - ruby-on-rails

I have a model class as shown below:
class Product < ActiveRecord::Base
belongs_to :user;
attr_accessible :price
before_save :check_if_price_changed
after_save :notify_about_price_change
def check_if_price_changed
if (price.changed?) then
#price_changed = true
else
#price_changed = false
end
end
....
I want to record if the price of the product changed before I save it to the database. Then I have a routine that will do the notification once the product has been persisted successfully with the new price. But I get the following error when I try to check if the price attribute is dirty:
undefined method `changed?' for 20:Fixnum
Is that method not supported out of the box in rails 3.1? Am I calling it incorrectly or in the incorrect layer (model vs controller)?

This will return a boolean reflecting whether or not the price value has changed.
def check_if_price_changed
self.price_changed?
end
Have a look at the ActiveModel::Dirty documentation.

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.

Update another column if specific column exist on updation

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

unable to add attribute value on Error message

i have a custom validation method on user_calendar model:
class UserCalendar < ActiveRecord::Base
belongs_to :user
validate :should_match_company_calendars
...
private
def should_match_company_calendars
day_company_calendars = (..query..)
errors.add(:user, :company_calendar_match) if day_company_calendars.count < 1
end
And this is the message on en.yml:
company_calendar_match: "%{value.full_name} does not work in the selected time lapse"
Validation works fine but the message shows '%{value.full_name}' instead of the actual value for full_name. What am i missing?
You should call I18n.t method and pass value you want to show like below.
# model
errors.add(:user, I18n.t('... company_calendar_match', full_name: 'set value you want')) if day_company_calendars.count < 1
# en.yml
company_calendar_match: "%{full_name} does not work in the selected time lapse"
More detail is Rails Guides - i18n api features

Why does after_save not trigger when using touch?

Recent days , I was trying to cache rails app use Redis store.
I have two models:
class Category < ActiveRecord::Base
has_many :products
after_save :clear_redis_cache
private
def clear_redis_cache
puts "heelllooooo"
$redis.del 'products'
end
end
and
class Product < ActiveRecord::Base
belongs_to :category, touch: true
end
in controller
def index
#products = $redis.get('products')
if #products.nil?
#products = Product.joins(:category).pluck("products.id", "products.name", "categories.name")
$redis.set('products', #products)
$redis.expire('products', 3.hour.to_i)
end
#products = JSON.load(#products) if #products.is_a?(String)
end
With this code , the cache worked fine.
But when I updated or created new product (I have used touch method in relationship) it's not trigger after_save callback in Category model.
Can you explain me why ?
Have you read documentation for touch method?
Saves the record with the updated_at/on attributes set to the current
time. Please note that no validation is performed and only the
after_touch, after_commit and after_rollback callbacks are executed.
If an attribute name is passed, that attribute is updated along with
updated_at/on attributes.
If you want after_save callbacks to be executed when calling touch on some model, you can add
after_touch :save
to this model.
If you set the :touch option to :true, then the updated_at or updated_on timestamp on the associated object will be set to the current time whenever this object is saved or destroyed
this the doc :
http://guides.rubyonrails.org/association_basics.html#touch
You can also (at least in rails 6) just use after_touch: callback
From the docs:
after_touch: Registers a callback to be called after a record is touched. See ActiveRecord::Callbacks for more information.

accepts_nested_attributes_for :reject_if to trigger another method

I've got a multi-level nested form using formtastic_cocoon (jquery version of formtastic).
I am trying to do some validation in the sense of
if value is_numeric do
insert into database
else do
database lookup on text
insert id as association
end
I was hoping tha the accepts_nested_attributes_for would have an :if option, but apparently there is only the :reject_if.
Is there a way to create a validation like I describe as part of the accepts_nested_attributes_for??
-----------------------Updated as per Zubin's Response ---------------------------
I believe Zubin is on the right track with a method, but I can't seem to get it working just right. The method I am using is
def lookup_prereq=(lookup_prereq)
return if lookup_prereq.blank?
case lookup_prereq
when lookup_prereq.is_a?(Numeric) == true
self.task_id = lookup_prereq
else
self.task = Task.find_by_title(lookup_prereq)
end
end
When I trigger this function, the self.task_id is being put in the database as '0' rather than the Task.id.
I'm wondering if I'm missing something else.
I'm not completely sure that the method is actually being called. Shouldn't I need to say
lookup_prereq(attr[:prereq_id)
at some point?
-------------------further edit -----------------------
I think from what I can find that the method is called only if it is named with the same name as the value for the database, therefore I've changed the method to
def completed_task=(completed_task)
Unfortunately this is still resulting in 0 as the value in the database.
Sounds like you need a method in your nested model to handle that, eg:
class Post < ActiveRecord::Base
has_many :comments
accepts_nested_attributes_for :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
belongs_to :author
def lookup_author=(lookup_author)
return if lookup_author.blank?
case lookup_author
when /^\d+$/
self.author_id = lookup_author
else
self.author = Author.find_by_name(lookup_author)
end
end
end

Resources