0

I have an events object which is a wrapper around hash in ruby. I use it to store events that have occurred and are yet to occur. Infomration includes planned date, actual date event type mode of event etc.

I made use of serialize method in Active Record to load the events object. It is being stored as jsonb in posgresql .

When i call save! or update_attribute on it, it does not add the new transshipment event to it but when i call update_column, it produces desired result.

here is my code to update events

    serialize :events, Shipload::Events::Serializer

    def add_transshipment_event
      self.events.add_transshipment
      # update_column(:events, self.events)
      update_attribute(:events, self.events)
      # save!
    end

here is events.rb

class Shipload
class Events

attr_reader :events

# Takes a hash of events
#
# @param [Hash]
def initialize(events)
  unless events.present?
    events = {}
    events[Shipload::Events::Event::GATE_IN_EVENT_KEY] = Shipload::Events::Event.new(event_type: Shipload::Events::Event::GATE_IN)
    events[Shipload::Events::Event::ORIGIN_DEPARTURE_EVENT_KEY] = Shipload::Events::Event.new(event_type: Shipload::Events::Event::ORIGIN_DEPARTURE)
    events[Shipload::Events::Event::ARRIVAL_EVENT_KEY] = Shipload::Events::Event.new(event_type: Shipload::Events::Event::ARRIVAL)
    events[Shipload::Events::Event::GATE_OUT_EVENT_KEY] = Shipload::Events::Event.new(event_type: Shipload::Events::Event::GATE_OUT)
  end
  @events = events
end

# Takes in the event has received from HTTP request and translates it into {Shipload::Events} object
#
# @param events_hash [Hash]
# @return [Shipload::Events]
def self.parse(events_hash)
  events_hash = events_hash["events"]
  events = {}
  events_hash.keys.each do |key|
    event = events_hash[key]
    event["mode"] = event["mode"].present? ? event["mode"].to_i : nil
    event["planned_date"] = event["planned_date"].present? ? event["planned_date"].to_datetime : nil
    event["original_planned_date"] = event["original_planned_date"].present? ? event["original_planned_date"].to_datetime : nil
    event["actual_date"] = event["actual_date"].present? ? event["actual_date"].to_datetime : nil
    event["event_type"] = event["event_type"].present? ? event["event_type"].to_i : nil
    events[key] = Shipload::Events::Event.new(Util.to_h_with_symbol_keys(event))
  end
  new(events)
end

# Returns total number of events
#
# @return [Integer]
def count
  @events.count
end

# Returns the arrival event if present, nil otherwise
#
# @return [Shipload::Events::Event]
def arrival_event
  @events[Shipload::Events::Event::ARRIVAL_EVENT_KEY]
end

# Returns event identified by key
#
# @param key [String/ Float]
# @return [Shipload::Events::Event]
def [](key)
  @events[key.to_s]
end

# Assigns Event to the key in events
#
# @param key [String/ Float]
# @return [Shipload::Events::Event]
def []=(key, event)
  @events[key.to_s] = event
end

# Returns all the values of @events hash
#
# @return [Array]
def values
  @events.values
end

# Returns all the keys in @events hash
#
# @return [Array]
def keys
  @events.keys.sort
end

# # Returns the Events object in hash format
# #
# @return [Hash]
def to_h
  @events.to_h
end

# Adds a transhipment arrival and departure event.
def add_transshipment
  add_trans_arrival_event
  add_trans_departure_event
end

# Deletes latest transhipment arrival and departure event.
def delete_transshipment
  sorted_transshipment_keys = @events.keys.sort[2..-3]
  2.times do
    @events.except!(sorted_transshipment_keys.last) if sorted_transshipment_keys.present?
    sorted_transshipment_keys -= [sorted_transshipment_keys.last]
  end
end

# Returns the current event. The most latest event with actual date set is current event
#
# @return [Event]
def current_event
  keys.sort.reverse.each do |key|
    return self.events[key] if self.events[key].actual_date.present?
  end
  nil
end

# Returns the next event. It is the event after current event. In case of current_event being nil,
# it returns first event.
#
# @return [Event]
def next_event(current_event)
  current_event.nil? ? @events.first.last : @events[keys[keys.index(@events.key(current_event)) + 1]]
end

# Sorts the events on basis of keys
def sort_on_keys!
  @events = @events.sort.to_h
end

# Returns total events present
#
# @return [Integer]
def total_events
  @events.length
end

# Returns total events yet to occur
#
# @return [Integer]
def pending_events
  total_events - completed_events
end

# Returns total events which have occured
#
# @return [Integer]
def completed_events
  keys.index(@events.key(current_event)).present? ? keys.index(@events.key(current_event)).to_i + 1 : 0
end

private

# Returns the next key to be
def generate_transshipment_event_key
  key = @events.keys.sort[2..-3].last || Shipload::Events::Event::TRANS_SHIPMENT_MASTER_KEY
  integer_part, fraction_part = key.split(".")
  fraction_part = fraction_part.to_i + Shipload::Events::Event::TRANS_SHIPMENT_KEY_STEP
  integer_part+"."+format("%02d",fraction_part)
end

def add_trans_arrival_event
  key = generate_transshipment_event_key
  trans_arrival = Shipload::Events::Event.new(event_type: Shipload::Events::Event::TRANS_SHIPMENT_ARRIVAL)
  @events[key] = trans_arrival
end

def add_trans_departure_event
  key = generate_transshipment_event_key
  trans_departure = Shipload::Events::Event.new(event_type: Shipload::Events::Event::TRANS_SHIPMENT_DEPARTURE)
  @events[key] = trans_departure
end

def latest_transshipment_event
  @events[@events.keys.sort[2..-3].last] || nil
end
end
end

serializer.rb

class Shipload
class Events
class Serializer
  # Translates events object to format which can be stored in DB
  #
  # @param [Shipload::Events]
  # @return [Hash]
  def self.dump(events)
    byebug
    events.to_h
  end

  def self.load(events_h)
    events = {}
    if events_h.present?
      events_h.each do |key, value|
        %w(planned_date actual_date original_planned_date).each do |date|
          value[date] = value[date].to_datetime unless value[date].nil?
        end
        events[key] = Shipload::Events::Event.new(Util.to_h_with_symbol_keys(value))
      end
    end
    Shipload::Events.new(events)
  end
end
end
end

1 Answers1

0

See doc: https://maxivak.com/update_attribute-and-update_attributes-ruby-on-rails/

See link: Rails: update_column works, but not update_attributes

update_column doesn't validate the record, doesn't call callbacks, doesn't call save method, though it does update record in the database.

May be some callbacks restrict to save this by using update_attribute.

Just use update_attributes as update_attributes tries to validate the record, calls callbacks and saves. You can check the error then.

update_attribute doesn't validate the record, calls callbacks and saves;

You should use update_attributes and avoid the two other methods unless you know exactly what you are doing. If you add validations and callbacks later to your model, using update_attribute and update_column can lead to nasty behaviour that is really difficult to debug.

Pragya Sriharsh
  • 529
  • 3
  • 6
  • I understand the difference between these methods. Just realized someone changed a before_create to before_save. Lost 2.5 hours on this – Rahul Ahuja Apr 29 '19 at 19:43