toystore 0.13.1 → 0.13.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/Changelog.md +5 -3
  2. data/Gemfile +3 -1
  3. data/README.md +29 -1
  4. data/gemfiles/rails_3_0.gemfile +2 -0
  5. data/gemfiles/rails_3_1.gemfile +2 -0
  6. data/lib/toy.rb +10 -13
  7. data/lib/toy/callbacks.rb +1 -1
  8. data/lib/toy/dirty_store.rb +2 -2
  9. data/lib/toy/identity_map.rb +2 -2
  10. data/lib/toy/instrumentation/active_support_notifications.rb +5 -0
  11. data/lib/toy/instrumentation/log_subscriber.rb +49 -0
  12. data/lib/toy/instrumentation/metriks.rb +5 -0
  13. data/lib/toy/instrumentation/metriks_subscriber.rb +16 -0
  14. data/lib/toy/instrumentation/statsd.rb +5 -0
  15. data/lib/toy/instrumentation/statsd_subscriber.rb +22 -0
  16. data/lib/toy/instrumentation/subscriber.rb +38 -0
  17. data/lib/toy/instrumenters/memory.rb +27 -0
  18. data/lib/toy/instrumenters/noop.rb +9 -0
  19. data/lib/toy/middleware/identity_map.rb +10 -21
  20. data/lib/toy/object.rb +0 -1
  21. data/lib/toy/persistence.rb +22 -4
  22. data/lib/toy/querying.rb +46 -18
  23. data/lib/toy/version.rb +1 -1
  24. data/perf/reads.rb +1 -4
  25. data/perf/writes.rb +0 -3
  26. data/spec/helper.rb +4 -12
  27. data/spec/support/fake_udp_socket.rb +27 -0
  28. data/spec/support/instrumenter_helpers.rb +14 -0
  29. data/spec/toy/instrumentation/log_subscriber_spec.rb +85 -0
  30. data/spec/toy/instrumentation/metriks_subscriber_spec.rb +37 -0
  31. data/spec/toy/instrumentation/statsd_subscriber_spec.rb +47 -0
  32. data/spec/toy/instrumenters/memory_spec.rb +26 -0
  33. data/spec/toy/instrumenters/noop_spec.rb +22 -0
  34. data/spec/toy/persistence_spec.rb +45 -3
  35. data/spec/toy/querying_spec.rb +121 -36
  36. data/spec/toy_spec.rb +14 -16
  37. metadata +27 -7
  38. data/lib/toy/logger.rb +0 -15
  39. data/spec/toy/logger_spec.rb +0 -21
@@ -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
- ## master
5
+ ## 0.13.2
6
6
 
7
- * No longer defaulting uuid to new instance, use :default instead. This allows for nil values for attributes of uuid type.
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.14.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 :rubygems
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 [![Build Status](https://secure.travis-ci.org/jnunemaker/toystore.png)](http://travis-ci.org/jnunemaker/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)
@@ -6,6 +6,8 @@ gem 'activemodel', '~> 3.0.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'
@@ -6,6 +6,8 @@ gem 'activemodel', '~> 3.1.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/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'
@@ -7,7 +7,7 @@ module Toy
7
7
  define_model_callbacks :save, :create, :update, :destroy
8
8
  end
9
9
 
10
- def save(*)
10
+ def save(options={})
11
11
  run_callbacks(:save) { super }
12
12
  end
13
13
 
@@ -22,11 +22,11 @@ module Toy
22
22
  end
23
23
  end
24
24
 
25
- def save(*)
25
+ def save(options={})
26
26
  super.tap do
27
27
  @previously_changed = changes
28
28
  @changed_attributes.clear if @changed_attributes
29
29
  end
30
30
  end
31
31
  end
32
- end
32
+ end
@@ -62,11 +62,11 @@ module Toy
62
62
  end
63
63
  end
64
64
 
65
- def save(*)
65
+ def save(options={})
66
66
  super.tap { |result| add_to_identity_map if result }
67
67
  end
68
68
 
69
- def delete(*)
69
+ def delete
70
70
  super.tap { remove_from_identity_map }
71
71
  end
72
72
 
@@ -0,0 +1,5 @@
1
+ require 'securerandom'
2
+ require 'active_support/notifications'
3
+
4
+ # Set the instrumenter to active support notifications.
5
+ Toy.instrumenter = ActiveSupport::Notifications
@@ -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,5 @@
1
+ require 'toy/instrumentation/active_support_notifications'
2
+ require 'toy/instrumentation/metriks_subscriber'
3
+
4
+ ActiveSupport::Notifications.subscribe /toystore/,
5
+ Toy::Instrumentation::MetriksSubscriber
@@ -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,5 @@
1
+ require 'toy/instrumentation/active_support_notifications'
2
+ require 'toy/instrumentation/statsd_subscriber'
3
+
4
+ ActiveSupport::Notifications.subscribe /toystore/,
5
+ Toy::Instrumentation::StatsdSubscriber
@@ -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
@@ -0,0 +1,9 @@
1
+ module Toy
2
+ module Instrumenters
3
+ class Noop
4
+ def self.instrument(name, payload = {})
5
+ yield payload if block_given?
6
+ end
7
+ end
8
+ end
9
+ 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
- status, headers, body = @app.call(env)
31
- [status, headers, Body.new(body, enabled)]
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
@@ -10,7 +10,6 @@ module Toy
10
10
  include Dirty
11
11
  include Equality
12
12
  include Inspect
13
- include Logger
14
13
  include Inheritance
15
14
  include Serialization
16
15
 
@@ -75,8 +75,18 @@ module Toy
75
75
  !new_record? && !destroyed?
76
76
  end
77
77
 
78
- def save(*)
79
- new_record? ? create : update
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
- delete
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