snail_trail 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/lib/generators/snail_trail/install/USAGE +3 -0
- data/lib/generators/snail_trail/install/install_generator.rb +108 -0
- data/lib/generators/snail_trail/install/templates/add_object_changes_to_versions.rb.erb +12 -0
- data/lib/generators/snail_trail/install/templates/add_transaction_id_column_to_versions.rb.erb +12 -0
- data/lib/generators/snail_trail/install/templates/create_versions.rb.erb +41 -0
- data/lib/generators/snail_trail/migration_generator.rb +38 -0
- data/lib/generators/snail_trail/update_item_subtype/USAGE +4 -0
- data/lib/generators/snail_trail/update_item_subtype/templates/update_versions_for_item_subtype.rb.erb +85 -0
- data/lib/generators/snail_trail/update_item_subtype/update_item_subtype_generator.rb +19 -0
- data/lib/snail_trail/attribute_serializers/README.md +10 -0
- data/lib/snail_trail/attribute_serializers/attribute_serializer_factory.rb +41 -0
- data/lib/snail_trail/attribute_serializers/cast_attribute_serializer.rb +51 -0
- data/lib/snail_trail/attribute_serializers/object_attribute.rb +51 -0
- data/lib/snail_trail/attribute_serializers/object_changes_attribute.rb +54 -0
- data/lib/snail_trail/cleaner.rb +60 -0
- data/lib/snail_trail/compatibility.rb +51 -0
- data/lib/snail_trail/config.rb +40 -0
- data/lib/snail_trail/errors.rb +33 -0
- data/lib/snail_trail/events/base.rb +343 -0
- data/lib/snail_trail/events/create.rb +32 -0
- data/lib/snail_trail/events/destroy.rb +42 -0
- data/lib/snail_trail/events/update.rb +76 -0
- data/lib/snail_trail/frameworks/active_record/models/snail_trail/version.rb +16 -0
- data/lib/snail_trail/frameworks/active_record.rb +12 -0
- data/lib/snail_trail/frameworks/cucumber.rb +33 -0
- data/lib/snail_trail/frameworks/rails/controller.rb +103 -0
- data/lib/snail_trail/frameworks/rails/railtie.rb +34 -0
- data/lib/snail_trail/frameworks/rails.rb +3 -0
- data/lib/snail_trail/frameworks/rspec/helpers.rb +29 -0
- data/lib/snail_trail/frameworks/rspec.rb +42 -0
- data/lib/snail_trail/has_snail_trail.rb +92 -0
- data/lib/snail_trail/model_config.rb +265 -0
- data/lib/snail_trail/queries/versions/where_attribute_changes.rb +50 -0
- data/lib/snail_trail/queries/versions/where_object.rb +65 -0
- data/lib/snail_trail/queries/versions/where_object_changes.rb +70 -0
- data/lib/snail_trail/queries/versions/where_object_changes_from.rb +57 -0
- data/lib/snail_trail/queries/versions/where_object_changes_to.rb +57 -0
- data/lib/snail_trail/record_history.rb +51 -0
- data/lib/snail_trail/record_trail.rb +375 -0
- data/lib/snail_trail/reifier.rb +147 -0
- data/lib/snail_trail/request.rb +180 -0
- data/lib/snail_trail/serializers/json.rb +36 -0
- data/lib/snail_trail/serializers/yaml.rb +68 -0
- data/lib/snail_trail/type_serializers/postgres_array_serializer.rb +35 -0
- data/lib/snail_trail/version_concern.rb +407 -0
- data/lib/snail_trail/version_number.rb +23 -0
- data/lib/snail_trail.rb +141 -1
- metadata +369 -13
- data/lib/snail_trail/version.rb +0 -5
@@ -0,0 +1,147 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "snail_trail/attribute_serializers/object_attribute"
|
4
|
+
|
5
|
+
module SnailTrail
|
6
|
+
# Given a version record and some options, builds a new model object.
|
7
|
+
# @api private
|
8
|
+
module Reifier
|
9
|
+
class << self
|
10
|
+
# See `VersionConcern#reify` for documentation.
|
11
|
+
# @api private
|
12
|
+
def reify(version, options)
|
13
|
+
options = apply_defaults_to(options, version)
|
14
|
+
attrs = version.object_deserialized
|
15
|
+
model = init_model(attrs, options, version)
|
16
|
+
reify_attributes(model, version, attrs)
|
17
|
+
model.send :"#{model.class.version_association_name}=", version
|
18
|
+
model
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# Given a hash of `options` for `.reify`, return a new hash with default
|
24
|
+
# values applied.
|
25
|
+
# @api private
|
26
|
+
def apply_defaults_to(options, version)
|
27
|
+
{
|
28
|
+
version_at: version.created_at,
|
29
|
+
mark_for_destruction: false,
|
30
|
+
has_one: false,
|
31
|
+
has_many: false,
|
32
|
+
belongs_to: false,
|
33
|
+
has_and_belongs_to_many: false,
|
34
|
+
unversioned_attributes: :nil
|
35
|
+
}.merge(options)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Initialize a model object suitable for reifying `version` into. Does
|
39
|
+
# not perform reification, merely instantiates the appropriate model
|
40
|
+
# class and, if specified by `options[:unversioned_attributes]`, sets
|
41
|
+
# unversioned attributes to `nil`.
|
42
|
+
#
|
43
|
+
# Normally a polymorphic belongs_to relationship allows us to get the
|
44
|
+
# object we belong to by calling, in this case, `item`. However this
|
45
|
+
# returns nil if `item` has been destroyed, and we need to be able to
|
46
|
+
# retrieve destroyed objects.
|
47
|
+
#
|
48
|
+
# In this situation we constantize the `item_type` to get hold of the
|
49
|
+
# class...except when the stored object's attributes include a `type`
|
50
|
+
# key. If this is the case, the object we belong to is using single
|
51
|
+
# table inheritance (STI) and the `item_type` will be the base class,
|
52
|
+
# not the actual subclass. If `type` is present but empty, the class is
|
53
|
+
# the base class.
|
54
|
+
def init_model(attrs, options, version)
|
55
|
+
klass = version_reification_class(version, attrs)
|
56
|
+
|
57
|
+
# The `dup` option and destroyed version always returns a new object,
|
58
|
+
# otherwise we should attempt to load item or to look for the item
|
59
|
+
# outside of default scope(s).
|
60
|
+
model = if options[:dup] == true || version.event == "destroy"
|
61
|
+
klass.new
|
62
|
+
else
|
63
|
+
version.item || init_model_by_finding_item_id(klass, version) || klass.new
|
64
|
+
end
|
65
|
+
|
66
|
+
if options[:unversioned_attributes] == :nil && !model.new_record?
|
67
|
+
init_unversioned_attrs(attrs, model)
|
68
|
+
end
|
69
|
+
|
70
|
+
model
|
71
|
+
end
|
72
|
+
|
73
|
+
# @api private
|
74
|
+
def init_model_by_finding_item_id(klass, version)
|
75
|
+
klass.unscoped.where(klass.primary_key => version.item_id).first
|
76
|
+
end
|
77
|
+
|
78
|
+
# Look for attributes that exist in `model` and not in this version.
|
79
|
+
# These attributes should be set to nil. Modifies `attrs`.
|
80
|
+
# @api private
|
81
|
+
def init_unversioned_attrs(attrs, model)
|
82
|
+
(model.attribute_names - attrs.keys).each { |k| attrs[k] = nil }
|
83
|
+
end
|
84
|
+
|
85
|
+
# Reify onto `model` an attribute named `k` with value `v` from `version`.
|
86
|
+
#
|
87
|
+
# `ObjectAttribute#deserialize` will return the mapped enum value and in
|
88
|
+
# Rails < 5, the []= uses the integer type caster from the column
|
89
|
+
# definition (in general) and thus will turn a (usually) string to 0
|
90
|
+
# instead of the correct value.
|
91
|
+
#
|
92
|
+
# @api private
|
93
|
+
def reify_attribute(k, v, model, version)
|
94
|
+
if model.has_attribute?(k)
|
95
|
+
model[k.to_sym] = v
|
96
|
+
elsif model.respond_to?(:"#{k}=")
|
97
|
+
model.send(:"#{k}=", v)
|
98
|
+
elsif version.logger
|
99
|
+
version.logger.warn(
|
100
|
+
"Attribute #{k} does not exist on #{version.item_type} (Version id: #{version.id})."
|
101
|
+
)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Reify onto `model` all the attributes of `version`.
|
106
|
+
# @api private
|
107
|
+
def reify_attributes(model, version, attrs)
|
108
|
+
AttributeSerializers::ObjectAttribute.new(model.class).deserialize(attrs)
|
109
|
+
attrs.each do |k, v|
|
110
|
+
reify_attribute(k, v, model, version)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Given a `version`, return the class to reify. This method supports
|
115
|
+
# Single Table Inheritance (STI) with custom inheritance columns and
|
116
|
+
# custom inheritance column values.
|
117
|
+
#
|
118
|
+
# For example, imagine a `version` whose `item_type` is "Animal". The
|
119
|
+
# `animals` table is an STI table (it has cats and dogs) and it has a
|
120
|
+
# custom inheritance column, `species`. If `attrs["species"]` is "Dog",
|
121
|
+
# this method returns the constant `Dog`. If `attrs["species"]` is blank,
|
122
|
+
# this method returns the constant `Animal`.
|
123
|
+
#
|
124
|
+
# The values contained in the inheritance columns may be non-camelized
|
125
|
+
# strings (e.g. 'dog' instead of 'Dog'). To reify classes in this case
|
126
|
+
# we need to call the parents class `sti_class_for` method to retrieve
|
127
|
+
# the correct record class.
|
128
|
+
#
|
129
|
+
# You can see these particular examples in action in
|
130
|
+
# `spec/models/animal_spec.rb` and `spec/models/plant_spec.rb`
|
131
|
+
def version_reification_class(version, attrs)
|
132
|
+
clazz = version.item_type.constantize
|
133
|
+
inheritance_column_name = clazz.inheritance_column
|
134
|
+
inher_col_value = attrs[inheritance_column_name]
|
135
|
+
return clazz if inher_col_value.blank?
|
136
|
+
|
137
|
+
# Rails 6.1 adds a public method for clients to use to customize STI classes. If that
|
138
|
+
# method is not available, fall back to using the private one
|
139
|
+
if clazz.public_methods.include?(:sti_class_for)
|
140
|
+
return clazz.sti_class_for(inher_col_value)
|
141
|
+
end
|
142
|
+
|
143
|
+
clazz.send(:find_sti_class, inher_col_value)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "request_store"
|
4
|
+
|
5
|
+
module SnailTrail
|
6
|
+
# Manages variables that affect the current HTTP request, such as `whodunnit`.
|
7
|
+
#
|
8
|
+
# Please do not use `SnailTrail::Request` directly, use `SnailTrail.request`.
|
9
|
+
# Currently, `Request` is a `Module`, but in the future it is quite possible
|
10
|
+
# we may make it a `Class`. If we make such a choice, we will not provide any
|
11
|
+
# warning and will not treat it as a breaking change. You've been warned :)
|
12
|
+
#
|
13
|
+
# @api private
|
14
|
+
module Request
|
15
|
+
class << self
|
16
|
+
# @api private
|
17
|
+
def clear_transaction_id
|
18
|
+
self.transaction_id = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
# @api private
|
22
|
+
def transaction_id
|
23
|
+
store[:transaction_id]
|
24
|
+
end
|
25
|
+
|
26
|
+
# @api private
|
27
|
+
def transaction_id=(id)
|
28
|
+
store[:transaction_id] = id
|
29
|
+
end
|
30
|
+
|
31
|
+
# Sets any data from the controller that you want SnailTrail to store.
|
32
|
+
# See also `SnailTrail::Rails::Controller#info_for_snail_trail`.
|
33
|
+
#
|
34
|
+
# SnailTrail.request.controller_info = { ip: request_user_ip }
|
35
|
+
# SnailTrail.request.controller_info # => { ip: '127.0.0.1' }
|
36
|
+
#
|
37
|
+
# @api public
|
38
|
+
def controller_info=(value)
|
39
|
+
store[:controller_info] = value
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the data from the controller that you want SnailTrail to store.
|
43
|
+
# See also `SnailTrail::Rails::Controller#info_for_snail_trail`.
|
44
|
+
#
|
45
|
+
# SnailTrail.request.controller_info = { ip: request_user_ip }
|
46
|
+
# SnailTrail.request.controller_info # => { ip: '127.0.0.1' }
|
47
|
+
#
|
48
|
+
# @api public
|
49
|
+
def controller_info
|
50
|
+
store[:controller_info]
|
51
|
+
end
|
52
|
+
|
53
|
+
# Switches SnailTrail off for the given model.
|
54
|
+
# @api public
|
55
|
+
def disable_model(model_class)
|
56
|
+
enabled_for_model(model_class, false)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Switches SnailTrail on for the given model.
|
60
|
+
# @api public
|
61
|
+
def enable_model(model_class)
|
62
|
+
enabled_for_model(model_class, true)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Sets whether SnailTrail is enabled or disabled for the current request.
|
66
|
+
# @api public
|
67
|
+
def enabled=(value)
|
68
|
+
store[:enabled] = value
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns `true` if SnailTrail is enabled for the request, `false` otherwise.
|
72
|
+
# See `SnailTrail::Rails::Controller#snail_trail_enabled_for_controller`.
|
73
|
+
# @api public
|
74
|
+
def enabled?
|
75
|
+
!!store[:enabled]
|
76
|
+
end
|
77
|
+
|
78
|
+
# Sets whether SnailTrail is enabled or disabled for this model in the
|
79
|
+
# current request.
|
80
|
+
# @api public
|
81
|
+
def enabled_for_model(model, value)
|
82
|
+
store[:"enabled_for_#{model}"] = value
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns `true` if SnailTrail is enabled for this model in the current
|
86
|
+
# request, `false` otherwise.
|
87
|
+
# @api public
|
88
|
+
def enabled_for_model?(model)
|
89
|
+
model.include?(::SnailTrail::Model::InstanceMethods) &&
|
90
|
+
!!store.fetch(:"enabled_for_#{model}", true)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Temporarily set `options` and execute a block.
|
94
|
+
# @api private
|
95
|
+
def with(options)
|
96
|
+
return unless block_given?
|
97
|
+
validate_public_options(options)
|
98
|
+
before = to_h
|
99
|
+
merge(options)
|
100
|
+
yield
|
101
|
+
ensure
|
102
|
+
set(before)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Sets who is responsible for any changes that occur during request. You
|
106
|
+
# would normally use this in a migration or on the console, when working
|
107
|
+
# with models directly.
|
108
|
+
#
|
109
|
+
# `value` is usually a string, the name of a person, but you can set
|
110
|
+
# anything that responds to `to_s`. You can also set a Proc, which will
|
111
|
+
# not be evaluated until `whodunnit` is called later, usually right before
|
112
|
+
# inserting a `Version` record.
|
113
|
+
#
|
114
|
+
# @api public
|
115
|
+
def whodunnit=(value)
|
116
|
+
store[:whodunnit] = value
|
117
|
+
end
|
118
|
+
|
119
|
+
# Returns who is reponsible for any changes that occur during request.
|
120
|
+
#
|
121
|
+
# @api public
|
122
|
+
def whodunnit
|
123
|
+
who = store[:whodunnit]
|
124
|
+
who.respond_to?(:call) ? who.call : who
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
# @api private
|
130
|
+
def merge(options)
|
131
|
+
options.to_h.each do |k, v|
|
132
|
+
store[k] = v
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# @api private
|
137
|
+
def set(options)
|
138
|
+
store.clear
|
139
|
+
merge(options)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Returns a Hash, initializing with default values if necessary.
|
143
|
+
# @api private
|
144
|
+
def store
|
145
|
+
RequestStore.store[:snail_trail] ||= {
|
146
|
+
enabled: true
|
147
|
+
}
|
148
|
+
end
|
149
|
+
|
150
|
+
# Returns a deep copy of the internal hash from our RequestStore. Keys are
|
151
|
+
# all symbols. Values are mostly primitives, but whodunnit can be a Proc.
|
152
|
+
# We cannot use Marshal.dump here because it doesn't support Proc. It is
|
153
|
+
# unclear exactly how `deep_dup` handles a Proc, but it doesn't complain.
|
154
|
+
# @api private
|
155
|
+
def to_h
|
156
|
+
store.deep_dup
|
157
|
+
end
|
158
|
+
|
159
|
+
# Provide a helpful error message if someone has a typo in one of their
|
160
|
+
# option keys. We don't validate option values here. That's traditionally
|
161
|
+
# been handled with casting (`to_s`, `!!`) in the accessor method.
|
162
|
+
# @api private
|
163
|
+
def validate_public_options(options)
|
164
|
+
options.each do |k, _v|
|
165
|
+
case k
|
166
|
+
when :controller_info,
|
167
|
+
/enabled_for_/,
|
168
|
+
:enabled,
|
169
|
+
:whodunnit
|
170
|
+
next
|
171
|
+
when :transaction_id
|
172
|
+
raise ::SnailTrail::Request::InvalidOption, "Cannot set private option: transaction_id"
|
173
|
+
else
|
174
|
+
raise InvalidOption, "Invalid option: #{k}"
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SnailTrail
|
4
|
+
module Serializers
|
5
|
+
# An alternate serializer for, e.g. `versions.object`.
|
6
|
+
module JSON
|
7
|
+
extend self # makes all instance methods become module methods as well
|
8
|
+
|
9
|
+
def load(string)
|
10
|
+
ActiveSupport::JSON.decode string
|
11
|
+
end
|
12
|
+
|
13
|
+
def dump(object)
|
14
|
+
ActiveSupport::JSON.encode object
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns a SQL LIKE condition to be used to match the given field and
|
18
|
+
# value in the serialized object.
|
19
|
+
def where_object_condition(arel_field, field, value)
|
20
|
+
# Convert to JSON to handle strings and nulls correctly.
|
21
|
+
json_value = value.to_json
|
22
|
+
|
23
|
+
# If the value is a number, we need to ensure that we find the next
|
24
|
+
# character too, which is either `,` or `}`, to ensure that searching
|
25
|
+
# for the value 12 doesn't yield false positives when the value is
|
26
|
+
# 123.
|
27
|
+
if value.is_a? Numeric
|
28
|
+
arel_field.matches("%\"#{field}\":#{json_value},%").
|
29
|
+
or(arel_field.matches("%\"#{field}\":#{json_value}}%"))
|
30
|
+
else
|
31
|
+
arel_field.matches("%\"#{field}\":#{json_value}%")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "yaml"
|
4
|
+
|
5
|
+
module SnailTrail
|
6
|
+
module Serializers
|
7
|
+
# The default serializer for, e.g. `versions.object`.
|
8
|
+
module YAML
|
9
|
+
extend self # makes all instance methods become module methods as well
|
10
|
+
|
11
|
+
def load(string)
|
12
|
+
if use_safe_load?
|
13
|
+
::YAML.safe_load(
|
14
|
+
string,
|
15
|
+
permitted_classes: yaml_column_permitted_classes,
|
16
|
+
aliases: true
|
17
|
+
)
|
18
|
+
elsif ::YAML.respond_to?(:unsafe_load)
|
19
|
+
::YAML.unsafe_load(string)
|
20
|
+
else
|
21
|
+
::YAML.load(string)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param object (Hash | HashWithIndifferentAccess) - Coming from
|
26
|
+
# `recordable_object` `object` will be a plain `Hash`. However, due to
|
27
|
+
# recent [memory optimizations](https://github.com/BrandsInsurance/snail_trail/pull/1189),
|
28
|
+
# when coming from `recordable_object_changes`, it will be a `HashWithIndifferentAccess`.
|
29
|
+
def dump(object)
|
30
|
+
object = object.to_hash if object.is_a?(ActiveSupport::HashWithIndifferentAccess)
|
31
|
+
::YAML.dump object
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns a SQL LIKE condition to be used to match the given field and
|
35
|
+
# value in the serialized object.
|
36
|
+
def where_object_condition(arel_field, field, value)
|
37
|
+
arel_field.matches("%\n#{field}: #{value}\n%")
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def use_safe_load?
|
43
|
+
if ::ActiveRecord.gem_version >= Gem::Version.new("7.0.3.1")
|
44
|
+
# `use_yaml_unsafe_load` may be removed in the future, at which point
|
45
|
+
# safe loading will be the default.
|
46
|
+
!defined?(ActiveRecord.use_yaml_unsafe_load) || !ActiveRecord.use_yaml_unsafe_load
|
47
|
+
elsif defined?(ActiveRecord::Base.use_yaml_unsafe_load)
|
48
|
+
# Rails 5.2.8.1, 6.0.5.1, 6.1.6.1
|
49
|
+
!ActiveRecord::Base.use_yaml_unsafe_load
|
50
|
+
else
|
51
|
+
false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def yaml_column_permitted_classes
|
56
|
+
if defined?(ActiveRecord.yaml_column_permitted_classes)
|
57
|
+
# Rails >= 7.0.3.1
|
58
|
+
ActiveRecord.yaml_column_permitted_classes
|
59
|
+
elsif defined?(ActiveRecord::Base.yaml_column_permitted_classes)
|
60
|
+
# Rails 5.2.8.1, 6.0.5.1, 6.1.6.1
|
61
|
+
ActiveRecord::Base.yaml_column_permitted_classes
|
62
|
+
else
|
63
|
+
[]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SnailTrail
|
4
|
+
module TypeSerializers
|
5
|
+
# Provides an alternative method of serialization
|
6
|
+
# and deserialization of PostgreSQL array columns.
|
7
|
+
class PostgresArraySerializer
|
8
|
+
def initialize(subtype, delimiter)
|
9
|
+
@subtype = subtype
|
10
|
+
@delimiter = delimiter
|
11
|
+
end
|
12
|
+
|
13
|
+
def serialize(array)
|
14
|
+
array
|
15
|
+
end
|
16
|
+
|
17
|
+
def deserialize(array)
|
18
|
+
case array
|
19
|
+
# Needed for legacy data. If serialized array is a string
|
20
|
+
# then it was serialized with Rails < 5.0.2.
|
21
|
+
when ::String then deserialize_with_ar(array)
|
22
|
+
else array
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def deserialize_with_ar(array)
|
29
|
+
ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.
|
30
|
+
new(@subtype, @delimiter).
|
31
|
+
deserialize(array)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|