Skip to content

Commit

Permalink
Improve model generator (#20)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
guillermoap committed Nov 16, 2023
1 parent f10a88e commit 53caa02
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 11 deletions.
2 changes: 1 addition & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ AllCops:
NewCops: enable
TargetRubyVersion: 3.0
Exclude:
- lib/generators/active_outbox/templates/migration.rb
- lib/generators/active_outbox/templates/*

Gemspec/RequireMFA:
Enabled: false
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
active_outbox (0.1.3)
active_outbox (0.1.4)
dry-configurable (~> 1.0)
rails (>= 6.1)

Expand Down
22 changes: 16 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,16 @@ gem install active_outbox

## Usage
### Setup
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.
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.
```bash
rails g active_outbox:install
rails g active_outbox:model <optional model_name>

create db/migrate/20231115182800_active_outbox_create_<model_name_>outboxes.rb
create app/models/<model_name_>outbox.rb
```
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`.
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.
```bash
rails g active_outbox:model <optional model_name>
rails g active_outbox:install
```

To allow models to store Outbox records on changes, you will have to include the `Outboxable` concern.
Expand Down Expand Up @@ -71,8 +74,15 @@ By default our Outbox migration has an `aggregate_identifier` field which serves
```bash
rails g active_outbox:model <optional model_name> --uuid
```
### Multiple Outbox mappings
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.
### Modularized Outbox Mappings
If more granularity is desired multiple outbox classes can be configured. Using the provided generators we can specify namespaces and the folder structure.
```bash
rails g active_outbox:model user_access/ --component-path packs/user_access

create packs/user_access/db/migrate/20231115181205_active_outbox_create_user_access_outboxes.rb
create packs/user_access/app/models/user_access/outbox.rb
```
After creating the needed `Outbox` classes for each module you can specify multiple mappings in the initializer.
```ruby
# frozen_string_literal: true

Expand Down
2 changes: 1 addition & 1 deletion active_outbox.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Gem::Specification.new do |spec|
spec.files = Dir['LICENSE.txt', 'README.md', 'lib/**/*', 'lib/active_outbox.rb']
spec.name = 'active_outbox'
spec.summary = 'A Transactional Outbox implementation for ActiveRecord'
spec.version = '0.1.3'
spec.version = '0.1.4'

spec.email = 'guillermoaguirre1@gmail.com'
spec.executables = ['outbox']
Expand Down
21 changes: 19 additions & 2 deletions lib/generators/active_outbox/model/model_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,35 @@ def create_migration_file
"#{migration_path}/active_outbox_create_#{table_name}.rb",
migration_version: migration_version
)

template('model.rb', "#{root_path}/app/models/#{path_name}.rb")
end

def root_path
options['component_path'] || Rails.root
path = options['component_path'].blank? ? '' : "/#{options['component_path']}"
"#{Rails.root}#{path}"
end

def migration_version
"[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
end

def table_name
model_name.blank? ? 'outboxes' : "#{model_name}_outboxes"
*namespace, name = model_name.downcase.split('/')
name = name.blank? ? 'outboxes' : "#{name}_outboxes"
namespace = namespace.join('_')
namespace.blank? ? name : "#{namespace}_#{name}"
end

def path_name
name = ''
*namespace = model_name.downcase.split('/')
if (model_name.include?('/') && model_name.last != '/' && namespace.length > 1) || !model_name.include?('/')
name = namespace.pop
end
name = name.blank? ? 'outbox' : "#{name}_outbox"
namespace = namespace.join('/')
namespace.blank? ? name : "#{namespace}/#{name}"
end

def aggregate_identifier_type
Expand Down
5 changes: 5 additions & 0 deletions lib/generators/active_outbox/templates/model.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

class <%= path_name.camelize %> < ApplicationRecord
validates_presence_of :identifier, :payload, :aggregate, :aggregate_identifier, :event
end
44 changes: 44 additions & 0 deletions spec/lib/active_outbox/generators/model_generator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,21 @@
end
let(:timestamp_of_migration) { DateTime.now.in_time_zone('UTC').strftime('%Y%m%d%H%M%S') }

shared_examples 'creates the correct model file' do
let(:expected_content) do
<<~MODEL
class #{path_name.camelize} < ApplicationRecord
validates_presence_of :identifier, :payload, :aggregate, :aggregate_identifier, :event
end
MODEL
end

it 'create the model file with the correct content' do
generate
expect(actual_content).to include(expected_content)
end
end

shared_examples 'creates the correct migrations for supported adapters' do
context 'when it is a mysql migration' do
before do
Expand Down Expand Up @@ -120,6 +135,10 @@ def change
"#{destination_root}/db/migrate/#{timestamp_of_migration}_active_outbox_create_outboxes.rb"
end

let(:model_file_path) do
"#{destination_root}/app/models/outbox.rb"
end

context 'without root_component_path' do
before do
allow(Rails).to receive(:root).and_return(destination_root)
Expand All @@ -128,16 +147,27 @@ def change
it 'creates the expected files' do
run_generator
assert_file migration_file_path
assert_file model_file_path
end
end

context 'with root_component_path' do
it 'creates the expected files' do
run_generator(["--component_path=#{destination_root}"])
assert_file migration_file_path
assert_file model_file_path
end
end

describe 'model content' do
subject(:generate) { run_generator(["--component_path=#{destination_root}"]) }

let(:actual_content) { File.read(model_file_path) }
let(:path_name) { 'outbox' }

include_examples 'creates the correct model file'
end

describe 'migration content' do
let(:actual_content) { File.read(migration_file_path) }
let(:active_record_dependency) { ActiveRecord::VERSION::STRING.to_f }
Expand All @@ -162,6 +192,10 @@ def change

context 'with custom outbox name' do
let(:table_name) { 'custom_table_name' }
let(:path_name) { "#{table_name}_outbox" }
let(:model_file_path) do
"#{destination_root}/app/models/#{path_name}.rb"
end

context 'without root_component_path' do
before do
Expand All @@ -171,16 +205,26 @@ def change
it 'creates the expected files' do
run_generator [table_name]
assert_file migration_file_path
assert_file model_file_path
end
end

context 'with root_component_path' do
it 'creates the expected files' do
run_generator([table_name, "--component_path=#{destination_root}"])
assert_file migration_file_path
assert_file model_file_path
end
end

describe 'model content' do
subject(:generate) { run_generator([table_name, "--component_path=#{destination_root}"]) }

let(:actual_content) { File.read(model_file_path) }

include_examples 'creates the correct model file'
end

describe 'migration content' do
let(:actual_content) { File.read(migration_file_path) }
let(:active_record_dependency) { ActiveRecord::VERSION::STRING.to_f }
Expand Down

0 comments on commit 53caa02

Please sign in to comment.