familia 1.0.0.pre.rc3 → 1.0.0.pre.rc4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/README.md +198 -48
  4. data/VERSION.yml +1 -1
  5. data/lib/familia/base.rb +29 -1
  6. data/lib/familia/features/expiration.rb +90 -0
  7. data/lib/familia/features/quantization.rb +56 -0
  8. data/lib/familia/features/safe_dump.rb +2 -33
  9. data/lib/familia/features.rb +5 -4
  10. data/lib/familia/horreum/class_methods.rb +112 -46
  11. data/lib/familia/horreum/commands.rb +9 -3
  12. data/lib/familia/horreum/relations_management.rb +2 -2
  13. data/lib/familia/horreum/serialization.rb +23 -42
  14. data/lib/familia/horreum/settings.rb +0 -8
  15. data/lib/familia/horreum/utils.rb +0 -1
  16. data/lib/familia/horreum.rb +1 -1
  17. data/lib/familia/logging.rb +26 -4
  18. data/lib/familia/redistype/serialization.rb +60 -38
  19. data/lib/familia/redistype.rb +45 -17
  20. data/lib/familia/settings.rb +11 -1
  21. data/lib/familia/tools.rb +68 -0
  22. data/lib/familia/types/hashkey.rb +5 -5
  23. data/lib/familia/types/list.rb +2 -2
  24. data/lib/familia/types/sorted_set.rb +12 -12
  25. data/lib/familia/types/string.rb +1 -1
  26. data/lib/familia/types/unsorted_set.rb +2 -2
  27. data/lib/familia/utils.rb +106 -51
  28. data/lib/familia/version.rb +2 -2
  29. data/try/10_familia_try.rb +4 -4
  30. data/try/20_redis_type_try.rb +9 -6
  31. data/try/26_redis_bool_try.rb +1 -1
  32. data/try/27_redis_horreum_try.rb +1 -1
  33. data/try/30_familia_object_try.rb +3 -2
  34. data/try/40_customer_try.rb +3 -3
  35. data/try/test_helpers.rb +9 -2
  36. metadata +5 -5
  37. data/lib/familia/features/api_version.rb +0 -19
  38. data/lib/familia/features/atomic_saves.rb +0 -8
  39. data/lib/familia/features/quantizer.rb +0 -35
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c5cdd5bc9ccadb7e69c324e7a2716b378fa5e5fe9938c4ed44a61b23eef99c6a
4
- data.tar.gz: de5bb8d9e3b6b09906e777f9a5586eff32a5b1d3ec7c5e51a3a26dac2bd85415
3
+ metadata.gz: b514a58bc45692f39123cce853a679078eccd78362a78facc397a1df6d94629d
4
+ data.tar.gz: d29686d172bec525bee366ab54ad4e81a5903547a9a2d98c68df8ca81853c7dc
5
5
  SHA512:
6
- metadata.gz: 76ffa691585dde5c45aaa17e1d02171bacb3fad0267f638b02d7c2cf0a65eaed4d0062656be2496c27ff9bd9788839a71d8475f3e14577ef607684cd179209c1
7
- data.tar.gz: 96d52e17f1c3f3092d4ec39ad0fbd1455ef54a902a2f2aaa65c5531d11cdd8b53ee50ef1fa0bd967bd4fa49fd4b0693d89031bcb6decb6cc2def5cb4e1d85c9a
6
+ metadata.gz: cd750e1ee120eb666563e9c8c552f721926ccceaa032cd0cefcf4fad7920225ab1edc4961cf3a5a7c38d81ecab3c21b446e91a75347d51a4bc196c14f5ed94fd
7
+ data.tar.gz: 6c52919952e7ce8232cf1023fafe567655580a73e4d36dd0a38b8842cb618387272db6950380f71efa01797439d9542303d3addd8c94e841255923998b93589f
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- familia (1.0.0.pre.rc2)
4
+ familia (1.0.0.pre.rc4)
5
5
  redis (>= 4.8.1, < 6.0)
6
6
  uri-redis (~> 1.3)
