I'm building a Rails 6 app on top of a database whose schema cannot be altered, as the database is shared across multiple legacy apps.
Our Product class has a string code attribute which is composed of various parts. Using a custom object and Rails' attribute API, we've been deserialising the code into its constituent parts:
# config/initializers/active_record_types.rb
ActiveRecord::Type.register(:product_code, ProductCodeType)
# app/types/product_code_type.rb
class ProductCodeType < ActiveModel::Type::String
def cast(value)
return if value.nil?
ProductCode.new(value)
end
def serialize(value)
value.to_s
end
end
# app/types/product_code.rb
class ProductCode
PATTERN = /<<REGEX HERE>>/.freeze
attr_reader :family_code, :version_code, :flags
def initialize(source)
matches = source.match(PATTERN)
@family_code = matches[:family_code]
@version_code = matches[:version_code]
@flags = matches[:flags]
end
def to_s
[family_code, version_code, flags].join
end
end
This all works great, and allows us to greatly simplify a lot of code throughout the app.
The problem comes in another part of the codebase, where we have a table that joins to products via the code column:
# in another app (Rails 4) using the same DB
# product.rb
class Product < ActiveRecord::Base
has_one :interest_rate, foreign_key: :code, primary_key: :code
end
But if we replicate that in the new app where code is deserialized:
# product.rb
class Product < ApplicationRecord
has_one :interest_rate, foreign_key: :code, primary_key: :code
end
% p = Product.find 1357
# => #<Product:0x00007ff0941c2aa0 ...>
% p.interest_rate
# => TypeError: can't quote ProductCode
from /bundle/ruby/2.7.0/gems/activerecord-6.1.3.2/lib/active_record/connection_adapters/abstract/quoting.rb:231:in `_quote'
Is there a way to be able to deserialize code for most uses, while also being able to use its serialized string form as a key for this join?