Reputation: 2380
What is the preferred way in Rails 5 with activerecord to update the attribute only if it is currently nil.
car = Car.first
car.connected_at = Time.zone.now
car.save
OR
car = Car.first
car.update!(connected_at: Time.zone.now)
it should update only if car.connected_at is nil
Upvotes: 2
Views: 5898
Reputation: 514
I would propose to use the before_update
callback and rephrase the intention of the OP as "discard updates if my attribute already has a value".
I came up with this solution (which works well with mass assignments such as Car.update(car_params)
):
before_update :ignore_updates_to_connected_at
def ignore_updates_to_connected_at
return unless connected_at.present? && connected_at_changed?
clear_attribute_change(:connected_at)
end
The <attribute_name>_changed?
and clear_attribute_change
methods come from ActiveModel::Dirty.
Upvotes: 0
Reputation: 1159
You could:
car = Car.first
car.connected_at ||= Time.zone.now
car.save
That will only assign if connected_at
is nil
of false
.
Upvotes: 0
Reputation: 116
this is my understanding of your question.
Car can update only if connected_at
is nil
class Car < ApplicationRecord
before_save :updatable?
def updatable?
connected_at.blank?
end
end
The point is return false
when before_save.
Upvotes: 0
Reputation: 2009
It sounds like you're saying you want to modify the behaviour of how a particular attribute works so it quietly ignores you. I think the instinct behind why you want to seal this off is reasonable one but if you think about it a bit more you might consider that if you do this kind of thing in a lot of places then using your objects will start to become confusing particularly for someone else who doesn't know the code well.
Perhaps you want to do this because there's other code using the Car model that wants to make connections but doesn't really have the full picture so it tries stuff which you only want to succeed the first time. It's much better to handle such operations solely inside a class which does have the full picture such as the Car model or a service object.
If you still really want to control this "connecting" behaviour outside the Car then you can override the attr_writer completely in the Car class. I'd definitely recommend doing this on before_save callback instead though.
def connected_at=(new_value)
if @connected_at
raise StandardError, 'connected_at has already been set'
end
@connected_at = new_value
end
That will work whichever way you try to assign the value. If you're wondering about what's going on above have a read about attr_accessor in ruby.
Upvotes: 0
Reputation: 6121
You can simply check for #nil?
car = Car.first
car.update_attribute(:connected_at, Time.zone.now) if car.connected_at.nil?
That's not generic enough. I want something like before_validation etc. I am just not sure which way is the preferred one.
Well if you want to go for validation, it would look something like this..
before_save :validate_connected_at
private
def validate_connected_at
connected_at = connected_at_was if connected_at_changed? && connected_at_was.present?
end
OR
before_save :set_connected_at
private
def set_connected_at
connected_at = Time.zone.now if connected_at.nil?
end
As you can see, more checks, more methods. I would definitely go for the first one.
However, if you want to add error message, then this is the way
errors.add(:connected_at, 'Already present!')
So "#{attr}_was" is always available on all the defined attrs in before_save method?
They are available in general and not only in before_save
, e.g. in the console..
car = Car.first
car.connected_at
=> 'some value'
car.connected_at = 'some other value'
car.connected_at
=> 'some other value'
car.connected_at_was
=> 'some value'
Upvotes: 4