smart_domain 0.1.1 → 0.1.2
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/.rubocop.yml +65 -0
- data/README.md +5 -0
- data/Rakefile +3 -3
- data/lib/generators/smart_domain/domain/domain_generator.rb +16 -16
- data/lib/generators/smart_domain/install/install_generator.rb +13 -13
- data/lib/smart_domain/configuration.rb +1 -1
- data/lib/smart_domain/domain/exceptions.rb +2 -2
- data/lib/smart_domain/domain/service.rb +1 -1
- data/lib/smart_domain/event/adapters/memory.rb +24 -9
- data/lib/smart_domain/event/base.rb +26 -13
- data/lib/smart_domain/event/handler.rb +5 -3
- data/lib/smart_domain/event/mixins.rb +11 -1
- data/lib/smart_domain/event/registration.rb +4 -4
- data/lib/smart_domain/generators/domain_generator.rb +1 -1
- data/lib/smart_domain/generators/install_generator.rb +1 -1
- data/lib/smart_domain/handlers/audit_handler.rb +22 -24
- data/lib/smart_domain/handlers/metrics_handler.rb +8 -1
- data/lib/smart_domain/integration/active_record.rb +6 -8
- data/lib/smart_domain/railtie.rb +24 -24
- data/lib/smart_domain/tasks/domains.rake +18 -14
- data/lib/smart_domain/version.rb +1 -1
- data/lib/smart_domain.rb +20 -20
- data/smart_domain.gemspec +26 -25
- metadata +18 -18
- data/examples/blog_app/config/master.key +0 -1
- data/smart_domain-0.1.0.gem +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dc4986884824f0ebda3f3ea7051e7303bb416b7497f1b3213a749bdbd2a80918
|
|
4
|
+
data.tar.gz: 5a65d52f74b31c7ebc0da5d8a63d8b8942b5262746de39826f185d61129f32e7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 06015b7d5dc2598a35fa0ebda62d2c2ece2b1c7a1cfc4719713d70e54fdebde7b713c84f713334853d9244d9f994b88d6a4937cc6b7dcedb25f8800bd7d87551
|
|
7
|
+
data.tar.gz: 2af6c6ee30a91e21947907885f4662b3565576b9a53631eb216a799167e2ff88f5088bee7b6bf6c5e04c01b9062bec243518aee6f556eb023cda2b784d6f5ea5
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
NewCops: enable
|
|
3
|
+
TargetRubyVersion: 3.0
|
|
4
|
+
SuggestExtensions: false
|
|
5
|
+
Exclude:
|
|
6
|
+
- 'vendor/**/*'
|
|
7
|
+
- 'bin/**/*'
|
|
8
|
+
- 'node_modules/**/*'
|
|
9
|
+
- 'examples/**/*'
|
|
10
|
+
|
|
11
|
+
# Allow longer lines in tests
|
|
12
|
+
Layout/LineLength:
|
|
13
|
+
Max: 120
|
|
14
|
+
Exclude:
|
|
15
|
+
- 'spec/**/*'
|
|
16
|
+
|
|
17
|
+
# Allow long blocks in specs and rake tasks
|
|
18
|
+
Metrics/BlockLength:
|
|
19
|
+
Exclude:
|
|
20
|
+
- 'spec/**/*'
|
|
21
|
+
- '*.gemspec'
|
|
22
|
+
- 'lib/**/*.rake'
|
|
23
|
+
|
|
24
|
+
# Allow longer methods for generators and complex logic
|
|
25
|
+
Metrics/MethodLength:
|
|
26
|
+
Max: 30
|
|
27
|
+
|
|
28
|
+
# Allow more complex methods
|
|
29
|
+
Metrics/AbcSize:
|
|
30
|
+
Max: 30
|
|
31
|
+
Exclude:
|
|
32
|
+
- 'lib/generators/**/*'
|
|
33
|
+
|
|
34
|
+
# Allow more cyclomatic complexity
|
|
35
|
+
Metrics/CyclomaticComplexity:
|
|
36
|
+
Max: 10
|
|
37
|
+
|
|
38
|
+
# Allow higher perceived complexity
|
|
39
|
+
Metrics/PerceivedComplexity:
|
|
40
|
+
Max: 10
|
|
41
|
+
|
|
42
|
+
# Allow longer classes for handlers
|
|
43
|
+
Metrics/ClassLength:
|
|
44
|
+
Max: 150
|
|
45
|
+
|
|
46
|
+
# Allow defining constants in test blocks
|
|
47
|
+
Lint/ConstantDefinitionInBlock:
|
|
48
|
+
Exclude:
|
|
49
|
+
- 'spec/**/*'
|
|
50
|
+
|
|
51
|
+
# Allow duplicate branches (false positives)
|
|
52
|
+
Lint/DuplicateBranch:
|
|
53
|
+
Enabled: false
|
|
54
|
+
|
|
55
|
+
# Allow development dependencies in gemspec
|
|
56
|
+
Gemspec/DevelopmentDependencies:
|
|
57
|
+
Enabled: false
|
|
58
|
+
|
|
59
|
+
# Allow more than one class per file in specs
|
|
60
|
+
Style/ClassAndModuleChildren:
|
|
61
|
+
Enabled: false
|
|
62
|
+
|
|
63
|
+
# Don't require documentation for all classes
|
|
64
|
+
Style/Documentation:
|
|
65
|
+
Enabled: false
|
data/README.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# SmartDomain
|
|
2
2
|
|
|
3
|
+
[](https://badge.fury.io/rb/smart_domain)
|
|
4
|
+
[](https://rubygems.org/gems/smart_domain)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](https://www.ruby-lang.org)
|
|
7
|
+
|
|
3
8
|
**Domain-Driven Design and Event-Driven Architecture for Rails**
|
|
4
9
|
|
|
5
10
|
SmartDomain brings battle-tested DDD/EDA patterns from platform to Ruby on Rails applications. It provides domain events, an event bus, generic handlers, and Rails generators for rapid domain scaffolding.
|
data/Rakefile
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require 'bundler/gem_tasks'
|
|
4
|
+
require 'rspec/core/rake_task'
|
|
5
5
|
|
|
6
6
|
RSpec::Core::RakeTask.new(:spec)
|
|
7
7
|
|
|
8
|
-
require
|
|
8
|
+
require 'rubocop/rake_task'
|
|
9
9
|
|
|
10
10
|
RuboCop::RakeTask.new
|
|
11
11
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require 'rails/generators'
|
|
4
|
+
require 'rails/generators/named_base'
|
|
5
5
|
|
|
6
6
|
module SmartDomain
|
|
7
7
|
module Generators
|
|
@@ -22,18 +22,18 @@ module SmartDomain
|
|
|
22
22
|
# user_updated_event.rb
|
|
23
23
|
# user_deleted_event.rb
|
|
24
24
|
class DomainGenerator < Rails::Generators::NamedBase
|
|
25
|
-
source_root File.expand_path(
|
|
25
|
+
source_root File.expand_path('templates', __dir__)
|
|
26
26
|
|
|
27
|
-
desc
|
|
27
|
+
desc 'Generate a complete domain structure with service, events, and policy'
|
|
28
28
|
|
|
29
29
|
class_option :skip_service, type: :boolean, default: false,
|
|
30
|
-
|
|
30
|
+
desc: 'Skip generating domain service'
|
|
31
31
|
class_option :skip_policy, type: :boolean, default: false,
|
|
32
|
-
|
|
32
|
+
desc: 'Skip generating domain policy'
|
|
33
33
|
class_option :skip_events, type: :boolean, default: false,
|
|
34
|
-
|
|
34
|
+
desc: 'Skip generating domain events'
|
|
35
35
|
class_option :skip_setup, type: :boolean, default: false,
|
|
36
|
-
|
|
36
|
+
desc: 'Skip generating setup file'
|
|
37
37
|
|
|
38
38
|
# Generate domain directory structure
|
|
39
39
|
def create_domain_directory
|
|
@@ -44,30 +44,30 @@ module SmartDomain
|
|
|
44
44
|
def create_service
|
|
45
45
|
return if options[:skip_service]
|
|
46
46
|
|
|
47
|
-
template
|
|
47
|
+
template 'service.rb.tt', "#{domain_path}/#{file_name}_service.rb"
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
# Generate domain events in app/events/
|
|
51
51
|
def create_events
|
|
52
52
|
return if options[:skip_events]
|
|
53
53
|
|
|
54
|
-
template
|
|
55
|
-
template
|
|
56
|
-
template
|
|
54
|
+
template 'events/created_event.rb.tt', "app/events/#{file_name}_created_event.rb"
|
|
55
|
+
template 'events/updated_event.rb.tt', "app/events/#{file_name}_updated_event.rb"
|
|
56
|
+
template 'events/deleted_event.rb.tt', "app/events/#{file_name}_deleted_event.rb"
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
# Generate domain policy
|
|
60
60
|
def create_policy
|
|
61
61
|
return if options[:skip_policy]
|
|
62
62
|
|
|
63
|
-
template
|
|
63
|
+
template 'policy.rb.tt', "app/policies/#{file_name}_policy.rb"
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
# Generate setup file for event registration
|
|
67
67
|
def create_setup
|
|
68
68
|
return if options[:skip_setup]
|
|
69
69
|
|
|
70
|
-
template
|
|
70
|
+
template 'setup.rb.tt', "#{domain_path}/setup.rb"
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
# Show instructions
|
|
@@ -84,10 +84,10 @@ module SmartDomain
|
|
|
84
84
|
say " #{domain_path}/setup.rb" unless options[:skip_setup]
|
|
85
85
|
|
|
86
86
|
say "\nNext steps:"
|
|
87
|
-
say
|
|
87
|
+
say ' 1. Review and customize the generated files'
|
|
88
88
|
say " 2. Add business logic to #{class_name}Service"
|
|
89
89
|
say " 3. Customize authorization rules in #{class_name}Policy"
|
|
90
|
-
say
|
|
90
|
+
say ' 4. Restart your Rails server to load the domain setup'
|
|
91
91
|
end
|
|
92
92
|
|
|
93
93
|
private
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require 'rails/generators'
|
|
4
4
|
|
|
5
5
|
module SmartDomain
|
|
6
6
|
module Generators
|
|
@@ -16,42 +16,42 @@ module SmartDomain
|
|
|
16
16
|
# - app/handlers/ directory
|
|
17
17
|
# - app/policies/ directory
|
|
18
18
|
class InstallGenerator < Rails::Generators::Base
|
|
19
|
-
source_root File.expand_path(
|
|
19
|
+
source_root File.expand_path('templates', __dir__)
|
|
20
20
|
|
|
21
|
-
desc
|
|
21
|
+
desc 'Install SmartDomain into your Rails application'
|
|
22
22
|
|
|
23
23
|
# Create initializer
|
|
24
24
|
def create_initializer
|
|
25
|
-
template
|
|
25
|
+
template 'initializer.rb', 'config/initializers/smart_domain.rb'
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
# Create directory structure
|
|
29
29
|
def create_directory_structure
|
|
30
|
-
create_file
|
|
31
|
-
create_file
|
|
32
|
-
create_file
|
|
33
|
-
create_file
|
|
34
|
-
create_file
|
|
30
|
+
create_file 'app/domains/.keep'
|
|
31
|
+
create_file 'app/events/.keep'
|
|
32
|
+
create_file 'app/handlers/.keep'
|
|
33
|
+
create_file 'app/policies/.keep'
|
|
34
|
+
create_file 'app/services/.keep'
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
# Create base event class
|
|
38
38
|
def create_application_event
|
|
39
|
-
template
|
|
39
|
+
template 'application_event.rb', 'app/events/application_event.rb'
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
# Create base policy class
|
|
43
43
|
def create_application_policy
|
|
44
|
-
template
|
|
44
|
+
template 'application_policy.rb', 'app/policies/application_policy.rb'
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
# Create base service class
|
|
48
48
|
def create_application_service
|
|
49
|
-
template
|
|
49
|
+
template 'application_service.rb', 'app/services/application_service.rb'
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
# Show post-install message
|
|
53
53
|
def show_readme
|
|
54
|
-
readme
|
|
54
|
+
readme 'README' if behavior == :invoke
|
|
55
55
|
end
|
|
56
56
|
end
|
|
57
57
|
end
|
|
@@ -84,7 +84,7 @@ module SmartDomain
|
|
|
84
84
|
# )
|
|
85
85
|
class BusinessRuleError < Error
|
|
86
86
|
def initialize(message, code: :business_rule_violation, details: {})
|
|
87
|
-
super
|
|
87
|
+
super
|
|
88
88
|
end
|
|
89
89
|
end
|
|
90
90
|
|
|
@@ -134,7 +134,7 @@ module SmartDomain
|
|
|
134
134
|
# 'User does not have permission to delete this resource'
|
|
135
135
|
# )
|
|
136
136
|
class UnauthorizedError < Error
|
|
137
|
-
def initialize(message =
|
|
137
|
+
def initialize(message = 'Unauthorized', action: nil, resource: nil)
|
|
138
138
|
super(
|
|
139
139
|
message,
|
|
140
140
|
code: :unauthorized,
|
|
@@ -150,7 +150,7 @@ module SmartDomain
|
|
|
150
150
|
attributes[:organization_id] ||= current_organization_id
|
|
151
151
|
|
|
152
152
|
# Auto-fill actor fields if event includes ActorMixin
|
|
153
|
-
if event_class.
|
|
153
|
+
if event_class.method_defined?(:actor_id)
|
|
154
154
|
attributes[:actor_id] ||= current_user_id&.to_s
|
|
155
155
|
attributes[:actor_email] ||= current_user&.email
|
|
156
156
|
end
|
|
@@ -18,7 +18,10 @@ module SmartDomain
|
|
|
18
18
|
class Memory
|
|
19
19
|
def initialize
|
|
20
20
|
@handlers = Hash.new { |h, k| h[k] = [] }
|
|
21
|
-
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def logger
|
|
24
|
+
@logger ||= SmartDomain.configuration.logger
|
|
22
25
|
end
|
|
23
26
|
|
|
24
27
|
# Subscribe a handler to an event type
|
|
@@ -30,16 +33,30 @@ module SmartDomain
|
|
|
30
33
|
|
|
31
34
|
# Publish an event to all subscribed handlers
|
|
32
35
|
# @param event [SmartDomain::Event::Base] Event to publish
|
|
36
|
+
# @raise [ValidationError] If event is invalid
|
|
33
37
|
def publish(event)
|
|
38
|
+
# Validate event before publishing
|
|
39
|
+
unless event.is_a?(SmartDomain::Event::Base)
|
|
40
|
+
raise SmartDomain::Event::ValidationError, 'Event must be a SmartDomain::Event::Base'
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
begin
|
|
44
|
+
unless event.valid?
|
|
45
|
+
raise SmartDomain::Event::ValidationError, "Invalid event: #{event.errors.full_messages.join(', ')}"
|
|
46
|
+
end
|
|
47
|
+
rescue NoMethodError => e
|
|
48
|
+
raise SmartDomain::Event::ValidationError, "Malformed event: #{e.message}"
|
|
49
|
+
end
|
|
50
|
+
|
|
34
51
|
# Find all matching handlers (exact match + wildcard patterns)
|
|
35
52
|
matching_handlers = find_matching_handlers(event.event_type)
|
|
36
53
|
|
|
37
54
|
if matching_handlers.empty?
|
|
38
|
-
|
|
55
|
+
logger.debug "[SmartDomain::Memory] No handlers for event type: #{event.event_type}"
|
|
39
56
|
return
|
|
40
57
|
end
|
|
41
58
|
|
|
42
|
-
|
|
59
|
+
logger.debug "[SmartDomain::Memory] Notifying #{matching_handlers.size} handler(s) for #{event.event_type}"
|
|
43
60
|
|
|
44
61
|
matching_handlers.each do |handler|
|
|
45
62
|
handle_event(handler, event)
|
|
@@ -69,9 +86,7 @@ module SmartDomain
|
|
|
69
86
|
handlers = []
|
|
70
87
|
|
|
71
88
|
@handlers.each do |pattern, pattern_handlers|
|
|
72
|
-
if event_type_matches?(event_type, pattern)
|
|
73
|
-
handlers.concat(pattern_handlers)
|
|
74
|
-
end
|
|
89
|
+
handlers.concat(pattern_handlers) if event_type_matches?(event_type, pattern)
|
|
75
90
|
end
|
|
76
91
|
|
|
77
92
|
handlers.uniq
|
|
@@ -86,7 +101,7 @@ module SmartDomain
|
|
|
86
101
|
return true if event_type == pattern
|
|
87
102
|
|
|
88
103
|
# Wildcard pattern match (e.g., "user.*" matches "user.created")
|
|
89
|
-
if pattern.end_with?(
|
|
104
|
+
if pattern.end_with?('.*')
|
|
90
105
|
prefix = pattern[0..-3] # Remove ".*"
|
|
91
106
|
return event_type.start_with?("#{prefix}.")
|
|
92
107
|
end
|
|
@@ -100,8 +115,8 @@ module SmartDomain
|
|
|
100
115
|
def handle_event(handler, event)
|
|
101
116
|
handler.handle(event)
|
|
102
117
|
rescue StandardError => e
|
|
103
|
-
|
|
104
|
-
|
|
118
|
+
logger.error "[SmartDomain::Memory] Error in event handler #{handler.class.name}: #{e.message}"
|
|
119
|
+
logger.error e.backtrace.join("\n")
|
|
105
120
|
# Swallow exception to not affect other handlers
|
|
106
121
|
end
|
|
107
122
|
end
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
3
|
+
require 'active_model'
|
|
4
|
+
require 'active_support/core_ext/object/blank'
|
|
5
|
+
require 'securerandom'
|
|
6
|
+
require 'logger'
|
|
7
7
|
|
|
8
8
|
module SmartDomain
|
|
9
9
|
module Event
|
|
10
|
+
# Validation error for domain events
|
|
11
|
+
class ValidationError < StandardError; end
|
|
12
|
+
|
|
10
13
|
# Base class for all domain events in the system.
|
|
11
14
|
#
|
|
12
15
|
# Domain events represent significant business occurrences that other
|
|
@@ -58,7 +61,9 @@ module SmartDomain
|
|
|
58
61
|
def initialize(attributes = {})
|
|
59
62
|
super
|
|
60
63
|
freeze_event
|
|
61
|
-
|
|
64
|
+
return if valid?
|
|
65
|
+
|
|
66
|
+
raise ValidationError, "Event validation failed: #{errors.full_messages.join(', ')}"
|
|
62
67
|
end
|
|
63
68
|
|
|
64
69
|
# Convert event to hash
|
|
@@ -101,13 +106,16 @@ module SmartDomain
|
|
|
101
106
|
# event = UserCreatedEvent.new(...)
|
|
102
107
|
# bus.publish(event)
|
|
103
108
|
class Bus
|
|
104
|
-
attr_reader :adapter
|
|
109
|
+
attr_reader :adapter
|
|
105
110
|
|
|
106
111
|
# Initialize event bus with optional adapter
|
|
107
112
|
# @param adapter [Object, Symbol] Event bus adapter or adapter name (default: Memory adapter)
|
|
108
113
|
def initialize(adapter: nil)
|
|
109
114
|
@adapter = resolve_adapter(adapter)
|
|
110
|
-
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def logger
|
|
118
|
+
@logger ||= SmartDomain.configuration.logger
|
|
111
119
|
end
|
|
112
120
|
|
|
113
121
|
# Subscribe a handler to a specific event type
|
|
@@ -115,7 +123,7 @@ module SmartDomain
|
|
|
115
123
|
# @param handler [Object] Handler object that responds to #handle(event)
|
|
116
124
|
def subscribe(event_type, handler)
|
|
117
125
|
@adapter.subscribe(event_type, handler)
|
|
118
|
-
|
|
126
|
+
logger.info "[SmartDomain] Event handler subscribed: #{handler.class.name} -> #{event_type}"
|
|
119
127
|
end
|
|
120
128
|
|
|
121
129
|
# Publish an event to all registered handlers
|
|
@@ -124,8 +132,8 @@ module SmartDomain
|
|
|
124
132
|
def publish(event)
|
|
125
133
|
validate_event!(event)
|
|
126
134
|
|
|
127
|
-
|
|
128
|
-
|
|
135
|
+
logger.info "[SmartDomain] Publishing event: #{event.event_type} (#{event.event_id})"
|
|
136
|
+
logger.debug "[SmartDomain] Event details: #{event.to_h}"
|
|
129
137
|
|
|
130
138
|
@adapter.publish(event)
|
|
131
139
|
end
|
|
@@ -149,15 +157,20 @@ module SmartDomain
|
|
|
149
157
|
|
|
150
158
|
# Validate event before publishing
|
|
151
159
|
# @param event [Object] Event to validate
|
|
152
|
-
# @raise [ArgumentError] If event is not
|
|
160
|
+
# @raise [ArgumentError] If event is not a Base instance
|
|
161
|
+
# @raise [ValidationError] If event validation fails
|
|
153
162
|
def validate_event!(event)
|
|
154
163
|
unless event.is_a?(Base)
|
|
155
164
|
raise ArgumentError, "Event must be a SmartDomain::Event::Base, got #{event.class.name}"
|
|
156
165
|
end
|
|
157
166
|
|
|
158
|
-
|
|
167
|
+
begin
|
|
168
|
+
return if event.valid?
|
|
159
169
|
|
|
160
|
-
|
|
170
|
+
raise ValidationError, "Event validation failed: #{event.errors.full_messages.join(', ')}"
|
|
171
|
+
rescue NoMethodError => e
|
|
172
|
+
raise ValidationError, "Malformed event: #{e.message}"
|
|
173
|
+
end
|
|
161
174
|
end
|
|
162
175
|
end
|
|
163
176
|
|
|
@@ -37,7 +37,7 @@ module SmartDomain
|
|
|
37
37
|
# @param event [SmartDomain::Event::Base] Event to handle
|
|
38
38
|
# @raise [NotImplementedError] If not implemented by subclass
|
|
39
39
|
def handle(event)
|
|
40
|
-
raise NotImplementedError,
|
|
40
|
+
raise NotImplementedError, 'Subclasses must implement #handle(event)'
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
# Check if this handler can handle a specific event type
|
|
@@ -48,7 +48,7 @@ module SmartDomain
|
|
|
48
48
|
# @param event_type [String] Event type to check
|
|
49
49
|
# @return [Boolean] True if handler can handle this event type
|
|
50
50
|
def can_handle?(event_type)
|
|
51
|
-
raise NotImplementedError,
|
|
51
|
+
raise NotImplementedError, 'Subclasses must implement #can_handle?(event_type)'
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
# Handle event asynchronously using ActiveJob
|
|
@@ -58,12 +58,14 @@ module SmartDomain
|
|
|
58
58
|
#
|
|
59
59
|
# @param event [SmartDomain::Event::Base] Event to handle
|
|
60
60
|
# @raise [RuntimeError] If ActiveJob is not loaded
|
|
61
|
+
# @raise [ValidationError] If event is invalid
|
|
61
62
|
def handle_async(event)
|
|
62
63
|
unless defined?(ActiveJob)
|
|
63
64
|
raise "ActiveJob is required for async event handling. Please require 'active_job' in your application."
|
|
64
65
|
end
|
|
65
66
|
|
|
66
|
-
event.
|
|
67
|
+
raise ValidationError, "Event validation failed: #{event.errors.full_messages.join(', ')}" unless event.valid?
|
|
68
|
+
|
|
67
69
|
HandlerJob.perform_later(self.class.name, event.to_h)
|
|
68
70
|
end
|
|
69
71
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require 'active_support/concern'
|
|
4
4
|
|
|
5
5
|
module SmartDomain
|
|
6
6
|
module Event
|
|
@@ -89,6 +89,14 @@ module SmartDomain
|
|
|
89
89
|
attribute :changed_fields, default: -> { [] }
|
|
90
90
|
attribute :old_values, default: -> { {} }
|
|
91
91
|
attribute :new_values, default: -> { {} }
|
|
92
|
+
|
|
93
|
+
validate :changed_fields_must_not_be_empty
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
def changed_fields_must_not_be_empty
|
|
98
|
+
errors.add(:changed_fields, "can't be blank") if changed_fields.blank? || changed_fields.empty?
|
|
99
|
+
end
|
|
92
100
|
end
|
|
93
101
|
|
|
94
102
|
# Helper to extract changes from an ActiveRecord model
|
|
@@ -150,6 +158,8 @@ module SmartDomain
|
|
|
150
158
|
|
|
151
159
|
included do
|
|
152
160
|
attribute :reason, :string
|
|
161
|
+
|
|
162
|
+
validates :reason, presence: true
|
|
153
163
|
end
|
|
154
164
|
end
|
|
155
165
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative
|
|
4
|
-
require_relative
|
|
3
|
+
require_relative '../handlers/audit_handler'
|
|
4
|
+
require_relative '../handlers/metrics_handler'
|
|
5
5
|
|
|
6
6
|
module SmartDomain
|
|
7
7
|
module Event
|
|
@@ -86,8 +86,8 @@ module SmartDomain
|
|
|
86
86
|
# Log what was registered
|
|
87
87
|
if include_audit || include_metrics
|
|
88
88
|
handlers_registered = []
|
|
89
|
-
handlers_registered <<
|
|
90
|
-
handlers_registered <<
|
|
89
|
+
handlers_registered << 'audit' if include_audit
|
|
90
|
+
handlers_registered << 'metrics' if include_metrics
|
|
91
91
|
|
|
92
92
|
logger.info "[SmartDomain::Registration] Standard handlers registered for #{domain} domain: " \
|
|
93
93
|
"#{handlers_registered.join(', ')} (#{events.size} events)"
|
|
@@ -41,7 +41,7 @@ module SmartDomain
|
|
|
41
41
|
# @param event_type [String] Event type to check
|
|
42
42
|
# @return [Boolean] True if event belongs to this domain
|
|
43
43
|
def can_handle?(event_type)
|
|
44
|
-
return true if @domain ==
|
|
44
|
+
return true if @domain == '*'
|
|
45
45
|
|
|
46
46
|
event_type.start_with?("#{@domain}.")
|
|
47
47
|
end
|
|
@@ -49,7 +49,7 @@ module SmartDomain
|
|
|
49
49
|
# Handle audit logging for an event
|
|
50
50
|
# @param event [SmartDomain::Event::Base] Event to audit
|
|
51
51
|
def handle(event)
|
|
52
|
-
action = event.event_type.split(
|
|
52
|
+
action = event.event_type.split('.').last
|
|
53
53
|
|
|
54
54
|
# 1. Log to Rails logger (structured)
|
|
55
55
|
log_audit_event(event, action)
|
|
@@ -117,17 +117,15 @@ module SmartDomain
|
|
|
117
117
|
return unless defined?(AuditEvent)
|
|
118
118
|
|
|
119
119
|
AuditEvent.create!(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
120
|
+
event_id: event.event_id,
|
|
121
|
+
event_type: event.event_type,
|
|
122
|
+
aggregate_id: event.aggregate_id,
|
|
123
|
+
aggregate_type: event.aggregate_type,
|
|
123
124
|
organization_id: event.organization_id,
|
|
124
|
-
|
|
125
|
-
user_agent: extract_user_agent(event),
|
|
126
|
-
old_values: extract_old_values(event),
|
|
127
|
-
new_values: extract_new_values(event),
|
|
128
|
-
occurred_at: event.occurred_at,
|
|
125
|
+
category: map_event_category(event.event_type),
|
|
129
126
|
risk_level: assess_risk_level(event.event_type),
|
|
130
|
-
|
|
127
|
+
event_data: event.to_h,
|
|
128
|
+
occurred_at: event.occurred_at
|
|
131
129
|
)
|
|
132
130
|
rescue StandardError => e
|
|
133
131
|
@logger.warn "[SmartDomain::AuditHandler] Failed to write to audit table: #{e.message}"
|
|
@@ -138,14 +136,14 @@ module SmartDomain
|
|
|
138
136
|
# @return [String] Audit category
|
|
139
137
|
def map_event_category(event_type)
|
|
140
138
|
case event_type
|
|
141
|
-
when /^auth
|
|
142
|
-
|
|
139
|
+
when /^auth\.|logged_in|logged_out|login|logout|authenticated|password/
|
|
140
|
+
'authentication'
|
|
143
141
|
when /accessed|viewed|patient\./
|
|
144
|
-
|
|
142
|
+
'data_access'
|
|
145
143
|
when /created|updated|deleted|assigned|removed/
|
|
146
|
-
|
|
144
|
+
'admin_action'
|
|
147
145
|
else
|
|
148
|
-
|
|
146
|
+
'system_event'
|
|
149
147
|
end
|
|
150
148
|
end
|
|
151
149
|
|
|
@@ -155,11 +153,11 @@ module SmartDomain
|
|
|
155
153
|
def assess_risk_level(event_type)
|
|
156
154
|
case event_type
|
|
157
155
|
when /suspended|deleted|revoked|failed|rejected/
|
|
158
|
-
|
|
156
|
+
'HIGH'
|
|
159
157
|
when /updated|changed|assigned/
|
|
160
|
-
|
|
158
|
+
'MEDIUM'
|
|
161
159
|
else
|
|
162
|
-
|
|
160
|
+
'LOW'
|
|
163
161
|
end
|
|
164
162
|
end
|
|
165
163
|
|
|
@@ -167,35 +165,35 @@ module SmartDomain
|
|
|
167
165
|
# @param event [SmartDomain::Event::Base] Event
|
|
168
166
|
# @return [String, nil] Actor ID
|
|
169
167
|
def extract_actor_id(event)
|
|
170
|
-
event.try(:actor_id) || event.attributes[
|
|
168
|
+
event.try(:actor_id) || event.attributes['actor_id']
|
|
171
169
|
end
|
|
172
170
|
|
|
173
171
|
# Extract ip_address from event (SecurityContextMixin)
|
|
174
172
|
# @param event [SmartDomain::Event::Base] Event
|
|
175
173
|
# @return [String, nil] IP address
|
|
176
174
|
def extract_ip_address(event)
|
|
177
|
-
event.try(:ip_address) || event.attributes[
|
|
175
|
+
event.try(:ip_address) || event.attributes['ip_address']
|
|
178
176
|
end
|
|
179
177
|
|
|
180
178
|
# Extract user_agent from event (SecurityContextMixin)
|
|
181
179
|
# @param event [SmartDomain::Event::Base] Event
|
|
182
180
|
# @return [String, nil] User agent
|
|
183
181
|
def extract_user_agent(event)
|
|
184
|
-
event.try(:user_agent) || event.attributes[
|
|
182
|
+
event.try(:user_agent) || event.attributes['user_agent']
|
|
185
183
|
end
|
|
186
184
|
|
|
187
185
|
# Extract old_values from event (ChangeTrackingMixin)
|
|
188
186
|
# @param event [SmartDomain::Event::Base] Event
|
|
189
187
|
# @return [Hash, nil] Old values
|
|
190
188
|
def extract_old_values(event)
|
|
191
|
-
event.try(:old_values) || event.attributes[
|
|
189
|
+
event.try(:old_values) || event.attributes['old_values']
|
|
192
190
|
end
|
|
193
191
|
|
|
194
192
|
# Extract new_values from event (ChangeTrackingMixin)
|
|
195
193
|
# @param event [SmartDomain::Event::Base] Event
|
|
196
194
|
# @return [Hash, nil] New values
|
|
197
195
|
def extract_new_values(event)
|
|
198
|
-
event.try(:new_values) || event.attributes[
|
|
196
|
+
event.try(:new_values) || event.attributes['new_values']
|
|
199
197
|
end
|
|
200
198
|
|
|
201
199
|
# Build compliance flags for audit record
|
|
@@ -36,7 +36,7 @@ module SmartDomain
|
|
|
36
36
|
# @param event_type [String] Event type to check
|
|
37
37
|
# @return [Boolean] True if event belongs to this domain
|
|
38
38
|
def can_handle?(event_type)
|
|
39
|
-
return true if @domain ==
|
|
39
|
+
return true if @domain == '*'
|
|
40
40
|
|
|
41
41
|
event_type.start_with?("#{@domain}.")
|
|
42
42
|
end
|
|
@@ -47,7 +47,14 @@ module SmartDomain
|
|
|
47
47
|
metric_name = build_metric_name(event)
|
|
48
48
|
tags = build_metric_tags(event)
|
|
49
49
|
|
|
50
|
+
# Emit counter metric
|
|
50
51
|
emit_metric(metric_name, tags)
|
|
52
|
+
|
|
53
|
+
# Emit timing metric if duration is present
|
|
54
|
+
if event.respond_to?(:duration) && event.duration
|
|
55
|
+
timing_name = "#{metric_name}.duration"
|
|
56
|
+
emit_metric(timing_name, tags.merge(duration_ms: event.duration))
|
|
57
|
+
end
|
|
51
58
|
rescue StandardError => e
|
|
52
59
|
# Never fail on metrics handler errors
|
|
53
60
|
@logger.warn "[SmartDomain::MetricsHandler] Metrics collection failed: #{e.message}"
|
|
@@ -145,14 +145,12 @@ module SmartDomain
|
|
|
145
145
|
return if @pending_domain_events.blank?
|
|
146
146
|
|
|
147
147
|
@pending_domain_events.each do |event|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
logger.error e.backtrace.join("\n")
|
|
155
|
-
end
|
|
148
|
+
SmartDomain::Event.bus.publish(event)
|
|
149
|
+
rescue StandardError => e
|
|
150
|
+
# Log error but don't raise (events should be fire-and-forget)
|
|
151
|
+
logger = defined?(Rails) ? Rails.logger : Logger.new($stdout)
|
|
152
|
+
logger.error "[SmartDomain] Failed to publish event: #{e.message}"
|
|
153
|
+
logger.error e.backtrace.join("\n")
|
|
156
154
|
end
|
|
157
155
|
|
|
158
156
|
clear_domain_events
|
data/lib/smart_domain/railtie.rb
CHANGED
|
@@ -12,8 +12,8 @@ module SmartDomain
|
|
|
12
12
|
|
|
13
13
|
# Add generators to load path
|
|
14
14
|
generators do
|
|
15
|
-
require_relative
|
|
16
|
-
require_relative
|
|
15
|
+
require_relative 'generators/install_generator'
|
|
16
|
+
require_relative 'generators/domain_generator'
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
# Configuration hook
|
|
@@ -27,35 +27,35 @@ module SmartDomain
|
|
|
27
27
|
|
|
28
28
|
# Rake tasks
|
|
29
29
|
rake_tasks do
|
|
30
|
-
load
|
|
30
|
+
load 'smart_domain/tasks/domains.rake'
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
-
private
|
|
34
|
-
|
|
35
33
|
# Load all domain setup files
|
|
36
34
|
def self.load_domain_setups
|
|
37
|
-
setup_files = Dir[Rails.root.join(
|
|
35
|
+
setup_files = Dir[Rails.root.join('app/domains/**/setup.rb')]
|
|
38
36
|
|
|
39
37
|
setup_files.each do |setup_file|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
Rails.logger.
|
|
56
|
-
Rails.logger.error e.message
|
|
57
|
-
Rails.logger.error e.backtrace.join("\n")
|
|
38
|
+
require setup_file
|
|
39
|
+
|
|
40
|
+
# Extract domain module name from path
|
|
41
|
+
# e.g., app/domains/user_management/setup.rb -> UserManagement
|
|
42
|
+
domain_path = setup_file.gsub(Rails.root.join('app/domains/').to_s, '')
|
|
43
|
+
domain_name = domain_path.split('/').first.camelize
|
|
44
|
+
|
|
45
|
+
# Call setup! method if defined
|
|
46
|
+
domain_module = begin
|
|
47
|
+
domain_name.constantize
|
|
48
|
+
rescue StandardError
|
|
49
|
+
nil
|
|
50
|
+
end
|
|
51
|
+
if domain_module.respond_to?(:setup!)
|
|
52
|
+
domain_module.setup!
|
|
53
|
+
Rails.logger.info "[SmartDomain] Loaded domain: #{domain_name}"
|
|
58
54
|
end
|
|
55
|
+
rescue StandardError => e
|
|
56
|
+
Rails.logger.error "[SmartDomain] Failed to load domain setup: #{setup_file}"
|
|
57
|
+
Rails.logger.error e.message
|
|
58
|
+
Rails.logger.error e.backtrace.join("\n")
|
|
59
59
|
end
|
|
60
60
|
end
|
|
61
61
|
end
|
|
@@ -1,43 +1,47 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
namespace :active_domain do
|
|
4
|
-
desc
|
|
4
|
+
desc 'List all registered domains'
|
|
5
5
|
task domains: :environment do
|
|
6
|
-
setup_files = Dir[Rails.root.join(
|
|
6
|
+
setup_files = Dir[Rails.root.join('app/domains/**/setup.rb')]
|
|
7
7
|
|
|
8
8
|
if setup_files.empty?
|
|
9
|
-
puts
|
|
10
|
-
puts
|
|
9
|
+
puts 'No domains found in app/domains/'
|
|
10
|
+
puts 'Generate your first domain with: rails generate active_domain:domain User'
|
|
11
11
|
else
|
|
12
|
-
puts
|
|
12
|
+
puts 'Registered domains:'
|
|
13
13
|
setup_files.each do |setup_file|
|
|
14
|
-
domain_path = setup_file.gsub(Rails.root.join(
|
|
15
|
-
domain_name = domain_path.split(
|
|
14
|
+
domain_path = setup_file.gsub(Rails.root.join('app/domains/').to_s, '')
|
|
15
|
+
domain_name = domain_path.split('/').first
|
|
16
16
|
puts " - #{domain_name}"
|
|
17
17
|
end
|
|
18
18
|
puts "\nTotal: #{setup_files.size} domain(s)"
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
desc
|
|
22
|
+
desc 'Reload all domain setups'
|
|
23
23
|
task reload: :environment do
|
|
24
|
-
setup_files = Dir[Rails.root.join(
|
|
24
|
+
setup_files = Dir[Rails.root.join('app/domains/**/setup.rb')]
|
|
25
25
|
|
|
26
26
|
puts "Reloading #{setup_files.size} domain(s)..."
|
|
27
27
|
|
|
28
28
|
setup_files.each do |setup_file|
|
|
29
29
|
load setup_file
|
|
30
30
|
|
|
31
|
-
domain_path = setup_file.gsub(Rails.root.join(
|
|
32
|
-
domain_name = domain_path.split(
|
|
31
|
+
domain_path = setup_file.gsub(Rails.root.join('app/domains/').to_s, '')
|
|
32
|
+
domain_name = domain_path.split('/').first.camelize
|
|
33
33
|
|
|
34
|
-
domain_module =
|
|
35
|
-
|
|
34
|
+
domain_module = begin
|
|
35
|
+
domain_name.constantize
|
|
36
|
+
rescue StandardError
|
|
37
|
+
nil
|
|
38
|
+
end
|
|
39
|
+
if domain_module.respond_to?(:setup!)
|
|
36
40
|
domain_module.setup!
|
|
37
41
|
puts " ✓ #{domain_name}"
|
|
38
42
|
end
|
|
39
43
|
end
|
|
40
44
|
|
|
41
|
-
puts
|
|
45
|
+
puts 'Done!'
|
|
42
46
|
end
|
|
43
47
|
end
|
data/lib/smart_domain/version.rb
CHANGED
data/lib/smart_domain.rb
CHANGED
|
@@ -1,38 +1,38 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
7
|
-
require
|
|
3
|
+
require 'active_model'
|
|
4
|
+
require 'active_support'
|
|
5
|
+
require 'active_support/core_ext'
|
|
6
|
+
require 'active_record'
|
|
7
|
+
require 'logger'
|
|
8
8
|
|
|
9
|
-
require_relative
|
|
10
|
-
require_relative
|
|
9
|
+
require_relative 'smart_domain/version'
|
|
10
|
+
require_relative 'smart_domain/configuration'
|
|
11
11
|
|
|
12
12
|
# Event system
|
|
13
|
-
require_relative
|
|
14
|
-
require_relative
|
|
15
|
-
require_relative
|
|
16
|
-
require_relative
|
|
13
|
+
require_relative 'smart_domain/event/base'
|
|
14
|
+
require_relative 'smart_domain/event/mixins'
|
|
15
|
+
require_relative 'smart_domain/event/handler'
|
|
16
|
+
require_relative 'smart_domain/event/adapters/memory'
|
|
17
17
|
|
|
18
18
|
# Handlers
|
|
19
|
-
require_relative
|
|
20
|
-
require_relative
|
|
19
|
+
require_relative 'smart_domain/handlers/audit_handler'
|
|
20
|
+
require_relative 'smart_domain/handlers/metrics_handler'
|
|
21
21
|
|
|
22
22
|
# Event registration
|
|
23
|
-
require_relative
|
|
23
|
+
require_relative 'smart_domain/event/registration'
|
|
24
24
|
|
|
25
25
|
# Domain patterns
|
|
26
|
-
require_relative
|
|
27
|
-
require_relative
|
|
28
|
-
require_relative
|
|
26
|
+
require_relative 'smart_domain/domain/exceptions'
|
|
27
|
+
require_relative 'smart_domain/domain/policy'
|
|
28
|
+
require_relative 'smart_domain/domain/service'
|
|
29
29
|
|
|
30
30
|
# Rails integration
|
|
31
|
-
require_relative
|
|
32
|
-
require_relative
|
|
31
|
+
require_relative 'smart_domain/integration/active_record'
|
|
32
|
+
require_relative 'smart_domain/integration/multi_tenancy'
|
|
33
33
|
|
|
34
34
|
# Railtie (loads automatically if Rails is present)
|
|
35
|
-
require_relative
|
|
35
|
+
require_relative 'smart_domain/railtie' if defined?(Rails::Railtie)
|
|
36
36
|
|
|
37
37
|
# SmartDomain - Domain-Driven Design and Event-Driven Architecture for Rails
|
|
38
38
|
#
|
data/smart_domain.gemspec
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative
|
|
3
|
+
require_relative 'lib/smart_domain/version'
|
|
4
4
|
|
|
5
5
|
Gem::Specification.new do |spec|
|
|
6
|
-
spec.name =
|
|
6
|
+
spec.name = 'smart_domain'
|
|
7
7
|
spec.version = SmartDomain::VERSION
|
|
8
|
-
spec.authors = [
|
|
9
|
-
spec.email = [
|
|
8
|
+
spec.authors = ['Rachid Al Maach']
|
|
9
|
+
spec.email = ['rachid@qraft.nl']
|
|
10
10
|
|
|
11
|
-
spec.summary =
|
|
11
|
+
spec.summary = 'Smart Domain-Driven Design and Event-Driven Architecture for Rails'
|
|
12
12
|
spec.description = <<~DESC
|
|
13
13
|
SmartDomain brings battle-tested DDD/EDA patterns to Rails applications.
|
|
14
14
|
Inspired by the Aeyes healthcare platform, it provides domain events,
|
|
@@ -16,13 +16,14 @@ Gem::Specification.new do |spec|
|
|
|
16
16
|
Features 70% boilerplate reduction through intelligent event handling and
|
|
17
17
|
AI-augmented development patterns.
|
|
18
18
|
DESC
|
|
19
|
-
spec.homepage =
|
|
20
|
-
spec.license =
|
|
21
|
-
spec.required_ruby_version =
|
|
19
|
+
spec.homepage = 'https://github.com/rachid/smart_domain'
|
|
20
|
+
spec.license = 'MIT'
|
|
21
|
+
spec.required_ruby_version = '>= 3.0.0'
|
|
22
22
|
|
|
23
|
-
spec.metadata[
|
|
24
|
-
spec.metadata[
|
|
25
|
-
spec.metadata[
|
|
23
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
24
|
+
spec.metadata['source_code_uri'] = 'https://github.com/rachid/smart_domain'
|
|
25
|
+
spec.metadata['changelog_uri'] = 'https://github.com/rachid/smart_domain/blob/main/CHANGELOG.md'
|
|
26
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
26
27
|
|
|
27
28
|
spec.files = Dir.chdir(__dir__) do
|
|
28
29
|
`git ls-files -z`.split("\x0").reject do |f|
|
|
@@ -31,23 +32,23 @@ Gem::Specification.new do |spec|
|
|
|
31
32
|
end
|
|
32
33
|
end
|
|
33
34
|
|
|
34
|
-
spec.bindir =
|
|
35
|
+
spec.bindir = 'exe'
|
|
35
36
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
36
|
-
spec.require_paths = [
|
|
37
|
+
spec.require_paths = ['lib']
|
|
37
38
|
|
|
38
39
|
# Core dependencies
|
|
39
|
-
spec.add_dependency
|
|
40
|
-
spec.add_dependency
|
|
41
|
-
spec.add_dependency
|
|
42
|
-
spec.add_dependency
|
|
40
|
+
spec.add_dependency 'activemodel', '>= 7.0'
|
|
41
|
+
spec.add_dependency 'activerecord', '>= 7.0'
|
|
42
|
+
spec.add_dependency 'activesupport', '>= 7.0'
|
|
43
|
+
spec.add_dependency 'rails', '>= 7.0'
|
|
43
44
|
|
|
44
45
|
# Development dependencies
|
|
45
|
-
spec.add_development_dependency
|
|
46
|
-
spec.add_development_dependency
|
|
47
|
-
spec.add_development_dependency
|
|
48
|
-
spec.add_development_dependency
|
|
49
|
-
spec.add_development_dependency
|
|
50
|
-
spec.add_development_dependency
|
|
51
|
-
spec.add_development_dependency
|
|
52
|
-
spec.add_development_dependency
|
|
46
|
+
spec.add_development_dependency 'factory_bot_rails', '~> 6.2'
|
|
47
|
+
spec.add_development_dependency 'faker', '~> 3.2'
|
|
48
|
+
spec.add_development_dependency 'rspec', '~> 3.12'
|
|
49
|
+
spec.add_development_dependency 'rspec-rails', '~> 6.0'
|
|
50
|
+
spec.add_development_dependency 'rubocop', '~> 1.50'
|
|
51
|
+
spec.add_development_dependency 'rubocop-rails', '~> 2.19'
|
|
52
|
+
spec.add_development_dependency 'rubocop-rspec', '~> 2.20'
|
|
53
|
+
spec.add_development_dependency 'sqlite3', '>= 2.1'
|
|
53
54
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: smart_domain
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rachid Al Maach
|
|
@@ -11,7 +11,7 @@ cert_chain: []
|
|
|
11
11
|
date: 2025-12-31 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
14
|
+
name: activemodel
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
@@ -25,7 +25,7 @@ dependencies:
|
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '7.0'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
|
-
name:
|
|
28
|
+
name: activerecord
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
31
|
- - ">="
|
|
@@ -53,7 +53,7 @@ dependencies:
|
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
54
|
version: '7.0'
|
|
55
55
|
- !ruby/object:Gem::Dependency
|
|
56
|
-
name:
|
|
56
|
+
name: rails
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
|
58
58
|
requirements:
|
|
59
59
|
- - ">="
|
|
@@ -67,61 +67,61 @@ dependencies:
|
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
68
|
version: '7.0'
|
|
69
69
|
- !ruby/object:Gem::Dependency
|
|
70
|
-
name:
|
|
70
|
+
name: factory_bot_rails
|
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
|
72
72
|
requirements:
|
|
73
73
|
- - "~>"
|
|
74
74
|
- !ruby/object:Gem::Version
|
|
75
|
-
version: '
|
|
75
|
+
version: '6.2'
|
|
76
76
|
type: :development
|
|
77
77
|
prerelease: false
|
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
79
|
requirements:
|
|
80
80
|
- - "~>"
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: '
|
|
82
|
+
version: '6.2'
|
|
83
83
|
- !ruby/object:Gem::Dependency
|
|
84
|
-
name:
|
|
84
|
+
name: faker
|
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
|
86
86
|
requirements:
|
|
87
87
|
- - "~>"
|
|
88
88
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: '
|
|
89
|
+
version: '3.2'
|
|
90
90
|
type: :development
|
|
91
91
|
prerelease: false
|
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
93
|
requirements:
|
|
94
94
|
- - "~>"
|
|
95
95
|
- !ruby/object:Gem::Version
|
|
96
|
-
version: '
|
|
96
|
+
version: '3.2'
|
|
97
97
|
- !ruby/object:Gem::Dependency
|
|
98
|
-
name:
|
|
98
|
+
name: rspec
|
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
|
100
100
|
requirements:
|
|
101
101
|
- - "~>"
|
|
102
102
|
- !ruby/object:Gem::Version
|
|
103
|
-
version: '
|
|
103
|
+
version: '3.12'
|
|
104
104
|
type: :development
|
|
105
105
|
prerelease: false
|
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
|
107
107
|
requirements:
|
|
108
108
|
- - "~>"
|
|
109
109
|
- !ruby/object:Gem::Version
|
|
110
|
-
version: '
|
|
110
|
+
version: '3.12'
|
|
111
111
|
- !ruby/object:Gem::Dependency
|
|
112
|
-
name:
|
|
112
|
+
name: rspec-rails
|
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
|
114
114
|
requirements:
|
|
115
115
|
- - "~>"
|
|
116
116
|
- !ruby/object:Gem::Version
|
|
117
|
-
version: '
|
|
117
|
+
version: '6.0'
|
|
118
118
|
type: :development
|
|
119
119
|
prerelease: false
|
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
|
121
121
|
requirements:
|
|
122
122
|
- - "~>"
|
|
123
123
|
- !ruby/object:Gem::Version
|
|
124
|
-
version: '
|
|
124
|
+
version: '6.0'
|
|
125
125
|
- !ruby/object:Gem::Dependency
|
|
126
126
|
name: rubocop
|
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -191,6 +191,7 @@ extensions: []
|
|
|
191
191
|
extra_rdoc_files: []
|
|
192
192
|
files:
|
|
193
193
|
- ".rspec"
|
|
194
|
+
- ".rubocop.yml"
|
|
194
195
|
- CHANGELOG.md
|
|
195
196
|
- LICENSE
|
|
196
197
|
- README.md
|
|
@@ -286,7 +287,6 @@ files:
|
|
|
286
287
|
- examples/blog_app/config/initializers/inflections.rb
|
|
287
288
|
- examples/blog_app/config/initializers/smart_domain.rb
|
|
288
289
|
- examples/blog_app/config/locales/en.yml
|
|
289
|
-
- examples/blog_app/config/master.key
|
|
290
290
|
- examples/blog_app/config/puma.rb
|
|
291
291
|
- examples/blog_app/config/queue.yml
|
|
292
292
|
- examples/blog_app/config/recurring.yml
|
|
@@ -349,7 +349,6 @@ files:
|
|
|
349
349
|
- lib/smart_domain/railtie.rb
|
|
350
350
|
- lib/smart_domain/tasks/domains.rake
|
|
351
351
|
- lib/smart_domain/version.rb
|
|
352
|
-
- smart_domain-0.1.0.gem
|
|
353
352
|
- smart_domain.gemspec
|
|
354
353
|
homepage: https://github.com/rachid/smart_domain
|
|
355
354
|
licenses:
|
|
@@ -358,6 +357,7 @@ metadata:
|
|
|
358
357
|
homepage_uri: https://github.com/rachid/smart_domain
|
|
359
358
|
source_code_uri: https://github.com/rachid/smart_domain
|
|
360
359
|
changelog_uri: https://github.com/rachid/smart_domain/blob/main/CHANGELOG.md
|
|
360
|
+
rubygems_mfa_required: 'true'
|
|
361
361
|
post_install_message:
|
|
362
362
|
rdoc_options: []
|
|
363
363
|
require_paths:
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
c993036f99084b62923212c97ff582b8
|
data/smart_domain-0.1.0.gem
DELETED
|
Binary file
|