HHH v4

Rails enum issue when converting instance to different class using #becomes

Edit
equivalent Web Development
Public
In Rails you can use #becomes method to convert object/model to different class/model keeping the attributes. This is extremly handy if you are creating new version oof the model but you still want to keep old model around (legacy) so they both can work on same table

V2::Book.create!(status: "archived") # status that not exist in Book (version 1)

b2 = V2::Book.last
b1 = Book.last
b2.id == b1.id  # true

converted = b1.becomes(V2::Book)
converted.class # V2::Book  👍
converted.persisted # true  👍
converted.id == b2.id # true  👍
converted.status # nil  😬 - This is wrong, it should be "archived"


Reason is


b2.status
# "archived"

b1.status
# nil

So Rails enum will loos the status. This ,make sense as enum has protection agains unknow values:

Book.new(status: "archived") # raises ArgumentError: 'archived' is not a valid status

Solution? 


delegator

class Book
  enum :status, {
    draft: "draft",
    published: "published"
  }

  def to_v2
    V2::Campaign::BecomesV2Delegator.new(becomes(V2::Book))
  end
end

class V2::Book
  class BecomesV2Delegator < SimpleDelegator
    def status
      read_attribute_before_type_cast("status")
    end
  end

  # status is no longer enum
end

b1 = Book.last
b1.to_v2.status # "archived"



Other "Kind of" workarounds:


1

converted2 = V2::Book.new(b1.attributes_before_type_cast)
converted = b1.becomes(V2::Book)
converted.class # V2::Book  👍
converted.persisted # false  🫤 - I totaly breaks links
converted.id == b2.id # true  👍

2

conv3 = b1.becomes(V2::Book).tap do |b|
      b.status = b1.read_attribute_before_type_cast('status') # 'status;
    end
 # raises ArgumentError: 'archived' is not a valid status
 # for some reason

3

 V2::Book.find(b1.id)
or
b1.becomes(V2::Book).reload

both of which do DB call