notable 0.0.1

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1c151df90eb28af125393e544de6e488a7a44d0b
4
+ data.tar.gz: ce0488e566db76802d5647e56663d08e25adad45
5
+ SHA512:
6
+ metadata.gz: 998e60bed494684299530eb3282b8a8981c825c5402f8ce19cca9f7accd5e60769eaae9ec4a2ee1c7f82e9227f8225dc2bab06fc879d005d79e2242919fe3733
7
+ data.tar.gz: 706cefe72681ea3a33937a4d21ca69a6ab431aff1c617c4f71bbea09c2800d5d7e89e64ce6cbb17da3ac26418eb278cc391c4297d01300a077b923c3de2434b3
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in notable.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Andrew Kane
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,122 @@
1
+ # Notable
2
+
3
+ Track notable requests and background jobs
4
+
5
+ See users affected by:
6
+
7
+ - errors
8
+ - slow requests, jobs, and timeouts
9
+ - 404s
10
+ - validation failures
11
+ - CSRF failures
12
+ - unpermitted parameters
13
+ - blocked and throttled requests
14
+
15
+ :tangerine: Battle-tested at [Instacart](https://www.instacart.com)
16
+
17
+ ## Installation
18
+
19
+ Add this line to your application’s Gemfile:
20
+
21
+ ```ruby
22
+ gem 'notable'
23
+ ```
24
+
25
+ And run:
26
+
27
+ ```sh
28
+ rails generate notable:requests
29
+ rails generate notable:jobs # optional
30
+ rake db:migrate
31
+ ```
32
+
33
+ For a web interface, check out [Notable Web](https://github.com/ankane/notable_web).
34
+
35
+ ## Requests
36
+
37
+ A `Notable::Request` is created for:
38
+
39
+ - errors
40
+ - slow requests and timeouts
41
+ - 404s
42
+ - validation failures
43
+ - CSRF failures
44
+ - unpermitted parameters
45
+ - blocked and throttled requests
46
+
47
+ ## Jobs
48
+
49
+ A `Notable::Job` is created for:
50
+
51
+ - errors
52
+ - slow jobs
53
+
54
+ Currently works with Delayed Job and Sidekiq.
55
+
56
+ ## Manual Tracking
57
+
58
+ ```ruby
59
+ Notable.track(note_type, note)
60
+ ```
61
+
62
+ ## Customize
63
+
64
+ Disable tracking in certain environments
65
+
66
+ ```ruby
67
+ Notable.enabled = Rails.env.production?
68
+ ```
69
+
70
+ ### Requests
71
+
72
+ Set slow threshold
73
+
74
+ ```ruby
75
+ Notable.slow_request_threshold = 5 # seconds (default)
76
+ ```
77
+
78
+ Custom user method
79
+
80
+ ```ruby
81
+ Notable.user_method = proc do |env|
82
+ env["warden"].try(:user) || env["action_controller.instance"].try(:current_visit)
83
+ end
84
+ ```
85
+
86
+ Custom track method
87
+
88
+ ```ruby
89
+ Notable.track_request_method = proc do |data, env|
90
+ Notable::Request.create!(data)
91
+ end
92
+ ```
93
+
94
+ ### Jobs
95
+
96
+ Set slow threshold
97
+
98
+ ```ruby
99
+ Notable.slow_job_threshold = 60 # seconds (default)
100
+ ```
101
+
102
+ Custom track method
103
+
104
+ ```ruby
105
+ Notable.track_job_method = proc do |data|
106
+ Notable::Job.create!(data)
107
+ end
108
+ ```
109
+
110
+ ## TODO
111
+
112
+ - ability to disable features
113
+ - add indexes
114
+
115
+ ## Contributing
116
+
117
+ Everyone is encouraged to help improve this project. Here are a few ways you can help:
118
+
119
+ - [Report bugs](https://github.com/ankane/notable/issues)
120
+ - Fix bugs and [submit pull requests](https://github.com/ankane/notable/pulls)
121
+ - Write, clarify, or fix documentation
122
+ - Suggest or add new features
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,5 @@
1
+ module Notable
2
+ class Job < ActiveRecord::Base
3
+ self.table_name = "notable_jobs"
4
+ end
5
+ end
@@ -0,0 +1,8 @@
1
+ module Notable
2
+ class Request < ActiveRecord::Base
3
+ self.table_name = "notable_requests"
4
+
5
+ belongs_to :user, polymorphic: true
6
+ serialize :params, JSON
7
+ end
8
+ end
@@ -0,0 +1,30 @@
1
+ # taken from https://github.com/collectiveidea/audited/blob/master/lib/generators/audited/install_generator.rb
2
+ require "rails/generators"
3
+ require "rails/generators/migration"
4
+ require "active_record"
5
+ require "rails/generators/active_record"
6
+
7
+ module Notable
8
+ module Generators
9
+ class JobsGenerator < Rails::Generators::Base
10
+ include Rails::Generators::Migration
11
+
12
+ source_root File.expand_path("../templates", __FILE__)
13
+
14
+ # Implement the required interface for Rails::Generators::Migration.
15
+ def self.next_migration_number(dirname) #:nodoc:
16
+ next_migration_number = current_migration_number(dirname) + 1
17
+ if ActiveRecord::Base.timestamped_migrations
18
+ [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max
19
+ else
20
+ "%.3d" % next_migration_number
21
+ end
22
+ end
23
+
24
+ def copy_migration
25
+ migration_template "create_jobs.rb", "db/migrate/create_notable_jobs.rb"
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ # taken from https://github.com/collectiveidea/audited/blob/master/lib/generators/audited/install_generator.rb
2
+ require "rails/generators"
3
+ require "rails/generators/migration"
4
+ require "active_record"
5
+ require "rails/generators/active_record"
6
+
7
+ module Notable
8
+ module Generators
9
+ class RequestsGenerator < Rails::Generators::Base
10
+ include Rails::Generators::Migration
11
+
12
+ source_root File.expand_path("../templates", __FILE__)
13
+
14
+ # Implement the required interface for Rails::Generators::Migration.
15
+ def self.next_migration_number(dirname) #:nodoc:
16
+ next_migration_number = current_migration_number(dirname) + 1
17
+ if ActiveRecord::Base.timestamped_migrations
18
+ [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max
19
+ else
20
+ "%.3d" % next_migration_number
21
+ end
22
+ end
23
+
24
+ def copy_migration
25
+ migration_template "create_requests.rb", "db/migrate/create_notable_requests.rb"
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,14 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration
2
+ def change
3
+ create_table :notable_jobs do |t|
4
+ t.string :note_type
5
+ t.text :note
6
+ t.text :job
7
+ t.string :job_id
8
+ t.string :queue
9
+ t.decimal :runtime
10
+ t.decimal :queued_time
11
+ t.timestamp :created_at
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,22 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration
2
+ def change
3
+ create_table :notable_requests do |t|
4
+ t.string :note_type
5
+ t.text :note
6
+ t.integer :user_id
7
+ t.string :user_type
8
+ t.text :action
9
+ t.integer :status
10
+ t.text :url
11
+ t.string :request_id
12
+ t.string :ip
13
+ t.text :user_agent
14
+ t.text :referrer
15
+ t.text :params
16
+ t.decimal :request_time
17
+ t.timestamp :created_at
18
+ end
19
+
20
+ add_index :notable_requests, [:user_id, :user_type]
21
+ end
22
+ end
@@ -0,0 +1,15 @@
1
+ module Notable
2
+ module DebugExceptions
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ alias_method_chain :render_exception, :pass
7
+ end
8
+
9
+ def render_exception_with_pass(env, exception)
10
+ env["action_dispatch.exception"] = exception
11
+ render_exception_without_pass(env, exception)
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ module Notable
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Notable
4
+
5
+ initializer "notable" do |app|
6
+ app.config.middleware.insert_after RequestStore::Middleware, Notable::Middleware
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ module Notable
2
+ module JobBackends
3
+ class DelayedJob < Delayed::Plugin
4
+ callbacks do |lifecycle|
5
+ lifecycle.around(:invoke_job) do |job, *args, &block|
6
+ Notable.track_job job.name, job.id, job.queue, job.created_at do
7
+ block.call(job, *args)
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ Delayed::Worker.plugins << Notable::JobBackends::DelayedJob
@@ -0,0 +1,26 @@
1
+ module Notable
2
+ module JobBackends
3
+ class Sidekiq
4
+ WRAPPER_CLASSES = Set.new(["ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"])
5
+
6
+ def call(worker, job, queue)
7
+ name =
8
+ if WRAPPER_CLASSES.include?(job["class"])
9
+ job["args"].first["job_class"]
10
+ else
11
+ job["class"]
12
+ end
13
+
14
+ Notable.track_job name, job["jid"], queue, Time.at(job["enqueued_at"]) do
15
+ yield
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ Sidekiq.configure_server do |config|
23
+ config.server_middleware do |chain|
24
+ chain.add Notable::JobBackends::Sidekiq
25
+ end
26
+ end
@@ -0,0 +1,77 @@
1
+ module Notable
2
+ class Middleware
3
+
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ if Notable.enabled
10
+ start_time = Time.now
11
+ status, headers, body = @app.call(env)
12
+ request_time = Time.now - start_time
13
+
14
+ safely do
15
+ if env["action_dispatch.exception"]
16
+ e = env["action_dispatch.exception"]
17
+ message =
18
+ case status.to_i
19
+ when 404
20
+ "Not Found"
21
+ when 503
22
+ "Timeout"
23
+ else
24
+ "Error"
25
+ end
26
+ Notable.track message, "#{e.class.name}: #{e.message}"
27
+ elsif (!status or status.to_i >= 400) and !Notable.notes.any?
28
+ Notable.track Rack::Utils::HTTP_STATUS_CODES[status.to_i]
29
+ end
30
+
31
+ if request_time > Notable.slow_request_threshold and status.to_i != 503
32
+ Notable.track "Slow Request"
33
+ end
34
+
35
+ notes = Notable.notes
36
+ if notes.any?
37
+ request = ActionDispatch::Request.new(env)
38
+
39
+ # hack since Rails modifies PATH_INFO
40
+ # and we don't want to modify env
41
+ url = request.base_url + request.script_name + env["REQUEST_PATH"]
42
+ url << "?#{request.query_string}" unless request.query_string.empty?
43
+
44
+ controller = env["action_controller.instance"]
45
+ action = controller && "#{controller.params["controller"]}##{controller.params["action"]}"
46
+ params = controller && controller.request.filtered_parameters.except("controller", "action")
47
+
48
+ user = Notable.user_method.call(env)
49
+
50
+ notes.each do |note|
51
+ data = {
52
+ note_type: note[:note_type],
53
+ note: note[:note],
54
+ user: user,
55
+ action: action,
56
+ status: status,
57
+ params: params,
58
+ request_id: request.uuid,
59
+ ip: request.remote_ip,
60
+ user_agent: request.user_agent,
61
+ url: url,
62
+ referrer: request.referer,
63
+ request_time: request_time
64
+ }
65
+ Notable.track_request_method.call(data, env)
66
+ end
67
+ end
68
+ end
69
+
70
+ [status, headers, body]
71
+ else
72
+ @app.call(env)
73
+ end
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,5 @@
1
+ ActiveSupport::Notifications.subscribe "rack.attack" do |name, start, finish, request_id, req|
2
+ if [:blacklist, :throttle].include?(req.env["rack.attack.match_type"])
3
+ Notable.track "Throttle", req.env["rack.attack.matched"]
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ ActiveSupport::Notifications.subscribe "unpermitted_parameters.action_controller" do |name, start, finish, id, payload|
2
+ Notable.track "Unpermitted Parameters", payload[:keys].join(", ")
3
+ end
@@ -0,0 +1,19 @@
1
+ module Notable
2
+ module UnverifiedRequest
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ prepend_before_filter :track_unverified_request
7
+ end
8
+
9
+ def track_unverified_request
10
+ if !verified_request?
11
+ expected = form_authenticity_token
12
+ actual = form_authenticity_param || request.headers["X-CSRF-Token"]
13
+ Notable.track "Unverified Request", "#{actual || "nil"} != #{expected}"
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ ActionController::Base.send(:include, Notable::UnverifiedRequest)
@@ -0,0 +1,17 @@
1
+ module Notable
2
+ module ValidationErrors
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ after_validation :track_validation_errors
7
+ end
8
+
9
+ def track_validation_errors
10
+ if errors.any?
11
+ Notable.track "Validation Errors", "#{self.class.name}: #{errors.full_messages.join(", ")}"
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ ActiveRecord::Base.send(:include, Notable::ValidationErrors)
@@ -0,0 +1,3 @@
1
+ module Notable
2
+ VERSION = "0.0.1"
3
+ end
data/lib/notable.rb ADDED
@@ -0,0 +1,101 @@
1
+ require "notable/version"
2
+
3
+ require "request_store"
4
+ require "robustly"
5
+ require "action_dispatch/middleware/debug_exceptions"
6
+
7
+ # middleware
8
+ require "notable/middleware"
9
+ require "notable/engine" if defined?(Rails)
10
+
11
+ # requests
12
+ require "notable/unpermitted_parameters"
13
+ require "notable/unverified_request"
14
+ require "notable/validation_errors"
15
+ require "notable/debug_exceptions"
16
+ require "notable/throttle"
17
+
18
+ # jobs
19
+ require "notable/job_backends/sidekiq" if defined?(Sidekiq)
20
+ require "notable/job_backends/delayed_job" if defined?(Delayed::Job)
21
+
22
+ module Notable
23
+ class << self
24
+ attr_accessor :enabled
25
+
26
+ # requests
27
+ attr_accessor :track_request_method
28
+ attr_accessor :user_method
29
+ attr_accessor :slow_request_threshold
30
+
31
+ # jobs
32
+ attr_accessor :track_job_method
33
+ attr_accessor :slow_job_threshold
34
+ end
35
+ self.enabled = true
36
+
37
+ # requests
38
+ self.track_request_method = proc{|data, env| Notable::Request.create!(data) }
39
+ self.user_method = proc{|env| env["warden"].user if env["warden"] }
40
+ self.slow_request_threshold = 5
41
+
42
+ # jobs
43
+ self.track_job_method = proc{|data| Notable::Job.create!(data) }
44
+ self.slow_job_threshold = 60
45
+
46
+ def self.note(note_type, note = nil)
47
+ (RequestStore.store[:notable_notes] ||= []) << {note_type: note_type, note: note}
48
+ end
49
+
50
+ def self.notes
51
+ RequestStore.store[:notable_notes].to_a
52
+ end
53
+
54
+ def self.clear_notes
55
+ RequestStore.store.delete(:notable_notes)
56
+ end
57
+
58
+ def self.track_job(job, job_id, queue, created_at, &block)
59
+ if Notable.enabled
60
+ exception = nil
61
+ notes = nil
62
+ start_time = Time.now
63
+ queued_time = start_time - created_at
64
+ begin
65
+ yield
66
+ rescue Exception => e
67
+ exception = e
68
+ ensure
69
+ notes = Notable.notes
70
+ Notable.clear_notes
71
+ end
72
+ runtime = Time.now - start_time
73
+
74
+ safely do
75
+ notes << {note_type: "Error", note: "#{exception.class.name}: #{exception.message}"} if exception
76
+ notes << {note_type: "Slow Job"} if runtime > Notable.slow_job_threshold
77
+
78
+ notes.each do |note|
79
+ data = {
80
+ note_type: note[:note_type],
81
+ note: note[:note],
82
+ job: job,
83
+ job_id: job_id,
84
+ queue: queue,
85
+ runtime: runtime,
86
+ queued_time: queued_time
87
+ }
88
+
89
+ Notable.track_job_method.call(data)
90
+ end
91
+ end
92
+
93
+ raise exception if exception
94
+ else
95
+ yield
96
+ end
97
+ end
98
+
99
+ end
100
+
101
+ ActionDispatch::DebugExceptions.send(:include, Notable::DebugExceptions)
data/notable.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'notable/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "notable"
8
+ spec.version = Notable::VERSION
9
+ spec.authors = ["Andrew Kane"]
10
+ spec.email = ["andrew@chartkick.com"]
11
+ spec.summary = %q{Track notable requests and background jobs}
12
+ spec.description = %q{Track notable requests and background jobs}
13
+ spec.homepage = "https://github.com/ankane/notable"
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
+
21
+ spec.add_dependency "request_store"
22
+ spec.add_dependency "robustly"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.7"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ end
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: notable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Kane
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-12-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: request_store
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: robustly
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: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.7'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.7'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ description: Track notable requests and background jobs
70
+ email:
71
+ - andrew@chartkick.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - Gemfile
78
+ - LICENSE.txt
79
+ - README.md
80
+ - Rakefile
81
+ - app/models/notable/job.rb
82
+ - app/models/notable/request.rb
83
+ - lib/generators/notable/jobs_generator.rb
84
+ - lib/generators/notable/requests_generator.rb
85
+ - lib/generators/notable/templates/create_jobs.rb
86
+ - lib/generators/notable/templates/create_requests.rb
87
+ - lib/notable.rb
88
+ - lib/notable/debug_exceptions.rb
89
+ - lib/notable/engine.rb
90
+ - lib/notable/job_backends/delayed_job.rb
91
+ - lib/notable/job_backends/sidekiq.rb
92
+ - lib/notable/middleware.rb
93
+ - lib/notable/throttle.rb
94
+ - lib/notable/unpermitted_parameters.rb
95
+ - lib/notable/unverified_request.rb
96
+ - lib/notable/validation_errors.rb
97
+ - lib/notable/version.rb
98
+ - notable.gemspec
99
+ homepage: https://github.com/ankane/notable
100
+ licenses:
101
+ - MIT
102
+ metadata: {}
103
+ post_install_message:
104
+ rdoc_options: []
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ requirements: []
118
+ rubyforge_project:
119
+ rubygems_version: 2.2.2
120
+ signing_key:
121
+ specification_version: 4
122
+ summary: Track notable requests and background jobs
123
+ test_files: []