23 Sep 2020
Sometimes, you may often meet some requirements need you to save a hash or JSON to one column. Most time, you may use Rails default serialie to serialize it with YAML or JSON and save it to the column like this:
class User < ApplicationRecord
serialize :settings
end
user.settings = {
a: 1,
b: 2,
c: 3
}
user.save
This kind of simple design has a big problem which will increase the difficulty of maintenance. The key values in the column will change in all sorts of the way over time. Maybe after several years, you can’t tell what actually save in the columns. You have to check every record in the online database.
You may ask, what caused this happens. Recently, I also met this kind of issue. And I can tell the anwser is simple. It’s because you don’t have a schema for that column. You need a way to maintenance the data structure in your codebase not database.
My way for it is one of Rails way - customize serializer
class User < ApplicationRecord
serialize :settings, DescriptionSerializer
end
class DescriptionSerializer
class << self
def load(json_string)
return unless json_string.present?
hash = JSON.load(json_string).with_indifferent_access
UserSettings.new(hash)
end
def dump(description)
JSON.dump description.dump
end
end
end
class UserSettings < BaseJsonColumn
persistent_attributes :a, :b, :c, :d
end
class BaseJsonColumn
class << self
def persistent_attributes(*names)
self.class_variable_set(:@@__persistent_attributes, names.map(&:to_sym))
names.each do |name|
define_method name do
attributes[name]
end
define_method "#{name}=" do |value|
attributes[name] = value
end
end
end
def persistent_attribute_names
self.class_variable_get(:@@__persistent_attributes)
end
end
attr_reader :attributes
def initialize(attributes = {})
@attributes = attributes.with_indifferent_access
end
def dump
@attributes.select do |name, _|
persistent_attribute_names.include?(name.to_sym)
end
end
def persistent_attribute_names
self.class.persistent_attribute_names
end
end
Now, you can keep your eyes on the JSON/hash column structure with UserSettings
class. Each time you can put other data in it, you have to go back to here to deal with it and change it explicitly. Then you will never lose control on this kind of columns.