acts_as_warnable 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.
Files changed (33) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +27 -0
  4. data/app/assets/javascripts/acts_as_warnable/application.js +13 -0
  5. data/app/assets/stylesheets/acts_as_warnable/application.css +15 -0
  6. data/app/controllers/acts_as_warnable/application_controller.rb +4 -0
  7. data/app/controllers/acts_as_warnable/warning_emails_controller.rb +35 -0
  8. data/app/controllers/acts_as_warnable/warnings_controller.rb +70 -0
  9. data/app/helpers/acts_as_warnable/application_helper.rb +15 -0
  10. data/app/mailers/warning_email_mailer.rb +13 -0
  11. data/app/models/warning.rb +56 -0
  12. data/app/models/warning_email.rb +55 -0
  13. data/app/validators/name_and_email_validator.rb +9 -0
  14. data/app/views/acts_as_warnable/warning_emails/_form.html.erb +19 -0
  15. data/app/views/acts_as_warnable/warning_emails/edit.html.erb +2 -0
  16. data/app/views/acts_as_warnable/warning_emails/index.html.erb +37 -0
  17. data/app/views/acts_as_warnable/warning_emails/new.html.erb +2 -0
  18. data/app/views/acts_as_warnable/warnings/index.html.erb +37 -0
  19. data/app/views/acts_as_warnable/warnings/show.html.erb +17 -0
  20. data/app/views/layouts/acts_as_warnable/application.html.erb +14 -0
  21. data/app/views/public_activity/warning/_dismiss.html.erb +11 -0
  22. data/app/views/public_activity/warning/_issue.html.erb +13 -0
  23. data/app/views/warning_email_mailer/send_report.html.erb +26 -0
  24. data/config/routes.rb +4 -0
  25. data/lib/acts_as_warnable.rb +55 -0
  26. data/lib/acts_as_warnable/engine.rb +10 -0
  27. data/lib/acts_as_warnable/rails/routes.rb +18 -0
  28. data/lib/acts_as_warnable/version.rb +3 -0
  29. data/lib/generators/acts_as_warnable/warning_email_thread_generator.rb +9 -0
  30. data/lib/generators/acts_as_warnable/warning_generator.rb +9 -0
  31. data/lib/generators/templates/email_initializer.rb +15 -0
  32. data/lib/tasks/acts_as_warnable_tasks.rake +4 -0
  33. metadata +173 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d197bb659d0bf521569b61ebfa66a8d062c52bdd
