module ReeDto::DtoInstanceMethods

def ==(other)

def ==(other)
  return false unless other.is_a?(self.class)
  each_field.all? do |name, value|
    other.get_value(name) == value
  end
end

def attrs

def attrs
  @_attrs
end

def changed_fields

def changed_fields
  @changed_fields.to_a
end

def each_field(&proc)

def each_field(&proc)
  return enum_for(:each_field) unless block_given?
  
  self.class.fields.select { has_value?(_1.name) }.each do |field|
    proc.call(field.name, get_value(field.name))
  end
end

def get_meta(name)

def get_meta(name)
  self
    .class
    .fields
    .find { _1.name == name} || (raise ArgumentError.new("field :#{name} not defined for :#{self.class}"))
end

def get_value(name)

def get_value(name)
  @_attrs.fetch(name) do
    meta = get_meta(name)
    if !meta.has_default?
      raise FieldNotSetError.new("field `#{name}` not set for: #{self}")
    else
      @_attrs[name] = meta.default
    end
  end
end

def has_value?(name)

def has_value?(name)
  @_attrs.key?(name) || get_meta(name).has_default?
end

def initialize(attrs = nil, **kwargs)

def initialize(attrs = nil, **kwargs)
  @_attrs = attrs || kwargs
  list = self.class.fields.map(&:name)
  extra = attrs.keys - list
  if !extra.empty?
    puts("WARNING: #{self.class}.new does not have definition for #{extra.inspect} fields")
  end
end

def initialize(attrs = nil, **kwargs)

def initialize(attrs = nil, **kwargs)
  @_attrs = attrs || kwargs
end

def initialize_clone(_other)

def initialize_clone(_other)
  super
  @changed_fields = @changed_fields.dup if defined?(@changed_fields)
end

def initialize_copy(_other)

def initialize_copy(_other)
  deep_dupper = ReeObject::DeepDup.new
  @_attrs = deep_dupper.call(@_attrs)
  @collections = deep_dupper.call(@collections) if defined?(@collections)
end

def initialize_dup(_other)

def initialize_dup(_other)
  super
  @changed_fields = nil
end

def inspect

def inspect
  to_s
end

def inspect_value(v)

def inspect_value(v)
  if v.is_a?(DateTime) || v.is_a?(Date) || v.is_a?(Time)
    v.to_s.inspect
  else
    v.inspect
  end
end

def reset_changes

def reset_changes
  @changed_fields = nil
end

def set_as_changed(name)

def set_as_changed(name)
  if has_value?(name)
    @changed_fields ||= Set.new
    @changed_fields << name
  end
end

def set_attr(name, val)

def set_attr(name, val)
  @_attrs[name] = val
end

def set_value(name, val)

def set_value(name, val)
  if has_value?(name)
    old = get_value(name)
    return old if old == val
  end
  @changed_fields ||= Set.new
  @changed_fields << name
  @_attrs[name] = val
end

def to_h

def to_h
  each_field.to_h
end

def to_s

def to_s
  result = "#<dto #{self.class} "
  data = each_field.map do |name, value|
    "#{name}=#{inspect_value(value)}"
  end
  data += self.class.collections.select { send(_1.name).size > 0 }.map do |col|
    "#{col.name}=#{send(col.name).inspect}"
  end
  result << data.join(", ")
  result << ">"
end