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!, #final!, #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.



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
211
212
213
214
215
216
217
218
219
# File 'lib/types/props/serializable.rb', line 119

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::Props::Utils.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 rules[:need_nil_read_check]
        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



264
265
266
267
268
# File 'lib/types/props/serializable.rb', line 264

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?



258
259
260
261
# File 'lib/types/props/serializable.rb', line 258

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
99
100
101
102
103
104
105
106
107
# File 'lib/types/props/serializable.rb', line 18

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

  decorator.props.each do |prop, rules|
    hkey = rules[:serialized_form]

    val = decorator.get(self, prop, rules)

    if val.nil? && strict && !rules[:fully_optional]
      # 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 rules[:dont_store] || val.nil?

    if rules[:serializable_subtype]
      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
      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

    needs_clone = rules[:type_needs_clone]
    if needs_clone
      if needs_clone == :shallow
        val = val.dup
      else
        val = T::Props::Utils.deep_clone_object(val)
      end
    end

    h[hkey] = val
  end

  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.



222
223
224
# File 'lib/types/props/serializable.rb', line 222

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