Rails set model attribute if blank - ruby-on-rails

I have a User model that has an attribute called country. This attribute is set by a method called methodA.
Somewhere else in my code I may try to access User's country attribute and it might be blank if methodA never ran.
What I'm looking for is to run methodA if I try to access User's country attribute and it's blank.
I tried something like that in my Model :
def country
c = read_attribute(:country).presence
if c.blank?
methodA
else
return c
end
end
But I get an error when it first runs. If I reload the page, country has been set on the previous run (even tho the error) and it's all good.
I would love it to work on the first run and avoid the error page tho...
Thanks in advance for your help

You can just call super
def country
super.presence || "do whatever"
end
presence will check present? and if present? it will return its receiver; otherwise it returns a false-y value (nil).
Remember that if possible you should be setting a database default.

I have managed to achieve my goal using this :
class User < ApplicationRecord
after_find :check_country
def check_country
if country.blank?
methodA
end
end
def methodA
...code
end
end
It does work but I'm not sure if this is ideal... Because check_country will be executed everythime a User is fetched... Even if country is set.
I would prefer it to run only if country is blank.
Any idea ?

Related

Detecting if value of attribute changed during last update doesnt work with Active Model Dirty

I am trying to send a notification email in my rails app only if the value of my column status was modified by the current update. I tried using Active Model Dirty as was suggested in some post and the status_changed? method. Unfortunately my email is never sent because #partnership.status_changed? constantly returns false even though the value of status was indeed changed during the last update. Here's my controller code :
def update
authorize #partnership
if #partnership.update(partnership_params)
send_notification_email
render json: {success: "partnership successfully updated"}, status: 200
else
render_error(nil, #partnership)
end
end
private
def send_notification_email
PartnershipMailer.partnership_status_change(#partnership).deliver_now if #partnership.status_changed?
end
I have also included Active Model Dirty in my model :
class Partnership < ActiveRecord::Base
include ActiveModel::Dirty
What am I doing wrong ?
.update also saves the model after updating it's data, therefore resetting the dirty-values. Try using .assign_attributes. It will just assign the attributes, then you can check for changes, and finally remember to save the model.
As #Thounder pointed out, the ActiveModel::Dirty method <attribute>_changed? is reset whenever you save a record. Thus, it only tracks changes between saves.
For your use case, what you want to use is the previous_changes method, which returns a hash with the key being the attribute changed and the value being an array of 2 values: old and new.
person = Person.new(name: "Bob")
person.name_changed? # => true
person.save
person.name_changed? # => false (reset when save called)
person.previous_changes # => { name: [nil, "Bob"] }
person.previous_changes[:name] # => returns a "truthy" statement if :name attribute changed
My pseudo-code may be wrong, but the principle works. I've been bitten by this "gotcha" before, and I wish the Rails core team would change it.
I understand their reasoning, but it makes more sense to me to track <attribute>_changed? after a save as well, because that seems the common use case to me.
You can try this method to check the changed attributes for the active record.
#partnership.changed.include?("status")
If it returns true then we have status attribute which was changed in this record.
Use #partnership.saved_change_to_status? or #partnership.saved_change_to_attribute(:status) as per docs.
Here is a one line method you can into the model which is the best for your case :
after_commit :send_notification_email, if: Proc.new { |model| model.previous_changes[:status]}

How to skip_callback before_save for specific user?

I've a method named update inside my DailyOrdersController:
def update
if #daily_order.update( daily_order_params.merge({default_order:false}) )
respond_or_redirect(#daily_order)
else
render :edit
end
end
My DailyOrder model:
before_save :refresh_total
def refresh_total
# i do something here
end
What I'm trying to do now is, I want the refresh_total callback to be skipped if the update request is coming from current_admin.
I have 2 user model generated using Devise gem:
User (has current_user)
Admin (has current_admin)
I try to make it like this:
def update
if current_admin
DailyOrder.skip_callback :update, :before, :refresh_total
end
if #daily_order.update( daily_order_params.merge({default_order:false}) )
respond_or_redirect(#daily_order)
else
render :edit
end
end
But it's not working and still keep calling the refresh_total callback if the update request is coming from current_admin (when the logged-in user is admin user).
What should I do now?
I think this is all what you need:
http://guides.rubyonrails.org/active_record_callbacks.html#conditional-callbacks
If you skip callback, you should enable it later. Anyway, this does not look as the best solution. Perhaps you could avoid the callbacks otherwise.
One way would be to use update_all:
DailyOrder.where(id: #daily_order.id).update_all( daily_order_params.merge({default_order:false}) )
Or you could do something like this:
#in the model:
before_validation :refresh_total
#in the controller
#daily_order.assign_attributes( daily_order_params.merge({default_order:false}) )
#daily_order.save(validate: current_admin.nil?)
or maybe it would be the best to add a new column to the model: refresh_needed and then you would conditionally update that column on before_validation, and on before_save you would still call the same callback, but conditionally to the state of refresh_needed. In this callback you should reset that column. Please let me know if you would like me to illustrate this with some code.
This may come in handy:
http://www.davidverhasselt.com/set-attributes-in-activerecord/
UPDATE
Even better, you can call update_columns.
Here is what it says in the documentation of the method:
Updates the attributes directly in the database issuing an UPDATE SQL
statement and sets them in the receiver:
user.update_columns(last_request_at: Time.current)
This is the fastest way to update attributes because it goes straight to
the database, but take into account that in consequence the regular update
procedures are totally bypassed. In particular:
\Validations are skipped.
\Callbacks are skipped.
+updated_at+/+updated_on+ are not updated.
This method raises an ActiveRecord::ActiveRecordError when called on new
objects, or when at least one of the attributes is marked as readonly.

Rails if empty, use other value

In my rails application I have a model named "User". User has an column email and a column billing_email. When user creates account he enters the value for user.email. The column billing_email stays empty.
Inside the application I want to use "user.email" if "user.billing_email" is still empty. So unless user has enters value for his "user.billing_email", "user.email" is used.
Can someone point me in the right direction with this? And is the best place to put such code? Inside a helper or inside the user_model itself?
Override the getter in your model:
def billing_email
super.blank? ? email : super
end
But you'd rather use another method name, or keep the same name but in a decorator for instance.
I suggest you to create a new method that checks the presence of the billing_email and returns it if present, otherwise it defaults to email.
class User
def valid_email
billing_email.presence || email
end
end
you can override the billing_email
class User
def billing_email
read_attribute(:billing_email).presence || email
end
end
but I normally avoid this because it can lead to some side effects when the model gets saved or displayed.
In fact, in some cases your code may end-up storing the value of email if billing_email is blank, duplicating all the values.
It's better to go with a custom method.

how to capture the time a field is updated

I'm aware of updated_at and created_ate in rails.
But what I'm interested in is the ability to update a field within a model when another field is updated. Here's what I've tried:
in my model:
protected
def update_email_sent_on_date
if self.send_to_changed?
self.date_email_delivered = DateTime.now
end
end
and in the one place in my code that updates the field in question:
distribution.send(:update_email_sent_on_date)
the problem is, this doesn't seem to be doing anything to my db table at all. I even tried removed the check on "send_to" but still nothing.
what am I doing wrong?
You're not saving it after you make the change.
Change the method to this:
def update_email_sent_on_date
if send_to_changed?
self.date_email_delivered = DateTime.now
save
end
end
Or save the model after calling it like so:
distribution.update_email_sent_on_date
distribution.save

How to setup default attributes in a ruby model

I have a model User and when I create one, I want to pragmatically setup some API keys and what not, specifically:
#user.apikey = Digest::MD5.hexdigest(BCrypt::Password.create("jibberish").to_s)
I want to be able to run User.create!(:email=>"something#test.com") and have it create a user with a randomly generated API key, and secret.
I currently am doing this in the controller, but when I tried to add a default user to the seeds.rb file, I am getting an SQL error (saying my apikey is null).
I tried overriding the save definition, but that seemed to cause problems when I updated the model, because it would override the values. I tried overriding the initialize definition, but that is returning a nil:NilClass and breaking things.
Is there a better way to do this?
use callbacks and ||= ( = unless object is not nil ) :)
class User < ActiveRecord::Base
before_create :add_apikey #or before_save
private
def add_apikey
self.apikey ||= Digest::MD5.hexdigest(BCrypt::Password.create(self.password).to_s)
end
end
but you should definitely take a look at devise, authlogic or clearance gems
What if, in your save definition, you check if the apikey is nil, and if so, you set it?
Have a look at ActiveRecord::Callbacks & in particular before_validation.
class User
def self.create_user_with_digest(:options = { })
self.create(:options)
self.apikey = Digest::MD5.hexdigest(BCrypt::Password.create("jibberish").to_s)
self.save
return self
end
end
Then you can call User.create_user_with_digest(:name => "bob") and you'll get a digest created automatically and assigned to the user, You probably want to generate the api key with another library than MD5 such as SHA256 you should also probably put some user enterable field, a continuously increasing number (such as the current date-time) and a salt as well.
Hope this helps
I believe this works... just put the method in your model.
def apikey=(value)
self[:apikey] = Digest::MD5.hexdigest(BCrypt::Password.create("jibberish").to_s)
end

Resources