wisper-activerecord 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 451585256ebdbf75f46b68cc6553221ccde75b4e
4
+ data.tar.gz: ed8138655b491c53530bd4ef515b649e2bbfd57f
5
+ SHA512:
6
+ metadata.gz: 2af4ab484e153bdf2f0095e2dc29e52f205f3eef135a9d5755eccdfa49851196e2b878c277308e7ded811af7c16d99b15ea7ae03c1f2e37a769e52ac70d08d5c
7
+ data.tar.gz: f80a718f09e0f5102fef9df9932f516b19d4669818bf80831a2e7c30126edca13fae893570f060bc6276c7ad7e8086787c3cad02d3e304f02560e2a742398c91
@@ -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
@@ -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
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Kris Leech
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.
@@ -0,0 +1,210 @@
1
+ # Wisper::ActiveRecord
2
+
3
+ [![Build Status](https://travis-ci.org/krisleech/wisper-activerecord.png?branch=master)](https://travis-ci.org/krisleech/wisper-activerecord)
4
+
5
+ Transparently publish model lifecycle events to subscribers.
6
+
7
+ Using Wisper events is a better alternative to ActiveRecord callbacks and Observers.
8
+
9
+ Listeners are subscribed to models at runtime.
10
+
11
+ ## Installation
12
+
13
+ ```ruby
14
+ gem 'wisper-activerecord'
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ### The Model
20
+
21
+ ```ruby
22
+ class Meeting < ActiveRecord::Base
23
+ include Wisper.model
24
+
25
+ # ...
26
+ end
27
+ ```
28
+
29
+ If you wish all models to broadcast events without having explicitly include
30
+ `Wisper.model` in each you can add the following to an initializer:
31
+
32
+ ```ruby
33
+ # config/initializers/wisper.rb
34
+ Wisper::ActiveRecord.extend_all
35
+ ```
36
+
37
+ #### Subscribing Listeners
38
+
39
+ Subscribe a listener to instances of models:
40
+
41
+ ```ruby
42
+ meeting = Meeting.new
43
+ meeting.subscribe(Auditor.new)
44
+ ```
45
+
46
+ Subscribe a listener to all instances of a model:
47
+
48
+ ```ruby
49
+ Meeting.subscribe(Auditor.new)
50
+ ```
51
+
52
+ Please refer to the [Wisper README](https://github.com/krisleech/wisper) for full details about subscribing.
53
+
54
+ The events broadcast are:
55
+
56
+ * `before_{create, update, destroy}`
57
+ * `after_{create, update, destroy}`
58
+
59
+ ### Subscribing blocks
60
+
61
+ ```ruby
62
+ meeting.on(:create_meeting_successful) { |meeting| ... }
63
+ ```
64
+
65
+ ### Reacting to events
66
+
67
+ To receive an event the listener must implement a method matching the name of
68
+ the event with a single argument, the instance of the model.
69
+
70
+ ```ruby
71
+ def create_meeting_successful(meeting)
72
+ # ...
73
+ end
74
+ ```
75
+
76
+ ### Success and Failure events for Create and Update
77
+
78
+ To have event broadcast for success and failure of create and
79
+ update you must use the `commit` method.
80
+
81
+ ```ruby
82
+ meeting.title = 'My Meeting'
83
+ meeting.commit
84
+ ```
85
+
86
+ You can also pass attributes directly to `commit`:
87
+
88
+ ```ruby
89
+ meeting.commit(title: 'My Meeting')
90
+ ```
91
+
92
+ And use the class method for creating new records:
93
+
94
+ ```ruby
95
+ Meeting.commit(title: 'My Meeting')
96
+ ```
97
+
98
+ In addition the the regular events broadcast the following events are also broadcast:
99
+
100
+ * `{create, update}_<model_name>_{successful, failed}`
101
+
102
+ ### Example controller
103
+
104
+ ```ruby
105
+ class MeetingsController < ApplicationController
106
+ def new
107
+ @meeting = Meeting.new
108
+ end
109
+
110
+ def create
111
+ @meeting = Meeting.new(params[:meeting])
112
+ @meeting.subscribe(Auditor.instance)
113
+ @meeting.on(:meeting_create_successful) { redirect_to meeting_path }
114
+ @meeting.on(:meeting_create_failed) { render action: :new }
115
+ @meeting.commit
116
+ end
117
+
118
+ def edit
119
+ @meeting = Meeting.find(params[:id])
120
+ end
121
+
122
+ def update
123
+ @meeting = Meeting.find(params[:id])
124
+ @meeting.subscribe(Auditor.instance)
125
+ @meeting.on(:meeting_update_successful) { redirect_to meeting_path }
126
+ @meeting.on(:meeting_update_failed) { render :action => :edit }
127
+ @meeting.commit(params[:meeting])
128
+ end
129
+ end
130
+ ```
131
+
132
+ #### An example Listener
133
+
134
+ **Which simply logs all events in memory**
135
+
136
+ ```ruby
137
+ class Auditor
138
+ include Singleton
139
+
140
+ attr_accessor :audit
141
+
142
+ def initialize
143
+ @audit = []
144
+ end
145
+
146
+ def after_create(subject)
147
+ push_audit_for('create', subject)
148
+ end
149
+
150
+ def after_update(subject)
151
+ push_audit_for('update', subject)
152
+ end
153
+
154
+ def after_destroy(subject)
155
+ push_audit_for('destroy', subject)
156
+ end
157
+
158
+ def self.audit
159
+ instance.audit
160
+ end
161
+
162
+ private
163
+
164
+ def push_audit_for(action, subject)
165
+ audit.push(audit_for(action, subject))
166
+ end
167
+
168
+ def audit_for(action, subject)
169
+ {
170
+ action: action,
171
+ subject_id: subject.id,
172
+ subject_class: subject.class.to_s,
173
+ changes: subject.changes,
174
+ created_at: Time.now
175
+ }
176
+ end
177
+ end
178
+ ```
179
+
180
+ **Do some CRUD**
181
+
182
+ ```ruby
183
+ Meeting.create(:description => 'Team Retrospective', :starts_at => Time.now + 2.days)
184
+
185
+ meeting = Meeting.find(1)
186
+ meeting.starts_at = Time.now + 2.months
187
+ meeting.save
188
+ ```
189
+
190
+ **And check the audit**
191
+
192
+ ```ruby
193
+ Auditor.audit # => [...]
194
+ ```
195
+
196
+ ## Compatibility
197
+
198
+ Tested on 1.9.3, 2.x, Rubinius and JRuby for ActiveRecord ~> 3.0 and ~> 4.0
199
+
200
+ See the CI [build status](https://travis-ci.org/krisleech/wisper-activerecord) for more information.
201
+
202
+ ## Contributing
203
+
204
+ Please submit a Pull Request with specs.
205
+
206
+ ### Running the specs
207
+
208
+ ```
209
+ bundle exec rspec
210
+ ```
@@ -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,14 @@
1
+ require "wisper/active_record/version"
2
+ require "wisper/active_record/publisher"
3
+
4
+ module Wisper
5
+ def self.model
6
+ ::Wisper::ActiveRecord::Publisher
7
+ end
8
+
9
+ module ActiveRecord
10
+ def self.extend_all
11
+ ::ActiveRecord::Base.class_eval { include Wisper.model }
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,48 @@
1
+ module Wisper
2
+ module ActiveRecord
3
+ module Publisher
4
+ extend ActiveSupport::Concern
5
+ included do
6
+
7
+ include Wisper::Publisher
8
+
9
+ after_commit :publish_creation, on: :create
10
+ after_commit :publish_update, on: :update
11
+ after_commit :publish_destroy, on: :destroy
12
+ end
13
+
14
+ def commit(_attributes = nil)
15
+ _action = new_record? ? 'create' : 'update'
16
+ assign_attributes(_attributes) if _attributes.present?
17
+ result = save
18
+ if result
19
+ broadcast("#{_action}_#{self.class.model_name.param_key}_successful", self)
20
+ else
21
+ broadcast("#{_action}_#{self.class.model_name.param_key}_failed", self)
22
+ end
23
+ result
24
+ end
25
+
26
+ module ClassMethods
27
+ def commit(_attributes = nil)
28
+ new(_attributes).commit
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def publish_creation
35
+ broadcast(:after_create, self)
36
+ end
37
+
38
+ def publish_update
39
+ broadcast(:after_update, self)
40
+ end
41
+
42
+ def publish_destroy
43
+ broadcast(:after_destroy, self)
44
+ broadcast("destroy_#{self.class.model_name.param_key}_successful", self)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,5 @@
1
+ module Wisper
2
+ module ActiveRecord
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1 @@
1
+ require "wisper/active_record"
@@ -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/activerecord'
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,87 @@
1
+ describe 'ActiveRecord' 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 ActiveRecord module' do
9
+ expect(Wisper.model).to eq Wisper::ActiveRecord::Publisher
10
+ end
11
+
12
+ describe '.commit' do
13
+
14
+ describe 'when creating' do
15
+
16
+ context 'and model is valid' do
17
+ it 'publishes create_<model_name>_successful event to listener' do
18
+ expect(listener).to receive(:create_meeting_successful).with(instance_of(model_class))
19
+ model_class.subscribe(listener)
20
+ model_class.commit
21
+ end
22
+ end
23
+
24
+ context 'and model is not valid' do
25
+ it 'publishes create_<model_name>_failed event to listener' do
26
+ expect(listener).to receive(:create_meeting_failed).with(instance_of(model_class))
27
+ model_class.subscribe(listener)
28
+ model_class.commit(title: nil)
29
+ end
30
+ end
31
+ end
32
+
33
+ describe 'when updating' do
34
+ before { model_class.create! }
35
+
36
+ let(:model) { model_class.first }
37
+
38
+ context 'and model is valid' do
39
+ it 'publishes update_<model_name>_successful event to listener' do
40
+ expect(listener).to receive(:update_meeting_successful).with(instance_of(model_class))
41
+ model_class.subscribe(listener)
42
+ model.commit(title: 'foo')
43
+ end
44
+ end
45
+
46
+ context 'and model is not valid' do
47
+ it 'publishes update_<model_name>_failed event to listener' do
48
+ expect(listener).to receive(:update_meeting_failed).with(instance_of(model_class))
49
+ model_class.subscribe(listener)
50
+ model.commit(title: nil)
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ describe 'create' do
57
+ it 'publishes an after_create event to listener' do
58
+ expect(listener).to receive(:after_create).with(instance_of(model_class))
59
+ model_class.subscribe(listener)
60
+ model_class.create
61
+ end
62
+ end
63
+
64
+ describe 'update' do
65
+ before { model_class.create! }
66
+
67
+ let(:model) { model_class.first }
68
+
69
+ it 'publishes an after_update event to listener' do
70
+ expect(listener).to receive(:after_update).with(instance_of(model_class))
71
+ model.subscribe(listener)
72
+ model.update_attributes(title: 'new title')
73
+ end
74
+ end
75
+
76
+ describe 'destroy' do
77
+ before { model_class.create! }
78
+
79
+ let(:model) { model_class.first }
80
+
81
+ it 'publishes an on_destroy event to listener' do
82
+ expect(listener).to receive(:after_destroy).with(instance_of(model_class))
83
+ model_class.subscribe(listener)
84
+ model.destroy
85
+ end
86
+ end
87
+ 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_record/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "wisper-activerecord"
8
+ spec.version = Wisper::ActiveRecord::VERSION
9
+ spec.authors = ["Kris Leech"]
10
+ spec.email = ["kris.leech@gmail.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/krisleech/wisper-activerecord"
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-activerecord
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kris Leech
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-14 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
+ - kris.leech@gmail.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_record.rb
58
+ - lib/wisper/active_record/publisher.rb
59
+ - lib/wisper/active_record/version.rb
60
+ - lib/wisper/activerecord.rb
61
+ - spec/spec_helper.rb
62
+ - spec/support/models.rb
63
+ - spec/support/schema.rb
64
+ - spec/wisper/activerecord_spec.rb
65
+ - spec/wisper/publisher_spec.rb
66
+ - wisper-activerecord.gemspec
67
+ homepage: https://github.com/krisleech/wisper-activerecord
68
+ licenses:
69
+ - MIT
70
+ metadata: {}
71
+ post_install_message:
72
+ rdoc_options: []
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: 1.9.3
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ requirements: []
86
+ rubyforge_project:
87
+ rubygems_version: 2.2.2
88
+ signing_key:
89
+ specification_version: 4
90
+ summary: Subscribe to changes on ActiveRecord models
91
+ test_files:
92
+ - spec/spec_helper.rb
93
+ - spec/support/models.rb
94
+ - spec/support/schema.rb
95
+ - spec/wisper/activerecord_spec.rb
96
+ - spec/wisper/publisher_spec.rb