Module: T::Props::Serializable

Includes:
Optional, Plugin, PrettyPrintable
Included in:
InexactStruct
Defined in:
lib/types/props/serializable.rb

Overview

typed: false

Defined Under Namespace

Modules: ClassMethods, DecoratorMethods

Constant Summary

Constants included from Helpers

Helpers::Private

Instance Method Summary collapse

Methods included from PrettyPrintable

#inspect, #pretty_inspect

Methods included from Helpers

#abstract!, #interface!, #mixes_in_class_methods

Instance Method Details

#deserialize(hash, strict = false) ⇒ void

This method returns an undefined value.

Populates the property values on this object with the values from a hash. In general, prefer to use from_hash to construct a new instance, instead of loading into an existing instance.

Parameters:

  • hash (Hash<String, Object>)

    The hash to take property values from.

  • strict (T::Boolean) (defaults to: false)

    (false) If true, raise an exception if the hash contains keys that do not correspond to any known props on this instance.



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/types/props/serializable.rb', line 110

def deserialize(hash, strict=false)
  decorator = self.class.decorator

  matching_props = 0

  decorator.props.each do |p, rules|
    hkey = rules[:serialized_form]
    val = hash[hkey]
    if val.nil?
      if T::Utils::Props.required_prop?(rules)
        val = decorator.get_default(rules, self.class)
        if val.nil?
          msg = "Tried to deserialize a required prop from a nil value. It's "\
            "possible that a nil value exists in the database, so you should "\
            "provide a `default: or factory:` for this prop (see go/optional "\
            "for more details). If this is already the case, you probably "\
            "omitted a required prop from the `fields:` option when doing a "\
            "partial load."
          storytime = {prop: hkey, klass: self.class.name}

          # Notify the model owner if it exists, and always notify the API owner.
          begin
            if defined?(Opus) && defined?(Opus::Ownership) && decorator.decorated_class < Opus::Ownership
              T::Configuration.hard_assert_handler(
                msg,
                storytime: storytime,
                project: decorator.decorated_class.get_owner
              )
            end
          ensure
            T::Configuration.hard_assert_handler(msg, storytime: storytime)
          end
        end
      elsif T::Props::Utils.need_nil_read_check?(rules)
        self.required_prop_missing_from_deserialize(p)
      end

      matching_props += 1 if hash.key?(hkey)
    else
      if (subtype = rules[:serializable_subtype])
        val =
          if rules[:type_is_array_of_serializable]
            if subtype.is_a?(T::Props::CustomType)
              val.map {|el| el && subtype.deserialize(el)}
            else
              val.map {|el| el && subtype.from_hash(el)}
            end
          elsif rules[:type_is_hash_of_serializable_values] && rules[:type_is_hash_of_custom_type_keys]
            key_subtype = subtype[:keys]
            values_subtype = subtype[:values]
            if values_subtype.is_a?(T::Props::CustomType)
              val.each_with_object({}) do |(key, value), result|
                result[key_subtype.deserialize(key)] = value && values_subtype.deserialize(value)
              end
            else
              val.each_with_object({}) do |(key, value), result|
                result[key_subtype.deserialize(key)] = value && values_subtype.from_hash(value)
              end
            end
          elsif rules[:type_is_hash_of_serializable_values]
            if subtype.is_a?(T::Props::CustomType)
              val.transform_values {|v| v && subtype.deserialize(v)}
            else
              val.transform_values {|v| v && subtype.from_hash(v)}
            end
          elsif rules[:type_is_hash_of_custom_type_keys]
            val.map do |key, value|
              [subtype.deserialize(key), value]
            end.to_h
          else
            subtype.from_hash(val)
          end
      elsif (needs_clone = rules[:type_needs_clone])
        val =
          if needs_clone == :shallow
            val.dup
          else
            T::Props::Utils.deep_clone_object(val)
          end
      elsif rules[:type_is_custom_type]
        val = rules[:type].deserialize(val)
      end

      matching_props += 1
    end

    self.instance_variable_set(rules[:accessor_key], val) # rubocop:disable PrisonGuard/NoLurkyInstanceVariableAccess
  end

  # We compute extra_props this way specifically for performance
  if matching_props < hash.size
    pbsf = decorator.prop_by_serialized_forms
    h = hash.reject {|k, _| pbsf.key?(k)}

    if strict
      raise "Unknown properties for #{self.class.name}: #{h.keys.inspect}"
    else
      @_extra_props = h
    end
  end
end

#required_prop_missing_from_deserialize(prop) ⇒ Object

Returns Marks this property as missing during deserialize.

