rails_outbox 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +110 -0
- data/bin/outbox +7 -0
- data/lib/generators/rails_outbox/install/install_generator.rb +17 -0
- data/lib/generators/rails_outbox/model/model_generator.rb +74 -0
- data/lib/generators/rails_outbox/templates/initializer.rb +14 -0
- data/lib/generators/rails_outbox/templates/migration.rb +15 -0
- data/lib/generators/rails_outbox/templates/model.rb +5 -0
- data/lib/rails_outbox/adapter_helper.rb +33 -0
- data/lib/rails_outbox/errors.rb +21 -0
- data/lib/rails_outbox/outboxable.rb +115 -0
- data/lib/rails_outbox/railtie.rb +6 -0
- data/lib/rails_outbox.rb +14 -0
- metadata +83 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1d94b1bb0518502cdc2f533c9b2c62f74cba24977608ff9461bf26dcc199ae32
|
4
|
+
data.tar.gz: 0a391ea6f13216b6625ef7948356ff8e2a61d312b88670c9b5106708169de526
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 66ffbab3bf48d09fd3b9013487da810f8b66da36c0b0fa6fd284027437127a07b8b322a27f2c4a602f2be3d0a1b11d425ca3d6e3ac3bf3beae837000b1d0fb8c
|
7
|
+
data.tar.gz: 93488c76456fd42a6dc344eecf836c42d60013bfa80e72baa5eb17cb3c18b75c4ad88cacf2e41965e020de9f7ad3832c4ea34253f001ee60cc78b32c852701f4
|
data/README.md
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
# Rails Outbox
|
2
|
+
A Transactional Outbox implementation for Rails and ActiveRecord.
|
3
|
+
|
4
|
+
![transactional outbox pattern](./docs/images/transactional_outbox.png)
|
5
|
+
|
6
|
+
This gem aims to implement the event persistance side of the pattern, focusing only on providing a seamless way to store Outbox records whenever a change occurs on a given model (#1 in the diagram).
|
7
|
+
We do not provide an event publisher, nor a consumer as a part of this gem since the idea is to keep it as light weight as possible.
|
8
|
+
|
9
|
+
## Motivation
|
10
|
+
If you find yourself repeatedly defining a transaction block every time you need to persist an event, it might be a sign that something needs improvement. We believe that adopting a pattern should enhance your workflow, not hinder it. Creating, updating or destroying a record should remain a familiar and smooth process.
|
11
|
+
|
12
|
+
Our primary objective is to ensure a seamless experience without imposing our own opinions or previous experiences. That's why this gem exclusively focuses on persisting records. We leave the other aspects of the pattern entirely open for your customization. You can emit these events using Sidekiq jobs, or explore more sophisticated solutions like Kafka Connect.
|
13
|
+
|
14
|
+
## Why rails_outbox?
|
15
|
+
- Seamless integration with ActiveRecord
|
16
|
+
- CRUD events out of the box
|
17
|
+
- Ability to set custom events
|
18
|
+
- Test helpers to easily check Outbox records are being created correctly
|
19
|
+
- Customizable
|
20
|
+
|
21
|
+
## Installation
|
22
|
+
|
23
|
+
Add this line to your application's Gemfile:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
gem 'rails_outbox'
|
27
|
+
```
|
28
|
+
|
29
|
+
And then execute:
|
30
|
+
```bash
|
31
|
+
bundle install
|
32
|
+
```
|
33
|
+
Or install it yourself as:
|
34
|
+
```bash
|
35
|
+
gem install rails_outbox
|
36
|
+
```
|
37
|
+
|
38
|
+
## Usage
|
39
|
+
### Setup
|
40
|
+
Create the outbox table and model using the provided generator. Any model name can be passed as an argument but if empty it will default to `outboxes` and `Outbox` respectively.
|
41
|
+
```bash
|
42
|
+
rails g rails_outbox:model <optional model_name>
|
43
|
+
|
44
|
+
create db/migrate/20231115182800_rails_outbox_create_<model_name_>outboxes.rb
|
45
|
+
create app/models/<model_name_>outbox.rb
|
46
|
+
```
|
47
|
+
After running the migration, create an initializer under `config/initializers/rails_outbox.rb` and setup the default outbox class to the new `Outbox` model you just created.
|
48
|
+
```bash
|
49
|
+
rails g rails_outbox:install
|
50
|
+
```
|
51
|
+
|
52
|
+
To allow models to store Outbox records on changes, you will have to include the `Outboxable` concern.
|
53
|
+
```ruby
|
54
|
+
# app/models/user.rb
|
55
|
+
|
56
|
+
class User < ApplicationRecord
|
57
|
+
include RailsOutbox::Outboxable
|
58
|
+
end
|
59
|
+
```
|
60
|
+
### Base Events
|
61
|
+
Using the User model as an example, the default event names provided are:
|
62
|
+
- USER_CREATED
|
63
|
+
- USER_UPDATED
|
64
|
+
- USER_DESTROYED
|
65
|
+
|
66
|
+
This will live under `RailsOutbox::Events` wherever you include the `Outboxable` concern. The intent is to define it under `Object` for non-namespaced models, as well as under each model namespace that is encountered.
|
67
|
+
|
68
|
+
### Custom Events
|
69
|
+
If you want to persist a custom event other than the provided base events, you can do so.
|
70
|
+
```ruby
|
71
|
+
user.save(outbox_event: 'YOUR_CUSTOM_EVENT')
|
72
|
+
```
|
73
|
+
## Advanced Usage
|
74
|
+
### Supporting UUIDs
|
75
|
+
By default our Outbox migration has an `aggregate_identifier` field which serves the purpose of identifying which record was involved in the event emission. We default to integer IDs, but if you're using UUIDs as a primary key for your records you have to adjust the migrations accordingly. To do so just run the model generator with the `--uuid` flag.
|
76
|
+
```bash
|
77
|
+
rails g rails_outbox:model <optional model_name> --uuid
|
78
|
+
```
|
79
|
+
### Modularized Outbox Mappings
|
80
|
+
If more granularity is desired multiple outbox classes can be configured. Using the provided generators we can specify namespaces and the folder structure.
|
81
|
+
```bash
|
82
|
+
rails g rails_outbox:model user_access/ --component-path packs/user_access
|
83
|
+
|
84
|
+
create packs/user_access/db/migrate/20231115181205_rails_outbox_create_user_access_outboxes.rb
|
85
|
+
create packs/user_access/app/models/user_access/outbox.rb
|
86
|
+
```
|
87
|
+
After creating the needed `Outbox` classes for each module you can specify multiple mappings in the initializer.
|
88
|
+
```ruby
|
89
|
+
# frozen_string_literal: true
|
90
|
+
|
91
|
+
Rails.application.reloader.to_prepare do
|
92
|
+
RailsOutbox.configure do |config|
|
93
|
+
config.outbox_mapping = {
|
94
|
+
'member' => 'Member::Outbox',
|
95
|
+
'user_access' => 'UserAccess::Outbox'
|
96
|
+
}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
```
|
100
|
+
## Contributing
|
101
|
+
|
102
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/guillermoap/rails_outbox. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/guillermoap/rails_outbox/blob/main/CODE_OF_CONDUCT.md).
|
103
|
+
|
104
|
+
## License
|
105
|
+
|
106
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/license/mit/).
|
107
|
+
|
108
|
+
## Code of Conduct
|
109
|
+
|
110
|
+
Everyone interacting in the RailsOutbox project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/guillermoap/rails_outbox/blob/main/CODE_OF_CONDUCT.md).
|
data/bin/outbox
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails/generators/base'
|
4
|
+
|
5
|
+
module RailsOutbox
|
6
|
+
module Generators
|
7
|
+
class InstallGenerator < Rails::Generators::Base
|
8
|
+
source_root File.expand_path('../templates', __dir__)
|
9
|
+
|
10
|
+
desc 'Creates an initializer file at config/initializers/rails_outbox.rb'
|
11
|
+
|
12
|
+
def create_initializer_file
|
13
|
+
copy_file('initializer.rb', Rails.root.join('config', 'initializers', 'rails_outbox.rb'))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails'
|
4
|
+
require 'rails/generators/active_record'
|
5
|
+
require 'rails/generators/base'
|
6
|
+
require 'rails/generators/migration'
|
7
|
+
|
8
|
+
module RailsOutbox
|
9
|
+
module Generators
|
10
|
+
class ModelGenerator < Rails::Generators::Base
|
11
|
+
source_root File.expand_path('../templates', __dir__)
|
12
|
+
|
13
|
+
include RailsOutbox::AdapterHelper
|
14
|
+
include Rails::Generators::Migration
|
15
|
+
|
16
|
+
class << self
|
17
|
+
delegate :next_migration_number, to: ActiveRecord::Generators::Base
|
18
|
+
end
|
19
|
+
|
20
|
+
desc 'Creates the Outbox model migration'
|
21
|
+
|
22
|
+
argument :model_name, type: :string, default: ''
|
23
|
+
class_option :component_path,
|
24
|
+
type: :string,
|
25
|
+
desc: 'Indicates where to create the outbox migration'
|
26
|
+
class_option :uuid,
|
27
|
+
type: :boolean,
|
28
|
+
default: false,
|
29
|
+
desc: 'Use UUID to identify aggregate records in events. Defaults to ID'
|
30
|
+
|
31
|
+
def create_migration_file
|
32
|
+
migration_path = "#{root_path}/db/migrate"
|
33
|
+
migration_template(
|
34
|
+
'migration.rb',
|
35
|
+
"#{migration_path}/rails_outbox_create_#{table_name}.rb",
|
36
|
+
migration_version: migration_version
|
37
|
+
)
|
38
|
+
|
39
|
+
template('model.rb', "#{root_path}/app/models/#{path_name}.rb")
|
40
|
+
end
|
41
|
+
|
42
|
+
def root_path
|
43
|
+
path = options['component_path'].blank? ? '' : "/#{options['component_path']}"
|
44
|
+
"#{Rails.root}#{path}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def migration_version
|
48
|
+
"[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
|
49
|
+
end
|
50
|
+
|
51
|
+
def table_name
|
52
|
+
*namespace, name = model_name.downcase.split('/')
|
53
|
+
name = name.blank? ? 'outboxes' : "#{name}_outboxes"
|
54
|
+
namespace = namespace.join('_')
|
55
|
+
namespace.blank? ? name : "#{namespace}_#{name}"
|
56
|
+
end
|
57
|
+
|
58
|
+
def path_name
|
59
|
+
name = ''
|
60
|
+
*namespace = model_name.downcase.split('/')
|
61
|
+
if (model_name.include?('/') && model_name.last != '/' && namespace.length > 1) || !model_name.include?('/')
|
62
|
+
name = namespace.pop
|
63
|
+
end
|
64
|
+
name = name.blank? ? 'outbox' : "#{name}_outbox"
|
65
|
+
namespace = namespace.join('/')
|
66
|
+
namespace.blank? ? name : "#{namespace}/#{name}"
|
67
|
+
end
|
68
|
+
|
69
|
+
def aggregate_identifier_type
|
70
|
+
options['uuid'].present? ? RailsOutbox::AdapterHelper.uuid_type : RailsOutbox::AdapterHelper.bigint_type
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Rails.application.reloader.to_prepare do
|
4
|
+
RailsOutbox.configure do |config|
|
5
|
+
# To configure which Outbox class maps to which domain
|
6
|
+
# See https://github.com/guillermoap/rails_outbox#advanced-usage for advanced examples
|
7
|
+
config.outbox_mapping = {
|
8
|
+
'default' => 'Outbox'
|
9
|
+
}
|
10
|
+
|
11
|
+
# Configure database adapter
|
12
|
+
# config.adapter = :postgresql
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class RailsOutboxCreate<%= table_name.camelize %> < ActiveRecord::Migration<%= migration_version %>
|
4
|
+
def change
|
5
|
+
create_table :<%= table_name %> do |t|
|
6
|
+
t.<%= RailsOutbox::AdapterHelper.uuid_type %> :identifier, null: false, index: { unique: true }
|
7
|
+
t.string :event, null: false
|
8
|
+
t.<%= RailsOutbox::AdapterHelper.json_type %> :payload
|
9
|
+
t.string :aggregate, null: false
|
10
|
+
t.<%= aggregate_identifier_type %> :aggregate_identifier, null: false, index: true
|
11
|
+
|
12
|
+
t.timestamps
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsOutbox
|
4
|
+
module AdapterHelper
|
5
|
+
def self.uuid_type
|
6
|
+
return 'uuid' if postgres?
|
7
|
+
return 'string' if mysql?
|
8
|
+
|
9
|
+
'string'
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.json_type
|
13
|
+
return 'jsonb' if postgres?
|
14
|
+
return 'json' if mysql?
|
15
|
+
|
16
|
+
'string'
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.bigint_type
|
20
|
+
return 'bigint' if postgres? || mysql?
|
21
|
+
|
22
|
+
'integer'
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.postgres?
|
26
|
+
ActiveRecord::Base.connection.adapter_name.downcase == 'postgresql'
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.mysql?
|
30
|
+
ActiveRecord::Base.connection.adapter_name.downcase == 'mysql2'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsOutbox
|
4
|
+
class OutboxConfigurationError < StandardError; end
|
5
|
+
|
6
|
+
class OutboxClassNotFoundError < OutboxConfigurationError
|
7
|
+
def message
|
8
|
+
<<~MESSAGE
|
9
|
+
Missing Outbox class definition. Configure mapping in `config/initializers/rails_outbox.rb`:
|
10
|
+
|
11
|
+
Rails.application.reloader.to_prepare do
|
12
|
+
RailsOutbox.configure do |config|
|
13
|
+
config.outbox_mapping = {
|
14
|
+
'default' => <outbox model name>
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
MESSAGE
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/concern'
|
4
|
+
|
5
|
+
module RailsOutbox
|
6
|
+
module Outboxable
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
*namespace, klass = name.underscore.upcase.split('/')
|
11
|
+
namespace = namespace.reverse.join('.')
|
12
|
+
|
13
|
+
module_parent.const_set('RailsOutbox', Module.new) unless module_parent.const_defined?('RailsOutbox', false)
|
14
|
+
|
15
|
+
unless module_parent::RailsOutbox.const_defined?('Events', false)
|
16
|
+
module_parent::RailsOutbox.const_set('Events', Module.new)
|
17
|
+
end
|
18
|
+
|
19
|
+
{ create: 'CREATED', update: 'UPDATED', destroy: 'DESTROYED' }.each do |key, value|
|
20
|
+
const_name = "#{klass}_#{value}"
|
21
|
+
|
22
|
+
unless module_parent::RailsOutbox::Events.const_defined?(const_name)
|
23
|
+
module_parent::RailsOutbox::Events.const_set(const_name,
|
24
|
+
"#{const_name}#{namespace.blank? ? '' : '.'}#{namespace}")
|
25
|
+
end
|
26
|
+
|
27
|
+
event_name = module_parent::RailsOutbox::Events.const_get(const_name)
|
28
|
+
|
29
|
+
send("after_#{key}") { create_outbox!(key, event_name) }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def save(**options, &block)
|
34
|
+
assign_outbox_event(options)
|
35
|
+
super(**options, &block)
|
36
|
+
end
|
37
|
+
|
38
|
+
def save!(**options, &block)
|
39
|
+
assign_outbox_event(options)
|
40
|
+
super(**options, &block)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def assign_outbox_event(options)
|
46
|
+
@outbox_event = options[:outbox_event].underscore.upcase if options[:outbox_event].present?
|
47
|
+
end
|
48
|
+
|
49
|
+
def create_outbox!(action, event_name)
|
50
|
+
outbox = outbox_model.new(
|
51
|
+
aggregate: self.class.name,
|
52
|
+
aggregate_identifier: send(self.class.primary_key),
|
53
|
+
event: @outbox_event || event_name,
|
54
|
+
identifier: SecureRandom.uuid,
|
55
|
+
payload: formatted_payload(action)
|
56
|
+
)
|
57
|
+
@outbox_event = nil
|
58
|
+
handle_outbox_errors(outbox) if outbox.invalid?
|
59
|
+
outbox.save!
|
60
|
+
end
|
61
|
+
|
62
|
+
def outbox_model
|
63
|
+
module_parent = self.class.module_parent
|
64
|
+
# sets _inherit_ option to false so it doesn't lookup in ancestors for the constant
|
65
|
+
unless module_parent.const_defined?('OUTBOX_MODEL', false)
|
66
|
+
outbox_model = outbox_model_name!.safe_constantize
|
67
|
+
module_parent.const_set('OUTBOX_MODEL', outbox_model)
|
68
|
+
end
|
69
|
+
|
70
|
+
module_parent.const_get('OUTBOX_MODEL')
|
71
|
+
end
|
72
|
+
|
73
|
+
def outbox_model_name!
|
74
|
+
namespace_outbox_mapping || default_outbox_mapping || raise(OutboxClassNotFoundError)
|
75
|
+
end
|
76
|
+
|
77
|
+
def namespace_outbox_mapping
|
78
|
+
namespace = self.class.module_parent.name.underscore
|
79
|
+
|
80
|
+
RailsOutbox.config.outbox_mapping[namespace]
|
81
|
+
end
|
82
|
+
|
83
|
+
def default_outbox_mapping
|
84
|
+
RailsOutbox.config.outbox_mapping['default']
|
85
|
+
end
|
86
|
+
|
87
|
+
def handle_outbox_errors(outbox)
|
88
|
+
outbox.errors.each do |error|
|
89
|
+
errors.import(error, attribute: "outbox.#{error.attribute}")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def formatted_payload(action)
|
94
|
+
payload = construct_payload(action)
|
95
|
+
AdapterHelper.postgres? ? payload : payload.to_json
|
96
|
+
end
|
97
|
+
|
98
|
+
def construct_payload(action)
|
99
|
+
case action
|
100
|
+
when :create
|
101
|
+
{ before: nil, after: as_json }
|
102
|
+
when :update
|
103
|
+
changes = previous_changes.transform_values(&:first)
|
104
|
+
{ before: as_json.merge(changes), after: as_json }
|
105
|
+
when :destroy
|
106
|
+
{ before: as_json, after: nil }
|
107
|
+
else
|
108
|
+
raise ActiveRecord::RecordNotSaved.new(
|
109
|
+
"Failed to create Outbox payload for #{self.class.name}: #{send(self.class.primary_key)}",
|
110
|
+
self
|
111
|
+
)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/lib/rails_outbox.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_outbox/adapter_helper'
|
4
|
+
require 'rails_outbox/errors'
|
5
|
+
require 'rails_outbox/outboxable'
|
6
|
+
require 'rails_outbox/railtie' if defined?(Rails::Railtie)
|
7
|
+
require 'dry-configurable'
|
8
|
+
|
9
|
+
module RailsOutbox
|
10
|
+
extend Dry::Configurable
|
11
|
+
|
12
|
+
setting :adapter, default: :sqlite
|
13
|
+
setting :outbox_mapping, default: {}
|
14
|
+
end
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rails_outbox
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Guillermo Aguirre
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-09-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: dry-configurable
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rails
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '6.1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '6.1'
|
41
|
+
description:
|
42
|
+
email: guillermoaguirre@hey.com
|
43
|
+
executables:
|
44
|
+
- outbox
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- README.md
|
49
|
+
- bin/outbox
|
50
|
+
- lib/generators/rails_outbox/install/install_generator.rb
|
51
|
+
- lib/generators/rails_outbox/model/model_generator.rb
|
52
|
+
- lib/generators/rails_outbox/templates/initializer.rb
|
53
|
+
- lib/generators/rails_outbox/templates/migration.rb
|
54
|
+
- lib/generators/rails_outbox/templates/model.rb
|
55
|
+
- lib/rails_outbox.rb
|
56
|
+
- lib/rails_outbox/adapter_helper.rb
|
57
|
+
- lib/rails_outbox/errors.rb
|
58
|
+
- lib/rails_outbox/outboxable.rb
|
59
|
+
- lib/rails_outbox/railtie.rb
|
60
|
+
homepage: https://github.com/guillermoap/rails_outbox
|
61
|
+
licenses:
|
62
|
+
- MIT
|
63
|
+
metadata: {}
|
64
|
+
post_install_message:
|
65
|
+
rdoc_options: []
|
66
|
+
require_paths:
|
67
|
+
- lib
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '3.0'
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
requirements: []
|
79
|
+
rubygems_version: 3.5.16
|
80
|
+
signing_key:
|
81
|
+
specification_version: 4
|
82
|
+
summary: A Transactional Outbox implementation for ActiveRecord and Rails
|
83
|
+
test_files: []
|