7
7
 
data/README.md CHANGED
@@ -1,84 +1,234 @@
1
- # Familia - 1.0.0-rc2 (August 2024)
1
+ # Familia - 1.0.0-rc4 (August 2024)
2
2
 
3
- **Organize and store ruby objects in Redis. A Ruby ORM for Redis.**
3
+ **Organize and store Ruby objects in Redis. A powerful Ruby ORM (of sorts) for Redis.**
4
4
 
5
- Familia provides a powerful and flexible way to interact with Redis using Ruby objects. It's designed to make working with Redis as natural as working with Ruby classes.
5
+ Familia provides a flexible and feature-rich way to interact with Redis using Ruby objects. It's designed to make working with Redis as natural as working with Ruby classes, while offering advanced features for complex data management.
6
6
 
7
7
  ## Installation
8
8
 
9
+
9
10
  Get it in one of the following ways:
10
11
 
11
- * In your Gemfile: `gem 'familia', '>= 1.0.0-rc2'`
12
- * Install it by hand: `gem install familia`
12
+ * In your Gemfile: `gem 'familia', '>= 1.0.0-rc4'`
13
+ * Install it by hand: `gem install familia --pre`
13
14
  * Or for development: `git clone git@github.com:delano/familia.git`
14
15
 
15
- ## Basic Example
16
+
17
+ ## Core Concepts and Features
18
+
19
+ ### 1. Defining Horreum Classes
20
+
21
+ Familia uses the concept of "Horreum" classes to represent Redis-backed objects:
16
22
 
17
23
  ```ruby
18
24
  class Flower < Familia::Horreum
19
- identifier :generate_id
20
- field :token
21
- field :name
22
- list :owners
23
- set :tags
24
- zset :metrics
25
+ identifier :token
26
+ field :name
27
+ list :owners
28
+ set :tags
29
+ zset :metrics
25
30
  hashkey :props
26
- string :counter
31
+ string :counter
32
+ end
33
+ ```
34
+
35
+ ### 2. Flexible Identifiers
36
+
37
+ You can define identifiers in various ways:
38
+
39
+ ```ruby
40
+ class User < Familia::Horreum
41
+ identifier :email
42
+ # or
43
+ identifier -> (user) { "user:#{user.email}" }
44
+ # or
45
+ identifier [:type, :email]
46
+
47
+ field :email
48
+ field :type
49
+ end
50
+ ```
51
+
52
+ ### 3. Redis Data Types
53
+
54
+ Familia supports various Redis data types:
55
+
56
+ ```ruby
57
+ class Product < Familia::Horreum
58
+ string :name
59
+ list :categories
60
+ set :tags
61
+ zset :ratings
62
+ hashkey :attributes
63
+ end
64
+ ```
65
+
66
+ ### 4. Class-level Redis Types
67
+
68
+ You can also define Redis types at the class level:
69
+
70
+ ```ruby
71
+ class Customer < Familia::Horreum
72
+ class_sorted_set :values, key: 'project:customers'
73
+ class_hashkey :projects
74
+ class_list :customers, suffix: []
75
+ class_string :message
76
+ end
77
+ ```
78
+
79
+ ### 5. Automatic Expiration
80
+
81
+ Use the expiration feature to set TTL for objects:
82
+
83
+ ```ruby
84
+ class Session < Familia::Horreum
85
+ feature :expiration
86
+ ttl 180.minutes
87
+ end
88
+ ```
89
+
90
+ ### 6. Safe Dumping for APIs
91
+
92
+ Control which fields are exposed when serializing objects:
93
+
94
+ ```ruby
95
+ class User < Familia::Horreum
96
+ feature :safe_dump
97
+
98
+ @safe_dump_fields = [
99
+ :id,
100
+ :username,
101
+ {full_name: ->(user) { "#{user.first_name} #{user.last_name}" }}
102
+ ]
103
+ end
104
+ ```
105
+
106
+ ### 7. Quantization for Time-based Data
107
+
108
+ Use quantization for time-based metrics:
109
+
110
+ ```ruby
111
+ class DailyMetric < Familia::Horreum
112
+ feature :quantization
113
+ string :counter, ttl: 1.day, quantize: [10.minutes, '%H:%M']
27
114
  end
28
115
  ```
29
116
 
30
- ## What Familia::Horreum Can Do
117
+ ### 8. Custom Methods and Logic
31
118
 
32
- Familia::Horreum provides a powerful abstraction layer over Redis, allowing you to:
119
+ Add custom methods to your Horreum classes:
33
120
 
34
- 1. **Define Redis-backed Ruby Classes**: As shown in the example, you can easily define classes that map to Redis structures.
121
+ ```ruby
122
+ class User < Familia::Horreum
123
+ def full_name
124
+ "#{first_name} #{last_name}"
125
+ end
126
+
127
+ def active?
128
+ status == 'active'
129
+ end
130
+ end
131
+ ```
35
132
 
36
- 2. **Use Various Redis Data Types**: Familia supports multiple Redis data types:
37
- - `field`: For simple key-value pairs
38
- - `list`: For Redis lists
39
- - `set`: For Redis sets
40
- - `zset`: For Redis sorted sets
41
- - `hashkey`: For Redis hashes
42
- - `string`: For Redis strings
133
+ ### 9. Custom Methods and Logic
43
134
 
44
- 3. **Custom Identifiers**: Use the `identifier` method to specify how objects are uniquely identified in Redis.
135
+ You can add custom methods to your Horreum classes:
45
136
 
46
- 4. **Automatic Serialization**: Familia handles the serialization and deserialization of your objects to and from Redis.
137
+ ```ruby
138
+ class Customer < Familia::Horreum
139
+ def active?
140
+ verified && !reset_requested
141
+ end
142
+ end
47
143
 
48
- 5. **Redis Commands as Ruby Methods**: Interact with Redis using familiar Ruby syntax instead of raw Redis commands.
144
+ class Session < Familia::Horreum
145
+ def external_identifier
146
+ elements = [ipaddress || 'UNKNOWNIP', custid || 'anon']
147
+ @external_identifier ||= Familia.generate_sha_hash(elements)
148
+ end
149
+ end
150
+ ```
151
+ ### 10. Open-ended Serialization
49
152
 
50
- 6. **TTL Support**: Set expiration times for your objects in Redis.
153
+ ```ruby
154
+ class ComplexObject < Familia::Horreum
155
+ def to_redis
156
+ custom_serialization_method
157
+ end
158
+
159
+ def self.from_redis(data)
160
+ custom_deserialization_method(data)
161
+ end
162
+ end
163
+ ```
51
164
 
52
- 7. **Flexible Configuration**: Configure Redis connection details, serialization methods, and more.
165
+ ### 11. Transactional Operations
166
+
167
+ ```ruby
168
+ user.transaction do |conn|
169
+ conn.set("user:#{user.id}:status", "active")
170
+ conn.zadd("active_users", Time.now.to_i, user.id)
171
+ end
172
+ ```
53
173
 
54
- ## Advanced Features
55
174
 
56
- - **API Versioning**: Familia supports API versioning to help manage changes in your data model over time.
57
- - **Custom Serialization**: You can specify custom serialization methods for your objects.
58
- - **Redis URI Support**: Easily connect to Redis using URI strings.
59
- - **Debugging Tools**: Built-in debugging capabilities to help troubleshoot Redis interactions.
175
+ ## Usage Examples
60
176
 
61
- ## Usage Example
177
+ ### Creating and Saving Objects
62
178
 