Returns:

  • Marks this property as missing during deserialize



255
256
257
258
259
# File 'lib/types/props/serializable.rb', line 255

def required_prop_missing_from_deserialize(prop)
  @_required_props_missing_from_deserialize ||= Set[]
  @_required_props_missing_from_deserialize << prop
  nil
end

#required_prop_missing_from_deserialize?(prop) ⇒ T::Boolean

Returns Was this property missing during deserialize?.

Returns:

  • (T::Boolean)

    Was this property missing during deserialize?



249
250
251
252
# File 'lib/types/props/serializable.rb', line 249

def required_prop_missing_from_deserialize?(prop)
  return false if @_required_props_missing_from_deserialize.nil?
  @_required_props_missing_from_deserialize.include?(prop)
end

#serialize(strict = true) ⇒ Hash

Serializes this object to a hash, suitable for conversion to JSON/BSON.

Parameters:

  • strict (T::Boolean) (defaults to: true)

    (true) If false, do not raise an exception if this object has mandatory props with missing values.

Returns:

  • (Hash)

    A serialization of this object.



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/types/props/serializable.rb', line 18

def serialize(strict=true)
  decorator = self.class.decorator
  h = {}

  decorator.props.keys.each do |prop|
    rules = decorator.prop_rules(prop)
    hkey = rules[:serialized_form]

    val = decorator.get(self, prop)

    if strict && val.nil? && T::Props::Utils.need_nil_write_check?(rules)
      # If the prop was already missing during deserialization, that means the application
      # code already had to deal with a nil value, which means we wouldn't be accomplishing
      # much by raising here (other than causing an unnecessary breakage).
      if self.required_prop_missing_from_deserialize?(prop)
        T::Configuration.log_info_handler(
          "chalk-odm: missing required property in serialize",
          prop: prop, class: self.class.name, id: decorator.get_id(self)
        )
      else
        raise T::Props::InvalidValueError.new("#{self.class.name}.#{prop} not set for non-optional prop")
      end
    end

    # Don't serialize values that are nil to save space (both the
    # nil value itself and the field name in the serialized BSON
    # document)
    next if decorator.prop_dont_store?(prop) || val.nil?

    if rules[:type_is_serializable]
      val = val.serialize(strict)
    elsif rules[:type_is_array_of_serializable]
      if (subtype = rules[:serializable_subtype]).is_a?(T::Props::CustomType)
        val = val.map {|el| el && subtype.serialize(el)}
      else
        val = val.map {|el| el && el.serialize(strict)}
      end
    elsif rules[:type_is_hash_of_serializable_values] && rules[:type_is_hash_of_custom_type_keys]
      key_subtype = rules[:serializable_subtype][:keys]
      value_subtype = rules[:serializable_subtype][:values]
      if value_subtype.is_a?(T::Props::CustomType)
        val = val.each_with_object({}) do |(key, value), result|
          result[key_subtype.serialize(key)] = value && value_subtype.serialize(value)
        end
      else
        val = val.each_with_object({}) do |(key, value), result|
          result[key_subtype.serialize(key)] = value && value.serialize(strict)
        end
      end
    elsif rules[:type_is_hash_of_serializable_values]
      value_subtype = rules[:serializable_subtype]
      if value_subtype.is_a?(T::Props::CustomType)
        val = val.transform_values {|v| v && value_subtype.serialize(v)}
      else
        val = val.transform_values {|v| v && v.serialize(strict)}
      end
    elsif rules[:type_is_hash_of_custom_type_keys]
      key_subtype = rules[:serializable_subtype]
      val = val.each_with_object({}) do |(key, value), result|
        result[key_subtype.serialize(key)] = value
      end
    elsif rules[:type_is_custom_type]
      val = rules[:type].serialize(val)

      unless T::Props::CustomType.valid_serialization?(val, rules[:type])
        msg = "#{rules[:type]} did not serialize to a valid scalar type. It became a: #{val.class}"
        if val.is_a?(Hash)
          msg += "\nIf you want to store a structured Hash, consider using a T::Struct as your type."
        end
        raise T::Props::InvalidValueError.new(msg)
      end
    end

    h[hkey] = T::Props::Utils.deep_clone_object(val)
  end

  extra_props = decorator.extra_props(self)
  h.merge!(extra_props) if extra_props

  h
end

#with(changed_props) ⇒ Object

with() will clone the old object to the new object and merge the specified props to the new object.



213
214
215
# File 'lib/types/props/serializable.rb', line 213

def with(changed_props)
  with_existing_hash(changed_props, existing_hash: self.serialize)
end