Skip to content

Commit 53caa02

Browse files
authored
Improve model generator (#20)
* Rubocop ignore generator template folder * Support namespacing and create outbox model file in model generator * Downcase model_name argument * Update README * Fix namespacing when defining model_name. Add specs * Bump version to 0.1.4
1 parent f10a88e commit 53caa02

File tree

7 files changed

+87
-11
lines changed

7 files changed

+87
-11
lines changed

.rubocop.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ AllCops:
1010
NewCops: enable
1111
TargetRubyVersion: 3.0
1212
Exclude:
13-
- lib/generators/active_outbox/templates/migration.rb
13+
- lib/generators/active_outbox/templates/*
1414

1515
Gemspec/RequireMFA:
1616
Enabled: false

Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: .
33
specs:
4-
active_outbox (0.1.3)
4+
active_outbox (0.1.4)
55
dry-configurable (~> 1.0)
66
rails (>= 6.1)
77

README.md

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,16 @@ gem install active_outbox
3737

3838
## Usage
3939
### Setup
40-
Create an initializer under `config/initializers/active_outbox.rb` and setup the default outbox class to the `Outbox` model you will create in the next step.
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.
4141
```bash
42-
rails g active_outbox:install
42+
rails g active_outbox:model <optional model_name>
43+
44+
create db/migrate/20231115182800_active_outbox_create_<model_name_>outboxes.rb
45+
create app/models/<model_name_>outbox.rb
4346
```
44-
After creating the initializer, create an `Outbox` table using the provided generator and corresponding model. Any model name can be passed as an argument but if empty it will default to just `outboxes`. The generated table name will be `model_name_outboxes`.
47+
After running the migration, create an initializer under `config/initializers/active_outbox.rb` and setup the default outbox class to the new `Outbox` model you just created.
4548
```bash
46-
rails g active_outbox:model <optional model_name>
49+
rails g active_outbox:install
4750
```
4851

4952
To allow models to store Outbox records on changes, you will have to include the `Outboxable` concern.
@@ -71,8 +74,15 @@ By default our Outbox migration has an `aggregate_identifier` field which serves
7174
```bash
7275
rails g active_outbox:model <optional model_name> --uuid
7376
```
74-
### Multiple Outbox mappings
75-
If more granularity is desired multiple `Outbox` classes can be configured. After creating the needed `Outbox` classes for each module you can specify multiple mappings in the initializer.
77+
### Modularized Outbox Mappings
78+
If more granularity is desired multiple outbox classes can be configured. Using the provided generators we can specify namespaces and the folder structure.
79+
```bash
80+
rails g active_outbox:model user_access/ --component-path packs/user_access
81+
82+
create packs/user_access/db/migrate/20231115181205_active_outbox_create_user_access_outboxes.rb
83+
create packs/user_access/app/models/user_access/outbox.rb
84+
```
85+
After creating the needed `Outbox` classes for each module you can specify multiple mappings in the initializer.
7686
```ruby
7787
# frozen_string_literal: true
7888

active_outbox.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Gem::Specification.new do |spec|
55
spec.files = Dir['LICENSE.txt', 'README.md', 'lib/**/*', 'lib/active_outbox.rb']
66
spec.name = 'active_outbox'
77
spec.summary = 'A Transactional Outbox implementation for ActiveRecord'
8-
spec.version = '0.1.3'
8+
spec.version = '0.1.4'
99

1010
spec.email = 'guillermoaguirre1@gmail.com'
1111
spec.executables = ['outbox']

lib/generators/active_outbox/model/model_generator.rb

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,35 @@ def create_migration_file
3535
"#{migration_path}/active_outbox_create_#{table_name}.rb",
3636
migration_version: migration_version
3737
)
38+
39+
template('model.rb', "#{root_path}/app/models/#{path_name}.rb")
3840
end
3941

4042
def root_path
41-
options['component_path'] || Rails.root
43+
path = options['component_path'].blank? ? '' : "/#{options['component_path']}"
44+
"#{Rails.root}#{path}"
4245
end
4346

