journaled 2.5.0 → 3.0.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.
- checksums.yaml +4 -4
- data/README.md +21 -8
- data/Rakefile +7 -1
- data/app/jobs/journaled/application_job.rb +4 -0
- data/app/jobs/journaled/delivery_job.rb +96 -0
- data/app/models/journaled/delivery.rb +1 -1
- data/app/models/journaled/writer.rb +6 -4
- data/lib/journaled.rb +21 -3
- data/lib/journaled/engine.rb +5 -0
- data/lib/journaled/version.rb +1 -1
- data/spec/dummy/config/application.rb +1 -2
- data/spec/dummy/config/database.yml +4 -19
- data/spec/dummy/config/environments/development.rb +0 -13
- data/spec/dummy/config/environments/test.rb +3 -5
- data/spec/dummy/db/schema.rb +3 -16
- data/spec/jobs/journaled/delivery_job_spec.rb +221 -0
- data/spec/lib/journaled_spec.rb +39 -0
- data/spec/models/database_change_protection_spec.rb +19 -25
- data/spec/models/journaled/writer_spec.rb +15 -12
- data/spec/rails_helper.rb +1 -2
- data/spec/spec_helper.rb +1 -3
- metadata +32 -66
- data/config/routes.rb +0 -2
- data/lib/journaled/enqueue.rb +0 -13
- data/spec/dummy/config/environments/production.rb +0 -78
- data/spec/dummy/config/initializers/assets.rb +0 -8
- data/spec/dummy/db/migrate/20180606205114_create_delayed_jobs.rb +0 -18
- data/spec/support/delayed_job_spec_helper.rb +0 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 358c3ced2b976724c62e951ca88b635709d1fb7da0276ed4538bdc44bc02f468
|
4
|
+
data.tar.gz: ca65a5b0531ba4540de1b79fd36d247c7c164f3aef8b397254ed7b34503ebf0f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 236c6169ef41b1af9043f2549248ae43fad665a91d43cdd2f579e891685788aa5a008cf4f1dc8aba13abcce5dccfa6513c82232fdaaf3ab4f42f8a7b1b222bf7
|
7
|
+
data.tar.gz: 9e325f6c1e41eb577f5e0d7320517f5435b9570d7b0274c5b6ad9c593abc2a4a66dc6b6eb10d668617e645e5731804ecd65d4b6403fda63005de129443ed9332
|
data/README.md
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
# Journaled
|
2
2
|
|
3
|
-
A Rails engine to durably deliver schematized events to Amazon Kinesis via
|
3
|
+
A Rails engine to durably deliver schematized events to Amazon Kinesis via ActiveJob.
|
4
4
|
|
5
5
|
More specifically, `journaled` is composed of three opinionated pieces:
|
6
6
|
schema definition/validation via JSON Schema, transactional enqueueing
|
7
|
-
via
|
7
|
+
via ActiveJob (specifically, via a DB-backed queue adapter), and event
|
8
8
|
transmission via Amazon Kinesis. Our current use-cases include
|
9
9
|
transmitting audit events for durable storage in S3 and/or analytical
|
10
10
|
querying in Amazon Redshift.
|
11
11
|
|
12
12
|
Journaled provides an at-least-once event delivery guarantee assuming
|
13
|
-
|
13
|
+
ActiveJob's queue adapter is not configured to delete jobs on failure.
|
14
14
|
|
15
15
|
Note: Do not use the journaled gem to build an event sourcing solution
|
16
16
|
as it does not guarantee total ordering of events. It's possible we'll
|
@@ -20,9 +20,20 @@ durable, eventually consistent record that discrete events happened.
|
|
20
20
|
|
21
21
|
## Installation
|
22
22
|
|
23
|
-
1.
|
24
|
-
|
23
|
+
1. If you haven't already,
|
24
|
+
[configure ActiveJob](https://guides.rubyonrails.org/active_job_basics.html)
|
25
|
+
to use one of the following queue adapters:
|
25
26
|
|
27
|
+
- `:delayed_job` (via `delayed_job_active_record`)
|
28
|
+
- `:que`
|
29
|
+
- `:good_job`
|
30
|
+
- `:delayed`
|
31
|
+
|
32
|
+
Ensure that your queue adapter is not configured to delete jobs on failure.
|
33
|
+
|
34
|
+
**If you launch your application in production mode and the gem detects that
|
35
|
+
`ActiveJob::Base.queue_adapter` is not in the above list, it will raise an exception
|
36
|
+
and prevent your application from performing unsafe journaling.**
|
26
37
|
|
27
38
|
2. To integrate Journaled into your application, simply include the gem in your
|
28
39
|
app's Gemfile.
|
@@ -85,9 +96,11 @@ Journaling provides a number of different configuation options that can be set i
|
|
85
96
|
|
86
97
|
#### `Journaled.job_priority` (default: 20)
|
87
98
|
|
88
|
-
This can be used to configure what `priority` the
|
99
|
+
This can be used to configure what `priority` the ActiveJobs are enqueued with. This will be applied to all the `Journaled::DeliveryJob`s that are created by this application.
|
89
100
|
Ex: `Journaled.job_priority = 14`
|
90
101
|
|
102
|
+
_Note that job priority is only supported on Rails 6.0+. Prior Rails versions will ignore this parameter and enqueue jobs with the underlying ActiveJob adapter's default priority._
|
103
|
+
|
91
104
|
#### `Journaled.http_idle_timeout` (default: 1 second)
|
92
105
|
|
93
106
|
The number of seconds a persistent connection is allowed to sit idle before it should no longer be used.
|
@@ -100,9 +113,9 @@ Journaling provides a number of different configuation options that can be set i
|
|
100
113
|
|
101
114
|
The number of seconds before the :http_handler should timeout while waiting for a HTTP response.
|
102
115
|
|
103
|
-
####
|
116
|
+
#### ActiveJob `set` options
|
104
117
|
|
105
|
-
Both model-level directives accept additional options to be passed into
|
118
|
+
Both model-level directives accept additional options to be passed into ActiveJob's `set` method:
|
106
119
|
|
107
120
|
```ruby
|
108
121
|
# For change journaling:
|
data/Rakefile
CHANGED
@@ -28,7 +28,13 @@ if %w(development test).include? Rails.env
|
|
28
28
|
RuboCop::RakeTask.new
|
29
29
|
|
30
30
|
task(:default).clear
|
31
|
-
|
31
|
+
if ENV['APPRAISAL_INITIALIZED'] || ENV['CI']
|
32
|
+
task default: %i(rubocop spec)
|
33
|
+
else
|
34
|
+
require 'appraisal'
|
35
|
+
Appraisal::Task.new
|
36
|
+
task default: :appraisal
|
37
|
+
end
|
32
38
|
|
33
39
|
task 'db:test:prepare' => 'db:setup'
|
34
40
|
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Journaled
|
2
|
+
class DeliveryJob < ApplicationJob
|
3
|
+
DEFAULT_REGION = 'us-east-1'.freeze
|
4
|
+
|
5
|
+
rescue_from(Aws::Kinesis::Errors::InternalFailure, Aws::Kinesis::Errors::ServiceUnavailable, Aws::Kinesis::Errors::Http503Error) do |e|
|
6
|
+
Rails.logger.error "Kinesis Error - Server Error occurred - #{e.class}"
|
7
|
+
raise KinesisTemporaryFailure
|
8
|
+
end
|
9
|
+
|
10
|
+
rescue_from(Seahorse::Client::NetworkingError) do |e|
|
11
|
+
Rails.logger.error "Kinesis Error - Networking Error occurred - #{e.class}"
|
12
|
+
raise KinesisTemporaryFailure
|
13
|
+
end
|
14
|
+
|
15
|
+
def perform(serialized_event:, partition_key:, app_name:)
|
16
|
+
@serialized_event = serialized_event
|
17
|
+
@partition_key = partition_key
|
18
|
+
@app_name = app_name
|
19
|
+
|
20
|
+
journal!
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.stream_name(app_name:)
|
24
|
+
env_var_name = [app_name&.upcase, 'JOURNALED_STREAM_NAME'].compact.join('_')
|
25
|
+
ENV.fetch(env_var_name)
|
26
|
+
end
|
27
|
+
|
28
|
+
def kinesis_client_config
|
29
|
+
{
|
30
|
+
region: ENV.fetch('AWS_DEFAULT_REGION', DEFAULT_REGION),
|
31
|
+
retry_limit: 0,
|
32
|
+
http_idle_timeout: Journaled.http_idle_timeout,
|
33
|
+
http_open_timeout: Journaled.http_open_timeout,
|
34
|
+
http_read_timeout: Journaled.http_read_timeout,
|
35
|
+
}.merge(credentials)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
attr_reader :serialized_event, :partition_key, :app_name
|
41
|
+
|
42
|
+
def journal!
|
43
|
+
kinesis_client.put_record record if Journaled.enabled?
|
44
|
+
end
|
45
|
+
|
46
|
+
def record
|
47
|
+
{
|
48
|
+
stream_name: self.class.stream_name(app_name: app_name),
|
49
|
+
data: serialized_event,
|
50
|
+
partition_key: partition_key,
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
def kinesis_client
|
55
|
+
Aws::Kinesis::Client.new(kinesis_client_config)
|
56
|
+
end
|
57
|
+
|
58
|
+
def credentials
|
59
|
+
if ENV.key?('JOURNALED_IAM_ROLE_ARN')
|
60
|
+
{
|
61
|
+
credentials: iam_assume_role_credentials,
|
62
|
+
}
|
63
|
+
else
|
64
|
+
legacy_credentials_hash_if_present
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def legacy_credentials_hash_if_present
|
69
|
+
if ENV.key?('RUBY_AWS_ACCESS_KEY_ID')
|
70
|
+
{
|
71
|
+
access_key_id: ENV.fetch('RUBY_AWS_ACCESS_KEY_ID'),
|
72
|
+
secret_access_key: ENV.fetch('RUBY_AWS_SECRET_ACCESS_KEY'),
|
73
|
+
}
|
74
|
+
else
|
75
|
+
{}
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def sts_client
|
80
|
+
Aws::STS::Client.new({
|
81
|
+
region: ENV.fetch('AWS_DEFAULT_REGION', DEFAULT_REGION),
|
82
|
+
}.merge(legacy_credentials_hash_if_present))
|
83
|
+
end
|
84
|
+
|
85
|
+
def iam_assume_role_credentials
|
86
|
+
@iam_assume_role_credentials ||= Aws::AssumeRoleCredentials.new(
|
87
|
+
client: sts_client,
|
88
|
+
role_arn: ENV.fetch('JOURNALED_IAM_ROLE_ARN'),
|
89
|
+
role_session_name: "JournaledAssumeRoleAccess",
|
90
|
+
)
|
91
|
+
end
|
92
|
+
|
93
|
+
class KinesisTemporaryFailure < NotTrulyExceptionalError
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -27,7 +27,9 @@ class Journaled::Writer
|
|
27
27
|
def journal!
|
28
28
|
base_event_json_schema_validator.validate! serialized_event
|
29
29
|
json_schema_validator.validate! serialized_event
|
30
|
-
Journaled
|
30
|
+
Journaled::DeliveryJob
|
31
|
+
.set(journaled_enqueue_opts.reverse_merge(priority: Journaled.job_priority))
|
32
|
+
.perform_later(delivery_perform_args)
|
31
33
|
end
|
32
34
|
|
33
35
|
private
|
@@ -35,12 +37,12 @@ class Journaled::Writer
|
|
35
37
|
attr_reader :journaled_event
|
36
38
|
delegate(*EVENT_METHOD_NAMES, to: :journaled_event)
|
37
39
|
|
38
|
-
def
|
39
|
-
|
40
|
+
def delivery_perform_args
|
41
|
+
{
|
40
42
|
serialized_event: serialized_event,
|
41
43
|
partition_key: journaled_partition_key,
|
42
44
|
app_name: journaled_app_name,
|
43
|
-
|
45
|
+
}
|
44
46
|
end
|
45
47
|
|
46
48
|
def serialized_event
|
data/lib/journaled.rb
CHANGED
@@ -1,17 +1,19 @@
|
|
1
1
|
require "aws-sdk-kinesis"
|
2
|
-
require "
|
2
|
+
require "active_job"
|
3
3
|
require "json-schema"
|
4
4
|
require "request_store"
|
5
5
|
|
6
6
|
require "journaled/engine"
|
7
|
-
require 'journaled/enqueue'
|
8
7
|
|
9
8
|
module Journaled
|
9
|
+
SUPPORTED_QUEUE_ADAPTERS = %w(delayed delayed_job good_job que).freeze
|
10
|
+
|
10
11
|
mattr_accessor :default_app_name
|
11
12
|
mattr_accessor(:job_priority) { 20 }
|
12
13
|
mattr_accessor(:http_idle_timeout) { 5 }
|
13
14
|
mattr_accessor(:http_open_timeout) { 2 }
|
14
15
|
mattr_accessor(:http_read_timeout) { 60 }
|
16
|
+
mattr_accessor(:job_base_class_name) { 'ActiveJob::Base' }
|
15
17
|
|
16
18
|
def development_or_test?
|
17
19
|
%w(development test).include?(Rails.env)
|
@@ -33,5 +35,21 @@ module Journaled
|
|
33
35
|
Journaled::ActorUriProvider.instance.actor_uri
|
34
36
|
end
|
35
37
|
|
36
|
-
|
38
|
+
def detect_queue_adapter!
|
39
|
+
adapter = job_base_class_name.constantize.queue_adapter.class.name.split('::').last.underscore.gsub("_adapter", "")
|
40
|
+
unless SUPPORTED_QUEUE_ADAPTERS.include?(adapter)
|
41
|
+
raise <<~MSG
|
42
|
+
Journaled has detected an unsupported ActiveJob queue adapter: `:#{adapter}`
|
43
|
+
|
44
|
+
Journaled jobs must be enqueued transactionally to your primary database.
|
45
|
+
|
46
|
+
Please install the appropriate gems and set `queue_adapter` to one of the following:
|
47
|
+
#{SUPPORTED_QUEUE_ADAPTERS.map { |a| "- `:#{a}`" }.join("\n")}
|
48
|
+
|
49
|
+
Read more at https://github.com/Betterment/journaled
|
50
|
+
MSG
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
module_function :development_or_test?, :enabled?, :schema_providers, :commit_hash, :actor_uri, :detect_queue_adapter!
|
37
55
|
end
|
data/lib/journaled/engine.rb
CHANGED
data/lib/journaled/version.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
require File.expand_path('boot', __dir__)
|
2
2
|
|
3
3
|
require "active_record/railtie"
|
4
|
+
require "active_job/railtie"
|
4
5
|
require "active_model/railtie"
|
5
6
|
require "action_controller/railtie"
|
6
|
-
require "action_mailer/railtie"
|
7
|
-
require "sprockets/railtie"
|
8
7
|
|
9
8
|
Bundler.require(*Rails.groups)
|
10
9
|
require "journaled"
|
@@ -1,21 +1,6 @@
|
|
1
|
-
default: &default
|
2
|
-
pool: 5
|
3
|
-
encoding: unicode
|
4
|
-
|
5
|
-
postgresql:
|
6
|
-
default: &postgres_default
|
7
|
-
adapter: postgresql
|
8
|
-
url: <%= ENV['DATABASE_URL'] %>
|
9
|
-
test: &postgres_test
|
10
|
-
<<: *postgres_default
|
11
|
-
url: <%= ENV['DATABASE_URL'] || "postgresql://localhost:#{ENV.fetch('PGPORT', 5432)}/journaled_test" %>
|
12
|
-
database: journaled_test
|
13
|
-
development: &postgres_development
|
14
|
-
<<: *postgres_default
|
15
|
-
url: <%= ENV['DATABASE_URL'] || "postgresql://localhost:#{ENV.fetch('PGPORT', 5432)}/journaled_development" %>
|
16
|
-
database: journaled_development
|
17
|
-
|
18
1
|
development:
|
19
|
-
|
2
|
+
adapter: sqlite3
|
3
|
+
database: ":memory:"
|
20
4
|
test:
|
21
|
-
|
5
|
+
adapter: sqlite3
|
6
|
+
database: ":memory:"
|
@@ -13,25 +13,12 @@ Rails.application.configure do
|
|
13
13
|
config.consider_all_requests_local = true
|
14
14
|
config.action_controller.perform_caching = false
|
15
15
|
|
16
|
-
# Don't care if the mailer can't send.
|
17
|
-
config.action_mailer.raise_delivery_errors = false
|
18
|
-
|
19
16
|
# Print deprecation notices to the Rails logger.
|
20
17
|
config.active_support.deprecation = :log
|
21
18
|
|
22
19
|
# Raise an error on page load if there are pending migrations.
|
23
20
|
config.active_record.migration_error = :page_load
|
24
21
|
|
25
|
-
# Debug mode disables concatenation and preprocessing of assets.
|
26
|
-
# This option may cause significant delays in view rendering with a large
|
27
|
-
# number of complex assets.
|
28
|
-
config.assets.debug = true
|
29
|
-
|
30
|
-
# Adds additional error checking when serving assets at runtime.
|
31
|
-
# Checks for improperly declared sprockets dependencies.
|
32
|
-
# Raises helpful error messages.
|
33
|
-
config.assets.raise_runtime_errors = true
|
34
|
-
|
35
22
|
# Raises error for missing translations
|
36
23
|
# config.action_view.raise_on_missing_translations = true
|
37
24
|
end
|
@@ -26,14 +26,12 @@ Rails.application.configure do
|
|
26
26
|
# Disable request forgery protection in test environment.
|
27
27
|
config.action_controller.allow_forgery_protection = false
|
28
28
|
|
29
|
-
# Tell Action Mailer not to deliver emails to the real world.
|
30
|
-
# The :test delivery method accumulates sent emails in the
|
31
|
-
# ActionMailer::Base.deliveries array.
|
32
|
-
config.action_mailer.delivery_method = :test
|
33
|
-
|
34
29
|
# Print deprecation notices to the stderr.
|
35
30
|
config.active_support.deprecation = :stderr
|
36
31
|
|
37
32
|
# Raises error for missing translations
|
38
33
|
# config.action_view.raise_on_missing_translations = true
|
34
|
+
|
35
|
+
# Use ActiveJob test adapter
|
36
|
+
config.active_job.queue_adapter = :test
|
39
37
|
end
|
data/spec/dummy/db/schema.rb
CHANGED
@@ -11,21 +11,8 @@
|
|
11
11
|
# It's strongly recommended that you check this file into your version control system.
|
12
12
|
|
13
13
|
ActiveRecord::Schema.define(version: 20180606205114) do
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
create_table "delayed_jobs", force: :cascade do |t|
|
18
|
-
t.integer "priority", default: 0, null: false
|
19
|
-
t.integer "attempts", default: 0, null: false
|
20
|
-
t.text "handler", null: false
|
21
|
-
t.text "last_error"
|
22
|
-
t.datetime "run_at"
|
23
|
-
t.datetime "locked_at"
|
24
|
-
t.datetime "failed_at"
|
25
|
-
t.string "locked_by"
|
26
|
-
t.string "queue"
|
27
|
-
t.datetime "created_at"
|
28
|
-
t.datetime "updated_at"
|
29
|
-
t.index ["priority", "run_at"], name: "delayed_jobs_priority"
|
14
|
+
create_table "widgets", force: :cascade do |t|
|
15
|
+
t.string "name"
|
16
|
+
t.string "other_column"
|
30
17
|
end
|
31
18
|
end
|
@@ -0,0 +1,221 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
RSpec.describe Journaled::DeliveryJob do
|
4
|
+
let(:stream_name) { 'test_events' }
|
5
|
+
let(:partition_key) { 'fake_partition_key' }
|
6
|
+
let(:serialized_event) { '{"foo":"bar"}' }
|
7
|
+
let(:kinesis_client) { Aws::Kinesis::Client.new(stub_responses: true) }
|
8
|
+
let(:args) { { serialized_event: serialized_event, partition_key: partition_key, app_name: nil } }
|
9
|
+
|
10
|
+
around do |example|
|
11
|
+
with_env(JOURNALED_STREAM_NAME: stream_name) { example.run }
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '#perform' do
|
15
|
+
let(:return_status_body) { { shard_id: '101', sequence_number: '101123' } }
|
16
|
+
let(:return_object) { instance_double Aws::Kinesis::Types::PutRecordOutput, return_status_body }
|
17
|
+
|
18
|
+
before do
|
19
|
+
allow(Aws::AssumeRoleCredentials).to receive(:new).and_call_original
|
20
|
+
allow(Aws::Kinesis::Client).to receive(:new).and_return kinesis_client
|
21
|
+
kinesis_client.stub_responses(:put_record, return_status_body)
|
22
|
+
|
23
|
+
allow(Journaled).to receive(:enabled?).and_return(true)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'makes requests to AWS to put the event on the Kinesis with the correct body' do
|
27
|
+
event = described_class.perform_now(args)
|
28
|
+
|
29
|
+
expect(event.shard_id).to eq '101'
|
30
|
+
expect(event.sequence_number).to eq '101123'
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'when JOURNALED_IAM_ROLE_ARN is defined' do
|
34
|
+
let(:aws_sts_client) { Aws::STS::Client.new(stub_responses: true) }
|
35
|
+
|
36
|
+
around do |example|
|
37
|
+
with_env(JOURNALED_IAM_ROLE_ARN: 'iam-role-arn-for-assuming-kinesis-access') { example.run }
|
38
|
+
end
|
39
|
+
|
40
|
+
before do
|
41
|
+
allow(Aws::STS::Client).to receive(:new).and_return aws_sts_client
|
42
|
+
aws_sts_client.stub_responses(:assume_role, assume_role_response)
|
43
|
+
end
|
44
|
+
|
45
|
+
let(:assume_role_response) do
|
46
|
+
{
|
47
|
+
assumed_role_user: {
|
48
|
+
arn: 'iam-role-arn-for-assuming-kinesis-access',
|
49
|
+
assumed_role_id: "ARO123EXAMPLE123:Bob",
|
50
|
+
},
|
51
|
+
credentials: {
|
52
|
+
access_key_id: "AKIAIOSFODNN7EXAMPLE",
|
53
|
+
secret_access_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY",
|
54
|
+
session_token: "EXAMPLEtc764bNrC9SAPBSM22wDOk4x4HIZ8j4FZTwdQWLWsKWHGBuFqwAeMicRXmxfpSPfIeoIYRqTflfKD8YUuwthAx7mSEI",
|
55
|
+
expiration: Time.zone.parse("2011-07-15T23:28:33.359Z"),
|
56
|
+
},
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'initializes a Kinesis client with assume role credentials' do
|
61
|
+
described_class.perform_now(args)
|
62
|
+
|
63
|
+
expect(Aws::AssumeRoleCredentials).to have_received(:new).with(
|
64
|
+
client: aws_sts_client,
|
65
|
+
role_arn: "iam-role-arn-for-assuming-kinesis-access",
|
66
|
+
role_session_name: "JournaledAssumeRoleAccess",
|
67
|
+
)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'when the stream name env var is NOT set' do
|
72
|
+
let(:stream_name) { nil }
|
73
|
+
|
74
|
+
it 'raises an KeyError error' do
|
75
|
+
expect { described_class.perform_now(args) }.to raise_error KeyError
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'when Amazon responds with an InternalFailure' do
|
80
|
+
before do
|
81
|
+
kinesis_client.stub_responses(:put_record, 'InternalFailure')
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'catches the error and re-raises a subclass of NotTrulyExceptionalError and logs about the failure' do
|
85
|
+
allow(Rails.logger).to receive(:error)
|
86
|
+
expect { described_class.perform_now(args) }.to raise_error described_class::KinesisTemporaryFailure
|
87
|
+
expect(Rails.logger).to have_received(:error).with(
|
88
|
+
"Kinesis Error - Server Error occurred - Aws::Kinesis::Errors::InternalFailure",
|
89
|
+
).once
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context 'when Amazon responds with a ServiceUnavailable' do
|
94
|
+
before do
|
95
|
+
kinesis_client.stub_responses(:put_record, 'ServiceUnavailable')
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'catches the error and re-raises a subclass of NotTrulyExceptionalError and logs about the failure' do
|
99
|
+
allow(Rails.logger).to receive(:error)
|
100
|
+
expect { described_class.perform_now(args) }.to raise_error described_class::KinesisTemporaryFailure
|
101
|
+
expect(Rails.logger).to have_received(:error).with(/\AKinesis Error/).once
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context 'when we receive a 504 Gateway timeout' do
|
106
|
+
before do
|
107
|
+
kinesis_client.stub_responses(:put_record, 'Aws::Kinesis::Errors::ServiceError')
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'raises an error that subclasses Aws::Kinesis::Errors::ServiceError' do
|
111
|
+
expect { described_class.perform_now(args) }.to raise_error Aws::Kinesis::Errors::ServiceError
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context 'when the IAM user does not have permission to put_record to the specified stream' do
|
116
|
+
before do
|
117
|
+
kinesis_client.stub_responses(:put_record, 'AccessDeniedException')
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'raises an AccessDeniedException error' do
|
121
|
+
expect { described_class.perform_now(args) }.to raise_error Aws::Kinesis::Errors::AccessDeniedException
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context 'when the request timesout' do
|
126
|
+
before do
|
127
|
+
kinesis_client.stub_responses(:put_record, Seahorse::Client::NetworkingError.new(Timeout::Error.new))
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'catches the error and re-raises a subclass of NotTrulyExceptionalError and logs about the failure' do
|
131
|
+
allow(Rails.logger).to receive(:error)
|
132
|
+
expect { described_class.perform_now(args) }.to raise_error described_class::KinesisTemporaryFailure
|
133
|
+
expect(Rails.logger).to have_received(:error).with(
|
134
|
+
"Kinesis Error - Networking Error occurred - Seahorse::Client::NetworkingError",
|
135
|
+
).once
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe ".stream_name" do
|
141
|
+
context "when app_name is unspecified" do
|
142
|
+
it "is fetched from a prefixed ENV var if specified" do
|
143
|
+
allow(ENV).to receive(:fetch).and_return("expected_stream_name")
|
144
|
+
expect(described_class.stream_name(app_name: nil)).to eq("expected_stream_name")
|
145
|
+
expect(ENV).to have_received(:fetch).with("JOURNALED_STREAM_NAME")
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
context "when app_name is specified" do
|
150
|
+
it "is fetched from a prefixed ENV var if specified" do
|
151
|
+
allow(ENV).to receive(:fetch).and_return("expected_stream_name")
|
152
|
+
expect(described_class.stream_name(app_name: "my_funky_app_name")).to eq("expected_stream_name")
|
153
|
+
expect(ENV).to have_received(:fetch).with("MY_FUNKY_APP_NAME_JOURNALED_STREAM_NAME")
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
describe "#kinesis_client_config" do
|
159
|
+
it "is in us-east-1 by default" do
|
160
|
+
with_env(AWS_DEFAULT_REGION: nil) do
|
161
|
+
expect(subject.kinesis_client_config).to include(region: 'us-east-1')
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
it "respects AWS_DEFAULT_REGION env var" do
|
166
|
+
with_env(AWS_DEFAULT_REGION: 'us-west-2') do
|
167
|
+
expect(subject.kinesis_client_config).to include(region: 'us-west-2')
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
it "doesn't limit retry" do
|
172
|
+
expect(subject.kinesis_client_config).to include(retry_limit: 0)
|
173
|
+
end
|
174
|
+
|
175
|
+
it "provides no AWS credentials by default" do
|
176
|
+
with_env(RUBY_AWS_ACCESS_KEY_ID: nil, RUBY_AWS_SECRET_ACCESS_KEY: nil) do
|
177
|
+
expect(subject.kinesis_client_config).not_to have_key(:access_key_id)
|
178
|
+
expect(subject.kinesis_client_config).not_to have_key(:secret_access_key)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
it "will use legacy credentials if specified" do
|
183
|
+
with_env(RUBY_AWS_ACCESS_KEY_ID: 'key_id', RUBY_AWS_SECRET_ACCESS_KEY: 'secret') do
|
184
|
+
expect(subject.kinesis_client_config).to include(access_key_id: 'key_id', secret_access_key: 'secret')
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
it "will set http_idle_timeout by default" do
|
189
|
+
expect(subject.kinesis_client_config).to include(http_idle_timeout: 5)
|
190
|
+
end
|
191
|
+
|
192
|
+
it "will set http_open_timeout by default" do
|
193
|
+
expect(subject.kinesis_client_config).to include(http_open_timeout: 2)
|
194
|
+
end
|
195
|
+
|
196
|
+
it "will set http_read_timeout by default" do
|
197
|
+
expect(subject.kinesis_client_config).to include(http_read_timeout: 60)
|
198
|
+
end
|
199
|
+
|
200
|
+
context "when Journaled.http_idle_timeout is specified" do
|
201
|
+
it "will set http_idle_timeout by specified value" do
|
202
|
+
allow(Journaled).to receive(:http_idle_timeout).and_return(2)
|
203
|
+
expect(subject.kinesis_client_config).to include(http_idle_timeout: 2)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
context "when Journaled.http_open_timeout is specified" do
|
208
|
+
it "will set http_open_timeout by specified value" do
|
209
|
+
allow(Journaled).to receive(:http_open_timeout).and_return(1)
|
210
|
+
expect(subject.kinesis_client_config).to include(http_open_timeout: 1)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
context "when Journaled.http_read_timeout is specified" do
|
215
|
+
it "will set http_read_timeout by specified value" do
|
216
|
+
allow(Journaled).to receive(:http_read_timeout).and_return(2)
|
217
|
+
expect(subject.kinesis_client_config).to include(http_read_timeout: 2)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
data/spec/lib/journaled_spec.rb
CHANGED
@@ -49,4 +49,43 @@ RSpec.describe Journaled do
|
|
49
49
|
expect(described_class.actor_uri).to eq "my actor uri"
|
50
50
|
end
|
51
51
|
end
|
52
|
+
|
53
|
+
describe '.detect_queue_adapter!' do
|
54
|
+
it 'raises an error unless the queue adapter is DB-backed' do
|
55
|
+
expect { described_class.detect_queue_adapter! }.to raise_error <<~MSG
|
56
|
+
Journaled has detected an unsupported ActiveJob queue adapter: `:test`
|
57
|
+
|
58
|
+
Journaled jobs must be enqueued transactionally to your primary database.
|
59
|
+
|
60
|
+
Please install the appropriate gems and set `queue_adapter` to one of the following:
|
61
|
+
- `:delayed`
|
62
|
+
- `:delayed_job`
|
63
|
+
- `:good_job`
|
64
|
+
- `:que`
|
65
|
+
|
66
|
+
Read more at https://github.com/Betterment/journaled
|
67
|
+
MSG
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'when the queue adapter is supported' do
|
71
|
+
before do
|
72
|
+
stub_const("ActiveJob::QueueAdapters::DelayedAdapter", Class.new)
|
73
|
+
ActiveJob::Base.disable_test_adapter
|
74
|
+
ActiveJob::Base.queue_adapter = :delayed
|
75
|
+
end
|
76
|
+
|
77
|
+
around do |example|
|
78
|
+
begin
|
79
|
+
example.run
|
80
|
+
ensure
|
81
|
+
ActiveJob::Base.queue_adapter = :test
|
82
|
+
ActiveJob::Base.enable_test_adapter(ActiveJob::QueueAdapters::TestAdapter.new)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'does not raise an error' do
|
87
|
+
expect { described_class.detect_queue_adapter! }.not_to raise_error
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
52
91
|
end
|
@@ -4,38 +4,42 @@ if Rails::VERSION::MAJOR > 5 || (Rails::VERSION::MAJOR == 5 && Rails::VERSION::M
|
|
4
4
|
# rubocop:disable Rails/SkipsModelValidations
|
5
5
|
RSpec.describe "Raw database change protection" do
|
6
6
|
let(:journaled_class) do
|
7
|
-
Class.new(
|
7
|
+
Class.new(ActiveRecord::Base) do
|
8
8
|
include Journaled::Changes
|
9
9
|
|
10
|
-
|
10
|
+
self.table_name = 'widgets'
|
11
|
+
|
12
|
+
journal_changes_to :name, as: :attempt
|
11
13
|
end
|
12
14
|
end
|
13
15
|
|
14
16
|
let(:journaled_class_with_no_journaled_columns) do
|
15
|
-
Class.new(
|
17
|
+
Class.new(ActiveRecord::Base) do
|
16
18
|
include Journaled::Changes
|
19
|
+
|
20
|
+
self.table_name = 'widgets'
|
17
21
|
end
|
18
22
|
end
|
19
23
|
|
20
24
|
describe "the relation" do
|
21
25
|
describe "#update_all" do
|
22
26
|
it "refuses on journaled columns passed as hash" do
|
23
|
-
expect { journaled_class.update_all(
|
27
|
+
expect { journaled_class.update_all(name: nil) }.to raise_error(/aborted by Journaled/)
|
24
28
|
end
|
25
29
|
|
26
30
|
it "refuses on journaled columns passed as string" do
|
27
|
-
expect { journaled_class.update_all("\"
|
28
|
-
expect { journaled_class.update_all("
|
29
|
-
expect { journaled_class.update_all("
|
30
|
-
expect { journaled_class.update_all("
|
31
|
+
expect { journaled_class.update_all("\"name\" = NULL") }.to raise_error(/aborted by Journaled/)
|
32
|
+
expect { journaled_class.update_all("name = null") }.to raise_error(/aborted by Journaled/)
|
33
|
+
expect { journaled_class.update_all("widgets.name = null") }.to raise_error(/aborted by Journaled/)
|
34
|
+
expect { journaled_class.update_all("other_column = 'name'") }.not_to raise_error
|
31
35
|
end
|
32
36
|
|
33
37
|
it "succeeds on unjournaled columns" do
|
34
|
-
expect { journaled_class.update_all(
|
38
|
+
expect { journaled_class.update_all(other_column: "") }.not_to raise_error
|
35
39
|
end
|
36
40
|
|
37
41
|
it "succeeds when forced on journaled columns" do
|
38
|
-
expect { journaled_class.update_all({
|
42
|
+
expect { journaled_class.update_all({ name: nil }, force: true) }.not_to raise_error
|
39
43
|
end
|
40
44
|
end
|
41
45
|
|
@@ -69,29 +73,19 @@ if Rails::VERSION::MAJOR > 5 || (Rails::VERSION::MAJOR == 5 && Rails::VERSION::M
|
|
69
73
|
end
|
70
74
|
|
71
75
|
describe "an instance" do
|
72
|
-
|
73
|
-
module TestJob
|
74
|
-
def perform
|
75
|
-
"foo"
|
76
|
-
end
|
77
|
-
|
78
|
-
module_function :perform
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
subject { journaled_class.enqueue(job) }
|
76
|
+
subject { journaled_class.create!(name: 'foo') }
|
83
77
|
|
84
78
|
describe "#update_columns" do
|
85
79
|
it "refuses on journaled columns" do
|
86
|
-
expect { subject.update_columns(
|
80
|
+
expect { subject.update_columns(name: nil) }.to raise_error(/aborted by Journaled/)
|
87
81
|
end
|
88
82
|
|
89
83
|
it "succeeds on unjournaled columns" do
|
90
|
-
expect { subject.update_columns(
|
84
|
+
expect { subject.update_columns(other_column: "") }.not_to raise_error
|
91
85
|
end
|
92
86
|
|
93
87
|
it "succeeds when forced on journaled columns" do
|
94
|
-
expect { subject.update_columns({
|
88
|
+
expect { subject.update_columns({ name: nil }, force: true) }.not_to raise_error
|
95
89
|
end
|
96
90
|
end
|
97
91
|
|
@@ -101,7 +95,7 @@ if Rails::VERSION::MAJOR > 5 || (Rails::VERSION::MAJOR == 5 && Rails::VERSION::M
|
|
101
95
|
end
|
102
96
|
|
103
97
|
it "succeeds if no journaled columns exist" do
|
104
|
-
instance = journaled_class_with_no_journaled_columns.
|
98
|
+
instance = journaled_class_with_no_journaled_columns.create!
|
105
99
|
expect { instance.delete }.not_to raise_error
|
106
100
|
end
|
107
101
|
|
@@ -80,16 +80,12 @@ RSpec.describe Journaled::Writer do
|
|
80
80
|
)
|
81
81
|
end
|
82
82
|
|
83
|
-
around do |example|
|
84
|
-
with_jobs_delayed { example.run }
|
85
|
-
end
|
86
|
-
|
87
83
|
context 'when the journaled event does NOT comply with the base_event schema' do
|
88
84
|
let(:journaled_event_attributes) { { foo: 1 } }
|
89
85
|
|
90
86
|
it 'raises an error and does not enqueue anything' do
|
91
87
|
expect { subject.journal! }.to raise_error JSON::Schema::ValidationError
|
92
|
-
expect(
|
88
|
+
expect(enqueued_jobs.count).to eq 0
|
93
89
|
end
|
94
90
|
end
|
95
91
|
|
@@ -99,7 +95,7 @@ RSpec.describe Journaled::Writer do
|
|
99
95
|
|
100
96
|
it 'raises an error and does not enqueue anything' do
|
101
97
|
expect { subject.journal! }.to raise_error JSON::Schema::ValidationError
|
102
|
-
expect(
|
98
|
+
expect(enqueued_jobs.count).to eq 0
|
103
99
|
end
|
104
100
|
end
|
105
101
|
|
@@ -107,9 +103,8 @@ RSpec.describe Journaled::Writer do
|
|
107
103
|
let(:journaled_event_attributes) { { id: 'FAKE_UUID', event_type: 'fake_event', created_at: Time.zone.now, foo: :bar } }
|
108
104
|
|
109
105
|
it 'creates a delivery with the app name passed through' do
|
110
|
-
|
111
|
-
|
112
|
-
expect(Journaled::Delivery).to have_received(:new).with(hash_including(app_name: 'my_app'))
|
106
|
+
expect { subject.journal! }.to change { enqueued_jobs.count }.from(0).to(1)
|
107
|
+
expect(enqueued_jobs.first[:args].first).to include('app_name' => 'my_app')
|
113
108
|
end
|
114
109
|
|
115
110
|
context 'when there is no job priority specified in the enqueue opts' do
|
@@ -122,7 +117,11 @@ RSpec.describe Journaled::Writer do
|
|
122
117
|
|
123
118
|
it 'defaults to the global default' do
|
124
119
|
expect { subject.journal! }.to change {
|
125
|
-
|
120
|
+
if Rails::VERSION::MAJOR < 6
|
121
|
+
enqueued_jobs.select { |j| j[:job] == Journaled::DeliveryJob }.count
|
122
|
+
else
|
123
|
+
enqueued_jobs.select { |j| j['job_class'] == 'Journaled::DeliveryJob' && j['priority'] == 999 }.count
|
124
|
+
end
|
126
125
|
}.from(0).to(1)
|
127
126
|
end
|
128
127
|
end
|
@@ -130,9 +129,13 @@ RSpec.describe Journaled::Writer do
|
|
130
129
|
context 'when there is a job priority specified in the enqueue opts' do
|
131
130
|
let(:journaled_enqueue_opts) { { priority: 13 } }
|
132
131
|
|
133
|
-
it 'enqueues a Journaled::
|
132
|
+
it 'enqueues a Journaled::DeliveryJob with the given priority' do
|
134
133
|
expect { subject.journal! }.to change {
|
135
|
-
|
134
|
+
if Rails::VERSION::MAJOR < 6
|
135
|
+
enqueued_jobs.select { |j| j[:job] == Journaled::DeliveryJob }.count
|
136
|
+
else
|
137
|
+
enqueued_jobs.select { |j| j['job_class'] == 'Journaled::DeliveryJob' && j['priority'] == 13 }.count
|
138
|
+
end
|
136
139
|
}.from(0).to(1)
|
137
140
|
end
|
138
141
|
end
|
data/spec/rails_helper.rb
CHANGED
@@ -6,7 +6,6 @@ require 'rspec/rails'
|
|
6
6
|
require 'timecop'
|
7
7
|
require 'webmock/rspec'
|
8
8
|
require 'journaled/rspec'
|
9
|
-
require 'pry-rails'
|
10
9
|
|
11
10
|
Dir[Rails.root.join('..', 'support', '**', '*.rb')].each { |f| require f }
|
12
11
|
|
@@ -15,6 +14,6 @@ RSpec.configure do |config|
|
|
15
14
|
|
16
15
|
config.infer_spec_type_from_file_location!
|
17
16
|
|
18
|
-
config.include
|
17
|
+
config.include ActiveJob::TestHelper
|
19
18
|
config.include EnvironmentSpecHelper
|
20
19
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,9 +1,7 @@
|
|
1
|
-
require 'delayed_job_active_record'
|
2
1
|
rails_env = ENV['RAILS_ENV'] ||= 'test'
|
3
|
-
db_adapter = ENV['DB_ADAPTER'] ||= 'postgresql'
|
4
2
|
require File.expand_path('dummy/config/environment.rb', __dir__)
|
5
3
|
|
6
|
-
Rails.configuration.database_configuration[
|
4
|
+
Rails.configuration.database_configuration[rails_env].tap do |c|
|
7
5
|
ActiveRecord::Tasks::DatabaseTasks.create(c)
|
8
6
|
ActiveRecord::Base.establish_connection(c)
|
9
7
|
load File.expand_path('dummy/db/schema.rb', __dir__)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: journaled
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jake Lipson
|
@@ -11,24 +11,24 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2021-
|
14
|
+
date: 2021-05-18 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
|
-
name:
|
17
|
+
name: activejob
|
18
18
|
requirement: !ruby/object:Gem::Requirement
|
19
19
|
requirements:
|
20
|
-
- - "
|
20
|
+
- - ">="
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: '
|
22
|
+
version: '0'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
26
26
|
requirements:
|
27
|
-
- - "
|
27
|
+
- - ">="
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: '
|
29
|
+
version: '0'
|
30
30
|
- !ruby/object:Gem::Dependency
|
31
|
-
name:
|
31
|
+
name: activerecord
|
32
32
|
requirement: !ruby/object:Gem::Requirement
|
33
33
|
requirements:
|
34
34
|
- - ">="
|
@@ -41,6 +41,20 @@ dependencies:
|
|
41
41
|
- - ">="
|
42
42
|
- !ruby/object:Gem::Version
|
43
43
|
version: '0'
|
44
|
+
- !ruby/object:Gem::Dependency
|
45
|
+
name: aws-sdk-kinesis
|
46
|
+
requirement: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - "<"
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '2'
|
51
|
+
type: :runtime
|
52
|
+
prerelease: false
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - "<"
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '2'
|
44
58
|
- !ruby/object:Gem::Dependency
|
45
59
|
name: json-schema
|
46
60
|
requirement: !ruby/object:Gem::Requirement
|
@@ -56,7 +70,7 @@ dependencies:
|
|
56
70
|
- !ruby/object:Gem::Version
|
57
71
|
version: '0'
|
58
72
|
- !ruby/object:Gem::Dependency
|
59
|
-
name:
|
73
|
+
name: railties
|
60
74
|
requirement: !ruby/object:Gem::Requirement
|
61
75
|
requirements:
|
62
76
|
- - ">="
|
@@ -103,48 +117,6 @@ dependencies:
|
|
103
117
|
- - "~>"
|
104
118
|
- !ruby/object:Gem::Version
|
105
119
|
version: 2.2.0
|
106
|
-
- !ruby/object:Gem::Dependency
|
107
|
-
name: delayed_job_active_record
|
108
|
-
requirement: !ruby/object:Gem::Requirement
|
109
|
-
requirements:
|
110
|
-
- - ">="
|
111
|
-
- !ruby/object:Gem::Version
|
112
|
-
version: '0'
|
113
|
-
type: :development
|
114
|
-
prerelease: false
|
115
|
-
version_requirements: !ruby/object:Gem::Requirement
|
116
|
-
requirements:
|
117
|
-
- - ">="
|
118
|
-
- !ruby/object:Gem::Version
|
119
|
-
version: '0'
|
120
|
-
- !ruby/object:Gem::Dependency
|
121
|
-
name: pg
|
122
|
-
requirement: !ruby/object:Gem::Requirement
|
123
|
-
requirements:
|
124
|
-
- - ">="
|
125
|
-
- !ruby/object:Gem::Version
|
126
|
-
version: '0'
|
127
|
-
type: :development
|
128
|
-
prerelease: false
|
129
|
-
version_requirements: !ruby/object:Gem::Requirement
|
130
|
-
requirements:
|
131
|
-
- - ">="
|
132
|
-
- !ruby/object:Gem::Version
|
133
|
-
version: '0'
|
134
|
-
- !ruby/object:Gem::Dependency
|
135
|
-
name: pry-rails
|
136
|
-
requirement: !ruby/object:Gem::Requirement
|
137
|
-
requirements:
|
138
|
-
- - ">="
|
139
|
-
- !ruby/object:Gem::Version
|
140
|
-
version: '0'
|
141
|
-
type: :development
|
142
|
-
prerelease: false
|
143
|
-
version_requirements: !ruby/object:Gem::Requirement
|
144
|
-
requirements:
|
145
|
-
- - ">="
|
146
|
-
- !ruby/object:Gem::Version
|
147
|
-
version: '0'
|
148
120
|
- !ruby/object:Gem::Dependency
|
149
121
|
name: rspec-rails
|
150
122
|
requirement: !ruby/object:Gem::Requirement
|
@@ -216,19 +188,19 @@ dependencies:
|
|
216
188
|
- !ruby/object:Gem::Version
|
217
189
|
version: '0'
|
218
190
|
- !ruby/object:Gem::Dependency
|
219
|
-
name:
|
191
|
+
name: sqlite3
|
220
192
|
requirement: !ruby/object:Gem::Requirement
|
221
193
|
requirements:
|
222
|
-
- - "
|
194
|
+
- - ">="
|
223
195
|
- !ruby/object:Gem::Version
|
224
|
-
version: '
|
196
|
+
version: '0'
|
225
197
|
type: :development
|
226
198
|
prerelease: false
|
227
199
|
version_requirements: !ruby/object:Gem::Requirement
|
228
200
|
requirements:
|
229
|
-
- - "
|
201
|
+
- - ">="
|
230
202
|
- !ruby/object:Gem::Version
|
231
|
-
version: '
|
203
|
+
version: '0'
|
232
204
|
- !ruby/object:Gem::Dependency
|
233
205
|
name: timecop
|
234
206
|
requirement: !ruby/object:Gem::Requirement
|
@@ -272,6 +244,8 @@ files:
|
|
272
244
|
- README.md
|
273
245
|
- Rakefile
|
274
246
|
- app/controllers/concerns/journaled/actor.rb
|
247
|
+
- app/jobs/journaled/application_job.rb
|
248
|
+
- app/jobs/journaled/delivery_job.rb
|
275
249
|
- app/models/concerns/journaled/changes.rb
|
276
250
|
- app/models/journaled/actor_uri_provider.rb
|
277
251
|
- app/models/journaled/change.rb
|
@@ -283,13 +257,11 @@ files:
|
|
283
257
|
- app/models/journaled/not_truly_exceptional_error.rb
|
284
258
|
- app/models/journaled/writer.rb
|
285
259
|
- config/initializers/change_protection.rb
|
286
|
-
- config/routes.rb
|
287
260
|
- config/spring.rb
|
288
261
|
- journaled_schemas/base_event.json
|
289
262
|
- journaled_schemas/journaled/change.json
|
290
263
|
- lib/journaled.rb
|
291
264
|
- lib/journaled/engine.rb
|
292
|
-
- lib/journaled/enqueue.rb
|
293
265
|
- lib/journaled/relation_change_protection.rb
|
294
266
|
- lib/journaled/rspec.rb
|
295
267
|
- lib/journaled/version.rb
|
@@ -304,9 +276,7 @@ files:
|
|
304
276
|
- spec/dummy/config/database.yml
|
305
277
|
- spec/dummy/config/environment.rb
|
306
278
|
- spec/dummy/config/environments/development.rb
|
307
|
-
- spec/dummy/config/environments/production.rb
|
308
279
|
- spec/dummy/config/environments/test.rb
|
309
|
-
- spec/dummy/config/initializers/assets.rb
|
310
280
|
- spec/dummy/config/initializers/backtrace_silencers.rb
|
311
281
|
- spec/dummy/config/initializers/cookies_serializer.rb
|
312
282
|
- spec/dummy/config/initializers/filter_parameter_logging.rb
|
@@ -317,12 +287,12 @@ files:
|
|
317
287
|
- spec/dummy/config/locales/en.yml
|
318
288
|
- spec/dummy/config/routes.rb
|
319
289
|
- spec/dummy/config/secrets.yml
|
320
|
-
- spec/dummy/db/migrate/20180606205114_create_delayed_jobs.rb
|
321
290
|
- spec/dummy/db/schema.rb
|
322
291
|
- spec/dummy/public/404.html
|
323
292
|
- spec/dummy/public/422.html
|
324
293
|
- spec/dummy/public/500.html
|
325
294
|
- spec/dummy/public/favicon.ico
|
295
|
+
- spec/jobs/journaled/delivery_job_spec.rb
|
326
296
|
- spec/lib/journaled_spec.rb
|
327
297
|
- spec/models/concerns/journaled/actor_spec.rb
|
328
298
|
- spec/models/concerns/journaled/changes_spec.rb
|
@@ -335,7 +305,6 @@ files:
|
|
335
305
|
- spec/models/journaled/writer_spec.rb
|
336
306
|
- spec/rails_helper.rb
|
337
307
|
- spec/spec_helper.rb
|
338
|
-
- spec/support/delayed_job_spec_helper.rb
|
339
308
|
- spec/support/environment_spec_helper.rb
|
340
309
|
homepage: http://github.com/Betterment/journaled
|
341
310
|
licenses:
|
@@ -368,7 +337,6 @@ test_files:
|
|
368
337
|
- spec/dummy/config/secrets.yml
|
369
338
|
- spec/dummy/config/routes.rb
|
370
339
|
- spec/dummy/config/locales/en.yml
|
371
|
-
- spec/dummy/config/environments/production.rb
|
372
340
|
- spec/dummy/config/environments/development.rb
|
373
341
|
- spec/dummy/config/environments/test.rb
|
374
342
|
- spec/dummy/config/environment.rb
|
@@ -380,7 +348,6 @@ test_files:
|
|
380
348
|
- spec/dummy/config/initializers/filter_parameter_logging.rb
|
381
349
|
- spec/dummy/config/initializers/session_store.rb
|
382
350
|
- spec/dummy/config/initializers/wrap_parameters.rb
|
383
|
-
- spec/dummy/config/initializers/assets.rb
|
384
351
|
- spec/dummy/config/initializers/cookies_serializer.rb
|
385
352
|
- spec/dummy/config/initializers/inflections.rb
|
386
353
|
- spec/dummy/config.ru
|
@@ -390,7 +357,6 @@ test_files:
|
|
390
357
|
- spec/dummy/public/500.html
|
391
358
|
- spec/dummy/public/404.html
|
392
359
|
- spec/dummy/db/schema.rb
|
393
|
-
- spec/dummy/db/migrate/20180606205114_create_delayed_jobs.rb
|
394
360
|
- spec/dummy/README.rdoc
|
395
361
|
- spec/models/journaled/json_schema_model/validator_spec.rb
|
396
362
|
- spec/models/journaled/actor_uri_provider_spec.rb
|
@@ -401,7 +367,7 @@ test_files:
|
|
401
367
|
- spec/models/database_change_protection_spec.rb
|
402
368
|
- spec/models/concerns/journaled/changes_spec.rb
|
403
369
|
- spec/models/concerns/journaled/actor_spec.rb
|
404
|
-
- spec/support/delayed_job_spec_helper.rb
|
405
370
|
- spec/support/environment_spec_helper.rb
|
406
371
|
- spec/lib/journaled_spec.rb
|
372
|
+
- spec/jobs/journaled/delivery_job_spec.rb
|
407
373
|
- spec/rails_helper.rb
|
data/config/routes.rb
DELETED
data/lib/journaled/enqueue.rb
DELETED
@@ -1,78 +0,0 @@
|
|
1
|
-
Rails.application.configure do
|
2
|
-
# Settings specified here will take precedence over those in config/application.rb.
|
3
|
-
|
4
|
-
# Code is not reloaded between requests.
|
5
|
-
config.cache_classes = true
|
6
|
-
|
7
|
-
# Eager load code on boot. This eager loads most of Rails and
|
8
|
-
# your application in memory, allowing both threaded web servers
|
9
|
-
# and those relying on copy on write to perform better.
|
10
|
-
# Rake tasks automatically ignore this option for performance.
|
11
|
-
config.eager_load = true
|
12
|
-
|
13
|
-
# Full error reports are disabled and caching is turned on.
|
14
|
-
config.consider_all_requests_local = false
|
15
|
-
config.action_controller.perform_caching = true
|
16
|
-
|
17
|
-
# Enable Rack::Cache to put a simple HTTP cache in front of your application
|
18
|
-
# Add `rack-cache` to your Gemfile before enabling this.
|
19
|
-
# For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid.
|
20
|
-
# config.action_dispatch.rack_cache = true
|
21
|
-
|
22
|
-
# Disable Rails's static asset server (Apache or nginx will already do this).
|
23
|
-
config.serve_static_assets = false
|
24
|
-
|
25
|
-
# Compress JavaScripts and CSS.
|
26
|
-
config.assets.js_compressor = :uglifier
|
27
|
-
# config.assets.css_compressor = :sass
|
28
|
-
|
29
|
-
# Do not fallback to assets pipeline if a precompiled asset is missed.
|
30
|
-
config.assets.compile = false
|
31
|
-
|
32
|
-
# Generate digests for assets URLs.
|
33
|
-
config.assets.digest = true
|
34
|
-
|
35
|
-
# `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
|
36
|
-
|
37
|
-
# Specifies the header that your server uses for sending files.
|
38
|
-
# config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
|
39
|
-
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
|
40
|
-
|
41
|
-
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
|
42
|
-
# config.force_ssl = true
|
43
|
-
|
44
|
-
# Set to :debug to see everything in the log.
|
45
|
-
config.log_level = :info
|
46
|
-
|
47
|
-
# Prepend all log lines with the following tags.
|
48
|
-
# config.log_tags = [ :subdomain, :uuid ]
|
49
|
-
|
50
|
-
# Use a different logger for distributed setups.
|
51
|
-
# config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
|
52
|
-
|
53
|
-
# Use a different cache store in production.
|
54
|
-
# config.cache_store = :mem_cache_store
|
55
|
-
|
56
|
-
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
|
57
|
-
# config.action_controller.asset_host = "http://assets.example.com"
|
58
|
-
|
59
|
-
# Ignore bad email addresses and do not raise email delivery errors.
|
60
|
-
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
|
61
|
-
# config.action_mailer.raise_delivery_errors = false
|
62
|
-
|
63
|
-
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
|
64
|
-
# the I18n.default_locale when a translation cannot be found).
|
65
|
-
config.i18n.fallbacks = true
|
66
|
-
|
67
|
-
# Send deprecation notices to registered listeners.
|
68
|
-
config.active_support.deprecation = :notify
|
69
|
-
|
70
|
-
# Disable automatic flushing of the log to improve performance.
|
71
|
-
# config.autoflush_log = false
|
72
|
-
|
73
|
-
# Use default logging formatter so that PID and timestamp are not suppressed.
|
74
|
-
config.log_formatter = ::Logger::Formatter.new
|
75
|
-
|
76
|
-
# Do not dump schema after migrations.
|
77
|
-
config.active_record.dump_schema_after_migration = false
|
78
|
-
end
|
@@ -1,8 +0,0 @@
|
|
1
|
-
# Be sure to restart your server when you modify this file.
|
2
|
-
|
3
|
-
# Version of your assets, change this if you want to expire all your assets.
|
4
|
-
Rails.application.config.assets.version = '1.0'
|
5
|
-
|
6
|
-
# Precompile additional assets.
|
7
|
-
# application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
|
8
|
-
# Rails.application.config.assets.precompile += %w( search.js )
|
@@ -1,18 +0,0 @@
|
|
1
|
-
class CreateDelayedJobs < ActiveRecord::Migration[5.1]
|
2
|
-
def change
|
3
|
-
create_table :delayed_jobs do |t|
|
4
|
-
t.integer :priority, default: 0, null: false # Allows some jobs to jump to the front of the queue
|
5
|
-
t.integer :attempts, default: 0, null: false # Provides for retries, but still fail eventually.
|
6
|
-
t.text :handler, null: false # YAML-encoded string of the object that will do work
|
7
|
-
t.text :last_error # reason for last failure (See Note below)
|
8
|
-
t.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future.
|
9
|
-
t.datetime :locked_at # Set when a client is working on this object
|
10
|
-
t.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)
|
11
|
-
t.string :locked_by # Who is working on this object (if locked)
|
12
|
-
t.string :queue # The name of the queue this job is in
|
13
|
-
t.timestamps null: true
|
14
|
-
end
|
15
|
-
|
16
|
-
add_index :delayed_jobs, [:priority, :run_at], name: "delayed_jobs_priority"
|
17
|
-
end
|
18
|
-
end
|
@@ -1,11 +0,0 @@
|
|
1
|
-
module DelayedJobSpecHelper
|
2
|
-
def with_jobs_delayed(opts = {})
|
3
|
-
work_off = opts.fetch(:work_off, true)
|
4
|
-
original = Delayed::Worker.delay_jobs
|
5
|
-
Delayed::Worker.delay_jobs = true
|
6
|
-
yield
|
7
|
-
ensure
|
8
|
-
Delayed::Worker.delay_jobs = original
|
9
|
-
Delayed::Worker.new.work_off if work_off
|
10
|
-
end
|
11
|
-
end
|