ruby_event_store 1.2.2 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +3 -1
- data/lib/ruby_event_store.rb +3 -7
- data/lib/ruby_event_store/broker.rb +3 -3
- data/lib/ruby_event_store/client.rb +47 -23
- data/lib/ruby_event_store/composed_dispatcher.rb +2 -2
- data/lib/ruby_event_store/constants.rb +1 -0
- data/lib/ruby_event_store/errors.rb +0 -1
- data/lib/ruby_event_store/event.rb +8 -1
- data/lib/ruby_event_store/immediate_async_dispatcher.rb +2 -2
- data/lib/ruby_event_store/in_memory_repository.rb +98 -59
- data/lib/ruby_event_store/instrumented_dispatcher.rb +2 -2
- data/lib/ruby_event_store/instrumented_repository.rb +3 -3
- data/lib/ruby_event_store/mappers/default.rb +3 -8
- data/lib/ruby_event_store/mappers/encryption_mapper.rb +3 -4
- data/lib/ruby_event_store/mappers/instrumented_mapper.rb +4 -4
- data/lib/ruby_event_store/mappers/json_mapper.rb +7 -7
- data/lib/ruby_event_store/mappers/pipeline.rb +2 -5
- data/lib/ruby_event_store/mappers/pipeline_mapper.rb +2 -2
- data/lib/ruby_event_store/mappers/transformation/domain_event.rb +16 -8
- data/lib/ruby_event_store/mappers/transformation/encryption.rb +20 -12
- data/lib/ruby_event_store/mappers/transformation/event_class_remapper.rb +11 -4
- data/lib/ruby_event_store/mappers/transformation/stringify_metadata_keys.rb +12 -7
- data/lib/ruby_event_store/mappers/transformation/symbolize_metadata_keys.rb +12 -7
- data/lib/ruby_event_store/mappers/transformation/upcast.rb +37 -0
- data/lib/ruby_event_store/null.rb +13 -0
- data/lib/ruby_event_store/projection.rb +2 -1
- data/lib/ruby_event_store/record.rb +68 -0
- data/lib/ruby_event_store/serialized_record.rb +23 -4
- data/lib/ruby_event_store/spec/broker_lint.rb +9 -9
- data/lib/ruby_event_store/spec/event_repository_lint.rb +267 -105
- data/lib/ruby_event_store/spec/mapper_lint.rb +6 -6
- data/lib/ruby_event_store/spec/subscriptions_lint.rb +25 -0
- data/lib/ruby_event_store/specification.rb +100 -7
- data/lib/ruby_event_store/specification_reader.rb +2 -2
- data/lib/ruby_event_store/specification_result.rb +86 -2
- data/lib/ruby_event_store/subscriptions.rb +23 -9
- data/lib/ruby_event_store/transform_keys.rb +5 -5
- data/lib/ruby_event_store/version.rb +1 -1
- metadata +15 -21
- data/CHANGELOG.md +0 -93
- data/Gemfile +0 -11
- data/Makefile +0 -22
- data/lib/ruby_event_store/mappers/protobuf.rb +0 -24
- data/lib/ruby_event_store/mappers/transformation/item.rb +0 -56
- data/lib/ruby_event_store/mappers/transformation/proto_event.rb +0 -17
- data/lib/ruby_event_store/mappers/transformation/protobuf_encoder.rb +0 -30
- data/lib/ruby_event_store/mappers/transformation/protobuf_nested_struct_metadata.rb +0 -30
- data/lib/ruby_event_store/mappers/transformation/serialization.rb +0 -34
- data/lib/ruby_event_store/mappers/transformation/serialized_record.rb +0 -27
- data/ruby_event_store.gemspec +0 -29
@@ -1,17 +1,17 @@
|
|
1
1
|
module RubyEventStore
|
2
|
-
RSpec.shared_examples :mapper do |mapper, domain_event|
|
3
|
-
specify "
|
4
|
-
record = mapper.
|
2
|
+
::RSpec.shared_examples :mapper do |mapper, domain_event|
|
3
|
+
specify "event_to_record returns instance of Record" do
|
4
|
+
record = mapper.event_to_record(domain_event)
|
5
5
|
|
6
|
-
expect(record).to be_kind_of(
|
6
|
+
expect(record).to be_kind_of(Record)
|
7
7
|
expect(record.event_id).to eq(domain_event.event_id)
|
8
8
|
expect(record.event_type).to eq(domain_event.event_type)
|
9
9
|
end
|
10
10
|
|
11
11
|
specify "serialize and deserialize gives equal event" do
|
12
|
-
record = mapper.
|
12
|
+
record = mapper.event_to_record(domain_event)
|
13
13
|
|
14
|
-
expect(mapper.
|
14
|
+
expect(mapper.record_to_event(record)).to eq(domain_event)
|
15
15
|
end
|
16
16
|
end
|
17
17
|
end
|
@@ -39,10 +39,16 @@ RSpec.shared_examples :subscriptions do |subscriptions_class|
|
|
39
39
|
subscriptions.add_thread_subscription(handler, [Test1DomainEvent, Test3DomainEvent])
|
40
40
|
subscriptions.add_thread_subscription(another_handler, [Test2DomainEvent])
|
41
41
|
subscriptions.add_thread_global_subscription(global_handler)
|
42
|
+
t = Thread.new do
|
43
|
+
subscriptions.add_thread_subscription(handler, [Test2DomainEvent])
|
44
|
+
subscriptions.add_thread_global_subscription(another_handler)
|
45
|
+
expect(subscriptions.all_for('Test2DomainEvent')).to eq([another_handler, handler])
|
46
|
+
end
|
42
47
|
|
43
48
|
expect(subscriptions.all_for('Test1DomainEvent')).to eq([global_handler, handler])
|
44
49
|
expect(subscriptions.all_for('Test2DomainEvent')).to eq([global_handler, another_handler])
|
45
50
|
expect(subscriptions.all_for('Test3DomainEvent')).to eq([global_handler, handler])
|
51
|
+
t.join
|
46
52
|
end
|
47
53
|
|
48
54
|
it 'returns lambda as an output of global subscribe methods' do
|
@@ -108,4 +114,23 @@ RSpec.shared_examples :subscriptions do |subscriptions_class|
|
|
108
114
|
|
109
115
|
expect(subscriptions.all_for('Test1DomainEvent')).to eq([handler, handler])
|
110
116
|
end
|
117
|
+
|
118
|
+
it 'subscribes by type of event which is a class' do
|
119
|
+
handler = TestHandler.new
|
120
|
+
subscriptions.add_subscription(handler, [Test1DomainEvent])
|
121
|
+
subscriptions.add_thread_subscription(handler, [Test1DomainEvent])
|
122
|
+
|
123
|
+
expect(subscriptions.all_for('Test1DomainEvent')).to eq([handler, handler])
|
124
|
+
expect(subscriptions.all_for(Test1DomainEvent)).to eq([handler, handler])
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'subscribes by type of event which is a class or string with custom type resolver' do
|
128
|
+
subscriptions = subscriptions_class.new(event_type_resolver: ->(type) { type.to_s.reverse })
|
129
|
+
handler = TestHandler.new
|
130
|
+
subscriptions.add_subscription(handler, [Test1DomainEvent])
|
131
|
+
subscriptions.add_thread_subscription(handler, ['Test1DomainEvent'])
|
132
|
+
|
133
|
+
expect(subscriptions.all_for('Test1DomainEvent')).to eq([handler, handler])
|
134
|
+
expect(subscriptions.all_for(Test1DomainEvent)).to eq([handler, handler])
|
135
|
+
end
|
111
136
|
end
|
@@ -35,7 +35,7 @@ module RubyEventStore
|
|
35
35
|
# Limits the query to events before or after another event.
|
36
36
|
# {http://railseventstore.org/docs/read/ Find out more}.
|
37
37
|
#
|
38
|
-
# @param
|
38
|
+
# @param stop [String] id of event to start reading from.
|
39
39
|
# @return [Specification]
|
40
40
|
def to(stop)
|
41
41
|
raise InvalidPageStop if stop.nil? || stop.empty?
|
@@ -43,6 +43,99 @@ module RubyEventStore
|
|
43
43
|
Specification.new(reader, result.dup { |r| r.stop = stop })
|
44
44
|
end
|
45
45
|
|
46
|
+
# Limits the query to events that later than given time.
|
47
|
+
# {http://railseventstore.org/docs/read/ Find out more}.
|
48
|
+
#
|
49
|
+
# @param time [Time]
|
50
|
+
# @return [Specification]
|
51
|
+
def older_than(time)
|
52
|
+
raise ArgumentError unless time.respond_to?(:to_time)
|
53
|
+
Specification.new(
|
54
|
+
reader,
|
55
|
+
result.dup do |r|
|
56
|
+
r.older_than = time
|
57
|
+
r.older_than_or_equal = nil
|
58
|
+
end
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Limits the query to events that occurred on given time or later.
|
63
|
+
# {http://railseventstore.org/docs/read/ Find out more}.
|
64
|
+
#
|
65
|
+
# @param time [Time]
|
66
|
+
# @return [Specification]
|
67
|
+
def older_than_or_equal(time)
|
68
|
+
raise ArgumentError unless time.respond_to?(:to_time)
|
69
|
+
Specification.new(
|
70
|
+
reader,
|
71
|
+
result.dup do |r|
|
72
|
+
r.older_than = nil
|
73
|
+
r.older_than_or_equal = time
|
74
|
+
end
|
75
|
+
)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Limits the query to events that occurred earlier than given time.
|
79
|
+
# {http://railseventstore.org/docs/read/ Find out more}.
|
80
|
+
#
|
81
|
+
# @param time [Time]
|
82
|
+
# @return [Specification]
|
83
|
+
def newer_than(time)
|
84
|
+
raise ArgumentError unless time.respond_to?(:to_time)
|
85
|
+
Specification.new(
|
86
|
+
reader,
|
87
|
+
result.dup do |r|
|
88
|
+
r.newer_than_or_equal = nil
|
89
|
+
r.newer_than = time
|
90
|
+
end
|
91
|
+
)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Limits the query to events that occurred on given time or earlier.
|
95
|
+
# {http://railseventstore.org/docs/read/ Find out more}.
|
96
|
+
#
|
97
|
+
# @param time [Time]
|
98
|
+
# @return [Specification]
|
99
|
+
def newer_than_or_equal(time)
|
100
|
+
raise ArgumentError unless time.respond_to?(:to_time)
|
101
|
+
Specification.new(
|
102
|
+
reader,
|
103
|
+
result.dup do |r|
|
104
|
+
r.newer_than_or_equal = time
|
105
|
+
r.newer_than = nil
|
106
|
+
end
|
107
|
+
)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Limits the query to events within given time range.
|
111
|
+
# {http://railseventstore.org/docs/read/ Find out more}.
|
112
|
+
#
|
113
|
+
# @param time_range [Range]
|
114
|
+
# @return [Specification]
|
115
|
+
def between(time_range)
|
116
|
+
if time_range.exclude_end?
|
117
|
+
newer_than_or_equal(time_range.first).older_than(time_range.last)
|
118
|
+
else
|
119
|
+
newer_than_or_equal(time_range.first).older_than_or_equal(time_range.last)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Sets the order of time sorting using transaction time
|
124
|
+
# {http://railseventstore.org/docs/read/ Find out more}
|
125
|
+
#
|
126
|
+
# @return [Specification]
|
127
|
+
def as_at
|
128
|
+
Specification.new(reader, result.dup { |r| r.time_sort_by = :as_at})
|
129
|
+
end
|
130
|
+
|
131
|
+
# Sets the order of time sorting using validity time
|
132
|
+
# {http://railseventstore.org/docs/read/ Find out more}
|
133
|
+
#
|
134
|
+
# @return [Specification]
|
135
|
+
def as_of
|
136
|
+
Specification.new(reader, result.dup { |r| r.time_sort_by = :as_of })
|
137
|
+
end
|
138
|
+
|
46
139
|
# Sets the order of reading events to ascending (forward from the start).
|
47
140
|
# {http://railseventstore.org/docs/read/ Find out more}.
|
48
141
|
#
|
@@ -73,7 +166,7 @@ module RubyEventStore
|
|
73
166
|
# Yields each batch of records that was retrieved from the store.
|
74
167
|
# {http://railseventstore.org/docs/read/ Find out more}.
|
75
168
|
#
|
76
|
-
# @yield [Array<Event
|
169
|
+
# @yield [Array<Event>] batch of events
|
77
170
|
# @return [Enumerator, nil] Enumerator is returned when block not given
|
78
171
|
def each_batch
|
79
172
|
return to_enum(:each_batch) unless block_given?
|
@@ -87,7 +180,7 @@ module RubyEventStore
|
|
87
180
|
# Yields events read from the store if block given. Otherwise, returns enumerable collection.
|
88
181
|
# {http://railseventstore.org/docs/read/ Find out more}.
|
89
182
|
#
|
90
|
-
# @yield [Event
|
183
|
+
# @yield [Event] event
|
91
184
|
# @return [Enumerator, nil] Enumerator is returned when block not given
|
92
185
|
def each
|
93
186
|
return to_enum unless block_given?
|
@@ -111,7 +204,7 @@ module RubyEventStore
|
|
111
204
|
# built up to this point result using provided block.
|
112
205
|
# {http://railseventstore.org/docs/read/ Find out more}.
|
113
206
|
#
|
114
|
-
# @accumulator starting state for reduce operation
|
207
|
+
# @param accumulator starting state for reduce operation
|
115
208
|
# @return reduce result as defined by block given
|
116
209
|
def reduce(accumulator = nil, &block)
|
117
210
|
raise ArgumentError.new("Block must be given") unless block_given?
|
@@ -130,7 +223,7 @@ module RubyEventStore
|
|
130
223
|
# Returns array of domain events.
|
131
224
|
# {http://railseventstore.org/docs/read/ Find out more}.
|
132
225
|
#
|
133
|
-
# @return [Array<Event
|
226
|
+
# @return [Array<Event>]
|
134
227
|
def to_a
|
135
228
|
each.to_a
|
136
229
|
end
|
@@ -200,7 +293,7 @@ module RubyEventStore
|
|
200
293
|
# Limits the query to certain events by given even ids.
|
201
294
|
# {http://railseventstore.org/docs/read/ Find out more}.
|
202
295
|
#
|
203
|
-
# @param
|
296
|
+
# @param event_ids [Array(String)] ids of event to look for.
|
204
297
|
# @return [Specification]
|
205
298
|
def with_id(event_ids)
|
206
299
|
Specification.new(reader, result.dup{ |r| r.with_ids = event_ids })
|
@@ -231,7 +324,7 @@ module RubyEventStore
|
|
231
324
|
# read from the store if block given. Otherwise, returns enumerable collection.
|
232
325
|
# {http://railseventstore.org/docs/read/ Find out more}.
|
233
326
|
#
|
234
|
-
# @yield [Event
|
327
|
+
# @yield [Event] event
|
235
328
|
# @return [Enumerator] Enumerator is returned when block not given
|
236
329
|
def events(event_ids)
|
237
330
|
with_id(event_ids).each
|
@@ -14,14 +14,14 @@ module RubyEventStore
|
|
14
14
|
# @private
|
15
15
|
def one(specification_result)
|
16
16
|
record = repository.read(specification_result)
|
17
|
-
mapper.
|
17
|
+
mapper.record_to_event(record) if record
|
18
18
|
end
|
19
19
|
|
20
20
|
# @api private
|
21
21
|
# @private
|
22
22
|
def each(specification_result)
|
23
23
|
repository.read(specification_result).each do |batch|
|
24
|
-
yield batch.map { |
|
24
|
+
yield batch.map { |record| mapper.record_to_event(record) }
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
@@ -5,14 +5,48 @@ module RubyEventStore
|
|
5
5
|
def initialize(direction: :forward,
|
6
6
|
start: nil,
|
7
7
|
stop: nil,
|
8
|
+
older_than: nil,
|
9
|
+
older_than_or_equal: nil,
|
10
|
+
newer_than: nil,
|
11
|
+
newer_than_or_equal: nil,
|
12
|
+
time_sort_by: nil,
|
8
13
|
count: nil,
|
9
14
|
stream: Stream.new(GLOBAL_STREAM),
|
10
15
|
read_as: :all,
|
11
16
|
batch_size: Specification::DEFAULT_BATCH_SIZE,
|
12
17
|
with_ids: nil,
|
13
18
|
with_types: nil)
|
14
|
-
@attributes = Struct.new(
|
15
|
-
|
19
|
+
@attributes = Struct.new(
|
20
|
+
:direction,
|
21
|
+
:start,
|
22
|
+
:stop,
|
23
|
+
:older_than,
|
24
|
+
:older_than_or_equal,
|
25
|
+
:newer_than,
|
26
|
+
:newer_than_or_equal,
|
27
|
+
:time_sort_by,
|
28
|
+
:count,
|
29
|
+
:stream,
|
30
|
+
:read_as,
|
31
|
+
:batch_size,
|
32
|
+
:with_ids,
|
33
|
+
:with_types
|
34
|
+
).new(
|
35
|
+
direction,
|
36
|
+
start,
|
37
|
+
stop,
|
38
|
+
older_than,
|
39
|
+
older_than_or_equal,
|
40
|
+
newer_than,
|
41
|
+
newer_than_or_equal,
|
42
|
+
time_sort_by,
|
43
|
+
count,
|
44
|
+
stream,
|
45
|
+
read_as,
|
46
|
+
batch_size,
|
47
|
+
with_ids,
|
48
|
+
with_types
|
49
|
+
)
|
16
50
|
freeze
|
17
51
|
end
|
18
52
|
|
@@ -56,6 +90,46 @@ module RubyEventStore
|
|
56
90
|
attributes.stop
|
57
91
|
end
|
58
92
|
|
93
|
+
# Ending time.
|
94
|
+
# {http://railseventstore.org/docs/read/ Find out more}.
|
95
|
+
#
|
96
|
+
# @return [Time]
|
97
|
+
def older_than
|
98
|
+
attributes.older_than
|
99
|
+
end
|
100
|
+
|
101
|
+
# Ending time.
|
102
|
+
# {http://railseventstore.org/docs/read/ Find out more}.
|
103
|
+
#
|
104
|
+
# @return [Time]
|
105
|
+
def older_than_or_equal
|
106
|
+
attributes.older_than_or_equal
|
107
|
+
end
|
108
|
+
|
109
|
+
# Starting time.
|
110
|
+
# {http://railseventstore.org/docs/read/ Find out more}.
|
111
|
+
#
|
112
|
+
# @return [Time]
|
113
|
+
def newer_than
|
114
|
+
attributes.newer_than
|
115
|
+
end
|
116
|
+
|
117
|
+
# Starting time.
|
118
|
+
# {http://railseventstore.org/docs/read/ Find out more}.
|
119
|
+
#
|
120
|
+
# @return [Time]
|
121
|
+
def newer_than_or_equal
|
122
|
+
attributes.newer_than_or_equal
|
123
|
+
end
|
124
|
+
|
125
|
+
# Time sorting strategy. Nil when not specified.
|
126
|
+
# {http://railseventstore.org/docs/read/ Find out more}.
|
127
|
+
#
|
128
|
+
# @return [Symbol]
|
129
|
+
def time_sort_by
|
130
|
+
attributes.time_sort_by
|
131
|
+
end
|
132
|
+
|
59
133
|
# Read direction. True is reading forward
|
60
134
|
# {http://railseventstore.org/docs/read/ Find out more}.
|
61
135
|
#
|
@@ -179,6 +253,11 @@ module RubyEventStore
|
|
179
253
|
# * direction
|
180
254
|
# * start
|
181
255
|
# * stop
|
256
|
+
# * older_than
|
257
|
+
# * older_than_or_equal
|
258
|
+
# * newer_than
|
259
|
+
# * newer_than_or_equal
|
260
|
+
# * time_sort_by
|
182
261
|
# * count
|
183
262
|
# * stream
|
184
263
|
# * read_as
|
@@ -193,6 +272,11 @@ module RubyEventStore
|
|
193
272
|
get_direction,
|
194
273
|
start,
|
195
274
|
stop,
|
275
|
+
older_than,
|
276
|
+
older_than_or_equal,
|
277
|
+
newer_than,
|
278
|
+
newer_than_or_equal,
|
279
|
+
time_sort_by,
|
196
280
|
limit,
|
197
281
|
stream,
|
198
282
|
attributes.read_as,
|
@@ -4,14 +4,15 @@ require 'concurrent'
|
|
4
4
|
|
5
5
|
module RubyEventStore
|
6
6
|
class Subscriptions
|
7
|
-
def initialize
|
7
|
+
def initialize(event_type_resolver: default_event_type_resolver)
|
8
|
+
@event_type_resolver = event_type_resolver
|
8
9
|
@local = LocalSubscriptions.new
|
9
10
|
@global = GlobalSubscriptions.new
|
10
|
-
@thread
|
11
|
+
@thread = ThreadSubscriptions.new
|
11
12
|
end
|
12
13
|
|
13
14
|
def add_subscription(subscriber, event_types)
|
14
|
-
local.add(subscriber, event_types)
|
15
|
+
local.add(subscriber, resolve_event_types(event_types))
|
15
16
|
end
|
16
17
|
|
17
18
|
def add_global_subscription(subscriber)
|
@@ -19,7 +20,7 @@ module RubyEventStore
|
|
19
20
|
end
|
20
21
|
|
21
22
|
def add_thread_subscription(subscriber, event_types)
|
22
|
-
thread.local.add(subscriber, event_types)
|
23
|
+
thread.local.add(subscriber, resolve_event_types(event_types))
|
23
24
|
end
|
24
25
|
|
25
26
|
def add_thread_global_subscription(subscriber)
|
@@ -27,12 +28,25 @@ module RubyEventStore
|
|
27
28
|
end
|
28
29
|
|
29
30
|
def all_for(event_type)
|
30
|
-
|
31
|
+
resolved_event_type = resolve_event_type(event_type)
|
32
|
+
[local, global, thread].map{|r| r.all_for(resolved_event_type)}.reduce(&:+)
|
31
33
|
end
|
32
34
|
|
33
35
|
private
|
34
36
|
attr_reader :local, :global, :thread
|
35
37
|
|
38
|
+
def default_event_type_resolver
|
39
|
+
->(value) { value.to_s }
|
40
|
+
end
|
41
|
+
|
42
|
+
def resolve_event_types(event_types)
|
43
|
+
event_types.map(&method(:resolve_event_type))
|
44
|
+
end
|
45
|
+
|
46
|
+
def resolve_event_type(type)
|
47
|
+
@event_type_resolver.call(type)
|
48
|
+
end
|
49
|
+
|
36
50
|
class ThreadSubscriptions
|
37
51
|
def initialize
|
38
52
|
@local = ThreadLocalSubscriptions.new
|
@@ -51,8 +65,8 @@ module RubyEventStore
|
|
51
65
|
end
|
52
66
|
|
53
67
|
def add(subscription, event_types)
|
54
|
-
event_types.each{ |type| @subscriptions[type
|
55
|
-
->() {event_types.each{ |type| @subscriptions.fetch(type
|
68
|
+
event_types.each{ |type| @subscriptions[type] << subscription }
|
69
|
+
->() {event_types.each{ |type| @subscriptions.fetch(type).delete(subscription) } }
|
56
70
|
end
|
57
71
|
|
58
72
|
def all_for(event_type)
|
@@ -83,8 +97,8 @@ module RubyEventStore
|
|
83
97
|
end
|
84
98
|
|
85
99
|
def add(subscription, event_types)
|
86
|
-
event_types.each{ |type| @subscriptions.value[type
|
87
|
-
->() {event_types.each{ |type| @subscriptions.value.fetch(type
|
100
|
+
event_types.each{ |type| @subscriptions.value[type] << subscription }
|
101
|
+
->() {event_types.each{ |type| @subscriptions.value.fetch(type).delete(subscription) } }
|
88
102
|
end
|
89
103
|
|
90
104
|
def all_for(event_type)
|
@@ -4,23 +4,23 @@ module RubyEventStore
|
|
4
4
|
class TransformKeys
|
5
5
|
class << self
|
6
6
|
def stringify(data)
|
7
|
-
|
7
|
+
deep_transform(data, &:to_s)
|
8
8
|
end
|
9
9
|
|
10
10
|
def symbolize(data)
|
11
|
-
|
11
|
+
deep_transform(data, &:to_sym)
|
12
12
|
end
|
13
13
|
|
14
14
|
private
|
15
15
|
|
16
|
-
def
|
16
|
+
def deep_transform(data, &block)
|
17
17
|
data.each_with_object({}) do |(k, v), h|
|
18
18
|
h[yield(k)] =
|
19
19
|
case v
|
20
20
|
when Hash
|
21
|
-
|
21
|
+
deep_transform(v, &block)
|
22
22
|
when Array
|
23
|
-
v.map{|i| Hash === i ?
|
23
|
+
v.map{|i| Hash === i ? deep_transform(i, &block) : i}
|
24
24
|
else
|
25
25
|
v
|
26
26
|
end
|