Sean Magyar
Sean Magyar

Reputation: 2380

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

Upvotes: 2

Views: 5898

Answers (5)

Johannes
Johannes

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

bonafernando
bonafernando

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

unlimitedfocus
unlimitedfocus

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

Adamantish
Adamantish

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

Md. Farhan Memon
Md. Farhan Memon

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

Related Questions