# frozen_string_literal: true class PGTrunk::Operation # @private # Define getters/setters for the operation attributes module Attributes extend ActiveSupport::Concern include ActiveModel::Model include ActiveModel::Attributes # The special undefined value for getters/setters # to distinct it from the explicitly provided `nil`. UNDEFINED = Object.new.freeze private_constant :UNDEFINED class_methods do # list of aliases for attributes def attr_aliases @attr_aliases ||= {} end # Add an attribute to the operation def attribute(name, type, default: nil, aliases: nil) # prevent mutation of the default arrays, hashes etc. default = default.freeze if default.respond_to?(:freeze) super(name, type, default: default, &nil) # add the private attribute for the previous value attr_reader :"from_#{name}" redefine_getter(name) Array(aliases).each { |key| attr_aliases[key.to_sym] = name.to_sym } name.to_sym end private def redefine_getter(name) getter = instance_method(name) define_method(name) do |value = UNDEFINED, *args, from: nil| # fallback to the original getter w/o arguments return getter.bind(self).call if value == UNDEFINED # set a previous value to return to instance_variable_set("@from_#{name}", from) # arrays can be assigned as lists value = [value, *args] if args.any? send(:"#{name}=", value) end end def inherited(klass) klass.instance_variable_set(:@attr_aliases, attr_aliases.dup) super end end def initialize(**opts) # enable aliases during the initialization self.class.attr_aliases.each do |a, n| opts[n] = opts.delete(a) if opts.key?(a) end # ignore unknown attributes (to simplify calls of `#invert`) opts = opts.slice(*self.class.attribute_names.map(&:to_sym)) super(**opts) end # The hash of the operation's serialized attributes def attributes super.to_h do |k, v| [k.to_sym, self.class.attribute_types[k].serialize(v)] end end alias to_h attributes end end