4
+ data.tar.gz: e92d4711cd67f9e4b5ef5482964381efca51c7aa
5
+ SHA512:
6
+ metadata.gz: 79a8ac2bd90b660996be0ac7733cf44ccae1026a654997b493736c2d44594e4c3d5a23b217bbb0939dce48b0a8f48721645f09c6a837260d82b82d6c3ff446b1
7
+ data.tar.gz: e4233b6f27e12180a56a6a28a7009f3b6b4119a24566de5d0f6cd71daff4314f6c5237927d810dde521bede55189b726b46ed3302cb516cd0b4c7971c9cc9ed0
@@ -0,0 +1,20 @@
1
+ Copyright 2015 Nigel Baillie
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.
@@ -0,0 +1,27 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'ActsAsWarnable'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+ load 'rails/tasks/statistics.rake'
22
+
23
+
24
+
25
+ Bundler::GemHelper.install_tasks
26
+
27
+ require 'rake/testtask'
@@ -0,0 +1,13 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file.
9
+ //
10
+ // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree .
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any styles
10
+ * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11
+ * file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,4 @@
1
+ module ActsAsWarnable
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,35 @@
1
+ module ActsAsWarnable
2
+ class WarningEmailsController < ::InheritedResources::Base
3
+ respond_to :html
4
+
5
+ helper do
6
+ def method_missing(name, *args, &block)
7
+ if main_app.respond_to?(name)
8
+ main_app.send(name, *args, &block)
9
+ else
10
+ super
11
+ end
12
+ end
13
+
14
+ def warnable_classes
15
+ ::ActiveRecord::Base.descendants.select do |c|
16
+ c.reflect_on_all_associations.any? { |a| a.name == :warnings }
17
+ end
18
+ end
19
+ end
20
+
21
+ def show
22
+ redirect_to action: :edit
23
+ end
24
+
25
+ protected
26
+
27
+ def permitted_params
28
+ params.permit(
29
+ warning_email: [
30
+ :model, :recipient, :minutes, :url
31
+ ]
32
+ )
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,70 @@
1
+ module ActsAsWarnable
2
+ class WarningsController < ::ApplicationController
3
+ before_filter :fetch_warning, only: [:update, :show]
4
+ respond_to :html, :js
5
+
6
+ helper do
7
+ def method_missing(name, *args, &block)
8
+ if main_app.respond_to?(name)
9
+ main_app.send(name, *args, &block)
10
+ else
11
+ super
12
+ end
13
+ end
14
+ end
15
+
16
+ def index
17
+ @warnings = Warning.all
18
+ @warnings = @warnings.active if params[:active_only]
19
+
20
+ if params.key?(:warnable_id) && params.key?(:warnable_type)
21
+ @warnings = @warnings.where(warnable_id: params[:warnable_id], warnable_type: params[:warnable_type])
22
+
23
+ @warnable = params[:warnable_type].constantize.find(params[:warnable_id])
24
+ instance_variable_set('@'+params[:warnable_type].underscore, @warnable)
25
+
26
+ warnables = params[:warnable_type].underscore.pluralize
27
+ if lookup_context.exists?('warnings', warnables, false)
28
+ render "#{warnables}/warnings"
29
+ end
30
+ end
31
+ end
32
+
33
+ def update
34
+ was_dismissed = @warning.dismissed?
35
+ if @warning.update_attributes(permitted_params[:warning])
36
+ if !was_dismissed && @warning.dismissed?
37
+ flash[:success] = "Warning dismissed"
38
+ else
39
+ flash[:success] = "Successfully updated warning"
40
+ end
41
+ else
42
+ flash[:error] = "Failed to update warning: #{@warning.errors.full_messages.join(', ')}"
43
+ end
44
+
45
+ if params[:redirect_to]
46
+ redirect_to params[:redirect_to]
47
+ else
48
+ redirect_to warnings_path
49
+ end
50
+ end
51
+
52
+ def show
53
+ render params[:render] if params.key?(:render)
54
+ end
55
+
56
+ protected
57
+
58
+ def fetch_warning
59
+ @warning = Warning.find(params[:id])
60
+ end
61
+
62
+ def permitted_params
63
+ params.permit(
64
+ warning: [
65
+ :warnable_id, :warnable_type, :message, :dismissed_at, :dismisser_id
66
+ ]
67
+ )
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,15 @@
1
+ module ActsAsWarnable
2
+ module ApplicationHelper
3
+ def current_devise_user
4
+ send("current_#{Devise.mappings.values.first.name.to_s.underscore.singularize}")
5
+ end
6
+
7
+ def button_to_dismiss_warning(warning, options = {})
8
+ form_for(warning, options) do |f|
9
+ f.hidden_field(:dismisser_id, value: current_devise_user.id) +
10
+ hidden_field_tag(:redirect_to, request.fullpath) +
11
+ f.submit('Dismiss', class: 'btn btn-warning')
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ class WarningEmailMailer < ActionMailer::Base
2
+ def send_report(warning_email, warnings)
3
+ @warning_email = warning_email
4
+ @warnings = warnings
5
+ @site_url = defined?(Figaro) ? Figaro.env.site_url : ENV['SITE_URL']
6
+
7
+ mail(
8
+ to: @warning_email.recipient,
9
+ from: (defined?(Figaro) ? Figaro.env.warning_email_sender : ENV['WARNING_EMAIL_SENDER']) || raise('Please set ENV["WARNING_EMAIL_SENDER"]'),
10
+ subject: "[#{Time.now.strftime('%D %r')}] There are outstanding warnings for #{@warning_email.model}",
11
+ )
12
+ end
13
+ end
@@ -0,0 +1,56 @@
1
+ class Warning < ActiveRecord::Base
2
+ def self.dismisser_class
3
+ Devise.mappings.values.first.class_name
4
+ end
5
+
6
+ belongs_to :warnable, polymorphic: true
7
+ if Devise.mappings.size > 1
8
+ Rails.logger.warn "acts_as_warnable does not currently support multiple Devise models. "\
9
+ "Only #{dismisser_class} will be able to dismiss warnings."
10
+ end
11
+ belongs_to :dismisser, class_name: dismisser_class
12
+
13
+ validates :message, :source, presence: true
14
+
15
+ before_save :check_if_dismissed
16
+ after_save :fire_dismissal_activity, if: :just_dismissed?
17
+
18
+ def self.active
19
+ where(dismissed_at: nil)
20
+ end
21
+
22
+ def dismiss(user)
23
+ update_attributes dismissed_at: Time.now, dismisser: user
24
+ end
25
+
26
+ def dismissed?
27
+ !dismissed_at.nil? && !dismisser.nil?
28
+ end
29
+
30
+ def just_dismissed?
31
+ @just_dismissed
32
+ end
33
+
34
+ protected
35
+
36
+ def fire_dismissal_activity
37
+ return unless warnable.respond_to?(:create_activity)
38
+
39
+ warnable.create_activity(
40
+ key: 'warning.dismiss',
41
+ recipient: self,
42
+ owner: dismisser
43
+ )
44
+ end
45
+
46
+ def check_if_dismissed
47
+ if dismisser_id_changed?
48
+ if dismisser_id.nil?
49
+ self.dismissed_at = nil
50
+ else
51
+ self.dismissed_at = Time.now
52
+ @just_dismissed = true
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,55 @@
1
+ class WarningEmail < ActiveRecord::Base
2
+ validates :recipient, name_and_email: true
3
+ validate :model_is_actually_a_model
4
+
5
+ def warnings
6
+ Warning.where(warnable_type: model, dismissed_at: nil)
7
+ end
8
+
9
+ def send_report
10
+ WarningEmailMailer.send_report(self, warnings).deliver
11
+ end
12
+
13
+ def self.spawn_sender_thread
14
+ Thread.main[:warning_email_sender] = Thread.new do
15
+ loop do
16
+ begin
17
+ warning_times = {}
18
+
19
+ min_wait_time = 600
20
+ WarningEmail.find_each do |warning_email|
21
+ next if warning_email.minutes.blank?
22
+
23
+ warning_times[warning_email.id] ||= Time.now.to_i
24
+ wait_time = warning_email.minutes * 60.0 - (Time.now.to_i - warning_times[warning_email.id])
25
+
26
+ if wait_time < 0
27
+ warning_email.send_report if warning_email.warnings.any?
28
+ warning_times[warning_email.id] = Time.now.to_i
29
+ wait_time = warning_email.minutes * 60.0
30
+ end
31
+
32
+ min_wait_time = wait_time if wait_time < min_wait_time
33
+ end
34
+
35
+ ActiveRecord::Base.clear_active_connections!
36
+ sleep min_wait_time.seconds + 1
37
+ rescue StandardError => e
38
+ Rails.logger.error "WARNING_EMAILS ERROR DURING LOOP #{e.class.name}: #{e.message}"
39
+
40
+ ActiveRecord::Base.clear_active_connections!
41
+ sleep 1.minute
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def model_is_actually_a_model
50
+ model_class = model.constantize
51
+ unless model_class.ancestors.include?(ActiveRecord::Base)
52
+ errors.add 'model', 'must correspond to a valid model'
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,9 @@
1
+ unless defined?(NameAndEmailValidator)
2
+ class NameAndEmailValidator < ActiveModel::EachValidator
3
+ def validate_each(record, attribute, value)
4
+ unless value =~ /\A(.+)?\s<([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})?>\Z/i
5
+ record.errors[attribute] << (options[:message] || "is not a valid email address. Proper format is 'Name First <email@second.com>' separated by commas ")
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ <%= link_to 'List', ActsAsWarnable::Engine.routes.url_helpers.warning_emails_path, class: 'btn btn-sm btn-primary' %>
2
+
3
+ <%= form_for warning_email do |f| %>
4
+ <%= f.label :model, class: 'control-label' %>
5
+ <%= f.select :model, options_for_select(warnable_classes.map(&:name), f.object.try(:model)), {}, class: 'form-control' %>
6
+
7
+ <%= f.label :recipient, class: 'control-label' %>
8
+ <%= f.text_field :recipient, class: 'form-control' %>
9
+
10
+ <%= f.label :minutes, 'Send Every', class: 'control-label' %>
11
+ <div class='input-group'>
12
+ <%= f.text_field :minutes, class: 'form-control' %>
13
+ <span class='input-group-addon'>
14
+ minutes
15
+ </span>
16
+ </div>
17
+
18
+ <%= f.submit class: 'btn btn-primary' %>
19
+ <% end %>
@@ -0,0 +1,2 @@
1
+ <h1>Edit Warning Email</h1>
2
+ <%= render 'form', warning_email: @warning_email %>
@@ -0,0 +1,37 @@
1
+ <h1>Warning Emails</h1>
2
+
3
+ <dl>
4
+ <dt>Dispatcher Thread Status</dt>
5
+ <dd><%= Thread.main[:warning_email_sender].try(:status).try(:humanize) || '<em>Not Running</em>'.html_safe %></dd>
6
+ </dl>
7
+
8
+ <%= link_to 'New Warning Email', ActsAsWarnable::Engine.routes.url_helpers.new_warning_email_path, class: 'btn btn-primary' %>
9
+
10
+ <table class="table table-striped">
11
+ <thead>
12
+ <tr>
13
+ <th>Model</th>
14
+ <th>Recipient</th>
15
+ <th>Send Interval</th>
16
+ <th>Actions</th>
17
+ </tr>
18
+
19
+ <tbody>
20
+ <% @warning_emails.each do |warning_email| %>
21
+ <tr>
22
+ <td><%= warning_email.model %></td>
23
+ <td><%= warning_email.recipient %></td>
24
+ <td><%= warning_email.minutes || '<not set>' %> minutes</td>
25
+ <td>
26
+ <%= link_to(ActsAsWarnable::Engine.routes.url_helpers.edit_warning_email_path(warning_email), data: { toggle: 'tooltip' }, class: 'btn btn-xs btn-warning', title: 'Edit') do %>
27
+ <%= content_tag :i, '', class: 'glyphicon glyphicon-edit' %>
28
+ <% end %>
29
+ <%= link_to(warning_email, method: :delete, data: { toggle: 'tooltip' }, class: 'btn btn-xs btn-danger', title: 'Destroy') do %>
30
+ <%= content_tag :i, '', class: 'glyphicon glyphicon-remove-circle' %>
31
+ <% end %>
32
+ </td>
33
+ </tr>
34
+ <% end %>
35
+ </tbody>
36
+ </thead>
37
+ </table>
@@ -0,0 +1,2 @@
1
+ <h1>New Warning Email</h1>
2
+ <%= render 'form', warning_email: @warning_email %>
@@ -0,0 +1,37 @@
1
+ <% if params[:warnable_type] %>
2
+ <h1>
3
+ <%= link_to "#{params[:warnable_type].humanize} ##{params[:warnable_id]}", @warnable %> Warnings
4
+ </h1>
5
+ <% else %>
6
+ <h1>Warnings</h1>
7
+ <% end %>
8
+
9
+ <% if params[:active_only] %>
10
+ <% if params[:warnable_type] %>
11
+ <%= link_to "Show dismissed", send("#{params[:warnable_type].underscore}_warnings_path", params[:warnable_id]), { class: 'btn btn-info' } %>
12
+ <% else %>
13
+ <%= link_to "Show dismissed", ActsAsWarnable::Engine.routes.url_helpers.warnings_path, { class: 'btn btn-info' } %>
14
+ <% end %>
15
+ <% else %>
16
+ <% if params[:warnable_type] %>
17
+ <%= link_to "Hide dismissed", send("#{params[:warnable_type].underscore}_warnings_path", params[:warnable_id], active_only: true), { class: 'btn btn-info' } %>
18
+ <% else %>
19
+ <%= link_to "Hide dismissed", ActsAsWarnable::Engine.routes.url_helpers.warnings_path(active_only: true), { class: 'btn btn-info' } %>
20
+ <% end %>
21
+ <% end %>
22
+
23
+ <ul>
24
+ <% @warnings.each do |warning| %>
25
+ <li>
26
+ <% if warning.respond_to?(:created_at) && warning.created_at %>
27
+ (<%= warning.created_at %>)
28
+ <% end %>
29
+ <%= link_to "##{warning.id}: (#{warning.source}) #{warning.message.truncate(50)}", ActsAsWarnable::Engine.routes.url_helpers.warning_path(warning) %>
30
+ <% if warning.dismissed? %>
31
+ <%= "(dismissed by #{warning.dismisser.try(:full_name) || warning.dismisser.try(:email) || 'nobody'} on #{warning.dismissed_at.strftime('%c')})" if warning.dismissed? %>
32
+ <% else %>
33
+ <%= button_to_dismiss_warning(warning) %>
34
+ <% end %>
35
+ </li>
36
+ <% end %>
37
+ </ul>
@@ -0,0 +1,17 @@
1
+ <h1>Warning #<%= @warning.id %> (<%= @warning.source %>) from <%= link_to "#{@warning.warnable_type.underscore.humanize} ##{@warning.warnable_id}", @warning.warnable %></h1>
2
+ <% if @warning.respond_to?(:created_at) && @warning.created_at %>
3
+ (<%= warning.created_at %>)
4
+ <% end %>
5
+ <div class='well'>
6
+ <% @warning.message.each_line do |line| %>
7
+ <p style='word-wrap: break-word'><%= line %></p>
8
+ <% end %>
9
+ </div>
10
+ <div class=''>
11
+ <% if @warning.dismissed? %>
12
+ Dismissed by <%= @warning.dismisser.try(:full_name) || @warning.dismisser.try(:email) || 'nobody' %>
13
+ on <%= @warning.dismissed_at.strftime('%c') %>
14
+ <% else %>
15
+ <%= button_to_dismiss_warning(@warning) %>
16
+ <% end %>
17
+ </div>
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>ActsAsWarnable</title>
5
+ <%= stylesheet_link_tag "acts_as_warnable/application", media: "all" %>
6
+ <%= javascript_include_tag "acts_as_warnable/application" %>
7
+ <%= csrf_meta_tags %>
8
+ </head>
9
+ <body>
10
+
11
+ <%= yield %>
12
+
13
+ </body>
14
+ </html>
@@ -0,0 +1,11 @@
1
+ <% if activity.recipient %>
2
+ <%= link_to "Warning ##{activity.recipient_id}", ActsAsWarnable::Engine.routes.url_helpers.warning_path(activity.recipient_id) %> from "<%= activity.recipient.source %>"
3
+ was dismissed by <%= activity.owner.try(:full_name) || activity.owner.try(:email) || 'Someone' %>
4
+ <% else %>
5
+ Unknown warning was dismissed
6
+ <% if activity.owner %>
7
+ by <%= activity.owner.try(:full_name) || activity.owner.try(:email) || "Someone (#{activity.owner.inspect})" %>
8
+ <% else %>
9
+ and we don't know who did it.
10
+ <% end %>
11
+ <% end %>
@@ -0,0 +1,13 @@
1
+ <% if activity.recipient %>
2
+ <div>
3
+ "<%= activity.recipient.source %>" issued
4
+ <%= link_to "a warning (##{activity.recipient_id})", ActsAsWarnable::Engine.routes.url_helpers.warning_path(activity.recipient) %>
5
+ </div>
6
+
7
+ <div>
8
+ "<%= activity.recipient.message.truncate(40) %>"
9
+ </div>
10
+
11
+ <% else %>
12
+ Unknown warning was issued.
13
+ <% end %>
@@ -0,0 +1,26 @@
1
+ <h3>The following warnings have not been dismissed</h3>
2
+
3
+ <% if @site_url.blank? %>
4
+ <p>(Set ENV["SITE_URL"] to have these list entries link to their warning page for dismissal.)</p>
5
+ <% end %>
6
+
7
+ <ul>
8
+ <% @warnings.each do |warning| %>
9
+ <li>
10
+ <ul>
11
+ <li>
12
+ <% if @site_url.blank? %>
13
+ <span>#<%= warning.id %>: (<%= warning.source %>)</span>
14
+ <% else %>
15
+ <%= link_to "##{warning.id}: (#{warning.source})", @site_url + ActsAsWarnable::Engine.routes.url_helpers.warning_path(warning.id) %>
16
+ <% end %>
17
+ </li>
18
+ <li>
19
+ <% warning.message.each_line do |line| %>
20
+ <p style='word-wrap: break-word'><%= line %></p>
21
+ <% end %>
22
+ </li>
23
+ </ul>
24
+ </li>
25
+ <% end %>
26
+ </ul>
@@ -0,0 +1,4 @@
1
+ ActsAsWarnable::Engine.routes.draw do
2
+ resources :warnings
3
+ resources :warning_emails
4
+ end
@@ -0,0 +1,55 @@
1
+ require 'acts_as_warnable/version'
2
+ require 'acts_as_warnable/engine'
3
+ require 'acts_as_warnable/rails/routes'
4
+
5
+ module ActsAsWarnable
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+ def acts_as_warnable(options = {})
10
+ has_many :warnings, as: :warnable
11
+ include WarnableInstanceMethods
12
+ extend WarnableClassMethods
13
+ end
14
+ end
15
+
16
+ module WarnableClassMethods
17
+ def warn_on_failure_of(*methods)
18
+ options = methods.last.is_a?(Hash) ? methods.pop : {}
19
+
20
+ methods.each do |method_name|
21
+ define_method "#{method_name}_with_warning" do |*args, &block|
22
+ begin
23
+ send("#{method_name}_without_warning", *args, &block)
24
+ rescue Exception => e
25
+ issue_warning(
26
+ warning_source(method_name),
27
+ "#{e.class.name}: #{e.message}\n\n#{e.backtrace.join("\n")}"
28
+ )
29
+ raise if options[:raise_anyway]
30
+ end
31
+ end
32
+ alias_method_chain method_name, :warning
33
+ end
34
+ end
35
+ end
36
+
37
+ module WarnableInstanceMethods
38
+ def warning_source(method_name)
39
+ "#{self.class.name}##{method_name}"
40
+ end
41
+
42
+ def issue_warning(source, message)
43
+ warning = warnings.create(source: source, message: message)
44
+
45
+ if respond_to?(:create_activity)
46
+ create_activity(
47
+ key: 'warning.issue',
48
+ recipient: warning
49
+ )
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ ActiveRecord::Base.send :include, ActsAsWarnable
@@ -0,0 +1,10 @@
1
+ module ActsAsWarnable
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace ActsAsWarnable
4
+ initializer 'acts_as_warnable.action_controller' do |app|
5
+ ActiveSupport.on_load :action_controller do
6
+ helper ActsAsWarnable::ApplicationHelper
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,18 @@
1
+ module ActionDispatch
2
+ module Routing
3
+ class Mapper
4
+ def warning_paths_for(*resources)
5
+ resources.map(&:to_s).each do |resource|
6
+ get "/#{resource.pluralize}/:warnable_id/warnings",
7
+ to: 'acts_as_warnable/warnings#index',
8
+ defaults: { warnable_type: resource.singularize.camelize },
9
+ as: "#{resource.singularize}_warnings"
10
+
11
+ post "/#{resource.pluralize}/:warnable_id/warnings",
12
+ to: 'acts_as_warnable/warnings#post',
13
+ defaults: { warnable_type: resource.singularize.camelize }
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ module ActsAsWarnable
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,9 @@
1
+ class ActsAsWarnable::WarningEmailThreadGenerator < Rails::Generators::Base
2
+ desc "Generates an initializer that kicks off a thread that sends warning emails on their intervals."
3
+
4
+ source_root File.expand_path("../../templates", __FILE__)
5
+
6
+ def create_initializer
7
+ copy_file 'email_initializer.rb', 'config/initializers/send_warning_emails.rb'
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ class ActsAsWarnable::WarningGenerator < Rails::Generators::Base
2
+ desc "Generates the migration for warnings"
3
+
4
+ def create_migrations
5
+ generate 'migration', 'CreateWarnings warnable_id:integer warnable_type:string source:string message:text dismissed_at:datetime dismisser_id:integer'
6
+ generate 'migration', 'AddTimestampsToWarnings created_at:datetime updated_at:datetime'
7
+ generate 'migration', 'CreateWarningEmails model:string minutes:decimal\\{10,2\\} recipient:string url:string'
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ ### Here we spawn a thread that sends warning emails on their set intervals ###
2
+
3
+ # You may modify this to your needs
4
+ conditions = [
5
+ # Only send emails on production
6
+ Rails.env.production?,
7
+
8
+ # Don't send emails from rails console sessions
9
+ !defined?(Rails::Console),
10
+
11
+ # Don't send emails from rake tasks
12
+ $0 !~ /rake$/
13
+ ]
14
+
15
+ WarningEmail.spawn_sender_thread if conditions.all?
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :acts_as_warnable do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,173 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: acts_as_warnable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nigel Baillie
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-08-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 4.2.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 4.2.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: devise
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: inherited_resources
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: sqlite3
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: rspec-rails
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: factory_girl_rails
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 4.2.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: 4.2.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: shoulda-matchers
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: Allows models to issue warnings if a task goes wrong
112
+ email:
113
+ - metreckk@gmail.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - MIT-LICENSE
119
+ - Rakefile
120
+ - app/assets/javascripts/acts_as_warnable/application.js
121
+ - app/assets/stylesheets/acts_as_warnable/application.css
122
+ - app/controllers/acts_as_warnable/application_controller.rb
123
+ - app/controllers/acts_as_warnable/warning_emails_controller.rb
124
+ - app/controllers/acts_as_warnable/warnings_controller.rb
125
+ - app/helpers/acts_as_warnable/application_helper.rb
126
+ - app/mailers/warning_email_mailer.rb
127
+ - app/models/warning.rb
128
+ - app/models/warning_email.rb
129
+ - app/validators/name_and_email_validator.rb
130
+ - app/views/acts_as_warnable/warning_emails/_form.html.erb
131
+ - app/views/acts_as_warnable/warning_emails/edit.html.erb
132
+ - app/views/acts_as_warnable/warning_emails/index.html.erb
133
+ - app/views/acts_as_warnable/warning_emails/new.html.erb
134
+ - app/views/acts_as_warnable/warnings/index.html.erb
135
+ - app/views/acts_as_warnable/warnings/show.html.erb
136
+ - app/views/layouts/acts_as_warnable/application.html.erb
137
+ - app/views/public_activity/warning/_dismiss.html.erb
138
+ - app/views/public_activity/warning/_issue.html.erb
139
+ - app/views/warning_email_mailer/send_report.html.erb
140
+ - config/routes.rb
141
+ - lib/acts_as_warnable.rb
142
+ - lib/acts_as_warnable/engine.rb
143
+ - lib/acts_as_warnable/rails/routes.rb
144
+ - lib/acts_as_warnable/version.rb
145
+ - lib/generators/acts_as_warnable/warning_email_thread_generator.rb
146
+ - lib/generators/acts_as_warnable/warning_generator.rb
147
+ - lib/generators/templates/email_initializer.rb
148
+ - lib/tasks/acts_as_warnable_tasks.rake
149
+ homepage:
150
+ licenses:
151
+ - MIT
152
+ metadata: {}
153
+ post_install_message:
154
+ rdoc_options: []
155
+ require_paths:
156
+ - lib
157
+ required_ruby_version: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ version: '0'
162
+ required_rubygems_version: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ requirements: []
168
+ rubyforge_project:
169
+ rubygems_version: 2.4.8
170
+ signing_key:
171
+ specification_version: 4
172
+ summary: Allows models to issue warnings if a task goes wrong
173
+ test_files: []