wisper-active_tracker 0.1.0

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