63
179
  ```ruby
64
- # Create a new Flower
65
- rose = Flower.new
66
- rose.name = "Red Rose"
67
- rose.tags << "romantic" << "red"
68
- rose.owners.push("Alice", "Bob")
180
+ flower = Flower.create(name: "Red Rose", token: "rrose")
181
+ flower.owners.push("Alice", "Bob")
182
+ flower.tags.add("romantic")
183
+ flower.metrics.increment("views", 1)
184
+ flower.props[:color] = "red"
185
+ flower.save
186
+ ```
187
+
188
+ ### Retrieving and Updating Objects
189
+
190
+ ```ruby
191
+ rose = Flower.from_identifier("rrose")
192
+ rose.name = "Pink Rose"
69
193
  rose.save
194
+ ```
195
+
196
+ ### Using Safe Dump
197
+
198
+ ```ruby
199
+ user = User.create(username: "rosedog", first_name: "Rose", last_name: "Dog")
200
+ user.safe_dump
201
+ # => {id: "user:rosedog", username: "rosedog", full_name: "Rose Dog"}
202
+ ```
203
+
204
+ ### Working with Time-based Data
205
+
206
+ ```ruby
207
+ metric = DailyMetric.new
208
+ metric.counter.increment # Increments the counter for the current hour
209
+ ```
210
+
211
+ ### Bulk Operations
70
212
 
71
- # Retrieve a Flower
72
- retrieved_rose = Flower.get(rose.identifier)
73
- puts retrieved_rose.name # => "Red Rose"
74
- puts retrieved_rose.tags.members # => ["romantic", "red"]
213
+ ```ruby
214
+ Flower.multiget("rrose", "tulip", "daisy")
215
+ ```
216
+
217
+ ### Transactional Operations
218
+
219
+ ```ruby
220
+ user.transaction do |conn|
221
+ conn.set("user:#{user.id}:status", "active")
222
+ conn.zadd("active_users", Time.now.to_i, user.id)
223
+ end
75
224
  ```
76
225
 
77
- ## More Information
226
+ ## Conclusion
78
227
 
