feature_pack 0.10.1 → 0.11.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 +36 -63
- data/doc/feature_pack.md +2 -2
- data/lib/feature_pack/controller.rb +4 -57
- data/lib/feature_pack/feature_controller_setup.rb +111 -0
- data/lib/feature_pack/group_controller.rb +11 -41
- data/lib/feature_pack.rb +110 -67
- data/lib/generators/feature_pack/add_feature/add_feature_generator.rb +30 -3
- data/lib/generators/feature_pack/add_feature/templates/controller.rb.tt +3 -1
- metadata +18 -7
- data/doc/hooks.md +0 -108
- data/lib/feature_pack/api/controller.rb +0 -3
- data/lib/generators/feature_pack/add_feature/templates/after_initialize.rb.tt +0 -11
- data/lib/generators/feature_pack/add_group/templates/_group_space/after_initialize.rb.tt +0 -10
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4ad6b0b7d1fef825b25714c0b54b468935b5d30bbb45b1e1cdaa2f7023417345
|
|
4
|
+
data.tar.gz: 795e0c96835d48091f3e50f0d37add2cd0b0a459a65fc592c2be527550ac3519
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0cfcaddc9bdb103bafa8be27d9c24d6034455660cac7077d58f11abf7faf095f705869fa534ded2efa02aa574f52437d871eae361c877fa6a0ab35c7ed7de51e
|
|
7
|
+
data.tar.gz: 7bc13892f86bb7f7b720da3aa79bcdc85e482fbaa2b1e3489d96947145dd773e725dd0e170257d2e6f42c775126267313e4095089fbc4e0af691a8e206eb541d
|
data/README.md
CHANGED
|
@@ -152,6 +152,31 @@ class FeaturePack::HumanResources::EmployeesController < FeaturePack::HumanResou
|
|
|
152
152
|
end
|
|
153
153
|
```
|
|
154
154
|
|
|
155
|
+
Generated feature controllers inherit their group's controller, so group callbacks
|
|
156
|
+
(authentication, authorization, etc.) apply to every feature. The setup callback
|
|
157
|
+
resolves the context from the routed path (`feature_pack/<group>/<feature>`): when the
|
|
158
|
+
second segment is a registered feature the request receives `@group` and `@feature` and
|
|
159
|
+
uses the feature's views; otherwise it receives `@group` and the group's views. No
|
|
160
|
+
extra declaration is needed in feature controllers.
|
|
161
|
+
|
|
162
|
+
Feature controllers that don't need a group controller can still inherit
|
|
163
|
+
`FeaturePack::Controller`, which requires the path to name a registered feature.
|
|
164
|
+
|
|
165
|
+
The private hooks `set_view_lookup_context_prefix` and `set_layout_paths` can be
|
|
166
|
+
overridden in a group controller and apply to both group and feature requests.
|
|
167
|
+
|
|
168
|
+
#### Migrating from 0.10.x
|
|
169
|
+
|
|
170
|
+
- To share group callbacks, change a feature controller's superclass from
|
|
171
|
+
`FeaturePack::Controller` to `FeaturePack::<Group>Controller`.
|
|
172
|
+
- `__after_initialize.rb` hooks are no longer loaded. Move that code to a Rails
|
|
173
|
+
initializer or to the group/feature controller and delete the hook files. Leftover
|
|
174
|
+
files are ignored by Zeitwerk, but they are dead code.
|
|
175
|
+
- `FeaturePack::API::Controller` was removed; inherit `ActionController::API` directly.
|
|
176
|
+
- `FeaturePack.setup` now fails when `app/feature_packs` does not exist. Create the
|
|
177
|
+
directory, or call `FeaturePack.setup(require_features_path: false)` to boot with no
|
|
178
|
+
groups (a warning is logged).
|
|
179
|
+
|
|
155
180
|
## Routes
|
|
156
181
|
|
|
157
182
|
Routes are automatically configured based on manifest files:
|
|
@@ -247,68 +272,6 @@ Access aliased constants:
|
|
|
247
272
|
@feature.service # => FeaturePack::HumanResources::Employees::EmployeeService
|
|
248
273
|
```
|
|
249
274
|
|
|
250
|
-
## Hooks
|
|
251
|
-
|
|
252
|
-
### after_initialize Hook
|
|
253
|
-
|
|
254
|
-
O FeaturePack suporta hooks `after_initialize` que permitem executar código customizado após o carregamento de grupos e features.
|
|
255
|
-
|
|
256
|
-
#### Como Funciona
|
|
257
|
-
|
|
258
|
-
Durante o processo de setup do FeaturePack, após todos os grupos e features serem descobertos e configurados, o sistema procura e executa arquivos `__after_initialize.rb` específicos.
|
|
259
|
-
|
|
260
|
-
#### Localização dos Arquivos
|
|
261
|
-
|
|
262
|
-
- **Para grupos**: `app/feature_packs/[nome_do_grupo]/_group_space/__after_initialize.rb`
|
|
263
|
-
- **Para features**: `app/feature_packs/[nome_do_grupo]/[nome_da_feature]/__after_initialize.rb`
|
|
264
|
-
|
|
265
|
-
#### Contexto de Execução
|
|
266
|
-
|
|
267
|
-
Os arquivos `__after_initialize.rb` são executados no contexto do objeto group ou feature, permitindo acesso direto a todas as suas propriedades através de `self`.
|
|
268
|
-
|
|
269
|
-
#### Exemplos de Uso
|
|
270
|
-
|
|
271
|
-
**Hook para grupo:**
|
|
272
|
-
```ruby
|
|
273
|
-
# app/feature_packs/group_241209_human_resources/_group_space/__after_initialize.rb
|
|
274
|
-
|
|
275
|
-
# Registrar o grupo em um sistema de auditoria
|
|
276
|
-
Rails.logger.info "Grupo #{name} carregado com #{features.size} features"
|
|
277
|
-
|
|
278
|
-
# Configurar permissões globais do grupo
|
|
279
|
-
features.each do |feature|
|
|
280
|
-
Rails.logger.info " - Feature #{feature.name} disponível em #{feature.manifest[:url]}"
|
|
281
|
-
end
|
|
282
|
-
|
|
283
|
-
# Carregar configurações específicas do grupo
|
|
284
|
-
config_file = File.join(absolute_path, '_group_space', 'config.yml')
|
|
285
|
-
if File.exist?(config_file)
|
|
286
|
-
@config = YAML.load_file(config_file)
|
|
287
|
-
end
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
**Hook para feature:**
|
|
291
|
-
```ruby
|
|
292
|
-
# app/feature_packs/group_241209_human_resources/feature_241209_employees/__after_initialize.rb
|
|
293
|
-
|
|
294
|
-
# Registrar rotas dinâmicas
|
|
295
|
-
Rails.logger.info "Feature #{name} inicializada no grupo #{group.name}"
|
|
296
|
-
|
|
297
|
-
# Verificar dependências
|
|
298
|
-
required_gems = %w[devise cancancan]
|
|
299
|
-
required_gems.each do |gem_name|
|
|
300
|
-
unless Gem.loaded_specs.key?(gem_name)
|
|
301
|
-
Rails.logger.warn "Feature #{name} requer a gem #{gem_name}"
|
|
302
|
-
end
|
|
303
|
-
end
|
|
304
|
-
|
|
305
|
-
# Registrar a feature em um sistema de métricas
|
|
306
|
-
StatsD.increment("features.#{group.name}.#{name}.loaded") if defined?(StatsD)
|
|
307
|
-
|
|
308
|
-
# Configurar cache específico da feature
|
|
309
|
-
Rails.cache.write("feature:#{group.name}:#{name}:loaded_at", Time.current)
|
|
310
|
-
```
|
|
311
|
-
|
|
312
275
|
## Best Practices
|
|
313
276
|
|
|
314
277
|
1. **Group Organization**: Group related features that share common functionality
|
|
@@ -356,4 +319,14 @@ Gedean Dias - gedean.dias@gmail.com
|
|
|
356
319
|
|
|
357
320
|
- [GitHub Repository](https://github.com/gedean/feature_pack)
|
|
358
321
|
- [RubyGems](https://rubygems.org/gems/feature_pack)
|
|
359
|
-
- [Bug Reports](https://github.com/gedean/feature_pack/issues)
|
|
322
|
+
- [Bug Reports](https://github.com/gedean/feature_pack/issues)
|
|
323
|
+
|
|
324
|
+
## Tests
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
bundle install
|
|
328
|
+
bundle exec rspec
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
The suite covers discovery, setup rollback, first-time generation, duplicate
|
|
332
|
+
protection, inherited authorization, and Rails request rendering using ERB fixtures.
|
data/doc/feature_pack.md
CHANGED
|
@@ -30,7 +30,7 @@ The module defines several read-only attributes:
|
|
|
30
30
|
|
|
31
31
|
The `setup` method initializes the FeaturePack library:
|
|
32
32
|
|
|
33
|
-
1.
|
|
33
|
+
1. Resolves `features_path` to `Rails.root/app/feature_packs` and validates it (missing directory raises unless `require_features_path: false`)
|
|
34
34
|
2. Sets up ignored paths
|
|
35
35
|
3. Discovers and initializes groups and features
|
|
36
36
|
4. Sets up routes and controllers for groups and features
|
|
@@ -38,7 +38,7 @@ The `setup` method initializes the FeaturePack library:
|
|
|
38
38
|
### Usage
|
|
39
39
|
|
|
40
40
|
```ruby
|
|
41
|
-
FeaturePack.setup
|
|
41
|
+
FeaturePack.setup
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
## Groups
|
|
@@ -1,62 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
require_relative 'feature_controller_setup'
|
|
2
|
+
|
|
3
|
+
# Base controller for features without a custom group controller.
|
|
3
4
|
class FeaturePack::Controller < ApplicationController
|
|
5
|
+
include FeaturePack::FeatureControllerSetup
|
|
4
6
|
prepend_before_action :setup_feature
|
|
5
7
|
|
|
6
|
-
# Default index action
|
|
7
8
|
def index; end
|
|
8
|
-
|
|
9
|
-
private
|
|
10
|
-
|
|
11
|
-
# Main setup method that configures the feature environment
|
|
12
|
-
def setup_feature
|
|
13
|
-
set_group_and_feature
|
|
14
|
-
set_view_lookup_context_prefix
|
|
15
|
-
set_layout_paths
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
# Extracts and sets the group and feature from the controller path
|
|
19
|
-
def set_group_and_feature
|
|
20
|
-
group_name, feature_name = params['controller']
|
|
21
|
-
.delete_prefix('feature_pack/')
|
|
22
|
-
.split('/')
|
|
23
|
-
.map(&:to_sym)
|
|
24
|
-
|
|
25
|
-
@group = FeaturePack.group(group_name)
|
|
26
|
-
@feature = FeaturePack.feature(group_name, feature_name)
|
|
27
|
-
|
|
28
|
-
raise FeaturePack::Error::NoGroup, "Group '#{group_name}' not found" if @group.nil?
|
|
29
|
-
raise FeaturePack::Error::NoDataError, "Feature '#{feature_name}' not found in group '#{group_name}'" if @feature.nil?
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# Configures the view lookup path to include feature-specific views
|
|
33
|
-
def set_view_lookup_context_prefix
|
|
34
|
-
return if lookup_context.prefixes.include?(@feature.views_relative_path)
|
|
35
|
-
|
|
36
|
-
lookup_context.prefixes.prepend(@feature.views_relative_path)
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Sets up header and footer layout paths with fallback logic
|
|
40
|
-
# Search order:
|
|
41
|
-
# 1. Feature-specific partials
|
|
42
|
-
# 2. Group-level partials (fallback)
|
|
43
|
-
# 3. Application default (if neither exists)
|
|
44
|
-
def set_layout_paths
|
|
45
|
-
feature_partials_path = @feature.views_relative_path.join('partials')
|
|
46
|
-
group_partials_path = @feature.group.views_path.concat('/partials')
|
|
47
|
-
|
|
48
|
-
# Set header layout
|
|
49
|
-
if template_exists?('header', feature_partials_path, true)
|
|
50
|
-
@header_layout_path = @feature.view('partials/header')
|
|
51
|
-
elsif template_exists?('header', group_partials_path, true)
|
|
52
|
-
@header_layout_path = @feature.group.view('partials/header')
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
# Set footer layout
|
|
56
|
-
if template_exists?('footer', feature_partials_path, true)
|
|
57
|
-
@footer_layout_path = @feature.view('partials/footer')
|
|
58
|
-
elsif template_exists?('footer', group_partials_path, true)
|
|
59
|
-
@footer_layout_path = @feature.group.view('partials/footer')
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
9
|
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
require_relative '../feature_pack'
|
|
2
|
+
|
|
3
|
+
# Request setup shared by group controllers, feature controllers that inherit
|
|
4
|
+
# their group controller, and standalone feature controllers
|
|
5
|
+
# (FeaturePack::Controller).
|
|
6
|
+
#
|
|
7
|
+
# The group and feature are resolved from the routed controller path
|
|
8
|
+
# ("feature_pack/<group>[/<feature>]"). When the second segment names a
|
|
9
|
+
# registered feature the request gets feature context (@group and @feature);
|
|
10
|
+
# otherwise it gets group context (@group only). No opt-in is required, so a
|
|
11
|
+
# controller can never silently receive the wrong setup.
|
|
12
|
+
module FeaturePack::FeatureControllerSetup
|
|
13
|
+
ROUTE_NAMESPACE = 'feature_pack/'.freeze
|
|
14
|
+
PROTECTED_IVARS = %i[@_feature_pack_prefixes].freeze
|
|
15
|
+
|
|
16
|
+
# Implicit rendering and the lookup context both read `_prefixes`.
|
|
17
|
+
# Override it per instance so a request-specific prefix never leaks into the
|
|
18
|
+
# controller class' memoized array (which subclasses share by reference).
|
|
19
|
+
def _prefixes
|
|
20
|
+
@_feature_pack_prefixes || super
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Keep the internal prefix array out of view assigns
|
|
24
|
+
def _protected_ivars
|
|
25
|
+
super + PROTECTED_IVARS
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
# Resolves group and feature from the route and configures the request.
|
|
31
|
+
# Used by GroupController (and everything inheriting it).
|
|
32
|
+
def setup_feature_pack_context
|
|
33
|
+
group_name, feature_name = feature_pack_route_segments
|
|
34
|
+
set_group(group_name)
|
|
35
|
+
@feature = @group.feature(feature_name) if feature_name
|
|
36
|
+
|
|
37
|
+
set_view_lookup_context_prefix
|
|
38
|
+
set_layout_paths
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Configures a request that must resolve to a feature (FeaturePack::Controller).
|
|
42
|
+
def setup_feature
|
|
43
|
+
set_group_and_feature
|
|
44
|
+
set_view_lookup_context_prefix
|
|
45
|
+
set_layout_paths
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @return [Array<Symbol>] [group_name, feature_name]; feature_name may be nil
|
|
49
|
+
def feature_pack_route_segments
|
|
50
|
+
path = (params[:controller] || controller_path).to_s
|
|
51
|
+
path.delete_prefix(ROUTE_NAMESPACE).split('/').first(2).map(&:to_sym)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def set_group(group_name)
|
|
55
|
+
@group = FeaturePack.group(group_name)
|
|
56
|
+
raise FeaturePack::Error::NoGroup, "Group '#{group_name}' not found" if @group.nil?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Extracts and sets the group and feature from the controller path
|
|
60
|
+
def set_group_and_feature
|
|
61
|
+
group_name, feature_name = feature_pack_route_segments
|
|
62
|
+
set_group(group_name)
|
|
63
|
+
|
|
64
|
+
@feature = @group.feature(feature_name)
|
|
65
|
+
return unless @feature.nil?
|
|
66
|
+
|
|
67
|
+
raise FeaturePack::Error::NoDataError, "Feature '#{feature_name}' not found in group '#{group_name}'"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Configures the view lookup path to include feature (or group) views.
|
|
71
|
+
# Overridable in group controllers; applies to group and feature requests.
|
|
72
|
+
def set_view_lookup_context_prefix
|
|
73
|
+
prepend_view_prefix(@feature ? @feature.views_relative_path : @group.views_path)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Sets up header and footer layout paths with fallback logic
|
|
77
|
+
# Search order:
|
|
78
|
+
# 1. Feature-specific partials (feature requests only)
|
|
79
|
+
# 2. Group-level partials
|
|
80
|
+
# 3. Application default (if neither exists)
|
|
81
|
+
def set_layout_paths
|
|
82
|
+
@header_layout_path = resolve_layout_partial('header')
|
|
83
|
+
@footer_layout_path = resolve_layout_partial('footer')
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def prepend_view_prefix(prefix)
|
|
87
|
+
prefix = prefix.to_s
|
|
88
|
+
return if _prefixes.include?(prefix)
|
|
89
|
+
|
|
90
|
+
@_feature_pack_prefixes = [prefix, *_prefixes].freeze
|
|
91
|
+
lookup_context.prefixes = @_feature_pack_prefixes if defined?(@_lookup_context) && @_lookup_context
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# @return [String, nil] the view path of the first existing partial
|
|
95
|
+
def resolve_layout_partial(name)
|
|
96
|
+
layout_partial_candidates.each do |partials_prefix, view_path|
|
|
97
|
+
return format(view_path, name) if template_exists?(name, partials_prefix, true)
|
|
98
|
+
end
|
|
99
|
+
nil
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# @return [Array<Array(String, String)>] pairs of [partials_prefix, view path format with %s for the name]
|
|
103
|
+
def layout_partial_candidates
|
|
104
|
+
candidates = []
|
|
105
|
+
if @feature
|
|
106
|
+
candidates << [@feature.views_relative_path.join('partials').to_s, @feature.view('partials/%s')]
|
|
107
|
+
end
|
|
108
|
+
candidates << ["#{@group.views_path}/partials", @group.view('partials/%s')]
|
|
109
|
+
candidates
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -1,45 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
require_relative 'feature_controller_setup'
|
|
2
|
+
|
|
3
|
+
# Base controller for all group controllers.
|
|
4
|
+
#
|
|
5
|
+
# Feature controllers inherit their group controller, so group callbacks
|
|
6
|
+
# (authentication, authorization...) apply to every feature. The setup callback
|
|
7
|
+
# resolves group or feature context from the route, so no extra declaration is
|
|
8
|
+
# needed in feature controllers.
|
|
3
9
|
class FeaturePack::GroupController < ApplicationController
|
|
4
|
-
|
|
5
|
-
|
|
10
|
+
include FeaturePack::FeatureControllerSetup
|
|
11
|
+
prepend_before_action :setup_feature_pack_context
|
|
12
|
+
|
|
6
13
|
# Default index action
|
|
7
14
|
def index; end
|
|
8
|
-
|
|
9
|
-
private
|
|
10
|
-
|
|
11
|
-
# Main setup method that configures the group environment
|
|
12
|
-
def setup_group
|
|
13
|
-
set_group
|
|
14
|
-
set_view_lookup_context_prefix
|
|
15
|
-
set_layout_paths
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
# Extracts and sets the group from the controller path
|
|
19
|
-
def set_group
|
|
20
|
-
group_name = params[:controller].split('/')[1].to_sym
|
|
21
|
-
@group = FeaturePack.group(group_name)
|
|
22
|
-
|
|
23
|
-
raise FeaturePack::Error::NoGroup, "Group '#{group_name}' not found" if @group.nil?
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# Configures the view lookup path to include group-specific views
|
|
27
|
-
def set_view_lookup_context_prefix
|
|
28
|
-
return if lookup_context.prefixes.include?(@group.views_path)
|
|
29
|
-
|
|
30
|
-
lookup_context.prefixes.prepend(@group.views_path)
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
# Sets up header and footer layout paths for the group
|
|
34
|
-
def set_layout_paths
|
|
35
|
-
partials_path = @group.views_path.concat('/partials')
|
|
36
|
-
|
|
37
|
-
if template_exists?('header', partials_path, true)
|
|
38
|
-
@header_layout_path = @group.view('partials/header')
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
if template_exists?('footer', partials_path, true)
|
|
42
|
-
@footer_layout_path = @group.view('partials/footer')
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
15
|
end
|
data/lib/feature_pack.rb
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
require 'active_support/all'
|
|
2
|
+
require 'ostruct'
|
|
2
3
|
|
|
3
4
|
# FeaturePack module provides a way to organize Rails applications into
|
|
4
5
|
# groups and features, enabling better code organization and isolation
|
|
5
6
|
module FeaturePack
|
|
7
|
+
@initialized = false
|
|
8
|
+
|
|
9
|
+
# Raised when registry data is requested before `setup` succeeded
|
|
10
|
+
class NotInitializedError < StandardError; end
|
|
11
|
+
|
|
6
12
|
# Pattern constants for identifying groups and features
|
|
7
13
|
GROUP_ID_PATTERN = /^group_.*?_/.freeze
|
|
8
14
|
FEATURE_ID_PATTERN = /^feature_.*?_/.freeze
|
|
@@ -11,9 +17,10 @@ module FeaturePack
|
|
|
11
17
|
GROUP_SPACE_DIRECTORY = '_group_space'.freeze
|
|
12
18
|
MANIFEST_FILE_NAME = 'manifest.yaml'.freeze
|
|
13
19
|
CONTROLLER_FILE_NAME = 'controller.rb'.freeze
|
|
14
|
-
|
|
20
|
+
# Hook file from 0.10.x; no longer loaded but still kept away from Zeitwerk
|
|
21
|
+
LEGACY_HOOK_FILE_NAME = '__after_initialize.rb'.freeze
|
|
15
22
|
|
|
16
|
-
#
|
|
23
|
+
# Registry attributes populated by `setup`
|
|
17
24
|
ATTR_READERS = %i[
|
|
18
25
|
path
|
|
19
26
|
features_path
|
|
@@ -27,20 +34,39 @@ module FeaturePack
|
|
|
27
34
|
class << self
|
|
28
35
|
# Sets up the FeaturePack system
|
|
29
36
|
# This method should be called once during Rails initialization
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
# @param require_features_path [Boolean] when true (default), a missing
|
|
38
|
+
# app/feature_packs directory aborts the boot; pass false to boot with no
|
|
39
|
+
# groups (e.g. before generating the first group).
|
|
40
|
+
def setup(require_features_path: true)
|
|
41
|
+
raise 'FeaturePack already setup!' if @initialized
|
|
42
|
+
|
|
43
|
+
begin
|
|
44
|
+
initialize_paths(require_features_path)
|
|
45
|
+
load_dependencies
|
|
46
|
+
discover_groups
|
|
47
|
+
discover_features
|
|
48
|
+
finalize_setup
|
|
49
|
+
@initialized = true
|
|
50
|
+
rescue StandardError, ScriptError
|
|
51
|
+
# ScriptError covers LoadError/SyntaxError raised while loading files.
|
|
52
|
+
reset_state!
|
|
53
|
+
raise
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def initialized? = @initialized
|
|
58
|
+
|
|
59
|
+
ATTR_READERS.each do |attr|
|
|
60
|
+
define_method(attr) do
|
|
61
|
+
ensure_initialized!
|
|
62
|
+
instance_variable_get("@#{attr}")
|
|
63
|
+
end
|
|
38
64
|
end
|
|
39
65
|
|
|
40
66
|
# Finds a group by name
|
|
41
67
|
# @param group_name [Symbol] The name of the group
|
|
42
68
|
# @return [OpenStruct, nil] The group object or nil if not found
|
|
43
|
-
def group(group_name) =
|
|
69
|
+
def group(group_name) = groups.find { |group| group.name.eql?(group_name) }
|
|
44
70
|
|
|
45
71
|
# Finds a feature within a group
|
|
46
72
|
# @param group_name [Symbol] The name of the group
|
|
@@ -49,45 +75,68 @@ module FeaturePack
|
|
|
49
75
|
def feature(group_name, feature_name)
|
|
50
76
|
requested_group = group(group_name)
|
|
51
77
|
return nil if requested_group.nil?
|
|
52
|
-
|
|
78
|
+
|
|
53
79
|
requested_group.feature(feature_name)
|
|
54
80
|
end
|
|
55
81
|
|
|
56
82
|
private
|
|
57
83
|
|
|
58
|
-
def
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
84
|
+
def ensure_initialized!
|
|
85
|
+
return if @initialized
|
|
86
|
+
|
|
87
|
+
raise NotInitializedError, 'FeaturePack is not set up. Call FeaturePack.setup first.'
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def initialize_paths(require_features_path)
|
|
91
|
+
@path = Pathname.new(__dir__)
|
|
92
|
+
@features_path = Pathname.new(Rails.root.join('app/feature_packs'))
|
|
93
|
+
|
|
94
|
+
validate_features_path!(require_features_path)
|
|
95
|
+
|
|
96
|
+
@groups_controllers_paths = []
|
|
97
|
+
@features_controllers_paths = []
|
|
98
|
+
# Every entry under features_path is ignored by Zeitwerk; controllers and
|
|
99
|
+
# routes are loaded explicitly (see SETUP.md), and feature modules are
|
|
100
|
+
# registered through push_dir by the host application.
|
|
101
|
+
@ignored_paths = Dir.glob("#{@features_path}/*/")
|
|
102
|
+
@javascript_files_paths = discover_javascript_files
|
|
68
103
|
end
|
|
69
104
|
|
|
70
|
-
def load_dependencies = load
|
|
105
|
+
def load_dependencies = load(@path.join('feature_pack/error.rb'))
|
|
106
|
+
|
|
107
|
+
def validate_features_path!(require_features_path)
|
|
108
|
+
if @features_path.exist?
|
|
109
|
+
raise "Features path is not a directory: '#{@features_path}'" unless @features_path.directory?
|
|
110
|
+
return
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
message = "Features path '#{@features_path}' does not exist"
|
|
114
|
+
if require_features_path
|
|
115
|
+
raise "#{message}. Create app/feature_packs or call FeaturePack.setup(require_features_path: false)"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
log_warning("[FeaturePack] #{message}. No groups or features will be loaded.")
|
|
119
|
+
end
|
|
71
120
|
|
|
72
|
-
def
|
|
73
|
-
|
|
74
|
-
|
|
121
|
+
def log_warning(message)
|
|
122
|
+
logger = Rails.respond_to?(:logger) ? Rails.logger : nil
|
|
123
|
+
logger ? logger.warn(message) : warn(message)
|
|
75
124
|
end
|
|
76
125
|
|
|
77
126
|
def discover_javascript_files
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
.to_a
|
|
127
|
+
prefix = "#{@features_path}/"
|
|
128
|
+
Dir.glob("#{prefix}[!_]*/**/*.js").map { |js_path| js_path.delete_prefix(prefix) }
|
|
81
129
|
end
|
|
82
130
|
|
|
83
|
-
def finalize_setup
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
131
|
+
def finalize_setup = @ignored_paths << @path.join('feature_pack/feature_pack_routes.rb')
|
|
132
|
+
|
|
133
|
+
# Clears every registry attribute so a failed or repeated setup starts clean
|
|
134
|
+
def reset_state!
|
|
135
|
+
@initialized = false
|
|
136
|
+
ATTR_READERS.each { |attr| remove_instance_variable("@#{attr}") if instance_variable_defined?("@#{attr}") }
|
|
88
137
|
end
|
|
89
138
|
|
|
90
|
-
def discover_groups =
|
|
139
|
+
def discover_groups = @groups = Dir.glob("#{@features_path}/[!_]*/").map { |group_path| build_group(group_path) }
|
|
91
140
|
|
|
92
141
|
def build_group(group_path)
|
|
93
142
|
relative_path = Pathname.new(group_path)
|
|
@@ -96,7 +145,7 @@ module FeaturePack
|
|
|
96
145
|
validate_group_id!(base_path)
|
|
97
146
|
|
|
98
147
|
routes_file = find_group_routes_file(group_path, base_path)
|
|
99
|
-
|
|
148
|
+
register_group_controller(group_path)
|
|
100
149
|
|
|
101
150
|
group = create_group_struct(base_path, group_path, relative_path, routes_file)
|
|
102
151
|
setup_group_aliases(group)
|
|
@@ -111,6 +160,14 @@ module FeaturePack
|
|
|
111
160
|
end
|
|
112
161
|
end
|
|
113
162
|
|
|
163
|
+
# Groups without a controller are allowed (namespace-only groups);
|
|
164
|
+
# add_feature validates the controller when a feature needs to inherit it.
|
|
165
|
+
def register_group_controller(group_path)
|
|
166
|
+
controller_path = File.join(group_path, GROUP_SPACE_DIRECTORY, CONTROLLER_FILE_NAME)
|
|
167
|
+
@groups_controllers_paths << controller_path if File.exist?(controller_path)
|
|
168
|
+
@ignored_paths << File.join(group_path, GROUP_SPACE_DIRECTORY, LEGACY_HOOK_FILE_NAME)
|
|
169
|
+
end
|
|
170
|
+
|
|
114
171
|
def find_group_routes_file(group_path, base_path)
|
|
115
172
|
routes_path = File.join(group_path, GROUP_SPACE_DIRECTORY, 'routes.rb')
|
|
116
173
|
File.exist?(routes_path) ? File.join(base_path, GROUP_SPACE_DIRECTORY, 'routes') : nil
|
|
@@ -126,7 +183,7 @@ module FeaturePack
|
|
|
126
183
|
OpenStruct.new(
|
|
127
184
|
id: base_path.scan(GROUP_ID_PATTERN).first.delete_suffix('_'),
|
|
128
185
|
name: base_path.gsub(GROUP_ID_PATTERN, '').to_sym,
|
|
129
|
-
metadata_path:
|
|
186
|
+
metadata_path: @features_path.join(group_path, GROUP_SPACE_DIRECTORY),
|
|
130
187
|
relative_path: relative_path,
|
|
131
188
|
base_dir: File.basename(relative_path, File::SEPARATOR),
|
|
132
189
|
routes_file: routes_file,
|
|
@@ -142,8 +199,8 @@ module FeaturePack
|
|
|
142
199
|
end
|
|
143
200
|
|
|
144
201
|
def setup_group_aliases(group)
|
|
145
|
-
group.manifest.fetch(:const_aliases, []).each do
|
|
146
|
-
alias_method_name, alias_const_name =
|
|
202
|
+
group.manifest.fetch(:const_aliases, []).each do |const_alias|
|
|
203
|
+
alias_method_name, alias_const_name = const_alias.first
|
|
147
204
|
group.define_singleton_method(alias_method_name) do
|
|
148
205
|
"FeaturePack::#{group.name.to_s.camelize}::#{alias_const_name}".constantize
|
|
149
206
|
end
|
|
@@ -151,14 +208,14 @@ module FeaturePack
|
|
|
151
208
|
end
|
|
152
209
|
|
|
153
210
|
def define_group_methods(group)
|
|
154
|
-
def group.feature(feature_name) = features.find {
|
|
211
|
+
def group.feature(feature_name) = features.find { |feature| feature.name.eql?(feature_name) }
|
|
155
212
|
def group.views_path = "#{base_dir}/#{GROUP_SPACE_DIRECTORY}/views"
|
|
156
213
|
def group.view(view_name) = "#{base_dir}/#{GROUP_SPACE_DIRECTORY}/views/#{view_name}"
|
|
157
214
|
def group.javascript_module(javascript_file_name) = "#{base_dir}/#{GROUP_SPACE_DIRECTORY}/javascript/#{javascript_file_name}"
|
|
158
215
|
end
|
|
159
216
|
|
|
160
217
|
def discover_features
|
|
161
|
-
|
|
218
|
+
@groups.each do |group|
|
|
162
219
|
Dir.glob("#{group.relative_path}[!_]*/").each do |feature_path|
|
|
163
220
|
build_feature(group, feature_path)
|
|
164
221
|
end
|
|
@@ -166,7 +223,7 @@ module FeaturePack
|
|
|
166
223
|
end
|
|
167
224
|
|
|
168
225
|
def build_feature(group, feature_path)
|
|
169
|
-
absolute_path =
|
|
226
|
+
absolute_path = @features_path.join(feature_path)
|
|
170
227
|
relative_path = Pathname.new(feature_path)
|
|
171
228
|
base_path = File.basename(feature_path, File::SEPARATOR)
|
|
172
229
|
|
|
@@ -188,20 +245,6 @@ module FeaturePack
|
|
|
188
245
|
group.features << feature
|
|
189
246
|
end
|
|
190
247
|
|
|
191
|
-
def execute_after_initialize_hooks
|
|
192
|
-
# Executar hooks dos grupos
|
|
193
|
-
@@groups.each do |group|
|
|
194
|
-
hook_file = File.join(group.metadata_path, AFTER_INITIALIZE_FILE_NAME)
|
|
195
|
-
group.instance_eval(File.read(hook_file), hook_file) if File.exist?(hook_file)
|
|
196
|
-
|
|
197
|
-
# Executar hooks das features
|
|
198
|
-
group.features.each do |feature|
|
|
199
|
-
hook_file = File.join(feature.absolute_path, AFTER_INITIALIZE_FILE_NAME)
|
|
200
|
-
feature.instance_eval(File.read(hook_file), hook_file) if File.exist?(hook_file)
|
|
201
|
-
end
|
|
202
|
-
end
|
|
203
|
-
end
|
|
204
|
-
|
|
205
248
|
def validate_feature_id!(base_path, relative_path)
|
|
206
249
|
if base_path.scan(FEATURE_ID_PATTERN).empty?
|
|
207
250
|
raise "Feature '#{relative_path}' does not have a valid ID. Expected format: feature_<id>_<name>"
|
|
@@ -209,20 +252,20 @@ module FeaturePack
|
|
|
209
252
|
end
|
|
210
253
|
|
|
211
254
|
def setup_feature_paths(relative_path, routes_file_path)
|
|
212
|
-
# Handled after initialize hooks
|
|
213
|
-
@@ignored_paths << File.join(relative_path, AFTER_INITIALIZE_FILE_NAME)
|
|
214
|
-
|
|
215
255
|
# Custom routes file loads before Rails default routes
|
|
216
|
-
|
|
256
|
+
@ignored_paths << routes_file_path
|
|
217
257
|
|
|
218
258
|
# Controllers have special load process due to Zeitwerk
|
|
219
259
|
controller_path = relative_path.join(CONTROLLER_FILE_NAME)
|
|
220
|
-
|
|
221
|
-
|
|
260
|
+
@features_controllers_paths << controller_path
|
|
261
|
+
@ignored_paths << controller_path
|
|
262
|
+
# Feature directories are Zeitwerk roots (push_dir), so the group-level
|
|
263
|
+
# ignore does not cover leftover 0.10.x hook files.
|
|
264
|
+
@ignored_paths << relative_path.join(LEGACY_HOOK_FILE_NAME)
|
|
222
265
|
end
|
|
223
266
|
|
|
224
267
|
def create_feature_struct(base_path, feature_name, group, absolute_path, relative_path, routes_file_path, feature_path)
|
|
225
|
-
feature_sub_path = relative_path.sub(/^#{Regexp.escape(
|
|
268
|
+
feature_sub_path = relative_path.sub(/^#{Regexp.escape(@features_path.to_s)}\//, '')
|
|
226
269
|
manifest_path = File.join(feature_path, MANIFEST_FILE_NAME)
|
|
227
270
|
|
|
228
271
|
unless File.exist?(manifest_path)
|
|
@@ -239,8 +282,8 @@ module FeaturePack
|
|
|
239
282
|
routes_file_path: routes_file_path,
|
|
240
283
|
routes_file: feature_sub_path.join('routes'),
|
|
241
284
|
views_absolute_path: absolute_path.join('views'),
|
|
242
|
-
views_relative_path:
|
|
243
|
-
javascript_relative_path:
|
|
285
|
+
views_relative_path: feature_sub_path.join('views'),
|
|
286
|
+
javascript_relative_path: feature_sub_path.join('javascript'),
|
|
244
287
|
manifest: load_manifest(manifest_path)
|
|
245
288
|
)
|
|
246
289
|
end
|
|
@@ -253,8 +296,8 @@ module FeaturePack
|
|
|
253
296
|
end
|
|
254
297
|
|
|
255
298
|
def setup_feature_aliases(feature)
|
|
256
|
-
feature.manifest.fetch(:const_aliases, []).each do
|
|
257
|
-
alias_method_name, alias_const_name =
|
|
299
|
+
feature.manifest.fetch(:const_aliases, []).each do |const_alias|
|
|
300
|
+
alias_method_name, alias_const_name = const_alias.first
|
|
258
301
|
feature.define_singleton_method(alias_method_name) do
|
|
259
302
|
"#{class_name}::#{alias_const_name}".constantize
|
|
260
303
|
end
|
|
@@ -8,13 +8,15 @@ module FeaturePack
|
|
|
8
8
|
desc 'Creates a new Feature within an existing Group'
|
|
9
9
|
source_root File.expand_path('templates', __dir__)
|
|
10
10
|
|
|
11
|
-
argument :name, type: :string, required: true,
|
|
11
|
+
argument :name, type: :string, required: true,
|
|
12
|
+
desc: 'The group/feature name (snake_case) format: group_name/feature_name'
|
|
12
13
|
|
|
13
14
|
def add_feature
|
|
14
15
|
validate_feature_name!
|
|
15
16
|
parse_names
|
|
16
17
|
check_group_existence!
|
|
17
18
|
check_feature_existence!
|
|
19
|
+
check_group_controller!
|
|
18
20
|
|
|
19
21
|
@feature_id = generate_feature_id
|
|
20
22
|
@feature_dir = @group.relative_path.join("feature_#{@feature_id}_#{@feature_name}")
|
|
@@ -36,8 +38,8 @@ module FeaturePack
|
|
|
36
38
|
def parse_names
|
|
37
39
|
@group_name, @feature_name = name.split('/')
|
|
38
40
|
|
|
39
|
-
unless @group_name.match?(/^[a-
|
|
40
|
-
raise Thor::Error, "Group and feature names must be in snake_case format
|
|
41
|
+
unless @group_name.match?(/^[a-z_]+$/) && @feature_name.match?(/^[a-z_]+$/)
|
|
42
|
+
raise Thor::Error, "Group and feature names must be in snake_case format"
|
|
41
43
|
end
|
|
42
44
|
|
|
43
45
|
@group_class_name = @group_name.camelcase
|
|
@@ -52,6 +54,31 @@ module FeaturePack
|
|
|
52
54
|
end
|
|
53
55
|
end
|
|
54
56
|
|
|
57
|
+
# The generated feature controller inherits FeaturePack::<Group>Controller,
|
|
58
|
+
# so validate the resolved class independently of its declaration syntax.
|
|
59
|
+
def check_group_controller!
|
|
60
|
+
controller_path = @group.metadata_path.join(FeaturePack::CONTROLLER_FILE_NAME)
|
|
61
|
+
expected_class = "FeaturePack::#{@group_class_name}Controller"
|
|
62
|
+
|
|
63
|
+
unless File.exist?(controller_path)
|
|
64
|
+
raise Thor::Error,
|
|
65
|
+
"Group controller not found at #{controller_path}. The feature controller must inherit #{expected_class}."
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
controller_class = expected_class.safe_constantize || load_group_controller(controller_path, expected_class)
|
|
69
|
+
|
|
70
|
+
return if controller_class.is_a?(Class)
|
|
71
|
+
|
|
72
|
+
raise Thor::Error, "#{controller_path} does not define #{expected_class}, which the feature controller inherits."
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def load_group_controller(controller_path, expected_class)
|
|
76
|
+
load controller_path
|
|
77
|
+
expected_class.safe_constantize
|
|
78
|
+
rescue StandardError, ScriptError => e
|
|
79
|
+
raise Thor::Error, "Could not load group controller #{controller_path}: #{e.class}: #{e.message}"
|
|
80
|
+
end
|
|
81
|
+
|
|
55
82
|
def check_feature_existence!
|
|
56
83
|
if FeaturePack.feature(@group_name.to_sym, @feature_name.to_sym).present?
|
|
57
84
|
raise Thor::Error, "Feature '#{@feature_name}' already exists in group '#{@group_name}'"
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Feature controller for <%= @feature_class_name %>
|
|
2
|
-
class FeaturePack::<%= @group_class_name %>::<%= @feature_class_name %>Controller < FeaturePack
|
|
2
|
+
class FeaturePack::<%= @group_class_name %>::<%= @feature_class_name %>Controller < FeaturePack::<%= @group_class_name %>Controller
|
|
3
|
+
# Inherits the group controller and its callbacks. The request receives
|
|
4
|
+
# @group and @feature automatically based on the route.
|
|
3
5
|
# The 'index' action is already defined in the parent controller
|
|
4
6
|
# Add your feature-specific actions and logic here
|
|
5
7
|
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: feature_pack
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gedean Dias
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 2026-09-05 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: activesupport
|
|
@@ -29,6 +29,20 @@ dependencies:
|
|
|
29
29
|
- - "<"
|
|
30
30
|
- !ruby/object:Gem::Version
|
|
31
31
|
version: '9.0'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: ostruct
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - ">="
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '0'
|
|
39
|
+
type: :runtime
|
|
40
|
+
prerelease: false
|
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
42
|
+
requirements:
|
|
43
|
+
- - ">="
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '0'
|
|
32
46
|
description: |
|
|
33
47
|
Organizes and sets up the architecture of micro-applications within a Rails application,
|
|
34
48
|
enabling segregation, management, and isolation of functionalities, thereby supporting
|
|
@@ -40,16 +54,14 @@ extra_rdoc_files: []
|
|
|
40
54
|
files:
|
|
41
55
|
- README.md
|
|
42
56
|
- doc/feature_pack.md
|
|
43
|
-
- doc/hooks.md
|
|
44
57
|
- lib/feature_pack.rb
|
|
45
|
-
- lib/feature_pack/api/controller.rb
|
|
46
58
|
- lib/feature_pack/controller.rb
|
|
47
59
|
- lib/feature_pack/error.rb
|
|
60
|
+
- lib/feature_pack/feature_controller_setup.rb
|
|
48
61
|
- lib/feature_pack/feature_pack_routes.rb
|
|
49
62
|
- lib/feature_pack/group_controller.rb
|
|
50
63
|
- lib/generators/feature_pack/add_feature/USAGE
|
|
51
64
|
- lib/generators/feature_pack/add_feature/add_feature_generator.rb
|
|
52
|
-
- lib/generators/feature_pack/add_feature/templates/after_initialize.rb.tt
|
|
53
65
|
- lib/generators/feature_pack/add_feature/templates/controller.rb.tt
|
|
54
66
|
- lib/generators/feature_pack/add_feature/templates/doc/readme.md.tt
|
|
55
67
|
- lib/generators/feature_pack/add_feature/templates/manifest.yaml.tt
|
|
@@ -59,7 +71,6 @@ files:
|
|
|
59
71
|
- lib/generators/feature_pack/add_feature/templates/views/partials/_header.html.slim.tt
|
|
60
72
|
- lib/generators/feature_pack/add_group/USAGE
|
|
61
73
|
- lib/generators/feature_pack/add_group/add_group_generator.rb
|
|
62
|
-
- lib/generators/feature_pack/add_group/templates/_group_space/after_initialize.rb.tt
|
|
63
74
|
- lib/generators/feature_pack/add_group/templates/_group_space/controller.rb.tt
|
|
64
75
|
- lib/generators/feature_pack/add_group/templates/_group_space/manifest.yaml.tt
|
|
65
76
|
- lib/generators/feature_pack/add_group/templates/_group_space/routes.rb.tt
|
|
@@ -88,7 +99,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
88
99
|
- !ruby/object:Gem::Version
|
|
89
100
|
version: '0'
|
|
90
101
|
requirements: []
|
|
91
|
-
rubygems_version:
|
|
102
|
+
rubygems_version: 4.0.20
|
|
92
103
|
specification_version: 4
|
|
93
104
|
summary: A different approach to organizing Rails app features.
|
|
94
105
|
test_files: []
|
data/doc/hooks.md
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
# Sistema de Hooks do FeaturePack
|
|
2
|
-
|
|
3
|
-
## Hook after_initialize
|
|
4
|
-
|
|
5
|
-
O FeaturePack agora suporta hooks `after_initialize` que permitem executar código customizado após o carregamento de grupos e features.
|
|
6
|
-
|
|
7
|
-
### Como funciona
|
|
8
|
-
|
|
9
|
-
Durante o processo de setup do FeaturePack, após todos os grupos e features serem descobertos e configurados, o sistema procura e executa arquivos `after_initialize.rb` específicos.
|
|
10
|
-
|
|
11
|
-
### Localização dos arquivos
|
|
12
|
-
|
|
13
|
-
- **Para grupos**: `app/feature_packs/[nome_do_grupo]/_group_space/after_initialize.rb`
|
|
14
|
-
- **Para features**: `app/feature_packs/[nome_do_grupo]/[nome_da_feature]/after_initialize.rb`
|
|
15
|
-
|
|
16
|
-
### Contexto de execução
|
|
17
|
-
|
|
18
|
-
Os arquivos `after_initialize.rb` são executados no contexto do objeto group ou feature, permitindo acesso direto a todas as suas propriedades através de `self`.
|
|
19
|
-
|
|
20
|
-
### Exemplos de uso
|
|
21
|
-
|
|
22
|
-
#### Hook para grupo
|
|
23
|
-
|
|
24
|
-
```ruby
|
|
25
|
-
# app/feature_packs/group_admin/_group_space/after_initialize.rb
|
|
26
|
-
|
|
27
|
-
# Registrar o grupo em um sistema de auditoria
|
|
28
|
-
Rails.logger.info "Grupo #{name} carregado com #{features.size} features"
|
|
29
|
-
|
|
30
|
-
# Configurar permissões globais do grupo
|
|
31
|
-
features.each do |feature|
|
|
32
|
-
Rails.logger.info " - Feature #{feature.name} disponível em #{feature.manifest[:url]}"
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# Carregar configurações específicas do grupo
|
|
36
|
-
config_file = File.join(absolute_path, GROUP_SPACE_DIRECTORY, 'config.yml')
|
|
37
|
-
if File.exist?(config_file)
|
|
38
|
-
@config = YAML.load_file(config_file)
|
|
39
|
-
end
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
#### Hook para feature
|
|
43
|
-
|
|
44
|
-
```ruby
|
|
45
|
-
# app/feature_packs/group_admin/feature_users/after_initialize.rb
|
|
46
|
-
|
|
47
|
-
# Registrar rotas dinâmicas
|
|
48
|
-
Rails.logger.info "Feature #{name} inicializada no grupo #{group.name}"
|
|
49
|
-
|
|
50
|
-
# Verificar dependências
|
|
51
|
-
required_gems = %w[devise cancancan]
|
|
52
|
-
required_gems.each do |gem_name|
|
|
53
|
-
unless Gem.loaded_specs.key?(gem_name)
|
|
54
|
-
Rails.logger.warn "Feature #{name} requer a gem #{gem_name}"
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Registrar a feature em um sistema de métricas
|
|
59
|
-
StatsD.increment("features.#{group.name}.#{name}.loaded")
|
|
60
|
-
|
|
61
|
-
# Configurar cache específico da feature
|
|
62
|
-
Rails.cache.write("feature:#{group.name}:#{name}:loaded_at", Time.current)
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
### Propriedades disponíveis
|
|
66
|
-
|
|
67
|
-
#### No contexto de grupo
|
|
68
|
-
|
|
69
|
-
- `name` - Nome do grupo (symbol)
|
|
70
|
-
- `absolute_path` - Caminho absoluto do grupo
|
|
71
|
-
- `relative_path` - Caminho relativo do grupo
|
|
72
|
-
- `features` - Array com todas as features do grupo
|
|
73
|
-
- `manifest` - Hash com dados do manifest.yaml
|
|
74
|
-
- `routes_file` - Caminho do arquivo de rotas
|
|
75
|
-
|
|
76
|
-
#### No contexto de feature
|
|
77
|
-
|
|
78
|
-
- `name` - Nome da feature (symbol)
|
|
79
|
-
- `group` - Referência ao grupo pai
|
|
80
|
-
- `absolute_path` - Caminho absoluto da feature
|
|
81
|
-
- `relative_path` - Caminho relativo da feature
|
|
82
|
-
- `namespace` - Módulo Ruby da feature
|
|
83
|
-
- `manifest` - Hash com dados do manifest.yaml
|
|
84
|
-
- `routes_file` - Caminho do arquivo de rotas
|
|
85
|
-
|
|
86
|
-
### Casos de uso comuns
|
|
87
|
-
|
|
88
|
-
1. **Logging e auditoria** - Registrar quando grupos/features são carregados
|
|
89
|
-
2. **Validação de dependências** - Verificar se gems ou recursos necessários estão disponíveis
|
|
90
|
-
3. **Configuração dinâmica** - Carregar configurações específicas
|
|
91
|
-
4. **Registro em sistemas externos** - Integrar com sistemas de métricas ou monitoramento
|
|
92
|
-
5. **Inicialização de recursos** - Preparar caches, conexões ou outros recursos
|
|
93
|
-
6. **Verificação de segurança** - Validar permissões ou políticas de acesso
|
|
94
|
-
|
|
95
|
-
### Boas práticas
|
|
96
|
-
|
|
97
|
-
1. Mantenha os hooks leves e rápidos - eles são executados durante o boot da aplicação
|
|
98
|
-
2. Use logging apropriado para facilitar debug
|
|
99
|
-
3. Trate exceções adequadamente para não quebrar o processo de inicialização
|
|
100
|
-
4. Evite operações síncronas pesadas (I/O, rede, etc)
|
|
101
|
-
5. Use o hook apenas para configurações que realmente precisam acontecer após a carga completa
|
|
102
|
-
|
|
103
|
-
### Ordem de execução
|
|
104
|
-
|
|
105
|
-
1. Todos os grupos são descobertos e configurados
|
|
106
|
-
2. Todas as features são descobertas e configuradas
|
|
107
|
-
3. Hooks `after_initialize` dos grupos são executados
|
|
108
|
-
4. Hooks `after_initialize` das features são executados (na ordem de descoberta)
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# Hook executado após a inicialização da feature <%= @feature_class_name %>
|
|
2
|
-
# Este arquivo é executado no contexto do objeto feature
|
|
3
|
-
# Você tem acesso a todas as propriedades da feature através de 'self'
|
|
4
|
-
#
|
|
5
|
-
# Exemplo de uso:
|
|
6
|
-
# puts "Feature #{name} foi inicializada!"
|
|
7
|
-
# puts "Grupo da feature: #{group.name}"
|
|
8
|
-
# puts "Caminho absoluto: #{absolute_path}"
|
|
9
|
-
# puts "Namespace: #{namespace}"
|
|
10
|
-
#
|
|
11
|
-
# Adicione seu código customizado abaixo:
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
# Hook executado após a inicialização do grupo <%= @class_name %>
|
|
2
|
-
# Este arquivo é executado no contexto do objeto group
|
|
3
|
-
# Você tem acesso a todas as propriedades do grupo através de 'self'
|
|
4
|
-
#
|
|
5
|
-
# Exemplo de uso:
|
|
6
|
-
# puts "Grupo #{name} foi inicializado!"
|
|
7
|
-
# puts "Caminho do grupo: #{absolute_path}"
|
|
8
|
-
# puts "Features do grupo: #{features.map(&:name).join(', ')}"
|
|
9
|
-
#
|
|
10
|
-
# Adicione seu código customizado abaixo:
|