4447
def migration_version
4548
"[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
4649
end
4750

4851
def table_name
49-
model_name.blank? ? 'outboxes' : "#{model_name}_outboxes"
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}"
5067
end
5168

5269
def aggregate_identifier_type
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# frozen_string_literal: true
2+
3+
class <%= path_name.camelize %> < ApplicationRecord
4+
validates_presence_of :identifier, :payload, :aggregate, :aggregate_identifier, :event
5+
end

spec/lib/active_outbox/generators/model_generator_spec.rb

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,21 @@
2626
end
2727
let(:timestamp_of_migration) { DateTime.now.in_time_zone('UTC').strftime('%Y%m%d%H%M%S') }
2828

29+
shared_examples 'creates the correct model file' do
30+
let(:expected_content) do
31+
<<~MODEL
32+
class #{path_name.camelize} < ApplicationRecord
33+
validates_presence_of :identifier, :payload, :aggregate, :aggregate_identifier, :event
34+
end
35+
MODEL
36+
end
37+
38+
it 'create the model file with the correct content' do
39+
generate
40+
expect(actual_content).to include(expected_content)
41+
end
42+
end
43+
2944
shared_examples 'creates the correct migrations for supported adapters' do
3045
context 'when it is a mysql migration' do
3146
before do
@@ -120,6 +135,10 @@ def change
120135
"#{destination_root}/db/migrate/#{timestamp_of_migration}_active_outbox_create_outboxes.rb"
121136
end
122137

138+
let(:model_file_path) do
139+
"#{destination_root}/app/models/outbox.rb"
140+
end
141+
123142
context 'without root_component_path' do
124143
before do
125144
allow(Rails).to receive(:root).and_return(destination_root)
@@ -128,16 +147,27 @@ def change
128147
it 'creates the expected files' do
129148
run_generator
130149
assert_file migration_file_path
150+
assert_file model_file_path
131151
end
132152
end
133153

134154
context 'with root_component_path' do
135155
it 'creates the expected files' do
136156
run_generator(["--component_path=#{destination_root}"])
137157
assert_file migration_file_path
158+
assert_file model_file_path
138159
end
139160
end
140161

162+
describe 'model content' do
163+
subject(:generate) { run_generator(["--component_path=#{destination_root}"]) }
164+
165+
let(:actual_content) { File.read(model_file_path) }
166+
let(:path_name) { 'outbox' }
167+
168+
include_examples 'creates the correct model file'
169+
end
170+
141171
describe 'migration content' do
142172
let(:actual_content) { File.read(migration_file_path) }
143173
let(:active_record_dependency) { ActiveRecord::VERSION::STRING.to_f }
@@ -162,6 +192,10 @@ def change
162192

163193
context 'with custom outbox name' do
164194
let(:table_name) { 'custom_table_name' }
195+
let(:path_name) { "#{table_name}_outbox" }
196+
let(:model_file_path) do
197+
"#{destination_root}/app/models/#{path_name}.rb"
198+
end
165199

166200
context 'without root_component_path' do
167201
before do
@@ -171,16 +205,26 @@ def change
171205
it 'creates the expected files' do
172206
run_generator [table_name]
173207
assert_file migration_file_path
208+
assert_file model_file_path
174209
end
175210
end
176211

177212
context 'with root_component_path' do
178213
it 'creates the expected files' do
179214
run_generator([table_name, "--component_path=#{destination_root}"])
180215
assert_file migration_file_path
216+
assert_file model_file_path
181217
end
182218
end
183219

220+
describe 'model content' do
221+
subject(:generate) { run_generator([table_name, "--component_path=#{destination_root}"]) }
222+
223+
let(:actual_content) { File.read(model_file_path) }
224+
225+
include_examples 'creates the correct model file'
226+
end
227+
184228
describe 'migration content' do
185229
let(:actual_content) { File.read(migration_file_path) }
186230
let(:active_record_dependency) { ActiveRecord::VERSION::STRING.to_f }

0 commit comments

Comments
 (0)