Class: SerializableAttributes::Schema

Inherits:
Object
  • Object
show all
Defined in:
lib/serializable_attributes/schema.rb

Class Attribute Summary collapse

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(model, field, options) ⇒ Schema

Initializes a new Schema. See ‘ModelMethods#serialize_attributes`.

model - The ActiveRecord class. field - The String name of the ActiveRecord attribute that holds

data.

options - Optional Hash:

:blob      - The String name of the actual DB field.  Defaults to
             "raw_#{field}"
:formatter - The module that handles encoding and decoding the
             data.  The default is set in
             `Schema#default_formatter`.


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
108
109
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
# File 'lib/serializable_attributes/schema.rb', line 64

def initialize(model, field, options)
  @model, @field, @fields = model, field, {}
  @blob_field = options.delete(:blob) || "raw_#{@field}"
  @formatter  = options.delete(:formatter) || self.class.default_formatter
  blob_field  = @blob_field
  data_field  = @field

  meta_model = class << @model; self; end
  changed_ivar = "#{data_field}_changed"
  meta_model.send(:attr_accessor, "#{data_field}_schema")
  @model.send("#{data_field}_schema=", self)

  @model.class_eval do
    def reload(options = nil)
      reset_serialized_data
      super
    end
  end

  meta_model.send(:define_method, :attribute_names) do
    column_names + send("#{data_field}_schema").all_column_names
  end

  @model.send(:define_method, :reset_serialized_data) do
    instance_variable_set("@#{data_field}", nil)
  end

  @model.send(:define_method, :attribute_names) do
    (super() + send(data_field).keys - [blob_field]).
      map! { |s| s.to_s }.sort!
  end

  @model.send(:define_method, :read_attribute) do |attribute_name|
    schema = self.class.send("#{data_field}_schema")
    if schema.include?(attribute_name)
      data[attribute_name.to_s]
    else
      super(attribute_name)
    end
  end

  if defined?(ActiveRecord::VERSION) && ActiveRecord::VERSION::STRING >= '3.1'
    @model.send(:define_method, :attributes) do
      attributes = super().merge(send(data_field))
      attributes.delete blob_field
      attributes
    end
  end

  @model.send(:define_method, data_field) do
    instance_variable_get("@#{data_field}") || begin
      instance_variable_get("@#{changed_ivar}").clear if send("#{changed_ivar}?")
      schema   = self.class.send("#{data_field}_schema")
      hash     = schema.decode(send(blob_field), new_record?)
      instance_variable_set("@#{data_field}", hash)
      hash
    end
  end

  @model.send(:define_method, :write_serialized_field) do |name, value|
    raw_data = send(data_field) # load fields if needed
    name_str = name.to_s
    schema   = self.class.send("#{data_field}_schema")
    type     = schema.fields[name_str]
    changed_fields = send(changed_ivar)
    instance_variable_get("@#{changed_ivar}")[name_str] = raw_data[name_str] unless changed_fields.include?(name_str)
    parsed_value = type ? type.parse(value) : value
    if parsed_value.nil?
      raw_data.delete(name_str)
    else
      raw_data[name_str] = parsed_value
    end
    parsed_value
  end

  @model.send(:define_method, changed_ivar) do
    hash = instance_variable_get("@#{changed_ivar}") || instance_variable_set("@#{changed_ivar}", {})
    hash.keys
  end

  @model.send(:define_method, "#{changed_ivar}?") do
    !send(changed_ivar).empty?
  end

  @model.before_save do |r|
    schema = r.class.send("#{data_field}_schema")
    if r.send(blob_field).nil? || !r.send(changed_ivar).empty?
      r.send("#{blob_field}=", schema.encode(r.send(data_field)))
    end
  end
end

Class Attribute Details

.default_formatterObject



5
6
7
# File 'lib/serializable_attributes/schema.rb', line 5

def default_formatter
  @default_formatter ||= SerializableAttributes::Format::ActiveSupportJson
end

Instance Attribute Details

#field(type_name, *names) ⇒ Object (readonly)

Adds the accessors for a serialized field on this model. Also sets up the encoders and decoders.

type_name - The Symbol matching a valid type. *names - One or more Symbol field names. options - Optional Hash to be sent to the initialized Type.

:default - Sets the default value.

Returns nothing.



165
166
167
# File 'lib/serializable_attributes/schema.rb', line 165

def field
  @field
end

#fieldsObject (readonly)

Returns the value of attribute fields.



11
12
13
# File 'lib/serializable_attributes/schema.rb', line 11

def fields
  @fields
end

#formatterObject

Returns the value of attribute formatter.



10
11
12
# File 'lib/serializable_attributes/schema.rb', line 10

def formatter
  @formatter
end

#modelObject (readonly)

Returns the value of attribute model.



11
12
13
# File 'lib/serializable_attributes/schema.rb', line 11

def model
  @model
end

Instance Method Details

#all_column_namesObject



13
14
15
# File 'lib/serializable_attributes/schema.rb', line 13

def all_column_names
  fields ? fields.keys : []
end

#decode(data, is_new_record = false) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/serializable_attributes/schema.rb', line 27

def decode(data, is_new_record = false)
  decoded = formatter.decode(data)
  hash = ::Hash.new do |h, key|
    if type = fields[key]
      h[key] = type ? type.default : nil
    end
  end

  decoded.each do |k, v|
    next unless include?(k)
    type = fields[k]
    hash[k] = type ? type.parse(v) : v
  end

  if decoded.blank? && is_new_record
    fields.each do |key, type|
      hash[key] = type.default if type.default
    end
  end
  hash
end

#encode(body) ⇒ Object



17
18
19
20
21
22
23
24
25
# File 'lib/serializable_attributes/schema.rb', line 17

def encode(body)
  body = body.dup
  body.each do |key, value|
    if field = fields[key]
      body[key] = field.encode(value)
    end
  end
  formatter.encode(body)
end

#include?(key) ⇒ Boolean

Returns:



49
50
51
# File 'lib/serializable_attributes/schema.rb', line 49

def include?(key)
  @fields.include?(key.to_s)
end