activerecord-postgres_pub_sub 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1424251b97e52c73cf656eb628a1e37a3a0de3884ddd96b39cb5d6c1d09f7e7b
4
+ data.tar.gz: 825a01dcb7562495c2ff0e8e3159e5451ec90742daa5d404b2c7dc90177f85e3
5
+ SHA512:
6
+ metadata.gz: ccaaddb16fc67fcf2fe3717630d947eca0d2d8751d12a33c79a2384d1522459492df4df7af5c79a13bac0ad5d399570c8eb191e840e8a165024fe76518f607ef
7
+ data.tar.gz: 1de55a0455b85900796107620981b1782eba4b0cabc28a5aa010dfe56a5b52eb53b5910fd9dde2b49d751e8bbf3b7c23c81992cb3009407f92cfcf8b2b94ff15
@@ -0,0 +1,7 @@
1
+ # activerecord-postgres_pub_sub
2
+
3
+ ## v0.2.0
4
+ - Public release
5
+
6
+ ## v0.1.0
7
+ - Initial version
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://ezcater.jfrog.io/ezcater/api/gems/ezcater-gem-source"
4
+
5
+ # override the :github shortcut to be secure by using HTTPS
6
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}.git" }
7
+
8
+ # Specify your gem's dependencies in activerecord-postgres_pub_sub.gemspec
9
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 ezCater, Inc
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,98 @@
1
+ # activerecord-postgres_pub_sub
2
+
3
+ This gem contains support for PostgreSQL LISTEN and NOTIFY functionality:
4
+ [doc](https://www.postgresql.org/docs/9.6/static/libpq-notify.html).
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem "activerecord-postgres_pub_sub"
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install activerecord-postgres_pub_sub
21
+
22
+ ## Usage
23
+
24
+ ### Listener
25
+
26
+ The `Listener` class is used to handle notification messages.
27
+
28
+ The listener can be configured with three blocks:
29
+
30
+ * **on_notify**: called whenever a notification is received.
31
+ * **on_start**: called before receiving any notifications.
32
+ * **on_timeout**: called based on a configurable timeout, when no notifications
33
+ have been received.
34
+
35
+ When creating a listener, the following configuration is supported:
36
+
37
+ * **listen_timeout**: If set, the `on_timeout` block will be called if
38
+ no notifications are received within this period. (Default `nil`).
39
+ * **notify_only**: A payload string can be included in notifications. By default
40
+ the listener ignores the payload and coalesces multiple notifications into a
41
+ single call. When this option is `false`, the `on_notify` block is called with
42
+ the payload for each notification. (Default `true`).
43
+ * **exclusive_lock**: Acquire a lock using
44
+ [with_advisory_lock](https://github.com/ClosureTree/with_advisory_lock) prior to listening.
45
+ This option ensures that a process as a singleton listener. (Default `true`).
46
+
47
+ Example:
48
+
49
+ ```ruby
50
+ ActiveRecord::PostgresPubSub::Listener.listen("notify_channel", listen_timeout: 30) do |listener|
51
+ listener.on_start do
52
+ # when starting assume we missed something and perform regular activity
53
+ handle_notification
54
+ end
55
+
56
+ listener.on_notify do
57
+ handle_notification
58
+ end
59
+
60
+ listener.on_timeout do
61
+ perform_regular_maintenance
62
+ end
63
+ end
64
+ ```
65
+
66
+ ### Generator
67
+
68
+ This gem contains a Rails generator for a migration to add a trigger to notify on insert to a table.
69
+
70
+ The generator must be run with a model name corresponding to the table.
71
+
72
+ ```bash
73
+ rails generate active_record:postgres_pub_sub:notify_on_insert --model_name NameSpace::Entity
74
+ ```
75
+
76
+ In this example, notification events would be generated for the channel named `"name_space_entity"` based
77
+ on inserts to the `name_space_entities` table.
78
+
79
+ ## Development
80
+
81
+ After checking out the repo, run `bin/setup` to install dependencies. Then,
82
+ run `rake spec` to run the tests. You can also run `bin/console` for an
83
+ interactive prompt that will allow you to experiment.
84
+
85
+ To install this gem onto your local machine, run `bundle exec rake install`.
86
+
87
+ To release a new version, update the version number in `version.rb`, and then
88
+ run `bundle exec rake release`, which will create a git tag for the version,
89
+ push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
90
+
91
+ ## Contributing
92
+
93
+ Bug reports and pull requests are welcome on GitHub at
94
+ https://github.com/ezcater/activerecord-postgres_pub_sub.
95
+
96
+ ## License
97
+
98
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path("../lib", __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require "activerecord/postgres_pub_sub/version"
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "activerecord-postgres_pub_sub"
9
+ spec.version = ActiveRecord::PostgresPubSub::VERSION
10
+ spec.authors = ["ezCater, Inc"]
11
+ spec.email = ["engineering@ezcater.com"]
12
+ spec.summary = "Support for Postgres Notify/Listen"
13
+ spec.description = spec.summary
14
+ spec.homepage = "https://github.com/ezcater/activerecord-postgres_pub_sub"
15
+ spec.license = "MIT"
16
+
17
+ # Set "allowed_push_post" to control where this gem can be published.
18
+ if spec.respond_to?(:metadata)
19
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
20
+ else
21
+ raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
22
+ end
23
+
24
+ excluded_files = %w(.circleci/config.yml
25
+ .github/PULL_REQUEST_TEMPLATE.md
26
+ .gitignore
27
+ .rspec
28
+ .rubocop.yml
29
+ .ruby-gemset
30
+ .ruby-version
31
+ .travis.yml
32
+ bin/console
33
+ bin/setup
34
+ Rakefile)
35
+
36
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
37
+ f.match(/^(test|spec|features)\//)
38
+ end - excluded_files
39
+ spec.bindir = "bin"
40
+ spec.executables = []
41
+ spec.require_paths = ["lib"]
42
+
43
+ spec.add_runtime_dependency "activerecord", "~> 5.1.4"
44
+ spec.add_runtime_dependency "pg", "~> 0.18"
45
+ spec.add_runtime_dependency "private_attr"
46
+ spec.add_runtime_dependency "with_advisory_lock"
47
+
48
+ spec.add_development_dependency "bundler", "~> 1.12"
49
+ spec.add_development_dependency "database_cleaner"
50
+ spec.add_development_dependency "ezcater_rubocop", "0.52.7"
51
+ spec.add_development_dependency "overcommit"
52
+ spec.add_development_dependency "rake", "~> 10.0"
53
+ spec.add_development_dependency "rspec", "~> 3.4"
54
+ spec.add_development_dependency "rspec_junit_formatter", "0.2.2"
55
+ spec.add_development_dependency "simplecov"
56
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record"
4
+ require "activerecord/postgres_pub_sub/version"
5
+ require "activerecord/postgres_pub_sub/listener"
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "private_attr"
4
+ require "with_advisory_lock"
5
+
6
+ module ActiveRecord
7
+ module PostgresPubSub
8
+ class Listener
9
+ extend PrivateAttr
10
+
11
+ private_attr_reader :on_notify_blk, :on_start_blk, :on_timeout_blk,
12
+ :channel, :listen_timeout, :exclusive_lock, :notify_only
13
+
14
+ def self.listen(channel, listen_timeout: nil, exclusive_lock: true, notify_only: true)
15
+ listener = new(channel,
16
+ listen_timeout: listen_timeout,
17
+ exclusive_lock: exclusive_lock,
18
+ notify_only: notify_only)
19
+ yield(listener) if block_given?
20
+ listener.listen
21
+ end
22
+
23
+ def initialize(channel, listen_timeout: nil, exclusive_lock: true, notify_only: true)
24
+ @channel = channel
25
+ @listen_timeout = listen_timeout
26
+ @exclusive_lock = exclusive_lock
27
+ @notify_only = notify_only
28
+ end
29
+
30
+ def on_notify(&blk)
31
+ @on_notify_blk = blk
32
+ end
33
+
34
+ def on_start(&blk)
35
+ @on_start_blk = blk
36
+ end
37
+
38
+ def on_timeout(&blk)
39
+ @on_timeout_blk = blk
40
+ end
41
+
42
+ def listen
43
+ with_connection do |connection|
44
+ on_start_blk&.call
45
+
46
+ loop do
47
+ wait_for_notify(connection) do |payload|
48
+ notify_only ? on_notify_blk.call : on_notify_blk.call(payload)
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def with_connection
57
+ ActiveRecord::Base.connection_pool.with_connection do |connection|
58
+ with_optional_lock do
59
+ connection.execute("LISTEN #{channel}")
60
+
61
+ begin
62
+ yield(connection)
63
+ ensure
64
+ connection.execute("UNLISTEN #{channel}")
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ def with_optional_lock
71
+ if exclusive_lock
72
+ ActiveRecord::Base.with_advisory_lock(lock_name) { yield }
73
+ else
74
+ yield
75
+ end
76
+ end
77
+
78
+ def lock_name
79
+ "#{channel}-listener"
80
+ end
81
+
82
+ def empty_channel(connection)
83
+ while connection.wait_for_notify(0)
84
+ # call until nil is returned
85
+ end
86
+ end
87
+
88
+ def wait_for_notify(connection)
89
+ connection_pid = connection.raw_connection.backend_pid
90
+ event_result = connection.raw_connection.wait_for_notify(listen_timeout) do |_event, pid, payload|
91
+ if pid != connection_pid
92
+ empty_channel(connection.raw_connection) if notify_only
93
+ yield(payload)
94
+ end
95
+ end
96
+
97
+ on_timeout_blk&.call if event_result.nil?
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module PostgresPubSub
5
+ VERSION = "0.2.0"
6
+ end
7
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "rails/generators/migration"
5
+ require "rails/generators/active_record"
6
+
7
+ module ActiveRecord
8
+ module PostgresPubSub
9
+ class NotifyOnInsertGenerator < Rails::Generators::Base
10
+ include ActiveRecord::Generators::Migration
11
+
12
+ source_root File.join(File.dirname(__FILE__), "templates")
13
+
14
+ class_option :model_name, type: :string
15
+
16
+ def create_migration_file
17
+ migration_template("create_notify_on_insert_trigger.rb.erb",
18
+ "db/migrate/create_notify_on_#{table_name}_insert_trigger.rb")
19
+ end
20
+
21
+ private
22
+
23
+ def model_name
24
+ @model_name ||= options.fetch(:model_name)
25
+ end
26
+
27
+ def table_name
28
+ @table_name ||= model_name.tableize.tr("/", "_")
29
+ end
30
+
31
+ def notification_name
32
+ @notification_name || table_name.singularize
33
+ end
34
+
35
+ def table_module
36
+ @module_name ||= model_name.deconstantize.underscore.tr("/", "_")
37
+ end
38
+
39
+ def model_title
40
+ @model_title ||= table_name.camelize
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,31 @@
1
+ class CreateNotifyOn<%= model_title %>InsertTrigger < ActiveRecord::Migration[5.1]
2
+ TABLE_NAME = "<%= table_name %>".freeze
3
+ NOTIFICATION_NAME = "<%= notification_name %>".freeze
4
+ TABLE_MODULE = "<%= table_module %>".freeze
5
+
6
+ def up
7
+ execute <<-SQL
8
+ CREATE OR REPLACE FUNCTION notify_#{TABLE_MODULE}_listeners() RETURNS TRIGGER AS $$
9
+ DECLARE
10
+ BEGIN
11
+ PERFORM pg_notify('#{NOTIFICATION_NAME}', null);
12
+ RETURN NEW;
13
+ END;
14
+ $$ LANGUAGE plpgsql
15
+ SQL
16
+
17
+ execute <<-SQL
18
+ CREATE TRIGGER #{TABLE_MODULE}_trigger
19
+ AFTER INSERT
20
+ ON #{TABLE_NAME}
21
+ FOR EACH STATEMENT
22
+ EXECUTE PROCEDURE notify_#{TABLE_MODULE}_listeners()
23
+ SQL
24
+ end
25
+
26
+ def down
27
+ execute <<-SQL
28
+ DROP FUNCTION notify_#{TABLE_MODULE}_listeners() CASCADE
29
+ SQL
30
+ end
31
+ end
metadata ADDED
@@ -0,0 +1,223 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activerecord-postgres_pub_sub
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - ezCater, Inc
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-05-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 5.1.4
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 5.1.4
27
+ - !ruby/object:Gem::Dependency
28
+ name: pg
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.18'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.18'
41
+ - !ruby/object:Gem::Dependency
42
+ name: private_attr
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: with_advisory_lock
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: bundler
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.12'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.12'
83
+ - !ruby/object:Gem::Dependency
84
+ name: database_cleaner
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: ezcater_rubocop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '='
102
+ - !ruby/object:Gem::Version
103
+ version: 0.52.7
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '='
109
+ - !ruby/object:Gem::Version
110
+ version: 0.52.7
111
+ - !ruby/object:Gem::Dependency
112
+ name: overcommit
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rake
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '10.0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '10.0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rspec
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '3.4'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '3.4'
153
+ - !ruby/object:Gem::Dependency
154
+ name: rspec_junit_formatter
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - '='
158
+ - !ruby/object:Gem::Version
159
+ version: 0.2.2
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - '='
165
+ - !ruby/object:Gem::Version
166
+ version: 0.2.2
167
+ - !ruby/object:Gem::Dependency
168
+ name: simplecov
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ description: Support for Postgres Notify/Listen
182
+ email:
183
+ - engineering@ezcater.com
184
+ executables: []
185
+ extensions: []
186
+ extra_rdoc_files: []
187
+ files:
188
+ - CHANGELOG.md
189
+ - Gemfile
190
+ - LICENSE.txt
191
+ - README.md
192
+ - activerecord-postgres_pub_sub.gemspec
193
+ - lib/activerecord-postgres_pub_sub.rb
194
+ - lib/activerecord/postgres_pub_sub/listener.rb
195
+ - lib/activerecord/postgres_pub_sub/version.rb
196
+ - lib/generators/active_record/postgres_pub_sub/notify_on_insert_generator.rb
197
+ - lib/generators/active_record/postgres_pub_sub/templates/create_notify_on_insert_trigger.rb.erb
198
+ homepage: https://github.com/ezcater/activerecord-postgres_pub_sub
199
+ licenses:
200
+ - MIT
201
+ metadata:
202
+ allowed_push_host: https://rubygems.org
203
+ post_install_message:
204
+ rdoc_options: []
205
+ require_paths:
206
+ - lib
207
+ required_ruby_version: !ruby/object:Gem::Requirement
208
+ requirements:
209
+ - - ">="
210
+ - !ruby/object:Gem::Version
211
+ version: '0'
212
+ required_rubygems_version: !ruby/object:Gem::Requirement
213
+ requirements:
214
+ - - ">="
215
+ - !ruby/object:Gem::Version
216
+ version: '0'
217
+ requirements: []
218
+ rubyforge_project:
219
+ rubygems_version: 2.7.6
220
+ signing_key:
221
+ specification_version: 4
222
+ summary: Support for Postgres Notify/Listen
223
+ test_files: []