correspondent 1.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of correspondent might be problematic. Click here for more details.

checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 98f21b4a1f595bbd6c95b5c62eeb15bf1fb719a31c02c40edd4bfc629dc36bd7
4
+ data.tar.gz: ee6d238830d85fd14e3e5a890c2653318678b1e31064e58bdaa80a535bcd9293
5
+ SHA512:
6
+ metadata.gz: 333981e1f0659f0965a66f8e7527c7d03b1f40e16ce707e846ce84286858c21f6b906cdc5fbf1682015afb30924249f7f99622784d36baab0fd2cfdb347d7861
7
+ data.tar.gz: cf1c9571f5312cce1b9b70529ffa6c55aef7048299c11d8e4846dc24ee93e508847d7f88d05d603186383b5c3c3c846b1b298f5c12fbbbdba1cbf5ec95dfd35a
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2019 Vinicius Stock
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,252 @@
1
+ [![Build Status](https://travis-ci.com/vinistock/correspondent.svg?branch=master)](https://travis-ci.com/vinistock/correspondent) [![Maintainability](https://api.codeclimate.com/v1/badges/07592c6d6b946a7b71fc/maintainability)](https://codeclimate.com/github/vinistock/correspondent/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/07592c6d6b946a7b71fc/test_coverage)](https://codeclimate.com/github/vinistock/correspondent/test_coverage) [![Gem Version](https://badge.fury.io/rb/correspondent.svg)](https://badge.fury.io/rb/correspondent) ![](http://ruby-gem-downloads-badge.herokuapp.com/correspondent?color=brightgreen&type=total)
2
+
3
+ # Correspondent
4
+
5
+ Dead simple configurable user notifications using the Correspondent engine!
6
+
7
+ Configure subscribers and publishers and let Correspondent deal with all notification work with very little overhead.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'correspondent'
15
+ ```
16
+
17
+ And then execute:
18
+ ```bash
19
+ $ bundle
20
+ ```
21
+
22
+ Create the necessary migrations:
23
+
24
+ ```bash
25
+ $ rails g correspondent:install
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ ### Model configuration
31
+
32
+ Notifications can easily be setup using Correspondent. The following example goes through the basic usage. There are only two steps for the basic configuration:
33
+
34
+ 1. Invoke notifies and configure the subscriber (user in this case), the triggers (method purchase in this case) and desired options
35
+ 2. Define the to_notification method to configure information that needs to be used to create notifications
36
+
37
+ ```ruby
38
+ # Example model using Correspondent
39
+ # app/models/purchase.rb
40
+ class Purchase < ApplicationRecord
41
+ belongs_to :user
42
+ belongs_to :store
43
+
44
+ # Notifies configuration
45
+ # First argument is the subscriber (the one that receives a notification). Can be an NxN association as well (e.g.: users) which will create a notification for each associated record.
46
+ # Second argument are the triggers (the method inside that model that triggers notifications). Can be an array of symbols for multiple triggers for the same entity.
47
+ # Third argument are generic options as a hash
48
+ notifies :user, :purchase, avoid_duplicates: true
49
+
50
+ # Many notifies definitions can be used for different subscribers
51
+ # In the following case, every time purchase is invoked the following will happen:
52
+ # 1. A notification will be created for `user`
53
+ # 2. A notification will be created for `store`
54
+ # 3. An email will be triggered using the `StoreMailer` (invoking a method called purchase_email)
55
+ notifies :store, :purchase, avoid_duplicates: true, mailer: StoreMailer
56
+
57
+ # `notifies` will hook into the desired triggers.
58
+ # Every time this method is invoked by an instance of Purchase
59
+ # a notification will be created in the database using the
60
+ # `to_notification` method. The handling of notifications is
61
+ # done asynchronously to cause as little overhead as possible.
62
+ def purchase
63
+ # some business logic
64
+ end
65
+
66
+ # The to_notification method returns the information to be
67
+ # used for creating a notification. This will be invoked automatically
68
+ # by the gem when a trigger occurs.
69
+ # When calling this method, entity and trigger will be passed. Entity
70
+ # is the subscriber (in this example, `user`). Trigger is the method
71
+ # that triggered the notification. With this approach, the hash
72
+ # built to pass information can vary based on different triggers.
73
+ # If entity and trigger will not be used, this can simply be defined as
74
+ #
75
+ # def to_notification(*)
76
+ # # some hash
77
+ # end
78
+ def to_notification(entity:, trigger:)
79
+ {
80
+ title: "Purchase ##{id} for #{entity} #{send(entity).name}",
81
+ content: "Congratulations on your recent #{trigger} of #{name}",
82
+ image_url: "",
83
+ link_url: "/purchases/#{id}",
84
+ referrer_url: "/stores/#{store.id}"
85
+ }
86
+ end
87
+ end
88
+ ```
89
+
90
+ Correspondent can also trigger emails if desired. To trigger emails, the mailer class should be passed as an object and should implement a method follwing the naming convention.
91
+
92
+ ```ruby
93
+ # app/models/purchase.rb
94
+
95
+ class Purchase < ApplicationRecord
96
+ belongs_to :user
97
+
98
+ # Pass the desired mailer in the `mailer:` option
99
+ notifies :user, :purchase, mailer: ApplicationMailer
100
+
101
+ def purchase
102
+ # some business logic
103
+ end
104
+ end
105
+
106
+ # app/mailers/application_mailer.rb
107
+ class ApplicationMailer < ActionMailer::Base
108
+ default from: 'from@example.com'
109
+ layout 'mailer'
110
+
111
+ # The mailer should implement methods following the naming convention of
112
+ # #{trigger}_email(triggering_instance)
113
+ #
114
+ # In this case, the `trigger` is the method purchase, so Correspondent will look for
115
+ # the purchase_email method. It will always pass the instance that triggered the email
116
+ # as an argument.
117
+ def purchase_email(purchase)
118
+ @purchase = purchase
119
+ mail(to: purchase.user.email, subject: "Congratulations on the purchase of #{purchase.name}")
120
+ end
121
+ end
122
+ ```
123
+
124
+ To reference the created notifications in the desired model, use the following association:
125
+
126
+ ```ruby
127
+ # app/models/purchase.rb
128
+
129
+ class User < ApplicationRecord
130
+ has_many :purchases
131
+ has_many :notifications, class_name: "Correspondent::Notification", as: :subscriber
132
+ end
133
+
134
+ class Purchase < ApplicationRecord
135
+ belongs_to :user
136
+ has_many :notifications, class_name: "Correspondent::Notification", as: :publisher
137
+ end
138
+ ```
139
+
140
+ If a specific column is not needed for your project, remove them from the generated migrations and don't return the respective attribute inside the to_notification method.
141
+
142
+ ### Options
143
+
144
+ The available options, their default values and their explanations are listed below.
145
+
146
+ ```ruby
147
+ # Avoid duplicates
148
+ # Prevents creating new notifications if a non dismissed notification for the same publisher and same subscriber already exists
149
+ notifies :some_resouce, :trigger, avoid_duplicates: false
150
+
151
+ # Mailer
152
+ # The Mailer class that implements the desired mailer triggers to send emails. Default is nil (doesn't send emails).
153
+ notifies :some_resouce, :trigger, mailer: nil
154
+
155
+ # Email only
156
+ # For preventing the creation of notifications and only trigger emails, add the email_only option
157
+ notifies :some_resouce, :trigger, email_only: false
158
+
159
+ # Conditionals
160
+ # If or unless options can be passed either as procs/lambdas or symbols representing the name of a method
161
+ # These will be evaluated in an instance context, every time trigger is invoked
162
+ notifies :some_resource, :trigger, if: :should_be_notified?
163
+
164
+ notifies :some_resource, :trigger, unless: -> { should_be_notified? && is_eligible? }
165
+ ```
166
+
167
+ ### JSON API
168
+
169
+ Correspondent exposes a few APIs to be used for handling notification logic in the application.
170
+
171
+ All APIs use the `stale?` check. So if passing the If-None-Match header, the API will support returning 304 (not modified) if the collection hasn't changed.
172
+
173
+ ```json
174
+ Parameters
175
+
176
+ :subscriber_type -> The subscriber resource name - not in plural (e.g.: user)
177
+ :subscriber_id -> The id of the subscriber
178
+
179
+ Index
180
+
181
+ Retrieves all non dismissed notifications for a given subscriber.
182
+
183
+ Request
184
+ GET /correspondent/:subscriber_type/:subscriber_id/notifications
185
+
186
+ Response
187
+ [
188
+ {
189
+ "id":20,
190
+ "title":"Purchase #1 for user user",
191
+ "content":"Congratulations on your recent purchase of purchase",
192
+ "image_url":"",
193
+ "dismissed":false,
194
+ "publisher_type":"Purchase",
195
+ "publisher_id":1,
196
+ "created_at":"2019-03-01T14:19:31.273Z",
197
+ "link_url":"/purchases/1",
198
+ "referrer_url":"/stores/1"
199
+ }
200
+ ]
201
+
202
+ Preview
203
+
204
+ Returns total number of non dismissed notifications and the newest notification.
205
+
206
+ Request
207
+ GET /correspondent/:subscriber_type/:subscriber_id/notifications/preview
208
+
209
+ Response
210
+ {
211
+ "count": 3,
212
+ "notification": {
213
+ "id":20,
214
+ "title":"Purchase #1 for user user",
215
+ "content":"Congratulations on your recent purchase of purchase",
216
+ "image_url":"",
217
+ "dismissed":false,
218
+ "publisher_type":"Purchase",
219
+ "publisher_id":1,
220
+ "created_at":"2019-03-01T14:22:31.649Z",
221
+ "link_url":"/purchases/1",
222
+ "referrer_url":"/stores/1"
223
+ }
224
+ }
225
+
226
+
227
+ Dismiss
228
+
229
+ Dismisses a given notification.
230
+
231
+ Resquest
232
+ PUT /correspondent/:subscriber_type/:subscriber_id/notifications/:notification_id/dismiss
233
+
234
+ Response
235
+ STATUS no_content (204)
236
+
237
+ Destroy
238
+
239
+ Destroys a given notification.
240
+
241
+ Resquest
242
+ DELETE /correspondent/:subscriber_type/:subscriber_id/notifications/:notification_id
243
+
244
+ Response
245
+ STATUS no_content (204)
246
+ ```
247
+
248
+ ## Contributing
249
+
250
+ Contributions are very welcome! Don't hesitate to ask if you wish to contribute, but don't yet know how. Please refer to this simple [guideline].
251
+
252
+ [guideline]: https://github.com/vinistock/correspondent/blob/master/CONTRIBUTING.md
data/Rakefile ADDED
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "bundler/setup"
5
+ rescue LoadError
6
+ puts "You must `gem install bundler` and `bundle install` to run rake tasks"
7
+ end
8
+
9
+ require "rdoc/task"
10
+
11
+ RDoc::Task.new(:rdoc) do |rdoc|
12
+ rdoc.rdoc_dir = "rdoc"
13
+ rdoc.title = "Correspondent"
14
+ rdoc.options << "--line-numbers"
15
+ rdoc.rdoc_files.include("README.md")
16
+ rdoc.rdoc_files.include("lib/**/*.rb")
17
+ end
18
+
19
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
20
+ load "rails/tasks/engine.rake"
21
+ load "rails/tasks/statistics.rake"
22
+
23
+ require "bundler/gem_tasks"
24
+ require "rake/testtask"
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << "test"
28
+ t.pattern = Dir["test/**/*_test.rb"].reject { |path| path.include?("benchmarks") }
29
+ t.verbose = false
30
+ end
31
+
32
+ task default: :test
33
+
34
+ namespace :test do
35
+ Rake::TestTask.new(:benchmark) do |t|
36
+ t.libs << "test"
37
+ t.pattern = "test/benchmarks/**/*_test.rb"
38
+ t.verbose = false
39
+ end
40
+ end
41
+
42
+ task :all do
43
+ system(
44
+ "brakeman && "\
45
+ "rake && "\
46
+ "rubocop --parallel && "\
47
+ "rails_best_practices"
48
+ )
49
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Correspondent
4
+ class ApplicationController < ActionController::API # :nodoc:
5
+ end
6
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "correspondent/application_controller"
4
+
5
+ module Correspondent
6
+ # NotificationsController
7
+ #
8
+ # API for all notifications related
9
+ # endpoints.
10
+ class NotificationsController < ApplicationController
11
+ before_action :find_notification, only: %i[dismiss destroy]
12
+
13
+ # index
14
+ #
15
+ # Returns all notifications for a given subscriber.
16
+ def index
17
+ notifications = Correspondent::Notification.for_subscriber(params[:subscriber_type], params[:subscriber_id])
18
+ render(json: notifications) if stale?(notifications)
19
+ end
20
+
21
+ # preview
22
+ #
23
+ # Returns the newest notification and the total
24
+ # number of notifications for the given subscriber.
25
+ def preview
26
+ notifications = Correspondent::Notification.for_subscriber(params[:subscriber_type], params[:subscriber_id])
27
+
28
+ if stale?(notifications)
29
+ render(
30
+ json: {
31
+ count: notifications.count,
32
+ notification: notifications.limit(1).first
33
+ }
34
+ )
35
+ end
36
+ end
37
+
38
+ # dismiss
39
+ #
40
+ # Dismisses a given notification.
41
+ def dismiss
42
+ @notification&.dismiss!
43
+ head(:no_content)
44
+ end
45
+
46
+ # destroy
47
+ #
48
+ # Destroys a given notification.
49
+ def destroy
50
+ @notification&.destroy
51
+ head(:no_content)
52
+ end
53
+
54
+ private
55
+
56
+ def find_notification
57
+ @notification = Correspondent::Notification.select(:id)
58
+ .find_by(id: params[:id])
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Correspondent
4
+ class ApplicationJob < ActiveJob::Base # :nodoc:
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Correspondent
4
+ class ApplicationRecord < ActiveRecord::Base # :nodoc:
5
+ self.abstract_class = true
6
+ end
7
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Correspondent
4
+ # Notification
5
+ #
6
+ # Model to hold all notification logic.
7
+ class Notification < ApplicationRecord
8
+ belongs_to :subscriber, polymorphic: true
9
+ belongs_to :publisher, polymorphic: true
10
+
11
+ validates_presence_of :publisher, :subscriber
12
+ before_destroy :delete_cache_entry
13
+
14
+ scope :not_dismissed, -> { where(dismissed: false) }
15
+ scope :by_parents, lambda { |subscriber, publisher|
16
+ select(:id)
17
+ .where(subscriber: subscriber, publisher: publisher)
18
+ .not_dismissed
19
+ }
20
+
21
+ scope :for_subscriber, lambda { |type, id|
22
+ not_dismissed
23
+ .where(subscriber_type: type.capitalize, subscriber_id: id)
24
+ .order(id: :desc)
25
+ }
26
+
27
+ class << self
28
+ # create_for!
29
+ #
30
+ # Creates notification(s) for the given
31
+ # +instance+ of the publisher and given
32
+ # +entity+ (subscriber).
33
+ def create_for!(attrs, options = {})
34
+ attributes = attrs[:instance].to_notification(entity: attrs[:entity], trigger: attrs[:trigger])
35
+ attributes[:publisher] = attrs[:instance]
36
+
37
+ relation = attrs[:instance].send(attrs[:entity])
38
+
39
+ if relation.respond_to?(:each)
40
+ create_many!(attributes, relation, options)
41
+ else
42
+ create_single!(attributes, relation, options)
43
+ end
44
+ end
45
+
46
+ # create_many!
47
+ #
48
+ # Creates a notification for each
49
+ # record of the +relation+ so that
50
+ # a many to many relationship can
51
+ # notify all associated objects.
52
+ def create_many!(attributes, relation, options)
53
+ relation.each do |record|
54
+ unless options[:avoid_duplicates] && by_parents(record, attributes[:publisher]).exists?
55
+ create!(attributes.merge(subscriber: record))
56
+ end
57
+ end
58
+ end
59
+
60
+ # create_single!
61
+ #
62
+ # Creates a single notification for the
63
+ # passed entity.
64
+ def create_single!(attributes, relation, options)
65
+ attributes[:subscriber] = relation
66
+ create!(attributes) unless options[:avoid_duplicates] && by_parents(relation, attributes[:publisher]).exists?
67
+ end
68
+ end
69
+
70
+ private_class_method :create_many!, :create_single!
71
+
72
+ def as_json(*)
73
+ Rails.cache.fetch("correspondent_notification_#{id}") do
74
+ attributes.except("updated_at", "subscriber_type", "subscriber_id")
75
+ end
76
+ end
77
+
78
+ def dismiss!
79
+ delete_cache_entry
80
+ update_attribute(:dismissed, true)
81
+ end
82
+
83
+ private
84
+
85
+ def delete_cache_entry
86
+ Rails.cache.delete("correspondent_notification_#{id}")
87
+ end
88
+ end
89
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ Correspondent::Engine.routes.draw do
4
+ scope path: ":subscriber_type/:subscriber_id" do
5
+ resources :notifications, only: %i[index destroy] do
6
+ collection do
7
+ get :preview
8
+ end
9
+
10
+ member do
11
+ put :dismiss
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Correspondent
4
+ class Engine < ::Rails::Engine # :nodoc:
5
+ isolate_namespace Correspondent
6
+ config.generators.api_only = true
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Correspondent
4
+ VERSION = "1.0.0"
5
+ end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "correspondent/engine"
4
+ require "async"
5
+
6
+ module Correspondent # :nodoc:
7
+ class << self
8
+ attr_writer :patched_methods
9
+
10
+ # patched_methods
11
+ #
12
+ # Hash with information about methods
13
+ # that need to be patched.
14
+ def patched_methods
15
+ @patched_methods ||= {}.with_indifferent_access
16
+ end
17
+
18
+ # trigger_email
19
+ #
20
+ # Calls the method of a given mailer using the
21
+ # trigger. Triggering only happens if a mailer
22
+ # has been passed as an option.
23
+ #
24
+ # Will invoke methods in this manner:
25
+ #
26
+ # MyMailer.send("make_purchase_email", #<Purchase id: 1...>)
27
+ def trigger_email(data)
28
+ data.dig(:options, :mailer).send("#{data[:trigger]}_email", data[:instance]).deliver_now
29
+ end
30
+
31
+ # <<
32
+ #
33
+ # Adds the notification creation and email sending
34
+ # as asynchronous tasks.
35
+ def <<(data)
36
+ Async do
37
+ unless data.dig(:options, :email_only)
38
+ Correspondent::Notification.create_for!(data.except(:options), data[:options])
39
+ end
40
+
41
+ trigger_email(data) if data.dig(:options, :mailer)
42
+ end
43
+ end
44
+
45
+ # should_notify?
46
+ #
47
+ # Evaluates the if and unless options within
48
+ # the context of a model instance.
49
+ def should_notify?(context, opt)
50
+ if opt[:if].present?
51
+ evaluate_conditional(context, opt[:if])
52
+ elsif opt[:unless].present?
53
+ !evaluate_conditional(context, opt[:unless])
54
+ end
55
+ end
56
+
57
+ # evaluate_conditional
58
+ #
59
+ # Evaluates if or unless regardless of
60
+ # whether it is a proc or a symbol.
61
+ def evaluate_conditional(context, if_or_unless)
62
+ if if_or_unless.is_a?(Proc)
63
+ context.instance_exec(&if_or_unless)
64
+ else
65
+ context.method(if_or_unless).call
66
+ end
67
+ end
68
+ end
69
+
70
+ # notifies
71
+ #
72
+ # Hook to patch the desired methods +triggers+
73
+ # to asynchronously create notifications / emails.
74
+ #
75
+ # This will patch the methods +triggers+ to publish
76
+ # notifications using the method_added callback.
77
+ # Upon each +triggers+ method definition, the callback
78
+ # runs and patches the original method.
79
+ # If already patched, doesn't do anything (to avoid infinite loops).
80
+
81
+ # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
82
+ def notifies(entity, triggers, options = {})
83
+ save_trigger_info(entity, triggers, options)
84
+ return if methods(false).include?(:method_added)
85
+
86
+ class_eval do
87
+ # Method patching
88
+ #
89
+ # For each trigger method
90
+ # 1. Capture unbound instance method
91
+ # 2. Add it to patched methods to avoid trying to patch it again
92
+ # 3. Undefine it to avoid re-definition warnings
93
+ # 4. Define method again invoking original implementation and
94
+ # inserting a new task in Async
95
+ def self.method_added(name)
96
+ if Correspondent.patched_methods.key?(name)
97
+ original_method = instance_method(name)
98
+ undef_method(name)
99
+ patch_info = Correspondent.patched_methods.delete(name)
100
+
101
+ define_method(name) do |*args|
102
+ original_method.bind(self).call(*args)
103
+
104
+ patch_info.each do |info|
105
+ next unless Correspondent.should_notify?(self, info[:options])
106
+
107
+ Async do
108
+ Correspondent << {
109
+ instance: self,
110
+ entity: info[:entity],
111
+ trigger: name,
112
+ options: info[:options]
113
+ }
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ # rubocop:enable Metrics/MethodLength,Metrics/AbcSize
122
+
123
+ # ActiveRecord on load hook
124
+ #
125
+ # Extend the module after load so that
126
+ # model class methods are available.
127
+ ActiveSupport.on_load(:active_record) do
128
+ extend Correspondent
129
+ end
130
+
131
+ private
132
+
133
+ # save_trigger_info
134
+ #
135
+ # Saves trigger information in hash for future patching.
136
+ def save_trigger_info(entity, triggers, options)
137
+ triggers = [triggers] unless triggers.is_a?(Array)
138
+
139
+ triggers.each do |trigger|
140
+ Correspondent.patched_methods[trigger] ||= []
141
+ Correspondent.patched_methods[trigger] << { entity: entity, options: options }
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/migration"
4
+
5
+ module Correspondent
6
+ module Generators
7
+ # InstallGenerator
8
+ #
9
+ # Creates the necessary migrations to be able to
10
+ # use the engine.
11
+ class InstallGenerator < ::Rails::Generators::Base
12
+ include Rails::Generators::Migration
13
+
14
+ source_root File.expand_path("templates", __dir__)
15
+ desc "Create Correspondent migrations"
16
+
17
+ def self.next_migration_number(_path)
18
+ if @prev_migration_nr
19
+ @prev_migration_nr += 1
20
+ else
21
+ @prev_migration_nr = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
22
+ end
23
+
24
+ @prev_migration_nr.to_s
25
+ end
26
+
27
+ def copy_migrations
28
+ migration_template "create_correspondent_notifications.rb",
29
+ "db/migrate/create_correspondent_notifications.rb",
30
+ migration_version: migration_version
31
+ end
32
+
33
+ def migration_version
34
+ "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,19 @@
1
+ class CreateCorrespondentNotifications < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ create_table :correspondent_notifications do |t|
4
+ t.string :title
5
+ t.string :content
6
+ t.string :image_url
7
+ t.string :link_url
8
+ t.string :referrer_url
9
+ t.boolean :dismissed, default: false
10
+ t.string :publisher_type, null: false
11
+ t.integer :publisher_id, null: false
12
+ t.string :subscriber_type, null: false
13
+ t.integer :subscriber_id, null: false
14
+ t.index [:publisher_type, :publisher_id], name: "index_correspondent_on_publisher"
15
+ t.index [:subscriber_type, :subscriber_id], name: "index_correspondent_on_subscriber"
16
+ t.timestamps
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # desc "Explaining what the task does"
4
+ # task :correspondent do
5
+ # # Task goes here
6
+ # end
metadata ADDED
@@ -0,0 +1,268 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: correspondent
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Vinicius Stock
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-06-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: async
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: benchmark-ips
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
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: brakeman
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
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: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: byebug
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: codeclimate-test-reporter
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: minitest
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: purdytest
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rails_best_practices
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: rubocop
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: rubocop-performance
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
+ - !ruby/object:Gem::Dependency
182
+ name: ruby-prof
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ - !ruby/object:Gem::Dependency
196
+ name: simplecov
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - "~>"
200
+ - !ruby/object:Gem::Version
201
+ version: 0.16.1
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - "~>"
207
+ - !ruby/object:Gem::Version
208
+ version: 0.16.1
209
+ - !ruby/object:Gem::Dependency
210
+ name: sqlite3
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - "<"
214
+ - !ruby/object:Gem::Version
215
+ version: 1.4.0
216
+ type: :development
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - "<"
221
+ - !ruby/object:Gem::Version
222
+ version: 1.4.0
223
+ description: Dead simple configurable user notifications with little overhead.
224
+ email:
225
+ - vinicius.stock@outlook.com
226
+ executables: []
227
+ extensions: []
228
+ extra_rdoc_files: []
229
+ files:
230
+ - MIT-LICENSE
231
+ - README.md
232
+ - Rakefile
233
+ - app/controllers/correspondent/application_controller.rb
234
+ - app/controllers/correspondent/notifications_controller.rb
235
+ - app/jobs/correspondent/application_job.rb
236
+ - app/models/correspondent/application_record.rb
237
+ - app/models/correspondent/notification.rb
238
+ - config/routes.rb
239
+ - lib/correspondent.rb
240
+ - lib/correspondent/engine.rb
241
+ - lib/correspondent/version.rb
242
+ - lib/generators/correspondent/install/install_generator.rb
243
+ - lib/generators/correspondent/install/templates/create_correspondent_notifications.rb
244
+ - lib/tasks/correspondent_tasks.rake
245
+ homepage: https://github.com/vinistock/correspondent
246
+ licenses:
247
+ - MIT
248
+ metadata: {}
249
+ post_install_message:
250
+ rdoc_options: []
251
+ require_paths:
252
+ - lib
253
+ required_ruby_version: !ruby/object:Gem::Requirement
254
+ requirements:
255
+ - - ">="
256
+ - !ruby/object:Gem::Version
257
+ version: '0'
258
+ required_rubygems_version: !ruby/object:Gem::Requirement
259
+ requirements:
260
+ - - ">="
261
+ - !ruby/object:Gem::Version
262
+ version: '0'
263
+ requirements: []
264
+ rubygems_version: 3.0.3
265
+ signing_key:
266
+ specification_version: 4
267
+ summary: Dead simple configurable user notifications with little overhead.
268
+ test_files: []