79
- * [Github](https://github.com/delano/familia)
80
- * [Rubygems](https://rubygems.org/gems/familia)
228
+ Familia provides a powerful and flexible way to work with Redis in Ruby applications. Its features like automatic expiration, safe dumping, and quantization make it suitable for a wide range of use cases, from simple key-value storage to complex time-series data management.
81
229
 
82
- ## Contributing
230
+ For more information, visit:
231
+ - [Github Repository](https://github.com/delano/familia)
232
+ - [RubyGems Page](https://rubygems.org/gems/familia)
83
233
 
84
- Contributions are welcome! Please feel free to submit a Pull Request.
234
+ Contributions are welcome! Feel free to submit a Pull Request.
data/VERSION.yml CHANGED
@@ -2,4 +2,4 @@
2
2
  :MAJOR: 1
3
3
  :MINOR: 0
4
4
  :PATCH: 0
5
- :PRE: rc3
5
+ :PRE: rc4
data/lib/familia/base.rb CHANGED
@@ -24,10 +24,38 @@ module Familia
24
24
 
25
25
  def add_feature(klass, methname)
26
26
  @features ||= {}
27
- Familia.ld "[#{self}] Adding feature #{klass} as #{methname}"
27
+ Familia.ld "[#{self}] Adding feature #{klass} as #{methname.inspect}"
28
28
 
29
29
  features[methname] = klass
30
30
  end
31
31
  end
32
+
33
+ # Yo, this class is like that one friend who never checks expiration dates.
34
+ # It's living life on the edge, data-style!
35
+ #
36
+ # @param ttl [Integer, nil] Time To Live? More like Time To Laugh! This param
37
+ # is here for compatibility, but it's as useful as a chocolate teapot.
38
+ #
39
+ # @return [nil] Always returns nil. It's consistent in its laziness!
40
+ #
41
+ # @example Trying to teach an old dog new tricks
42
+ # immortal_data.update_expiration(86400) # Nice try, but this data is here to stay!
43
+ #
44
+ # @note This method is a no-op. It's like shouting into the void, but less echo-y.
45
+ #
46
+ def update_expiration(_ = nil)
47
+ Familia.info "[update_expiration] Skipped for #{rediskey}. #{self.class} data is immortal!"
48
+ nil
49
+ end
50
+
51
+ def generate_id
52
+ @key ||= Familia.generate_id
53
+ @key
54
+ end
55
+
56
+ def uuid
57
+ @uuid ||= SecureRandom.uuid
58
+ @uuid
59
+ end
32
60
  end
33
61
  end
@@ -0,0 +1,90 @@
1
+ # rubocop:disable all
2
+ # frozen_string_literal: true
3
+
4
+
5
+ module Familia::Features
6
+ #
7
+ module Expiration
8
+ @ttl = nil
9
+
10
+ module ClassMethods
11
+
12
+ attr_writer :ttl
13
+
14
+ def ttl(v = nil)
15
+ @ttl = v.to_f unless v.nil?
16
+ @ttl || parent&.ttl || Familia.ttl
17
+ end
18
+
19
+ end
20
+
21
+ def self.included base
22
+ Familia.ld "[#{base}] Loaded #{self}"
23
+ base.extend ClassMethods
24
+
25
+ # Optionally define ttl in the class to make
26
+ # sure we always have an array to work with.
27
+ unless base.instance_variable_defined?(:@ttl)
28
+ base.instance_variable_set(:@ttl, @ttl) # set above
29
+ end
30
+ end
31
+
32
+ def ttl=(v)
33
+ @ttl = v.to_f
34
+ end
35
+
36
+ def ttl
37
+ @ttl || self.class.ttl
38
+ end
39
+
40
+ # Yo, check it out! We're gonna give our Redis data an expiration date!
41
+ #
42
+ # It's like slapping a "Best Before" sticker on your favorite snack,
43
+ # but for data. How cool is that?
44
+ #
45
+ # @param ttl [Integer, nil] The Time To Live in seconds. Nil? No worries!
46
+ # We'll dig up the default from our secret stash.
47
+ #
48
+ # @return [Boolean] Did Redis pin that expiry note successfully?
49
+ # True for "Yep!", false for "Oops, butter fingers!"
50
+ #
51
+ # @example Teaching your pet rock the concept of mortality
52
+ # rocky.update_expiration(86400) # Dwayne gets to party in Redis for one whole day!
53
+ #
54
+ # @note If TTL is zero, your data gets a VIP pass to the Redis eternity club.
55
+ # Fancy, huh?
56
+ #
57
+ # @raise [Familia::Problem] If you try to feed it non-numbers or time-travel
58
+ # (negative numbers). It's strict, but fair!
59
+ #
60
+ def update_expiration(ttl = nil)
61
+ ttl ||= self.ttl
62
+ # It's important to raise exceptions here and not just log warnings. We
63
+ # don't want to silently fail at setting expirations and cause data
64
+ # retention issues (e.g. not removed in a timely fashion).
65
+ #
66
+ # For the same reason, we don't want to default to 0 bc there's not a
67
+ # good reason for the ttl to not be set in the first place. If the
68
+ # class doesn't have a ttl, the default comes from Familia.ttl (which
69
+ # is 0).
70
+ raise Familia::Problem, "TTL must be a number (#{ttl.class})" unless ttl.is_a?(Numeric)
71
+ raise Familia::Problem, "TTL must be positive (#{ttl})" unless ttl.is_a?(Numeric)
72
+
73
+ if ttl.zero?
74
+ return Familia.ld "[update_expiration] No expiration for #{self.class} (#{rediskey})"
75
+ end
76
+
77
+ Familia.info "[update_expiration] Expires #{rediskey} in #{ttl} seconds"
78
+
79
+ # Redis' EXPIRE command returns 1 if the timeout was set, 0 if key does
80
+ # not exist or the timeout could not be set. Via redis-rb here, it's
81
+ # a bool.
82
+ expire(ttl)
83
+ end
84
+
85
+ extend ClassMethods
86
+
87
+ Familia::Base.add_feature self, :expiration
88
+ end
89
+
90
+ end
@@ -0,0 +1,56 @@
1
+ # rubocop:disable all
2
+
3
+ module Familia::Features
4
+
5
+ module Quantization
6
+
7
+ module ClassMethods
8
+ # Generates a quantized timestamp based on the given parameters.
9
+ #
10
+ # @param quantum [Integer, Array, nil] The time quantum in seconds or an array of [quantum, pattern].
11
+ # @param pattern [String, nil] The strftime pattern to format the timestamp.
12
+ # @param now [Time, nil] The current time (default: Familia.now).
13
+ # @return [Integer, String] A unix timestamp or formatted timestamp string.
14
+ #
15
+ # This method rounds the current time to the nearest quantum and optionally formats it
16
+ # according to the given pattern. It's useful for creating time-based buckets
17
+ # or keys with reduced granularity.
18
+ #
19
+ # @example
20
+ # User.qstamp(1.hour, '%Y%m%d%H') # Returns a string like "2023060114" for 2:30 PM
21
+ # User.qstamp(10.minutes) # Returns an integer timestamp rounded to the nearest 10 minutes
22
+ # User.qstamp([1.hour, '%Y%m%d%H']) # Same as the first example
23
+ #
24
+ # @raise [ArgumentError] If quantum is not positive
25
+ #
26
+ def qstamp(quantum = nil, pattern: nil, time: nil)
27
+ # Handle default values and array input
28
+ if quantum.is_a?(Array)
29
+ quantum, pattern = quantum
30
+ end
31
+ quantum ||= @opts[:quantize] || ttl || 10.minutes
32
+
33
+ # Validate quantum
34
+ unless quantum.is_a?(Numeric) && quantum.positive?
35
+ raise ArgumentError, "Quantum must be positive (#{quantum.inspect} given)"
36
+ end
37
+
38
+ # Call Familia.qstamp with our processed parameters
39
+ Familia.qstamp(quantum, pattern: pattern, time: time)
40
+ end
41
+ end
42
+
43
+ def self.included base
44
+ Familia.ld "[#{base}] Loaded #{self}"
45
+ base.extend ClassMethods
46
+ end
47
+
48
+ def qstamp(quantum = nil, pattern: nil, time: nil)
49
+ self.class.qstamp(quantum || ttl, pattern: pattern, time: time)
50
+ end
51
+
52
+ extend ClassMethods
53
+
54
+ Familia::Base.add_feature self, :quantization
55
+ end
56
+ end
@@ -102,7 +102,7 @@ module Familia::Features
102
102
  end
103
103
 
104
104
  def self.included base
105
- Familia.ld "[Feature] Enabling SafeDump for #{base})"
105
+ Familia.ld "[#{self}] Enabled in #{base}"
106
106
  base.extend ClassMethods
107
107
 
108
108
  # Optionally define safe_dump_fields in the class to make
@@ -159,36 +159,5 @@ module Familia::Features
159
159
 
160
160
  Familia::Base.add_feature self, :safe_dump
161
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
162
+ # end SafeDump
194
163
  end
@@ -2,17 +2,14 @@
2
2
 
3
3
  module Familia
4
4
 
5
- @features_enabled = nil
6
-
7
5
  module Features
8
6
 
7
+ @features_enabled = nil
9
8
  attr_reader :features_enabled
10
9
 
11
10
  def feature(val = nil)
12
11
  @features_enabled ||= []
13
12
 
14
- Familia.ld "[Familia::Settings] feature: #{val.inspect}"
15
-
16
13
  # If there's a value provied check that it's a valid feature
17
14
  if val
18
15
  val = val.to_sym
@@ -24,6 +21,8 @@ module Familia
24
21
  return
25
22
  end
26
23
 
24
+ Familia.trace :FEATURE, nil, "#{self} includes #{val.inspect}", caller(1..1) if Familia.debug?
25
+
27
26
  klass = Familia::Base.features[val]
28
27
 
29
28
  # Extend the Familia::Base subclass (e.g. Customer) with the feature module
@@ -48,4 +47,6 @@ module Familia
48
47
 
49
48
  end
50
49
 
50
+ require_relative 'features/expiration'
51
+ require_relative 'features/quantization'
51
52
  require_relative 'features/safe_dump'