/usr/lib/ruby/vendor_ruby/sequel/plugins/serialization.rb is in ruby-sequel 4.15.0-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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 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 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 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 | module Sequel
module Plugins
# Sequel's built in Serialization plugin allows you to keep serialized
# ruby objects in the database, while giving you deserialized objects
# when you call an accessor.
#
# This plugin works by keeping the serialized value in the values, and
# adding a @deserialized_values hash. The reader method for serialized columns
# will check the @deserialized_values for the value, return it if present,
# or deserialized the entry in @values and return it. The writer method will
# set the @deserialized_values entry. This plugin adds a before_save hook
# that serializes all @deserialized_values to @values.
#
# You can specify the serialization format as a pair of serializer/deserializer
# callable objects. You can also specify the serialization format as a single
# symbol, if such a symbol has a registered serializer/deserializer pair in the
# plugin. By default, the plugin registers the :marshal, :yaml, and :json
# serialization formats. To register your own serialization formats, use
# Sequel::Plugins::Serialization.register_format.
# If you use yaml or json format, you need to require the libraries, Sequel
# does not do the requiring for you.
#
# You can specify the columns to serialize when loading the plugin, or later
# using the serialize_attributes class method.
#
# Because of how this plugin works, it must be used inside each model class
# that needs serialization, after any set_dataset method calls in that class.
# Otherwise, it is possible that the default column accessors will take
# precedence.
#
# == Example
#
# # Require json if you plan to use it, as the plugin doesn't require it for you.
# require 'json'
#
# # Register custom serializer/deserializer pair, if desired
# require 'sequel/plugins/serialization'
# Sequel::Plugins::Serialization.register_format(:reverse,
# lambda{|v| v.reverse},
# lambda{|v| v.reverse})
#
# class User < Sequel::Model
# # Built-in format support when loading the plugin
# plugin :serialization, :json, :permissions
#
# # Built-in format support after loading the plugin using serialize_attributes
# plugin :serialization
# serialize_attributes :marshal, :permissions
#
# # Use custom registered serialization format just like built-in format
# serialize_attributes :reverse, :password
#
# # Use a custom serializer/deserializer pair without registering
# serialize_attributes [lambda{|v| v.reverse}, lambda{|v| v.reverse}], :password
# end
# user = User.create
# user.permissions = { :global => 'read-only' }
# user.save
#
# Note that if you mutate serialized column values without reassigning them,
# those changes won't be picked up by <tt>Model#save_changes</tt> or
# <tt>Model#update</tt>. Example:
#
# user = User[1]
# user.permissions[:global] = 'foo'
# user.save_changes # Will not pick up changes to permissions
#
# You can use the +serialization_modification_detection+ plugin to pick
# up such changes.
module Serialization
# The default serializers supported by the serialization module.
# Use register_format to add serializers to this hash.
REGISTERED_FORMATS = {}
# Set up the column readers to do deserialization and the column writers
# to save the value in deserialized_values.
def self.apply(model, *args)
model.instance_eval do
@deserialization_map = {}
@serialization_map = {}
end
end
# Automatically call serialize_attributes with the format and columns unless
# no columns were provided.
def self.configure(model, format=nil, *columns)
model.serialize_attributes(format, *columns) unless columns.empty?
end
# Register a serializer/deserializer pair with a format symbol, to allow
# models to pick this format by name. Both serializer and deserializer
# should be callable objects.
def self.register_format(format, serializer, deserializer)
REGISTERED_FORMATS[format] = [serializer, deserializer]
end
register_format(:marshal, lambda{|v| [Marshal.dump(v)].pack('m')},
lambda do |v|
begin
Marshal.load(v.unpack('m')[0])
rescue => e
begin
# Backwards compatibility for unpacked marshal output.
Marshal.load(v)
rescue
raise e
end
end
end)
register_format(:yaml, lambda{|v| v.to_yaml}, lambda{|v| YAML.load(v)})
register_format(:json, lambda{|v| Sequel.object_to_json(v)}, lambda{|v| Sequel.parse_json(v)})
module ClassMethods
# A hash with column name symbols and callable values, with the value
# called to deserialize the column.
attr_reader :deserialization_map
# A hash with column name symbols and callable values, with the value
# called to serialize the column.
attr_reader :serialization_map
# Module to store the serialized column accessor methods, so they can
# call be overridden and call super to get the serialization behavior
attr_accessor :serialization_module
Plugins.inherited_instance_variables(self, :@deserialization_map=>:dup, :@serialization_map=>:dup)
# Create instance level reader that deserializes column values on request,
# and instance level writer that stores new deserialized values.
def serialize_attributes(format, *columns)
if format.is_a?(Symbol)
unless format = REGISTERED_FORMATS[format]
raise(Error, "Unsupported serialization format: #{format} (valid formats: #{REGISTERED_FORMATS.keys.map{|k| k.inspect}.join})")
end
end
serializer, deserializer = format
raise(Error, "No columns given. The serialization plugin requires you specify which columns to serialize") if columns.empty?
define_serialized_attribute_accessor(serializer, deserializer, *columns)
end
# The columns that will be serialized. This is only for
# backwards compatibility, use serialization_map in new code.
def serialized_columns
serialization_map.keys
end
private
# Add serializated attribute acessor methods to the serialization_module
def define_serialized_attribute_accessor(serializer, deserializer, *columns)
m = self
include(self.serialization_module ||= Module.new) unless serialization_module
serialization_module.class_eval do
columns.each do |column|
m.serialization_map[column] = serializer
m.deserialization_map[column] = deserializer
define_method(column) do
if deserialized_values.has_key?(column)
deserialized_values[column]
elsif frozen?
deserialize_value(column, super())
else
deserialized_values[column] = deserialize_value(column, super())
end
end
define_method("#{column}=") do |v|
if !changed_columns.include?(column) && (new? || send(column) != v)
changed_columns << column
end
deserialized_values[column] = v
end
end
end
end
end
module InstanceMethods
# Hash of deserialized values, used as a cache.
def deserialized_values
@deserialized_values ||= {}
end
# Freeze the deserialized values
def freeze
deserialized_values.freeze
super
end
private
# Serialize deserialized values before saving
def _before_validation
serialize_deserialized_values
super
end
# Clear any cached deserialized values when doing a manual refresh.
def _refresh_set_values(hash)
@deserialized_values.clear if @deserialized_values
super
end
# Deserialize the column value. Called when the model column accessor is called to
# return a deserialized value.
def deserialize_value(column, v)
unless v.nil?
raise Sequel::Error, "no entry in deserialization_map for #{column.inspect}" unless callable = model.deserialization_map[column]
callable.call(v)
end
end
# Dup the deserialized values when duping model instance.
def initialize_copy(other)
super
@deserialized_values = other.deserialized_values.dup
self
end
# Serialize all deserialized values
def serialize_deserialized_values
deserialized_values.each{|k,v| @values[k] = serialize_value(k, v)}
end
# Serialize the column value. Called before saving to ensure the serialized value
# is saved in the database.
def serialize_value(column, v)
unless v.nil?
raise Sequel::Error, "no entry in serialization_map for #{column.inspect}" unless callable = model.serialization_map[column]
callable.call(v)
end
end
end
end
end
end
|