toystore 0.13.1 → 0.13.2
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.
- data/Changelog.md +5 -3
- data/Gemfile +3 -1
- data/README.md +29 -1
- data/gemfiles/rails_3_0.gemfile +2 -0
- data/gemfiles/rails_3_1.gemfile +2 -0
- data/lib/toy.rb +10 -13
- data/lib/toy/callbacks.rb +1 -1
- data/lib/toy/dirty_store.rb +2 -2
- data/lib/toy/identity_map.rb +2 -2
- data/lib/toy/instrumentation/active_support_notifications.rb +5 -0
- data/lib/toy/instrumentation/log_subscriber.rb +49 -0
- data/lib/toy/instrumentation/metriks.rb +5 -0
- data/lib/toy/instrumentation/metriks_subscriber.rb +16 -0
- data/lib/toy/instrumentation/statsd.rb +5 -0
- data/lib/toy/instrumentation/statsd_subscriber.rb +22 -0
- data/lib/toy/instrumentation/subscriber.rb +38 -0
- data/lib/toy/instrumenters/memory.rb +27 -0
- data/lib/toy/instrumenters/noop.rb +9 -0
- data/lib/toy/middleware/identity_map.rb +10 -21
- data/lib/toy/object.rb +0 -1
- data/lib/toy/persistence.rb +22 -4
- data/lib/toy/querying.rb +46 -18
- data/lib/toy/version.rb +1 -1
- data/perf/reads.rb +1 -4
- data/perf/writes.rb +0 -3
- data/spec/helper.rb +4 -12
- data/spec/support/fake_udp_socket.rb +27 -0
- data/spec/support/instrumenter_helpers.rb +14 -0
- data/spec/toy/instrumentation/log_subscriber_spec.rb +85 -0
- data/spec/toy/instrumentation/metriks_subscriber_spec.rb +37 -0
- data/spec/toy/instrumentation/statsd_subscriber_spec.rb +47 -0
- data/spec/toy/instrumenters/memory_spec.rb +26 -0
- data/spec/toy/instrumenters/noop_spec.rb +22 -0
- data/spec/toy/persistence_spec.rb +45 -3
- data/spec/toy/querying_spec.rb +121 -36
- data/spec/toy_spec.rb +14 -16
- metadata +27 -7
- data/lib/toy/logger.rb +0 -15
- data/spec/toy/logger_spec.rb +0 -21
data/Changelog.md
CHANGED
@@ -2,12 +2,14 @@
|
|
2
2
|
|
3
3
|
I will do my best to keep this up to date with significant changes here, starting in 0.8.3.
|
4
4
|
|
5
|
-
##
|
5
|
+
## 0.13.2
|
6
6
|
|
7
|
-
*
|
7
|
+
* Added instrumentation and log subscriber
|
8
|
+
* Removed Toy.logger, Toy.logger? and Toy.logger=. All logging is now through log_subscriber. See the README.
|
8
9
|
|
9
|
-
## 0.
|
10
|
+
## 0.13.1
|
10
11
|
|
12
|
+
* No longer defaulting uuid to new instance, use :default instead. This allows for nil values for attributes of uuid type.
|
11
13
|
* No longer persisting nil attributes
|
12
14
|
* Added Toy::Types::JSON for storing serialized JSON as an attribute value
|
13
15
|
* Added #persisted_id and made it public so people can override confidently. It is now used in adapter.write and adapter.delete.
|
data/Gemfile
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
source
|
1
|
+
source "https://rubygems.org"
|
2
2
|
gemspec
|
3
3
|
|
4
4
|
gem 'activesupport', '~> 3.2.0'
|
@@ -6,6 +6,8 @@ gem 'activemodel', '~> 3.2.0'
|
|
6
6
|
gem 'rake', '~> 0.9.0'
|
7
7
|
gem 'oj', '~> 1.0.0'
|
8
8
|
gem 'multi_json', '~> 1.3.2'
|
9
|
+
gem 'metriks', :require => false
|
10
|
+
gem 'statsd-ruby', :require => false
|
9
11
|
|
10
12
|
group(:guard) do
|
11
13
|
gem 'guard', '~> 1.0.0'
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Toystore
|
1
|
+
# Toystore
|
2
2
|
|
3
3
|
An object mapper for any [adapter](https://github.com/jnunemaker/adapter) that can read, write, delete, and clear data.
|
4
4
|
|
@@ -206,6 +206,34 @@ If that doesn't excite you, nothing will. At this point, you are probably wishin
|
|
206
206
|
|
207
207
|
Luckily, there is an entire directory full of [examples](https://github.com/jnunemaker/toystore/tree/master/examples) and I created a few power user guides, which I will kindly link next.
|
208
208
|
|
209
|
+
## Instrumentation
|
210
|
+
|
211
|
+
ToyStore comes with a log subscriber and automatic metriks instrumentation. By
|
212
|
+
default these work with ActiveSupport::Notifications, but only require the
|
213
|
+
pieces of ActiveSupport that are needed and only do so if you actually attempt
|
214
|
+
to require the instrumentation files listed below.
|
215
|
+
|
216
|
+
To use the log subscriber:
|
217
|
+
|
218
|
+
```ruby
|
219
|
+
# Gemfile
|
220
|
+
gem 'activesupport'
|
221
|
+
|
222
|
+
# config/initializers/toystore.rb (or wherever you want it)
|
223
|
+
require 'toy/instrumentation/log_subscriber'
|
224
|
+
```
|
225
|
+
|
226
|
+
To use the metriks instrumentation:
|
227
|
+
|
228
|
+
```ruby
|
229
|
+
# Gemfile
|
230
|
+
gem 'activesupport'
|
231
|
+
gem 'metriks'
|
232
|
+
|
233
|
+
# config/initializers/toystore.rb (or wherever you want it)
|
234
|
+
require 'toy/instrumentation/metriks'
|
235
|
+
```
|
236
|
+
|
209
237
|
## ToyStore Power User Guides
|
210
238
|
|
211
239
|
* [Wiki Home](https://github.com/jnunemaker/toystore/wiki)
|
data/gemfiles/rails_3_0.gemfile
CHANGED
data/gemfiles/rails_3_1.gemfile
CHANGED
data/lib/toy.rb
CHANGED
@@ -14,21 +14,11 @@ require 'active_support/core_ext'
|
|
14
14
|
extensions_path = root_path.join('lib', 'toy', 'extensions')
|
15
15
|
Dir[extensions_path + '**/*.rb'].each { |file| require(file) }
|
16
16
|
|
17
|
+
require 'toy/instrumenters/noop'
|
18
|
+
|
17
19
|
module Toy
|
18
20
|
extend self
|
19
21
|
|
20
|
-
def logger
|
21
|
-
@logger
|
22
|
-
end
|
23
|
-
|
24
|
-
def logger?
|
25
|
-
@logger.present?
|
26
|
-
end
|
27
|
-
|
28
|
-
def logger=(logger)
|
29
|
-
@logger = logger
|
30
|
-
end
|
31
|
-
|
32
22
|
def key_factory=(key_factory)
|
33
23
|
@key_factory = key_factory
|
34
24
|
end
|
@@ -37,6 +27,14 @@ module Toy
|
|
37
27
|
@key_factory ||= Toy::Identity::UUIDKeyFactory.new
|
38
28
|
end
|
39
29
|
|
30
|
+
def instrumenter
|
31
|
+
@instrumenter || Toy::Instrumenters::Noop
|
32
|
+
end
|
33
|
+
|
34
|
+
def instrumenter=(instrumenter)
|
35
|
+
@instrumenter = instrumenter
|
36
|
+
end
|
37
|
+
|
40
38
|
module Middleware
|
41
39
|
autoload 'IdentityMap', 'toy/middleware/identity_map'
|
42
40
|
end
|
@@ -52,7 +50,6 @@ module Toy
|
|
52
50
|
autoload 'Equality', 'toy/equality'
|
53
51
|
autoload 'Inspect', 'toy/inspect'
|
54
52
|
autoload 'Inheritance', 'toy/inheritance'
|
55
|
-
autoload 'Logger', 'toy/logger'
|
56
53
|
autoload 'MassAssignmentSecurity', 'toy/mass_assignment_security'
|
57
54
|
autoload 'Persistence', 'toy/persistence'
|
58
55
|
autoload 'Querying', 'toy/querying'
|
data/lib/toy/callbacks.rb
CHANGED
data/lib/toy/dirty_store.rb
CHANGED
data/lib/toy/identity_map.rb
CHANGED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'toy/instrumentation/active_support_notifications'
|
2
|
+
require 'active_support/log_subscriber'
|
3
|
+
|
4
|
+
module Toy
|
5
|
+
module Instrumentation
|
6
|
+
class LogSubscriber < ::ActiveSupport::LogSubscriber
|
7
|
+
def read(event)
|
8
|
+
return unless logger.debug?
|
9
|
+
log_event :read, event
|
10
|
+
end
|
11
|
+
|
12
|
+
def read_multiple(event)
|
13
|
+
return unless logger.debug?
|
14
|
+
log_event :read_multiple, event
|
15
|
+
end
|
16
|
+
|
17
|
+
def key(event)
|
18
|
+
return unless logger.debug?
|
19
|
+
log_event :key, event
|
20
|
+
end
|
21
|
+
|
22
|
+
def create(event)
|
23
|
+
return unless logger.debug?
|
24
|
+
log_event :create, event
|
25
|
+
end
|
26
|
+
|
27
|
+
def update(event)
|
28
|
+
return unless logger.debug?
|
29
|
+
log_event :update, event
|
30
|
+
end
|
31
|
+
|
32
|
+
def destroy(event)
|
33
|
+
return unless logger.debug?
|
34
|
+
log_event :destroy, event
|
35
|
+
end
|
36
|
+
|
37
|
+
def log_event(action, event)
|
38
|
+
id = event.payload[:id]
|
39
|
+
model = event.payload[:model]
|
40
|
+
|
41
|
+
name = '%s (%.1fms)' % ["#{model.name} #{action}", event.duration]
|
42
|
+
|
43
|
+
debug " #{color(name, CYAN, true)} [ #{id.inspect} ]"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
Toy::Instrumentation::LogSubscriber.attach_to :toystore
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Note: You should never need to require this file directly if you are using
|
2
|
+
# ActiveSupport::Notifications. Instead, you should require the metriks file
|
3
|
+
# that lives in the same directory as this file. The benefit is that it
|
4
|
+
# subscribes to the correct events and does everything for your.
|
5
|
+
require 'toy/instrumentation/subscriber'
|
6
|
+
require 'metriks'
|
7
|
+
|
8
|
+
module Toy
|
9
|
+
module Instrumentation
|
10
|
+
class MetriksSubscriber < Subscriber
|
11
|
+
def update_timer(metric)
|
12
|
+
Metriks.timer(metric).update(@duration)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Note: You should never need to require this file directly if you are using
|
2
|
+
# ActiveSupport::Notifications. Instead, you should require the metriks file
|
3
|
+
# that lives in the same directory as this file. The benefit is that it
|
4
|
+
# subscribes to the correct events and does everything for your.
|
5
|
+
require 'toy/instrumentation/subscriber'
|
6
|
+
require 'statsd'
|
7
|
+
|
8
|
+
module Toy
|
9
|
+
module Instrumentation
|
10
|
+
class StatsdSubscriber < Subscriber
|
11
|
+
class << self
|
12
|
+
attr_accessor :client
|
13
|
+
end
|
14
|
+
|
15
|
+
def update_timer(metric)
|
16
|
+
if self.class.client
|
17
|
+
self.class.client.timing metric, (@duration * 1_000).round
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'toy/instrumentation/subscriber'
|
2
|
+
|
3
|
+
module Toy
|
4
|
+
module Instrumentation
|
5
|
+
class Subscriber
|
6
|
+
# Public: Use this as the subscribed block.
|
7
|
+
def self.call(name, start, ending, transaction_id, payload)
|
8
|
+
new(name, start, ending, transaction_id, payload).update
|
9
|
+
end
|
10
|
+
|
11
|
+
# Private: Initializes a new event processing instance.
|
12
|
+
def initialize(name, start, ending, transaction_id, payload)
|
13
|
+
@name = name
|
14
|
+
@start = start
|
15
|
+
@ending = ending
|
16
|
+
@payload = payload
|
17
|
+
@duration = ending - start
|
18
|
+
@transaction_id = transaction_id
|
19
|
+
|
20
|
+
@action = @name.split('.').first
|
21
|
+
@model = @payload[:model]
|
22
|
+
end
|
23
|
+
|
24
|
+
# Public: Actually update all the timers for the event.
|
25
|
+
#
|
26
|
+
# Returns nothing.
|
27
|
+
def update
|
28
|
+
update_timer "toystore.#{@action}"
|
29
|
+
update_timer "toystore.#{@model}.#{@action}"
|
30
|
+
end
|
31
|
+
|
32
|
+
# Internal: Override in subclass.
|
33
|
+
def update_timer(metric)
|
34
|
+
raise 'not implemented'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Toy
|
2
|
+
module Instrumenters
|
3
|
+
# Instrumentor that is useful for tests as it stores each of the events that
|
4
|
+
# are instrumented.
|
5
|
+
class Memory
|
6
|
+
Event = Struct.new(:name, :payload, :result)
|
7
|
+
|
8
|
+
attr_reader :events
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@events = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def instrument(name, payload = {})
|
15
|
+
result = if block_given?
|
16
|
+
yield payload
|
17
|
+
else
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
|
21
|
+
@events << Event.new(name, payload, result)
|
22
|
+
|
23
|
+
result
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -1,24 +1,8 @@
|
|
1
|
+
require 'rack/body_proxy'
|
2
|
+
|
1
3
|
module Toy
|
2
4
|
module Middleware
|
3
5
|
class IdentityMap
|
4
|
-
class Body
|
5
|
-
def initialize(target, original)
|
6
|
-
@target = target
|
7
|
-
@original = original
|
8
|
-
end
|
9
|
-
|
10
|
-
def each(&block)
|
11
|
-
@target.each(&block)
|
12
|
-
end
|
13
|
-
|
14
|
-
def close
|
15
|
-
@target.close if @target.respond_to?(:close)
|
16
|
-
ensure
|
17
|
-
Toy::IdentityMap.enabled = @original
|
18
|
-
Toy::IdentityMap.clear
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
6
|
def initialize(app)
|
23
7
|
@app = app
|
24
8
|
end
|
@@ -27,9 +11,14 @@ module Toy
|
|
27
11
|
Toy::IdentityMap.clear
|
28
12
|
enabled = Toy::IdentityMap.enabled
|
29
13
|
Toy::IdentityMap.enabled = true
|
30
|
-
|
31
|
-
|
14
|
+
|
15
|
+
response = @app.call(env)
|
16
|
+
response[2] = Rack::BodyProxy.new(response[2]) {
|
17
|
+
Toy::IdentityMap.enabled = enabled
|
18
|
+
Toy::IdentityMap.clear
|
19
|
+
}
|
20
|
+
response
|
32
21
|
end
|
33
22
|
end
|
34
23
|
end
|
35
|
-
end
|
24
|
+
end
|
data/lib/toy/object.rb
CHANGED
data/lib/toy/persistence.rb
CHANGED
@@ -75,8 +75,18 @@ module Toy
|
|
75
75
|
!new_record? && !destroyed?
|
76
76
|
end
|
77
77
|
|
78
|
-
def save(
|
79
|
-
|
78
|
+
def save(options={})
|
79
|
+
default_payload = {
|
80
|
+
:id => persisted_id,
|
81
|
+
:model => self.class,
|
82
|
+
}
|
83
|
+
|
84
|
+
new_record = new_record?
|
85
|
+
action = new_record ? 'create' : 'update'
|
86
|
+
|
87
|
+
Toy.instrumenter.instrument("#{action}.toystore", default_payload) { |payload|
|
88
|
+
new_record ? create : update
|
89
|
+
}
|
80
90
|
end
|
81
91
|
|
82
92
|
def update_attributes(attrs)
|
@@ -85,7 +95,14 @@ module Toy
|
|
85
95
|
end
|
86
96
|
|
87
97
|
def destroy
|
88
|
-
|
98
|
+
default_payload = {
|
99
|
+
:id => persisted_id,
|
100
|
+
:model => self.class,
|
101
|
+
}
|
102
|
+
|
103
|
+
Toy.instrumenter.instrument('destroy.toystore', default_payload) { |payload|
|
104
|
+
delete
|
105
|
+
}
|
89
106
|
end
|
90
107
|
|
91
108
|
def delete
|
@@ -112,7 +129,8 @@ module Toy
|
|
112
129
|
attributes
|
113
130
|
end
|
114
131
|
|
115
|
-
# Public: Choke point for overriding how data gets written.
|
132
|
+
# Public: Choke point for overriding how data gets written. Don't call this
|
133
|
+
# directory, but you can safely override it.
|
116
134
|
def persist
|
117
135
|
adapter.write(persisted_id, persisted_attributes)
|
118
136
|
end
|