toystore 0.13.1 → 0.13.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|