eventq 2.0.0.rc1

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.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +336 -0
  3. data/bin/console +14 -0
  4. data/bin/setup +8 -0
  5. data/lib/eventq/aws.rb +38 -0
  6. data/lib/eventq/eventq_aws/README.md +53 -0
  7. data/lib/eventq/eventq_aws/aws_eventq_client.rb +120 -0
  8. data/lib/eventq/eventq_aws/aws_queue_client.rb +64 -0
  9. data/lib/eventq/eventq_aws/aws_queue_manager.rb +68 -0
  10. data/lib/eventq/eventq_aws/aws_queue_worker.rb +168 -0
  11. data/lib/eventq/eventq_aws/aws_status_checker.rb +25 -0
  12. data/lib/eventq/eventq_aws/aws_subscription_manager.rb +65 -0
  13. data/lib/eventq/eventq_aws/jruby/aws_queue_worker.rb +370 -0
  14. data/lib/eventq/eventq_aws/sns.rb +64 -0
  15. data/lib/eventq/eventq_aws/sqs.rb +112 -0
  16. data/lib/eventq/eventq_base/configuration.rb +33 -0
  17. data/lib/eventq/eventq_base/event_raised_exchange.rb +7 -0
  18. data/lib/eventq/eventq_base/event_raised_queue.rb +7 -0
  19. data/lib/eventq/eventq_base/eventq_client_contract.rb +9 -0
  20. data/lib/eventq/eventq_base/eventq_logger.rb +28 -0
  21. data/lib/eventq/eventq_base/exceptions/invalid_signature_exception.rb +9 -0
  22. data/lib/eventq/eventq_base/exceptions/worker_thread_error.rb +10 -0
  23. data/lib/eventq/eventq_base/exceptions.rb +2 -0
  24. data/lib/eventq/eventq_base/exchange.rb +5 -0
  25. data/lib/eventq/eventq_base/message_args.rb +23 -0
  26. data/lib/eventq/eventq_base/nonce_manager.rb +57 -0
  27. data/lib/eventq/eventq_base/queue.rb +27 -0
  28. data/lib/eventq/eventq_base/queue_message.rb +31 -0
  29. data/lib/eventq/eventq_base/queue_worker_contract.rb +23 -0
  30. data/lib/eventq/eventq_base/serialization_providers/binary_serialization_provider.rb +15 -0
  31. data/lib/eventq/eventq_base/serialization_providers/jruby/oj/array_writer.rb +20 -0
  32. data/lib/eventq/eventq_base/serialization_providers/jruby/oj/attribute_writer.rb +24 -0
  33. data/lib/eventq/eventq_base/serialization_providers/jruby/oj/class_writer.rb +20 -0
  34. data/lib/eventq/eventq_base/serialization_providers/jruby/oj/date_time_writer.rb +33 -0
  35. data/lib/eventq/eventq_base/serialization_providers/jruby/oj/date_writer.rb +22 -0
  36. data/lib/eventq/eventq_base/serialization_providers/jruby/oj/hash_writer.rb +18 -0
  37. data/lib/eventq/eventq_base/serialization_providers/jruby/oj/rational_writer.rb +20 -0
  38. data/lib/eventq/eventq_base/serialization_providers/jruby/oj/serializer.rb +17 -0
  39. data/lib/eventq/eventq_base/serialization_providers/jruby/oj/time_writer.rb +18 -0
  40. data/lib/eventq/eventq_base/serialization_providers/jruby/oj/value_writer.rb +16 -0
  41. data/lib/eventq/eventq_base/serialization_providers/jruby/oj.rb +10 -0
  42. data/lib/eventq/eventq_base/serialization_providers/jruby/oj_serialization_provider.rb +25 -0
  43. data/lib/eventq/eventq_base/serialization_providers/jruby.rb +2 -0
  44. data/lib/eventq/eventq_base/serialization_providers/json_serialization_provider.rb +28 -0
  45. data/lib/eventq/eventq_base/serialization_providers/oj_serialization_provider.rb +24 -0
  46. data/lib/eventq/eventq_base/serialization_providers.rb +36 -0
  47. data/lib/eventq/eventq_base/signature_providers/sha256_signature_provider.rb +31 -0
  48. data/lib/eventq/eventq_base/signature_providers.rb +44 -0
  49. data/lib/eventq/eventq_base/subscription_manager_contract.rb +13 -0
  50. data/lib/eventq/eventq_base/version.rb +3 -0
  51. data/lib/eventq/eventq_base/worker_id.rb +20 -0
  52. data/lib/eventq/eventq_rabbitmq/README.md +36 -0
  53. data/lib/eventq/eventq_rabbitmq/default_queue.rb +12 -0
  54. data/lib/eventq/eventq_rabbitmq/jruby/rabbitmq_queue_worker.rb +367 -0
  55. data/lib/eventq/eventq_rabbitmq/rabbitmq_eventq_client.rb +140 -0
  56. data/lib/eventq/eventq_rabbitmq/rabbitmq_queue_client.rb +54 -0
  57. data/lib/eventq/eventq_rabbitmq/rabbitmq_queue_manager.rb +104 -0
  58. data/lib/eventq/eventq_rabbitmq/rabbitmq_queue_worker.rb +168 -0
  59. data/lib/eventq/eventq_rabbitmq/rabbitmq_status_checker.rb +62 -0
  60. data/lib/eventq/eventq_rabbitmq/rabbitmq_subscription_manager.rb +54 -0
  61. data/lib/eventq/queue_worker.rb +241 -0
  62. data/lib/eventq/rabbitmq.rb +49 -0
  63. data/lib/eventq/worker_status.rb +64 -0
  64. data/lib/eventq.rb +25 -0
  65. metadata +289 -0
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent'
4
+
5
+ module EventQ
6
+ module Amazon
7
+ # Helper SNS class to handle the API calls
8
+ class SNS
9
+ @@topic_arns = Concurrent::Hash.new
10
+
11
+ attr_reader :sns
12
+
13
+ def initialize(client)
14
+ @sns = client
15
+ end
16
+
17
+ # Create a TopicArn. if one already exists, it will return a pre-existing ARN from the cache.
18
+ # Even in the event of multiple threads trying to create one with AWS, AWS is idempotent and won't create
19
+ # duplicates
20
+ def create_topic_arn(event_type)
21
+ _event_type = EventQ.create_event_type(event_type)
22
+
23
+ arn = get_topic_arn(event_type)
24
+ unless arn
25
+ response = sns.create_topic(name: aws_safe_name(_event_type))
26
+ arn = response.topic_arn
27
+ @@topic_arns[_event_type] = arn
28
+ end
29
+
30
+ arn
31
+ end
32
+
33
+ # Check if a TopicArn exists. This will check with AWS if necessary and cache the results if one is found
34
+ # @return TopicArn [String]
35
+ def get_topic_arn(event_type)
36
+ _event_type = EventQ.create_event_type(event_type)
37
+
38
+ arn = @@topic_arns[_event_type]
39
+ unless arn
40
+ response = sns.list_topics
41
+ arn = response.topics.detect { |topic| topic.topic_arn.end_with?(_event_type) }&.topic_arn
42
+
43
+ @@topic_arns[_event_type] = arn if arn
44
+ end
45
+
46
+ arn
47
+ end
48
+
49
+ def drop_topic(event_type)
50
+ topic_arn = get_topic_arn(event_type)
51
+ sns.delete_topic(topic_arn: topic_arn)
52
+
53
+ _event_type = EventQ.create_event_type(event_type)
54
+ @@topic_arns.delete(_event_type)
55
+
56
+ true
57
+ end
58
+
59
+ def aws_safe_name(name)
60
+ return name[0..79].gsub(/[^a-zA-Z\d_\-]/,'')
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EventQ
4
+ module Amazon
5
+ # Helper SQS class to handle the API calls
6
+ class SQS
7
+ @@queue_arns = Concurrent::Hash.new
8
+ @@queue_urls = Concurrent::Hash.new
9
+
10
+ attr_reader :sqs
11
+
12
+ def initialize(client)
13
+ @sqs = client
14
+ end
15
+
16
+ # Create a new queue.
17
+ def create_queue(queue, attributes = {})
18
+ _queue_name = EventQ.create_queue_name(queue.name)
19
+
20
+ url = get_queue_url(queue)
21
+ unless url
22
+ response = sqs.create_queue(
23
+ {
24
+ queue_name: aws_safe_name(_queue_name),
25
+ attributes: attributes
26
+ }
27
+ )
28
+ url = response.queue_url
29
+ @@queue_urls[_queue_name] = url
30
+ end
31
+
32
+ url
33
+ end
34
+
35
+ # Update a queue
36
+ def update_queue(queue, attributes = {})
37
+ url = get_queue_url(queue)
38
+ sqs.set_queue_attributes(
39
+ {
40
+ queue_url: url, # required
41
+ attributes: attributes
42
+ }
43
+ )
44
+
45
+ url
46
+ end
47
+
48
+ # Returns the ARN of a queue. If none exists, nil will be returned.
49
+ #
50
+ # @param queue [EventQ::Queue]
51
+ # @return ARN [String]
52
+ def get_queue_arn(queue)
53
+ _queue_name = EventQ.create_queue_name(queue.name)
54
+
55
+ arn = @@queue_arns[_queue_name]
56
+ unless arn
57
+ url = get_queue_url(queue)
58
+ if url
59
+ response = sqs.get_queue_attributes(
60
+ {
61
+ queue_url: url,
62
+ attribute_names: ['QueueArn']
63
+ }
64
+ )
65
+ arn = response.attributes['QueueArn']
66
+ end
67
+ end
68
+
69
+ arn
70
+ end
71
+
72
+ # Returns the URL of the queue. If none exists, nil will be returned.
73
+ #
74
+ # @param queue [EventQ::Queue]
75
+ # @return URL [String]
76
+ def get_queue_url(queue)
77
+ _queue_name = EventQ.create_queue_name(queue.name)
78
+
79
+ url = @@queue_urls[_queue_name]
80
+ unless url
81
+ begin
82
+ response= sqs.get_queue_url(
83
+ queue_name: aws_safe_name(_queue_name)
84
+ )
85
+ url = response.queue_url
86
+ rescue Aws::SQS::Errors::NonExistentQueue
87
+ # Only want to return nil for this method when not found.
88
+ end
89
+
90
+ @@queue_urls[_queue_name] = url if url
91
+ end
92
+
93
+ url
94
+ end
95
+
96
+ def drop_queue(queue)
97
+ q = get_queue_url(queue)
98
+ sqs.delete_queue(queue_url: q)
99
+
100
+ _queue_name = EventQ.create_queue_name(queue.name)
101
+ @@queue_urls.delete(_queue_name)
102
+ @@queue_arns.delete(_queue_name)
103
+
104
+ true
105
+ end
106
+
107
+ def aws_safe_name(name)
108
+ return name[0..79].gsub(/[^a-zA-Z\d_\-]/,'')
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,33 @@
1
+ module EventQ
2
+ class Configuration
3
+
4
+ def self.serialization_provider=(value)
5
+ @serialization_provider = value
6
+ end
7
+
8
+ def self.serialization_provider
9
+ if RUBY_PLATFORM =~ /java/
10
+ @serialization_provider ||= EventQ::SerializationProviders::JSON_PROVIDER
11
+ else
12
+ @serialization_provider ||= EventQ::SerializationProviders::OJ_PROVIDER
13
+ end
14
+ end
15
+
16
+ def self.signature_provider=(value)
17
+ @signature_provider = value
18
+ end
19
+
20
+ def self.signature_provider
21
+ @signature_provider ||= EventQ::SignatureProviders::SHA256
22
+ end
23
+
24
+ def self.signature_secret=(value)
25
+ @signature_secret = value
26
+ end
27
+
28
+ def self.signature_secret
29
+ @signature_secret
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,7 @@
1
+ module EventQ
2
+ class EventRaisedExchange < Exchange
3
+ def initialize
4
+ @name = 'eventq.eventraised.ex'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module EventQ
2
+ class EventRaisedQueue < Queue
3
+ def initialize
4
+ @name = 'eventq.eventraised'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ module EventQ
2
+ class EventQClientContract
3
+
4
+ def raise_event(event_type, event)
5
+
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,28 @@
1
+ require 'logger'
2
+
3
+ module EventQ
4
+
5
+ def self.logger
6
+ return @@logger
7
+ end
8
+
9
+ def self.set_logger(logger)
10
+ @@logger = logger
11
+ end
12
+
13
+ def self.log(type, message)
14
+ case type
15
+ when :info
16
+ logger.info(message)
17
+ when :debug
18
+ logger.debug(message)
19
+ when :error
20
+ logger.error(message)
21
+ end
22
+ rescue
23
+ #do nothing
24
+ end
25
+
26
+ EventQ.set_logger(Logger.new(STDOUT))
27
+
28
+ end
@@ -0,0 +1,9 @@
1
+ module EventQ
2
+ module Exceptions
3
+ class InvalidSignatureException < StandardError
4
+ def initialize(message = "Invalid message signature.")
5
+ super(message)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ module EventQ
2
+ module Exceptions
3
+ # General thread error that signifies a thread about to shutdown.
4
+ class WorkerThreadError < StandardError
5
+ def initialize(message = 'Worker thread error')
6
+ super(message)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,2 @@
1
+ require_relative 'exceptions/invalid_signature_exception'
2
+ require_relative 'exceptions/worker_thread_error'
@@ -0,0 +1,5 @@
1
+ module EventQ
2
+ class Exchange
3
+ attr_accessor :name
4
+ end
5
+ end
@@ -0,0 +1,23 @@
1
+ module EventQ
2
+ class MessageArgs
3
+ attr_reader :type
4
+ attr_reader :content_type
5
+ attr_reader :retry_attempts
6
+ attr_accessor :abort
7
+ attr_accessor :drop
8
+ attr_reader :context
9
+ attr_reader :id
10
+ attr_reader :sent
11
+
12
+ def initialize(type:, retry_attempts:, context: {}, content_type:, id: nil, sent: nil)
13
+ @type = type
14
+ @retry_attempts = retry_attempts
15
+ @abort = false
16
+ @drop = false
17
+ @context = context
18
+ @content_type = content_type
19
+ @id = id
20
+ @sent = sent
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,57 @@
1
+ module EventQ
2
+ class NonceManager
3
+
4
+ def self.configure(server:,timeout:10000,lifespan:3600000)
5
+ @server_url = server
6
+ @timeout = timeout
7
+ @lifespan = lifespan
8
+ end
9
+
10
+ def self.server_url
11
+ @server_url
12
+ end
13
+
14
+ def self.timeout
15
+ @timeout
16
+ end
17
+
18
+ def self.lifespan
19
+ @lifespan
20
+ end
21
+
22
+ def self.is_allowed?(nonce)
23
+ if @server_url == nil
24
+ return true
25
+ end
26
+
27
+ require 'redlock'
28
+ lock = Redlock::Client.new([ @server_url ]).lock(nonce, @timeout)
29
+ if lock == false
30
+ EventQ.log(:info, "[#{self.class}] - Message has already been processed: #{nonce}")
31
+ return false
32
+ end
33
+
34
+ return true
35
+ end
36
+
37
+ def self.complete(nonce)
38
+ if @server_url != nil
39
+ Redis.new(url: @server_url).expire(nonce, @lifespan)
40
+ end
41
+ return true
42
+ end
43
+
44
+ def self.failed(nonce)
45
+ if @server_url != nil
46
+ Redis.new(url: @server_url).del(nonce)
47
+ end
48
+ return true
49
+ end
50
+
51
+ def self.reset
52
+ @server_url = nil
53
+ @timeout = nil
54
+ @lifespan = nil
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,27 @@
1
+ module EventQ
2
+ class Queue
3
+ attr_accessor :allow_retry
4
+ attr_accessor :allow_retry_back_off
5
+ attr_accessor :dlq
6
+ attr_accessor :max_retry_attempts
7
+ attr_accessor :max_retry_delay
8
+ attr_accessor :name
9
+ attr_accessor :max_receive_count
10
+ attr_accessor :require_signature
11
+ attr_accessor :retry_delay
12
+
13
+ def initialize
14
+ @allow_retry = false
15
+ # Default retry back off settings
16
+ @allow_retry_back_off = false
17
+ # Default max receive count is 30
18
+ @max_receive_count = 30
19
+ # Default max retry attempts is 5
20
+ @max_retry_attempts = 5
21
+ # Default require signature to false
22
+ @require_signature = false
23
+ # Default retry delay is 30 seconds
24
+ @retry_delay = 30000
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,31 @@
1
+ module EventQ
2
+ class QueueMessage
3
+ extend ClassKit
4
+
5
+ attr_accessor_type :id, type: String
6
+ attr_accessor_type :retry_attempts, type: Integer
7
+ attr_accessor_type :type, type: String
8
+ attr_accessor_type :content
9
+ attr_accessor_type :content_type, type: String
10
+ attr_accessor_type :created, type: Float
11
+ attr_accessor_type :signature, type: String
12
+ attr_accessor_type :context, type: Hash
13
+
14
+ def initialize
15
+ @retry_attempts = 0
16
+ @created = Time.now.to_f
17
+ @id = SecureRandom.uuid
18
+ @context = {}
19
+ end
20
+
21
+ # Creates a signature for the message
22
+ #
23
+ # @param provider [EventQ::SignatureProviders::Sha256SignatureProvider] Signature provider that implements
24
+ # a write method
25
+ def sign(provider)
26
+ return unless EventQ::Configuration.signature_secret
27
+
28
+ self.signature = provider.write(message: self, secret: EventQ::Configuration.signature_secret)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,23 @@
1
+ module EventQ
2
+ # Contract class for queue workers
3
+ class QueueWorkerContract
4
+
5
+ def start(queue, options = {}, &block)
6
+
7
+ end
8
+
9
+ def stop
10
+
11
+ end
12
+
13
+ def on_retry_exceeded(&block)
14
+
15
+ end
16
+
17
+ def running?
18
+
19
+ end
20
+
21
+ end
22
+ end
23
+
@@ -0,0 +1,15 @@
1
+ module EventQ
2
+ module SerializationProviders
3
+ class BinarySerializationProvider
4
+
5
+ def serialize(object)
6
+ Marshal::dump(object)
7
+ end
8
+
9
+ def deserialize(msg)
10
+ Marshal::load(msg)
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,20 @@
1
+ module EventQ
2
+ module SerializationProviders
3
+ module JRuby
4
+ module Oj
5
+ class ArrayWriter < AttributeWriter
6
+ def valid?(obj)
7
+ obj.is_a?(Array)
8
+ end
9
+ def exec(obj)
10
+ array = []
11
+ obj.each do |a|
12
+ array << AttributeWriter.exec(a)
13
+ end
14
+ array
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,24 @@
1
+ module EventQ
2
+ module SerializationProviders
3
+ module JRuby
4
+ module Oj
5
+ class AttributeWriter
6
+
7
+ def self.exec(obj)
8
+ aw = descendants.detect { |a| a.new.valid?(obj) } || ClassWriter
9
+ aw.new.exec(obj)
10
+ end
11
+
12
+ def self.descendants
13
+ descendants = []
14
+ ObjectSpace.each_object(singleton_class) do |k|
15
+ next if k.singleton_class?
16
+ descendants.unshift k unless k == self
17
+ end
18
+ descendants
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,20 @@
1
+ module EventQ
2
+ module SerializationProviders
3
+ module JRuby
4
+ module Oj
5
+ class ClassWriter < AttributeWriter
6
+ def valid?(obj)
7
+ false
8
+ end
9
+ def exec(obj)
10
+ hash = { '^o': obj.class }
11
+ obj.instance_variables.each do |key|
12
+ hash[key[1..-1]] = AttributeWriter.exec(obj.instance_variable_get(key))
13
+ end
14
+ hash
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,33 @@
1
+ module EventQ
2
+ module SerializationProviders
3
+ module JRuby
4
+ module Oj
5
+ class DateTimeWriter < AttributeWriter
6
+ def valid?(obj)
7
+ obj.is_a?(DateTime)
8
+ end
9
+ def exec(obj)
10
+ seconds = obj.strftime('%S%N')
11
+ d = 1_000_000_000
12
+ if seconds.start_with?('0')
13
+ seconds[0] = ''
14
+ d = 100_000_000
15
+ end
16
+
17
+ {
18
+ '^O': 'DateTime',
19
+ year: obj.year,
20
+ month: obj.month,
21
+ day: obj.day,
22
+ hour: obj.hour,
23
+ min: obj.min,
24
+ sec: RationalWriter.new.exec(Rational(Integer(seconds), d)),
25
+ offset: RationalWriter.new.exec(obj.offset),
26
+ start: obj.start
27
+ }
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,22 @@
1
+ module EventQ
2
+ module SerializationProviders
3
+ module JRuby
4
+ module Oj
5
+ class DateWriter < AttributeWriter
6
+ def valid?(obj)
7
+ obj.is_a?(Date) && !obj.is_a?(DateTime)
8
+ end
9
+ def exec(obj)
10
+ {
11
+ '^O': 'Date',
12
+ year: obj.year,
13
+ month: obj.month,
14
+ day: obj.day,
15
+ start: obj.start
16
+ }
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,18 @@
1
+ module EventQ
2
+ module SerializationProviders
3
+ module JRuby
4
+ module Oj
5
+ class HashWriter < AttributeWriter
6
+ def valid?(obj)
7
+ obj.is_a?(Hash)
8
+ end
9
+ def exec(obj)
10
+ obj.each do |key, value|
11
+ obj[key] = AttributeWriter.exec(value)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,20 @@
1
+ module EventQ
2
+ module SerializationProviders
3
+ module JRuby
4
+ module Oj
5
+ class RationalWriter < AttributeWriter
6
+ def valid?(obj)
7
+ obj.is_a?(Rational)
8
+ end
9
+ def exec(obj)
10
+ {
11
+ '^O': 'Rational',
12
+ numerator: obj.numerator,
13
+ denominator: obj.denominator
14
+ }
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,17 @@
1
+ module EventQ
2
+ module SerializationProviders
3
+ module JRuby
4
+ module Oj
5
+ class Serializer
6
+ def dump(obj)
7
+ JSON.dump(AttributeWriter.exec(obj))
8
+ end
9
+
10
+ def load(json)
11
+ raise NotImplementedError.new("[#{self.class}] - #load method has not yet been implemented.")
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,18 @@
1
+ module EventQ
2
+ module SerializationProviders
3
+ module JRuby
4
+ module Oj
5
+ class TimeWriter < AttributeWriter
6
+ def valid?(obj)
7
+ obj.is_a?(Time)
8
+ end
9
+ def exec(obj)
10
+ {
11
+ '^t': obj.to_f
12
+ }
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end