wisper-active_tracker 0.1.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7296ee2687e4995443c0faa8c5339a7d3d5e6086
4
+ data.tar.gz: ee65bb860af95cb1c49abc09e8b8507ad77d18f5
5
+ SHA512:
6
+ metadata.gz: f9758c4b02a34927db7a28f7fa8fb203fe7d20500391c4cd3f3408d1d6699aeb3ed8aa3fb37e90422fc642c16337b394a4c821194a029685c58e7340f449fe87
7
+ data.tar.gz: fbbde614ed2fb29e1f0d57f9688bf1956c31a5d6fa9580b409617918299d35865bb72741a7f654b7c358a34a0ca73ae73dccfe061cc67309681a505dc0ec7111
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ spec/db.sqlite3
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+ bundler_args: --without=metrics
3
+ script: bundle exec rspec spec
4
+ gemfile:
5
+ - gemfiles/activerecord-3.0
6
+ - gemfiles/activerecord-4.0
7
+ rvm:
8
+ - '1.9.3'
9
+ - '2.1.1'
10
+ - jruby
11
+ - rbx-2
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'rubysl', '~> 2.0', :platforms => :rbx
6
+
7
+ gem 'bundler', '~> 1.5'
8
+ gem 'rake'
9
+ gem 'rspec'
10
+ gem 'pry'
11
+
12
+ gem "sqlite3", :platform => :ruby
13
+ gem "activerecord-jdbcsqlite3-adapter", :platform => :jruby
14
+
15
+ group :metrics do
16
+ gem 'flay'
17
+ gem 'simplecov'
18
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Mikko Kokkonen
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,187 @@
1
+ # Wisper::ActiveChanges
2
+
3
+ Transparently publish model lifecycle events to subscribers.
4
+
5
+ Using Wisper events is a better alternative to ActiveRecord callbacks and Observers.
6
+
7
+ Listeners are subscribed to models at runtime.
8
+
9
+ ## Installation
10
+
11
+ ```ruby
12
+ gem 'wisper-activechanges'
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ### Setup a publisher
18
+
19
+ ```ruby
20
+ class Meeting < ActiveRecord::Base
21
+ include Wisper.model
22
+
23
+ # ...
24
+ end
25
+ ```
26
+
27
+ If you wish all models to broadcast events without having to explicitly include
28
+ `Wisper.model` add the following to an initializer:
29
+
30
+ ```ruby
31
+ Wisper::ActiveRecord.extend_all
32
+ ```
33
+
34
+ ### Subscribing
35
+
36
+ Subscribe a listener to model instances:
37
+
38
+ ```ruby
39
+ meeting = Meeting.new
40
+ meeting.subscribe(Auditor.new)
41
+ ```
42
+
43
+ Subscribe a block to model instances:
44
+
45
+ ```ruby
46
+ meeting.on(:create_meeting_successful) { |meeting_id, changes| ... }
47
+ ```
48
+
49
+ Subscribe a listener to _all_ instances of a model:
50
+
51
+ ```ruby
52
+ Meeting.subscribe(Auditor.new)
53
+ ```
54
+
55
+ Please refer to the [Wisper README](https://github.com/krisleech/wisper) for full details about subscribing.
56
+
57
+ The events which are automatically broadcast are:
58
+
59
+ * `after_create`
60
+ * `after_create`
61
+ * `after_destroy`
62
+ * `create_<model_name>_{successful, failed}`
63
+ * `update_<model_name>_{successful, failed}`
64
+ * `destroy_<model_name>_successful`
65
+ * `after_rollback`
66
+
67
+ ### Reacting to Events
68
+
69
+ To receive an event the listener must implement a method matching the name of
70
+ the event with a single argument, the instance of the model.
71
+
72
+ ```ruby
73
+ def create_meeting_successful(meeting_id, changes)
74
+ # ...
75
+ end
76
+ ```
77
+
78
+ ## Example
79
+
80
+ ### Controller
81
+
82
+ ```ruby
83
+ class MeetingsController < ApplicationController
84
+ def new
85
+ @meeting = Meeting.new
86
+ end
87
+
88
+ def create
89
+ @meeting = Meeting.new(params[:meeting])
90
+ @meeting.subscribe(Auditor.instance)
91
+ @meeting.on(:meeting_create_successful) { redirect_to meeting_path }
92
+ @meeting.on(:meeting_create_failed) { render action: :new }
93
+ @meeting.save
94
+ end
95
+
96
+ def edit
97
+ @meeting = Meeting.find(params[:id])
98
+ end
99
+
100
+ def update
101
+ @meeting = Meeting.find(params[:id])
102
+ @meeting.subscribe(Auditor.instance)
103
+ @meeting.on(:meeting_update_successful) { redirect_to meeting_path }
104
+ @meeting.on(:meeting_update_failed) { render :action => :edit }
105
+ @meeting.update_attributes(params[:meeting])
106
+ end
107
+ end
108
+ ```
109
+
110
+ Using `on` to subscribe a block to handle the response is optional,
111
+ you can still use `if @meeting.save` if you prefer.
112
+
113
+ ### Listener
114
+
115
+ **Which simply records an audit in memory**
116
+
117
+ ```ruby
118
+ class Auditor
119
+ include Singleton
120
+
121
+ attr_accessor :audit
122
+
123
+ def initialize
124
+ @audit = []
125
+ end
126
+
127
+ def after_create(subject_id, changes)
128
+ push_audit_for('create', subject)
129
+ end
130
+
131
+ def after_update(subject_id, changes)
132
+ push_audit_for('update', subject)
133
+ end
134
+
135
+ def after_destroy(subject_id, changes)
136
+ push_audit_for('destroy', subject)
137
+ end
138
+
139
+ def self.audit
140
+ instance.audit
141
+ end
142
+
143
+ private
144
+
145
+ def push_audit_for(action, subject)
146
+ audit.push(audit_for(action, subject))
147
+ end
148
+
149
+ def audit_for(action, subject)
150
+ {
151
+ action: action,
152
+ subject_id: subject.id,
153
+ subject_class: subject.class.to_s,
154
+ changes: subject.previous_changes,
155
+ created_at: Time.now
156
+ }
157
+ end
158
+ end
159
+ ```
160
+
161
+ **Do some CRUD**
162
+
163
+ ```ruby
164
+ Meeting.create(:description => 'Team Retrospective', :starts_at => Time.now + 2.days)
165
+
166
+ meeting = Meeting.find(1)
167
+ meeting.starts_at = Time.now + 2.months
168
+ meeting.save
169
+ ```
170
+
171
+ **And check the audit**
172
+
173
+ ```ruby
174
+ Auditor.audit # => [...]
175
+ ```
176
+
177
+ ## Compatibility
178
+
179
+ ## Contributing
180
+
181
+ Please submit a Pull Request with specs.
182
+
183
+ ### Running the specs
184
+
185
+ ```
186
+ bundle exec rspec
187
+ ```
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,12 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rubysl', '~> 2.0', :platforms => :rbx
4
+
5
+ gem 'rspec'
6
+
7
+ gem "sqlite3", :platform => :ruby
8
+ gem "activerecord-jdbcsqlite3-adapter", :platform => :jruby
9
+
10
+ gem 'activerecord', '~> 3.0'
11
+
12
+ gemspec :path => '../'
@@ -0,0 +1,12 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rubysl', '~> 2.0', :platforms => :rbx
4
+
5
+ gem 'rspec'
6
+
7
+ gem "sqlite3", :platform => :ruby
8
+ gem "activerecord-jdbcsqlite3-adapter", :platform => :jruby
9
+
10
+ gem 'activerecord', '~> 4.0'
11
+
12
+ gemspec :path => '../'
@@ -0,0 +1,73 @@
1
+ module Wisper
2
+ module ActiveTracker
3
+ module Tracker
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def has_tracker(options = {})
8
+ include Wisper::Publisher
9
+
10
+ after_validation :__track_after_validation_broadcast
11
+ after_commit :__track_after_create_broadcast, on: :create
12
+ after_commit :__track_after_update_broadcast, on: :update
13
+ after_commit :__track_after_destroy_broadcast, on: :destroy
14
+ after_rollback :__track_after_rollback_broadcast
15
+ before_destroy :__track_before_destroy_broadcast
16
+
17
+ class_attribute :__tracker_options
18
+ self.__tracker_options = options.dup
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def __track_after_validation_broadcast
25
+ action = new_record? ? 'create' : 'update'
26
+ broadcast("#{action}_#{self.class.model_name.param_key}_failed", args) unless errors.empty?
27
+ end
28
+
29
+ def __track_after_create_broadcast
30
+ broadcast(:after_create, args)
31
+ broadcast("create_#{self.class.model_name.param_key}_successful", args)
32
+ end
33
+
34
+ def __track_after_update_broadcast
35
+ broadcast(:after_update, args)
36
+ broadcast("update_#{self.class.model_name.param_key}_successful", args)
37
+ end
38
+
39
+ def __track_after_destroy_broadcast
40
+ broadcast(:after_destroy, args)
41
+ broadcast("destroy_#{self.class.model_name.param_key}_successful", args)
42
+ end
43
+
44
+ def __track_after_rollback_broadcast
45
+ broadcast(:after_rollback, args)
46
+ end
47
+
48
+ def args
49
+ args = {}
50
+
51
+ args[:tenant] = Apartment::Tenant.current if defined?(Apartment)
52
+
53
+ if self.respond_to?(:tracker_args)
54
+ args.merge(self.tracker_args)
55
+ else
56
+
57
+ if self.__tracker_options[:model]
58
+ args[:model] = self
59
+ else
60
+ args[:id] = self.id
61
+ end
62
+
63
+ unless self.__tracker_options[:skip_changes]
64
+ args[:changes] = self.changes.dup
65
+ end
66
+
67
+ end
68
+
69
+ args
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,5 @@
1
+ module Wisper
2
+ module ActiveTracker
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,14 @@
1
+ require "wisper/active_tracker/version"
2
+ require "wisper/active_tracker/tracker"
3
+
4
+ module Wisper
5
+ def self.tracker
6
+ ::Wisper::ActiveTracker::Tracker
7
+ end
8
+
9
+ module ActiveTracker
10
+ def self.extend_all
11
+ ::ActiveRecord::Base.class_eval { include Wisper.tracker }
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,48 @@
1
+ begin
2
+ require 'simplecov'
3
+ SimpleCov.start do
4
+ add_filter 'spec'
5
+ end
6
+ rescue LoadError
7
+ end
8
+
9
+ require 'wisper'
10
+ require 'active_record'
11
+
12
+ puts "Using ActiveRecord #{ActiveRecord::VERSION::STRING}"
13
+
14
+ adapter = RUBY_PLATFORM == "java" ? 'jdbcsqlite3' : 'sqlite3'
15
+
16
+ ActiveRecord::Base.establish_connection(:adapter => adapter,
17
+ :database => File.dirname(__FILE__) + "/db.sqlite3")
18
+
19
+ load File.dirname(__FILE__) + '/support/schema.rb'
20
+
21
+ require 'support/models'
22
+
23
+ require 'wisper/activechanges'
24
+
25
+ RSpec.configure do |config|
26
+ config.filter_run :focus
27
+ config.run_all_when_everything_filtered = true
28
+
29
+ if config.files_to_run.one?
30
+ config.full_backtrace = true
31
+ config.default_formatter = 'doc'
32
+ end
33
+
34
+ config.profile_examples = 10
35
+
36
+ config.order = :random
37
+
38
+ Kernel.srand config.seed
39
+
40
+ config.expect_with :rspec do |expectations|
41
+ expectations.syntax = :expect
42
+ end
43
+
44
+ config.mock_with :rspec do |mocks|
45
+ mocks.syntax = :expect
46
+ mocks.verify_partial_doubles = true
47
+ end
48
+ end
@@ -0,0 +1,3 @@
1
+ class Meeting < ActiveRecord::Base
2
+ validates :title, presence: true # database default to non-blank value
3
+ end
@@ -0,0 +1,9 @@
1
+ ActiveRecord::Schema.define do
2
+ self.verbose = false
3
+
4
+ create_table :meetings, :force => true do |t|
5
+ t.string :title, default: 'My Meeting'
6
+ t.string :location
7
+ t.timestamps
8
+ end
9
+ end
@@ -0,0 +1,85 @@
1
+ describe 'ActiveChanges' do
2
+ let(:listener) { double('Listener') }
3
+ let(:model_class) { Class.new(Meeting) { include Wisper.model } }
4
+
5
+ before { Wisper::GlobalListeners.clear }
6
+ before { allow(model_class).to receive(:name).and_return('Meeting') }
7
+
8
+ it '.model returns ActiveChanges module' do
9
+ expect(Wisper.model).to eq Wisper::ActiveChanges::Publisher
10
+ end
11
+
12
+ describe '.commit' do # DEPRECATED
13
+ describe 'when creating' do
14
+ context 'and model is valid' do
15
+ it 'publishes create_<model_name>_successful event to listener' do
16
+ expect(listener).to receive(:create_meeting_successful).with(instance_of(model_class))
17
+ model_class.subscribe(listener)
18
+ model_class.commit
19
+ end
20
+ end
21
+
22
+ context 'and model is not valid' do
23
+ it 'publishes create_<model_name>_failed event to listener' do
24
+ expect(listener).to receive(:create_meeting_failed).with(instance_of(model_class))
25
+ model_class.subscribe(listener)
26
+ model_class.commit(title: nil)
27
+ end
28
+ end
29
+ end
30
+
31
+ describe 'when updating' do
32
+ before { model_class.create! }
33
+
34
+ let(:model) { model_class.first }
35
+
36
+ context 'and model is valid' do
37
+ it 'publishes update_<model_name>_successful event to listener' do
38
+ expect(listener).to receive(:update_meeting_successful).with(instance_of(model_class))
39
+ model_class.subscribe(listener)
40
+ model.commit(title: 'foo')
41
+ end
42
+ end
43
+
44
+ context 'and model is not valid' do
45
+ it 'publishes update_<model_name>_failed event to listener' do
46
+ expect(listener).to receive(:update_meeting_failed).with(instance_of(model_class))
47
+ model_class.subscribe(listener)
48
+ model.commit(title: nil)
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ describe 'create' do
55
+ it 'publishes an after_create event to listener' do
56
+ expect(listener).to receive(:after_create).with(instance_of(model_class))
57
+ model_class.subscribe(listener)
58
+ model_class.create
59
+ end
60
+ end
61
+
62
+ describe 'update' do
63
+ before { model_class.create! }
64
+
65
+ let(:model) { model_class.first }
66
+
67
+ it 'publishes an after_update event to listener' do
68
+ expect(listener).to receive(:after_update).with(instance_of(model_class))
69
+ model.subscribe(listener)
70
+ model.update_attributes(title: 'new title')
71
+ end
72
+ end
73
+
74
+ describe 'destroy' do
75
+ before { model_class.create! }
76
+
77
+ let(:model) { model_class.first }
78
+
79
+ it 'publishes an on_destroy event to listener' do
80
+ expect(listener).to receive(:after_destroy).with(instance_of(model_class))
81
+ model_class.subscribe(listener)
82
+ model.destroy
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,10 @@
1
+ describe Wisper::ActiveRecord::Publisher do
2
+
3
+ it 'includes Wisper::Publisher' do
4
+ klass = Class.new(ActiveRecord::Base) do
5
+ include Wisper::ActiveRecord::Publisher
6
+ end
7
+
8
+ expect(klass.ancestors).to include Wisper::Publisher
9
+ end
10
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'wisper/active_tracker/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "wisper-active_tracker"
8
+ spec.version = Wisper::ActiveTracker::VERSION
9
+ spec.authors = ["Mikko Kokkonen"]
10
+ spec.email = ["mikko@mikian.com"]
11
+ spec.summary = %q{Subscribe to changes on ActiveRecord models}
12
+ spec.description = %q{Subscribe to changes on ActiveRecord models}
13
+ spec.homepage = "https://github.com/mikian/wisper-active_tracker"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+ spec.required_ruby_version = ">= 1.9.3"
21
+
22
+ spec.add_dependency "wisper", "~> 1.3"
23
+ spec.add_dependency "activerecord", ">= 3.0.0"
24
+ end
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wisper-active_tracker
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Mikko Kokkonen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-12-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: wisper
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activerecord
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 3.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 3.0.0
41
+ description: Subscribe to changes on ActiveRecord models
42
+ email:
43
+ - mikko@mikian.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - ".rspec"
50
+ - ".travis.yml"
51
+ - Gemfile
52
+ - LICENSE.txt
53
+ - README.md
54
+ - Rakefile
55
+ - gemfiles/activerecord-3.0
56
+ - gemfiles/activerecord-4.0
57
+ - lib/wisper/active_tracker.rb
58
+ - lib/wisper/active_tracker/tracker.rb
59
+ - lib/wisper/active_tracker/version.rb
60
+ - spec/spec_helper.rb
61
+ - spec/support/models.rb
62
+ - spec/support/schema.rb
63
+ - spec/wisper/activechanges_spec.rb
64
+ - spec/wisper/publisher_spec.rb
65
+ - wisper-active_tracker.gemspec
66
+ homepage: https://github.com/mikian/wisper-active_tracker
67
+ licenses:
68
+ - MIT
69
+ metadata: {}
70
+ post_install_message:
71
+ rdoc_options: []
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: 1.9.3
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ requirements: []
85
+ rubyforge_project:
86
+ rubygems_version: 2.2.2
87
+ signing_key:
88
+ specification_version: 4
89
+ summary: Subscribe to changes on ActiveRecord models
90
+ test_files:
91
+ - spec/spec_helper.rb
92
+ - spec/support/models.rb
93
+ - spec/support/schema.rb
94
+ - spec/wisper/activechanges_spec.rb
95
+ - spec/wisper/publisher_spec.rb
96
+ has_rdoc: