简体   繁体   中英

rails 5 update attribute only if it's currently nil

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

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 , eg 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'

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.

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.

You could:

car = Car.first
car.connected_at ||= Time.zone.now
car.save

That will only assign if connected_at is nil of false .

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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM