familia 0.10.2 → 1.0.0.pre.rc2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.pre-commit-config.yaml +1 -1
- data/.rubocop.yml +75 -0
- data/.rubocop_todo.yml +63 -0
- data/Gemfile +6 -1
- data/Gemfile.lock +47 -15
- data/README.md +65 -13
- data/VERSION.yml +4 -3
- data/familia.gemspec +18 -13
- data/lib/familia/base.rb +33 -0
- data/lib/familia/connection.rb +87 -0
- data/lib/familia/core_ext.rb +119 -124
- data/lib/familia/errors.rb +33 -0
- data/lib/familia/features/api_version.rb +19 -0
- data/lib/familia/features/atomic_saves.rb +8 -0
- data/lib/familia/features/quantizer.rb +35 -0
- data/lib/familia/features/safe_dump.rb +194 -0
- data/lib/familia/features.rb +51 -0
- data/lib/familia/horreum/class_methods.rb +292 -0
- data/lib/familia/horreum/commands.rb +106 -0
- data/lib/familia/horreum/relations_management.rb +141 -0
- data/lib/familia/horreum/serialization.rb +193 -0
- data/lib/familia/horreum/settings.rb +63 -0
- data/lib/familia/horreum/utils.rb +44 -0
- data/lib/familia/horreum.rb +248 -0
- data/lib/familia/logging.rb +232 -0
- data/lib/familia/redistype/commands.rb +56 -0
- data/lib/familia/redistype/serialization.rb +110 -0
- data/lib/familia/redistype.rb +185 -0
- data/lib/familia/refinements.rb +88 -0
- data/lib/familia/settings.rb +38 -0
- data/lib/familia/types/hashkey.rb +107 -0
- data/lib/familia/types/list.rb +155 -0
- data/lib/familia/types/sorted_set.rb +234 -0
- data/lib/familia/types/string.rb +115 -0
- data/lib/familia/types/unsorted_set.rb +123 -0
- data/lib/familia/utils.rb +125 -0
- data/lib/familia/version.rb +25 -0
- data/lib/familia.rb +57 -161
- data/lib/redis_middleware.rb +109 -0
- data/try/00_familia_try.rb +5 -4
- data/try/10_familia_try.rb +21 -17
- data/try/20_redis_type_try.rb +67 -0
- data/try/{21_redis_object_zset_try.rb → 21_redis_type_zset_try.rb} +2 -2
- data/try/{22_redis_object_set_try.rb → 22_redis_type_set_try.rb} +2 -2
- data/try/{23_redis_object_list_try.rb → 23_redis_type_list_try.rb} +2 -2
- data/try/{24_redis_object_string_try.rb → 24_redis_type_string_try.rb} +6 -6
- data/try/{25_redis_object_hash_try.rb → 25_redis_type_hash_try.rb} +3 -3
- data/try/26_redis_bool_try.rb +10 -6
- data/try/27_redis_horreum_try.rb +93 -0
- data/try/30_familia_object_try.rb +21 -20
- data/try/35_feature_safedump_try.rb +83 -0
- data/try/40_customer_try.rb +140 -0
- data/try/41_customer_safedump_try.rb +86 -0
- data/try/test_helpers.rb +194 -0
- metadata +51 -47
- data/lib/familia/helpers.rb +0 -70
- data/lib/familia/object.rb +0 -533
- data/lib/familia/redisobject.rb +0 -1017
- data/lib/familia/test_helpers.rb +0 -40
- data/lib/familia/tools.rb +0 -67
- data/try/20_redis_object_try.rb +0 -44
data/lib/familia/core_ext.rb
CHANGED
@@ -1,140 +1,135 @@
|
|
1
|
-
#
|
2
|
-
class Symbol
|
3
|
-
unless method_defined?(:to_proc)
|
4
|
-
def to_proc
|
5
|
-
proc { |obj, *args| obj.send(self, *args) }
|
6
|
-
end
|
7
|
-
end
|
8
|
-
end
|
1
|
+
# frozen_string_literal: true
|
9
2
|
|
10
|
-
class
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
end
|
15
|
-
def self.from_json str
|
16
|
-
MultiJson.decode str
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
class Array
|
21
|
-
unless method_defined?(:to_json)
|
22
|
-
def to_json
|
23
|
-
MultiJson.encode self
|
24
|
-
end
|
25
|
-
def self.from_json str
|
26
|
-
MultiJson.decode str
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
# Assumes Time::Units and Numeric mixins are available.
|
3
|
+
# Extends the String class with time-related functionality
|
4
|
+
#
|
5
|
+
# This implementaton assumes Time::Units and Numeric mixins are available.
|
6
|
+
#
|
32
7
|
class String
|
8
|
+
# Converts a string representation of time to seconds
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# "60m".in_seconds #=> 3600.0
|
12
|
+
#
|
13
|
+
# @return [Float, nil] The time in seconds, or nil if the string is invalid
|
33
14
|
def in_seconds
|
34
|
-
|
35
|
-
q,u = self.scan(/([\d\.]+)([s,m,h])?/).flatten
|
15
|
+
q, u = scan(/([\d.]+)([smh])?/).flatten
|
36
16
|
q &&= q.to_f and u ||= 's'
|
37
|
-
q
|
17
|
+
q&.in_seconds(u)
|
38
18
|
end
|
39
19
|
end
|
40
20
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
PER_MINUTE = 60.0.freeze
|
47
|
-
PER_HOUR = 3600.0.freeze
|
48
|
-
PER_DAY = 86400.0.freeze
|
49
|
-
|
50
|
-
def microseconds() seconds * PER_MICROSECOND end
|
51
|
-
def milliseconds() seconds * PER_MILLISECOND end
|
52
|
-
def seconds() self end
|
53
|
-
def minutes() seconds * PER_MINUTE end
|
54
|
-
def hours() seconds * PER_HOUR end
|
55
|
-
def days() seconds * PER_DAY end
|
56
|
-
def weeks() seconds * PER_DAY * 7 end
|
57
|
-
def years() seconds * PER_DAY * 365 end
|
58
|
-
|
59
|
-
def in_years() seconds / PER_DAY / 365 end
|
60
|
-
def in_weeks() seconds / PER_DAY / 7 end
|
61
|
-
def in_days() seconds / PER_DAY end
|
62
|
-
def in_hours() seconds / PER_HOUR end
|
63
|
-
def in_minutes() seconds / PER_MINUTE end
|
64
|
-
def in_milliseconds() seconds / PER_MILLISECOND end
|
65
|
-
def in_microseconds() seconds / PER_MICROSECOND end
|
66
|
-
|
67
|
-
def in_time
|
68
|
-
Time.at(self).utc
|
69
|
-
end
|
70
|
-
|
71
|
-
def in_seconds(u=nil)
|
72
|
-
case u.to_s
|
73
|
-
when /\A(y)|(years?)\z/
|
74
|
-
years
|
75
|
-
when /\A(w)|(weeks?)\z/
|
76
|
-
weeks
|
77
|
-
when /\A(d)|(days?)\z/
|
78
|
-
days
|
79
|
-
when /\A(h)|(hours?)\z/
|
80
|
-
hours
|
81
|
-
when /\A(m)|(minutes?)\z/
|
82
|
-
minutes
|
83
|
-
when /\A(ms)|(milliseconds?)\z/
|
84
|
-
milliseconds
|
85
|
-
when /\A(us)|(microseconds?)|(μs)\z/
|
86
|
-
microseconds
|
87
|
-
else
|
88
|
-
self
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
## JRuby doesn't like using instance_methods.select here.
|
93
|
-
## It could be a bug or something quirky with Attic
|
94
|
-
## (although it works in 1.8 and 1.9). The error:
|
95
|
-
##
|
96
|
-
## lib/attic.rb:32:in `select': yield called out of block (LocalJumpError)
|
97
|
-
## lib/stella/mixins/numeric.rb:24
|
98
|
-
##
|
99
|
-
## Create singular methods, like hour and day.
|
100
|
-
# instance_methods.select.each do |plural|
|
101
|
-
# singular = plural.to_s.chop
|
102
|
-
# alias_method singular, plural
|
103
|
-
# end
|
104
|
-
|
105
|
-
alias_method :ms, :milliseconds
|
106
|
-
alias_method :'μs', :microseconds
|
107
|
-
alias_method :second, :seconds
|
108
|
-
alias_method :minute, :minutes
|
109
|
-
alias_method :hour, :hours
|
110
|
-
alias_method :day, :days
|
111
|
-
alias_method :week, :weeks
|
112
|
-
alias_method :year, :years
|
21
|
+
# Extends the Time class with additional time unit functionality
|
22
|
+
class Time
|
23
|
+
# Provides methods for working with various time units
|
24
|
+
module Units
|
25
|
+
# rubocop:disable Style/SingleLineMethods, Layout/ExtraSpacing
|
113
26
|
|
114
|
-
|
115
|
-
|
27
|
+
PER_MICROSECOND = 0.000001
|
28
|
+
PER_MILLISECOND = 0.001
|
29
|
+
PER_MINUTE = 60.0
|
30
|
+
PER_HOUR = 3600.0
|
31
|
+
PER_DAY = 86_400.0
|
32
|
+
|
33
|
+
# Conversion methods
|
34
|
+
#
|
35
|
+
# From other time units -> seconds
|
36
|
+
#
|
37
|
+
def microseconds() seconds * PER_MICROSECOND end
|
38
|
+
def milliseconds() seconds * PER_MILLISECOND end
|
39
|
+
def seconds() self end
|
40
|
+
def minutes() seconds * PER_MINUTE end
|
41
|
+
def hours() seconds * PER_HOUR end
|
42
|
+
def days() seconds * PER_DAY end
|
43
|
+
def weeks() seconds * PER_DAY * 7 end
|
44
|
+
def years() seconds * PER_DAY * 365 end
|
116
45
|
|
117
|
-
|
118
|
-
|
46
|
+
# From seconds -> other time units
|
47
|
+
#
|
48
|
+
def in_years() seconds / PER_DAY / 365 end
|
49
|
+
def in_weeks() seconds / PER_DAY / 7 end
|
50
|
+
def in_days() seconds / PER_DAY end
|
51
|
+
def in_hours() seconds / PER_HOUR end
|
52
|
+
def in_minutes() seconds / PER_MINUTE end
|
53
|
+
def in_milliseconds() seconds / PER_MILLISECOND end
|
54
|
+
def in_microseconds() seconds / PER_MICROSECOND end
|
119
55
|
|
120
|
-
|
121
|
-
|
56
|
+
#
|
57
|
+
# Converts seconds to a Time object
|
58
|
+
#
|
59
|
+
# @return [Time] A Time object representing the seconds
|
60
|
+
def in_time
|
61
|
+
Time.at(self).utc
|
122
62
|
end
|
123
|
-
|
124
|
-
#
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
63
|
+
|
64
|
+
# Converts seconds to the specified time unit
|
65
|
+
#
|
66
|
+
# @param u [String, Symbol] The unit to convert to (e.g., 'y', 'w', 'd', 'h', 'm', 'ms', 'us')
|
67
|
+
# @return [Float] The converted time value
|
68
|
+
def in_seconds(u = nil)
|
69
|
+
case u.to_s
|
70
|
+
when /\A(y)|(years?)\z/
|
71
|
+
years
|
72
|
+
when /\A(w)|(weeks?)\z/
|
73
|
+
weeks
|
74
|
+
when /\A(d)|(days?)\z/
|
75
|
+
days
|
76
|
+
when /\A(h)|(hours?)\z/
|
77
|
+
hours
|
78
|
+
when /\A(m)|(minutes?)\z/
|
79
|
+
minutes
|
80
|
+
when /\A(ms)|(milliseconds?)\z/
|
81
|
+
milliseconds
|
82
|
+
when /\A(us)|(microseconds?)|(μs)\z/
|
83
|
+
microseconds
|
135
84
|
else
|
136
|
-
|
85
|
+
self
|
137
86
|
end
|
138
87
|
end
|
88
|
+
|
89
|
+
# Starring Jennifer Garner, Victor Garber, and Carl Lumbly
|
90
|
+
alias ms milliseconds
|
91
|
+
alias μs microseconds
|
92
|
+
alias second seconds
|
93
|
+
alias minute minutes
|
94
|
+
alias hour hours
|
95
|
+
alias day days
|
96
|
+
alias week weeks
|
97
|
+
alias year years
|
98
|
+
|
99
|
+
# rubocop:enable Style/SingleLineMethods, Layout/ExtraSpacing
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Extends the Numeric class with time unit and byte conversion functionality
|
104
|
+
class Numeric
|
105
|
+
include Time::Units
|
106
|
+
|
107
|
+
# Converts the number to milliseconds
|
108
|
+
#
|
109
|
+
# @return [Float] The number in milliseconds
|
110
|
+
def to_ms
|
111
|
+
(self * 1000.to_f)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Converts the number to a human-readable byte representation using binary units
|
115
|
+
#
|
116
|
+
# @return [String] A string representing the number in bytes, KiB, MiB, GiB, or TiB
|
117
|
+
#
|
118
|
+
# @example
|
119
|
+
# 1024.to_bytes #=> "1.00 KiB"
|
120
|
+
# 2_097_152.to_bytes #=> "2.00 MiB"
|
121
|
+
# 3_221_225_472.to_bytes #=> "3.00 GiB"
|
122
|
+
#
|
123
|
+
def to_bytes
|
124
|
+
units = %w[B KiB MiB GiB TiB]
|
125
|
+
size = abs.to_f
|
126
|
+
unit = 0
|
127
|
+
|
128
|
+
while size > 1024 && unit < units.length - 1
|
129
|
+
size /= 1024
|
130
|
+
unit += 1
|
131
|
+
end
|
132
|
+
|
133
|
+
format('%3.2f %s', size, units[unit])
|
139
134
|
end
|
140
135
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Familia
|
4
|
+
class Problem < RuntimeError; end
|
5
|
+
class NoIdentifier < Problem; end
|
6
|
+
class NonUniqueKey < Problem; end
|
7
|
+
|
8
|
+
class HighRiskFactor < Problem
|
9
|
+
attr_reader :value
|
10
|
+
|
11
|
+
def initialize(value)
|
12
|
+
@value = value
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def message
|
17
|
+
"High risk factor for serialization bugs: #{value}<#{value.class}>"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class NotConnected < Problem
|
22
|
+
attr_reader :uri
|
23
|
+
|
24
|
+
def initialize(uri)
|
25
|
+
@uri = uri
|
26
|
+
super
|
27
|
+
end
|
28
|
+
|
29
|
+
def message
|
30
|
+
"No client for #{uri.serverid}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# rubocop:disable all
|
2
|
+
#
|
3
|
+
module Familia::Features
|
4
|
+
module ApiVersion
|
5
|
+
|
6
|
+
def apiversion(val = nil, &blk)
|
7
|
+
if blk.nil?
|
8
|
+
@apiversion = val if val
|
9
|
+
else
|
10
|
+
tmp = @apiversion
|
11
|
+
@apiversion = val
|
12
|
+
yield
|
13
|
+
@apiversion = tmp
|
14
|
+
end
|
15
|
+
@apiversion
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# rubocop:disable all
|
2
|
+
|
3
|
+
module Familia::Features
|
4
|
+
|
5
|
+
module Quantizer
|
6
|
+
|
7
|
+
# From Familia::RedisType
|
8
|
+
#
|
9
|
+
def qstamp(quantum = nil, pattern = nil, now = Familia.now)
|
10
|
+
quantum ||= @opts[:quantize] || ttl || 10.minutes
|
11
|
+
case quantum
|
12
|
+
when Numeric
|
13
|
+
# Handle numeric quantum (e.g., seconds, minutes)
|
14
|
+
when Array
|
15
|
+
quantum, pattern = *quantum
|
16
|
+
end
|
17
|
+
now ||= Familia.now
|
18
|
+
rounded = now - (now % quantum)
|
19
|
+
|
20
|
+
if pattern.nil?
|
21
|
+
Time.at(rounded).utc.to_i # 3605 -> 3600
|
22
|
+
else
|
23
|
+
Time.at(rounded).utc.strftime(pattern || '%H%M') # 3605 -> '1:00'
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
# From Familia::Horreum::InstanceMethods:
|
29
|
+
#
|
30
|
+
#def qstamp(_quantum = nil, pattern = nil, now = Familia.now)
|
31
|
+
# self.class.qstamp ttl, pattern, now
|
32
|
+
#end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
# rubocop:disable all
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
|
5
|
+
module Familia::Features
|
6
|
+
# SafeDump is a mixin that allows models to define a list of fields that are
|
7
|
+
# safe to dump. This is useful for serializing objects to JSON or other
|
8
|
+
# formats where you want to ensure that only certain fields are exposed.
|
9
|
+
#
|
10
|
+
# To use SafeDump, include it in your model and define a list of fields that
|
11
|
+
# are safe to dump. The fields can be either symbols or hashes. If a field is
|
12
|
+
# a symbol, the method with the same name will be called on the object to
|
13
|
+
# retrieve the value. If the field is a hash, the key is the field name and
|
14
|
+
# the value is a lambda that will be called with the object as an argument.
|
15
|
+
# The hash syntax allows you to:
|
16
|
+
# * define a field name that is different from the method name
|
17
|
+
# * define a field that requires some computation on-the-fly
|
18
|
+
# * define a field that is not a method on the object
|
19
|
+
#
|
20
|
+
# Example:
|
21
|
+
#
|
22
|
+
# feature :safe_dump
|
23
|
+
#
|
24
|
+
# @safe_dump_fields = [
|
25
|
+
# :objid,
|
26
|
+
# :updated,
|
27
|
+
# :created,
|
28
|
+
# { :active => ->(obj) { obj.active? } }
|
29
|
+
# ]
|
30
|
+
#
|
31
|
+
# Internally, all fields are normalized to the hash syntax and stored in
|
32
|
+
# @safe_dump_field_map. `SafeDump.safe_dump_fields` returns only the list
|
33
|
+
# of symbols in the order they were defined. From the example above, it would
|
34
|
+
# return `[:objid, :updated, :created, :active]`.
|
35
|
+
#
|
36
|
+
# Standalone Usage:
|
37
|
+
#
|
38
|
+
# You can also use SafeDump by including it in your model and defining the
|
39
|
+
# safe dump fields using the class instance variable `@safe_dump_fields`.
|
40
|
+
#
|
41
|
+
# Example:
|
42
|
+
#
|
43
|
+
# class MyModel
|
44
|
+
# include Familia::Features::SafeDump
|
45
|
+
#
|
46
|
+
# @safe_dump_fields = [
|
47
|
+
# :id, :name, { active: ->(obj) { obj.active? } }
|
48
|
+
# ]
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
module SafeDump
|
52
|
+
@dump_method = :to_json
|
53
|
+
@load_method = :from_json
|
54
|
+
|
55
|
+
@safe_dump_fields = []
|
56
|
+
@safe_dump_field_map = {}
|
57
|
+
|
58
|
+
module ClassMethods
|
59
|
+
def set_safe_dump_fields(*fields)
|
60
|
+
@safe_dump_fields = fields
|
61
|
+
end
|
62
|
+
|
63
|
+
# `SafeDump.safe_dump_fields` returns only the list
|
64
|
+
# of symbols in the order they were defined.
|
65
|
+
def safe_dump_fields
|
66
|
+
@safe_dump_fields.map do |field|
|
67
|
+
field.is_a?(Symbol) ? field : field.keys.first
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# `SafeDump.safe_dump_field_map` returns the field map
|
72
|
+
# that is used to dump the fields. The keys are the
|
73
|
+
# field names and the values are callables that will
|
74
|
+
# expect to receive the instance object as an argument.
|
75
|
+
#
|
76
|
+
# The map is cached on the first call to this method.
|
77
|
+
#
|
78
|
+
def safe_dump_field_map
|
79
|
+
return @safe_dump_field_map if @safe_dump_field_map.any?
|
80
|
+
|
81
|
+
# Operate directly on the @safe_dump_fields array to
|
82
|
+
# build the map. This way we'll get the elements defined
|
83
|
+
# in the hash syntax (i.e. since the safe_dump_fields getter
|
84
|
+
# method returns only the symbols).
|
85
|
+
@safe_dump_field_map = @safe_dump_fields.each_with_object({}) do |el, map|
|
86
|
+
if el.is_a?(Symbol)
|
87
|
+
field_name = el
|
88
|
+
callable = lambda { |obj|
|
89
|
+
if obj.respond_to?(:[]) && obj[field_name]
|
90
|
+
obj[field_name] # Familia::RedisType classes
|
91
|
+
elsif obj.respond_to?(field_name)
|
92
|
+
obj.send(field_name) # Onetime::Models::RedisHash classes via method_missing 😩
|
93
|
+
end
|
94
|
+
}
|
95
|
+
else
|
96
|
+
field_name = el.keys.first
|
97
|
+
callable = el.values.first
|
98
|
+
end
|
99
|
+
map[field_name] = callable
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.included base
|
105
|
+
Familia.ld "[Feature] Enabling SafeDump for #{base})"
|
106
|
+
base.extend ClassMethods
|
107
|
+
|
108
|
+
# Optionally define safe_dump_fields in the class to make
|
109
|
+
# sure we always have an array to work with.
|
110
|
+
unless base.instance_variable_defined?(:@safe_dump_fields)
|
111
|
+
base.instance_variable_set(:@safe_dump_fields, [])
|
112
|
+
end
|
113
|
+
|
114
|
+
# Ditto for the field map
|
115
|
+
unless base.instance_variable_defined?(:@safe_dump_field_map)
|
116
|
+
base.instance_variable_set(:@safe_dump_field_map, {})
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Returns a hash of safe fields and their values. This method
|
121
|
+
# calls the callables defined in the safe_dump_field_map with
|
122
|
+
# the instance object as an argument.
|
123
|
+
#
|
124
|
+
# The return values are not cached, so if you call this method
|
125
|
+
# multiple times, the callables will be called each time.
|
126
|
+
#
|
127
|
+
# Example:
|
128
|
+
#
|
129
|
+
# class Customer < Familia::HashKey
|
130
|
+
# include SafeDump
|
131
|
+
# @safe_dump_fields = [
|
132
|
+
# :name,
|
133
|
+
# { :active => ->(cust) { cust.active? } }
|
134
|
+
# ]
|
135
|
+
#
|
136
|
+
# def active?
|
137
|
+
# true # or false
|
138
|
+
# end
|
139
|
+
#
|
140
|
+
# cust = Customer.new :name => 'Lucy'
|
141
|
+
# cust.safe_dump
|
142
|
+
# #=> { :name => 'Lucy', :active => true }
|
143
|
+
#
|
144
|
+
def safe_dump
|
145
|
+
self.class.safe_dump_field_map.transform_values do |callable|
|
146
|
+
transformed_value = callable.call(self)
|
147
|
+
|
148
|
+
# If the value is a relative ancestor of SafeDump we can
|
149
|
+
# call safe_dump on it, otherwise we'll just return the value as-is.
|
150
|
+
if transformed_value.is_a?(SafeDump)
|
151
|
+
transformed_value.safe_dump
|
152
|
+
else
|
153
|
+
transformed_value
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
extend ClassMethods
|
159
|
+
|
160
|
+
Familia::Base.add_feature self, :safe_dump
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
__END__
|
167
|
+
|
168
|
+
# Some leftovers related to dump_method and load_method
|
169
|
+
|
170
|
+
if value_to_distunguish.is_a?(Familia::Horreum)
|
171
|
+
Familia.trace :DISTINGUISHER, redis, "horreum", caller(1..1) if Familia.debug?
|
172
|
+
value_to_distunguish.identifier
|
173
|
+
elsif dump_method && value_to_distunguish.respond_to?(dump_method)
|
174
|
+
Familia.trace :DISTINGUISHER, redis, "#{value_to_distunguish.class}##{dump_method}", caller(1..1) if Familia.debug?
|
175
|
+
value_to_distunguish.send(dump_method)
|
176
|
+
else
|
177
|
+
if dump_method
|
178
|
+
msg = if dump_method.to_s.empty?
|
179
|
+
"No dump_method available for #{value_to_distunguish.class}"
|
180
|
+
else
|
181
|
+
"No such method: #{value_to_distunguish.class}##{dump_method}"
|
182
|
+
end
|
183
|
+
raise Familia::Problem, msg
|
184
|
+
else
|
185
|
+
Familia.trace :DISTINGUISHER, redis, "else", caller(1..1) if Familia.debug?
|
186
|
+
nil
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
|
191
|
+
if ret.nil? && dump_method && val.respond_to?(dump_method)
|
192
|
+
Familia.trace :TOREDIS, redis, "#{val.class}##{dump_method}", caller(1..1) if Familia.debug?
|
193
|
+
val.send dump_method
|
194
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# rubocop:disable all
|
2
|
+
|
3
|
+
module Familia
|
4
|
+
|
5
|
+
@features_enabled = nil
|
6
|
+
|
7
|
+
module Features
|
8
|
+
|
9
|
+
attr_reader :features_enabled
|
10
|
+
|
11
|
+
def feature(val = nil)
|
12
|
+
@features_enabled ||= []
|
13
|
+
|
14
|
+
Familia.ld "[Familia::Settings] feature: #{val.inspect}"
|
15
|
+
|
16
|
+
# If there's a value provied check that it's a valid feature
|
17
|
+
if val
|
18
|
+
val = val.to_sym
|
19
|
+
raise Familia::Problem, "Unsupported feature: #{val}" unless Familia::Base.features.key?(val)
|
20
|
+
|
21
|
+
# If the feature is already enabled, do nothing but log about it
|
22
|
+
if @features_enabled.member?(val)
|
23
|
+
Familia.warn "[Familia::Settings] feature already enabled: #{val}"
|
24
|
+
return
|
25
|
+
end
|
26
|
+
|
27
|
+
klass = Familia::Base.features[val]
|
28
|
+
|
29
|
+
# Extend the Familia::Base subclass (e.g. Customer) with the feature module
|
30
|
+
include klass
|
31
|
+
|
32
|
+
# NOTE: We may also want to extend Familia::RedisType here so that we can
|
33
|
+
# call safe_dump on relations fields (e.g. list, set, zset, hashkey). Or
|
34
|
+
# maybe that only makes sense for hashk/object relations.
|
35
|
+
#
|
36
|
+
# We'd need to avoid it getting included multiple times (i.e. once for each
|
37
|
+
# Familia::Horreum subclass that includes the feature).
|
38
|
+
|
39
|
+
# Now that the feature is loaded successfully, add it to the list
|
40
|
+
# enabled features for Familia::Base classes.
|
41
|
+
@features_enabled << val
|
42
|
+
end
|
43
|
+
|
44
|
+
features_enabled
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
require_relative 'features/safe_dump'
|