module Cistern::Attributes PROTECTED_METHODS = [:cistern, :service, :identity, :collection].freeze TRUTHY = ['true', '1'].freeze def self.parsers @parsers ||= { array: ->(v, _) { [*v] }, boolean: ->(v, _) { TRUTHY.include?(v.to_s.downcase) }, float: ->(v, _) { v && v.to_f }, integer: ->(v, _) { v && v.to_i }, string: ->(v, opts) { (opts[:allow_nil] && v.nil?) ? v : v.to_s }, time: ->(v, _) { v.is_a?(Time) ? v : v && Time.parse(v.to_s) }, } end def self.transforms @transforms ||= { squash: proc do |_k, _v, options| v = Cistern::Hash.stringify_keys(_v) squash = options[:squash] if v.is_a?(::Hash) && squash.is_a?(Array) travel = lambda do |tree, path| if tree.is_a?(::Hash) travel.call(tree[path.shift], path) else tree end end travel.call(v, squash.dup) elsif v.is_a?(::Hash) squash_s = squash.to_s if v.key?(key = squash_s.to_sym) v[key] elsif v.key?(squash_s) v[squash_s] else v end else v end end, none: ->(_, v, _) { v } } end def self.default_parser @default_parser ||= ->(v, _opts) { v } end module ClassMethods def _load(marshalled) new(Marshal.load(marshalled)) end def aliases @aliases ||= Hash.new { |h, k| h[k] = [] } end def attributes @attributes ||= {} end def attribute(_name, options = {}) if defined? Cistern::Coverage attribute_call = Cistern::Coverage.find_caller_before('cistern/attributes.rb') # Only use DSL attribute calls from within a model if attribute_call && attribute_call.label.start_with?(' v) } end def changed @changes ||= {} end protected def missing_attributes(args) args.select { |arg| send("#{arg}").nil? } end def changed!(attribute, from, to) changed[attribute] = if existing = changed[attribute] [existing.first, to] else [from, to] end end end end