feature_pack 0.9.0 → 0.10.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/doc/hooks.md +108 -0
- data/lib/feature_pack.rb +34 -45
- data/lib/generators/feature_pack/add_feature/templates/after_initialize.rb.tt +11 -0
- data/lib/generators/feature_pack/add_feature/templates/controller.rb.tt +1 -1
- data/lib/generators/feature_pack/add_group/templates/_group_space/after_initialize.rb.tt +10 -0
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7451b49f9dda65ec4aa60a6bb26a976f09258c292221ac884f6a1f2db7dd769b
|
4
|
+
data.tar.gz: eccbe2c439b2810c149a174225f9a20821a28f16b0af5876bf9bc32ef22d3f04
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eedb788af59f03e15dbc7d54d4da52c40491f9374e32160c40779f8318b084566961005802261b34b87c6c2cf9134b68c6fc21c9c7ed26671894bbd5744bfa44
|
7
|
+
data.tar.gz: 9ea9c70925ed3293e7bb1348e7eb3276d0db1ff2129bf17380b1b43769f043b7c1a98cd566605cd8cdad723d0c0efb87d98b2f6536725f07e4c004fd57b07ac5
|
data/doc/hooks.md
ADDED
@@ -0,0 +1,108 @@
|
|
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)
|
data/lib/feature_pack.rb
CHANGED
@@ -11,6 +11,7 @@ module FeaturePack
|
|
11
11
|
GROUP_SPACE_DIRECTORY = '_group_space'.freeze
|
12
12
|
MANIFEST_FILE_NAME = 'manifest.yaml'.freeze
|
13
13
|
CONTROLLER_FILE_NAME = 'controller.rb'.freeze
|
14
|
+
AFTER_INITIALIZE_FILE_NAME = '__after_initialize.rb'.freeze
|
14
15
|
|
15
16
|
# Attribute readers that will be dynamically defined
|
16
17
|
ATTR_READERS = %i[
|
@@ -39,9 +40,7 @@ module FeaturePack
|
|
39
40
|
# Finds a group by name
|
40
41
|
# @param group_name [Symbol] The name of the group
|
41
42
|
# @return [OpenStruct, nil] The group object or nil if not found
|
42
|
-
def group(group_name)
|
43
|
-
@@groups.find { |g| g.name.eql?(group_name) }
|
44
|
-
end
|
43
|
+
def group(group_name) = @@groups.find { it.name.eql?(group_name) }
|
45
44
|
|
46
45
|
# Finds a feature within a group
|
47
46
|
# @param group_name [Symbol] The name of the group
|
@@ -68,9 +67,7 @@ module FeaturePack
|
|
68
67
|
@@javascript_files_paths = discover_javascript_files
|
69
68
|
end
|
70
69
|
|
71
|
-
def load_dependencies
|
72
|
-
load @@path.join('feature_pack/error.rb')
|
73
|
-
end
|
70
|
+
def load_dependencies = load @@path.join('feature_pack/error.rb')
|
74
71
|
|
75
72
|
def validate_features_path!
|
76
73
|
raise "Invalid features_path: '#{@@features_path}'" if @@features_path.nil?
|
@@ -86,14 +83,11 @@ module FeaturePack
|
|
86
83
|
def finalize_setup
|
87
84
|
ATTR_READERS.each { |attr| define_singleton_method(attr) { class_variable_get("@@#{attr}") } }
|
88
85
|
@@ignored_paths << @@path.join('feature_pack/feature_pack_routes.rb')
|
86
|
+
execute_after_initialize_hooks
|
89
87
|
@@setup_executed_flag = true
|
90
88
|
end
|
91
89
|
|
92
|
-
def discover_groups
|
93
|
-
@@groups = Dir.glob("#{@@features_path}/[!_]*/").map do |group_path|
|
94
|
-
build_group(group_path)
|
95
|
-
end
|
96
|
-
end
|
90
|
+
def discover_groups = @@groups = Dir.glob("#{@@features_path}/[!_]*/").map { build_group(it) }
|
97
91
|
|
98
92
|
def build_group(group_path)
|
99
93
|
relative_path = Pathname.new(group_path)
|
@@ -148,8 +142,8 @@ module FeaturePack
|
|
148
142
|
end
|
149
143
|
|
150
144
|
def setup_group_aliases(group)
|
151
|
-
group.manifest.fetch(:const_aliases, []).each do
|
152
|
-
alias_method_name, alias_const_name =
|
145
|
+
group.manifest.fetch(:const_aliases, []).each do
|
146
|
+
alias_method_name, alias_const_name = it.first
|
153
147
|
group.define_singleton_method(alias_method_name) do
|
154
148
|
"FeaturePack::#{group.name.to_s.camelize}::#{alias_const_name}".constantize
|
155
149
|
end
|
@@ -157,21 +151,10 @@ module FeaturePack
|
|
157
151
|
end
|
158
152
|
|
159
153
|
def define_group_methods(group)
|
160
|
-
def group.feature(feature_name)
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
def group.views_path
|
165
|
-
"#{base_dir}/#{GROUP_SPACE_DIRECTORY}/views"
|
166
|
-
end
|
167
|
-
|
168
|
-
def group.view(view_name)
|
169
|
-
"#{base_dir}/#{GROUP_SPACE_DIRECTORY}/views/#{view_name}"
|
170
|
-
end
|
171
|
-
|
172
|
-
def group.javascript_module(javascript_file_name)
|
173
|
-
"#{base_dir}/#{GROUP_SPACE_DIRECTORY}/javascript/#{javascript_file_name}"
|
174
|
-
end
|
154
|
+
def group.feature(feature_name) = features.find { it.name.eql?(feature_name) }
|
155
|
+
def group.views_path = "#{base_dir}/#{GROUP_SPACE_DIRECTORY}/views"
|
156
|
+
def group.view(view_name) = "#{base_dir}/#{GROUP_SPACE_DIRECTORY}/views/#{view_name}"
|
157
|
+
def group.javascript_module(javascript_file_name) = "#{base_dir}/#{GROUP_SPACE_DIRECTORY}/javascript/#{javascript_file_name}"
|
175
158
|
end
|
176
159
|
|
177
160
|
def discover_features
|
@@ -205,6 +188,20 @@ module FeaturePack
|
|
205
188
|
group.features << feature
|
206
189
|
end
|
207
190
|
|
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
|
+
|
208
205
|
def validate_feature_id!(base_path, relative_path)
|
209
206
|
if base_path.scan(FEATURE_ID_PATTERN).empty?
|
210
207
|
raise "Feature '#{relative_path}' does not have a valid ID. Expected format: feature_<id>_<name>"
|
@@ -212,6 +209,9 @@ module FeaturePack
|
|
212
209
|
end
|
213
210
|
|
214
211
|
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
215
|
# Custom routes file loads before Rails default routes
|
216
216
|
@@ignored_paths << routes_file_path
|
217
217
|
|
@@ -246,26 +246,15 @@ module FeaturePack
|
|
246
246
|
end
|
247
247
|
|
248
248
|
def define_feature_methods(feature)
|
249
|
-
def feature.class_name
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
def feature.namespace
|
254
|
-
class_name.constantize
|
255
|
-
end
|
256
|
-
|
257
|
-
def feature.view(view_name)
|
258
|
-
"#{views_relative_path}/#{view_name}"
|
259
|
-
end
|
260
|
-
|
261
|
-
def feature.javascript_module(javascript_file_name)
|
262
|
-
"#{javascript_relative_path}/#{javascript_file_name}"
|
263
|
-
end
|
249
|
+
def feature.class_name = "FeaturePack::#{group.name.to_s.camelize}::#{name.to_s.camelize}"
|
250
|
+
def feature.namespace = class_name.constantize
|
251
|
+
def feature.view(view_name) = "#{views_relative_path}/#{view_name}"
|
252
|
+
def feature.javascript_module(javascript_file_name) = "#{javascript_relative_path}/#{javascript_file_name}"
|
264
253
|
end
|
265
254
|
|
266
255
|
def setup_feature_aliases(feature)
|
267
|
-
feature.manifest.fetch(:const_aliases, []).each do
|
268
|
-
alias_method_name, alias_const_name =
|
256
|
+
feature.manifest.fetch(:const_aliases, []).each do
|
257
|
+
alias_method_name, alias_const_name = it.first
|
269
258
|
feature.define_singleton_method(alias_method_name) do
|
270
259
|
"#{class_name}::#{alias_const_name}".constantize
|
271
260
|
end
|
@@ -0,0 +1,11 @@
|
|
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,5 +1,5 @@
|
|
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::Controller
|
3
3
|
# The 'index' action is already defined in the parent controller
|
4
4
|
# Add your feature-specific actions and logic here
|
5
5
|
|
@@ -0,0 +1,10 @@
|
|
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:
|
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.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gedean Dias
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-08-03 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: activesupport
|
@@ -40,6 +40,7 @@ extra_rdoc_files: []
|
|
40
40
|
files:
|
41
41
|
- README.md
|
42
42
|
- doc/feature_pack.md
|
43
|
+
- doc/hooks.md
|
43
44
|
- lib/feature_pack.rb
|
44
45
|
- lib/feature_pack/api/controller.rb
|
45
46
|
- lib/feature_pack/controller.rb
|
@@ -48,6 +49,7 @@ files:
|
|
48
49
|
- lib/feature_pack/group_controller.rb
|
49
50
|
- lib/generators/feature_pack/add_feature/USAGE
|
50
51
|
- lib/generators/feature_pack/add_feature/add_feature_generator.rb
|
52
|
+
- lib/generators/feature_pack/add_feature/templates/after_initialize.rb.tt
|
51
53
|
- lib/generators/feature_pack/add_feature/templates/controller.rb.tt
|
52
54
|
- lib/generators/feature_pack/add_feature/templates/doc/readme.md.tt
|
53
55
|
- lib/generators/feature_pack/add_feature/templates/manifest.yaml.tt
|
@@ -57,6 +59,7 @@ files:
|
|
57
59
|
- lib/generators/feature_pack/add_feature/templates/views/partials/_header.html.slim.tt
|
58
60
|
- lib/generators/feature_pack/add_group/USAGE
|
59
61
|
- lib/generators/feature_pack/add_group/add_group_generator.rb
|
62
|
+
- lib/generators/feature_pack/add_group/templates/_group_space/after_initialize.rb.tt
|
60
63
|
- lib/generators/feature_pack/add_group/templates/_group_space/controller.rb.tt
|
61
64
|
- lib/generators/feature_pack/add_group/templates/_group_space/manifest.yaml.tt
|
62
65
|
- lib/generators/feature_pack/add_group/templates/_group_space/routes.rb.tt
|
@@ -85,7 +88,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
85
88
|
- !ruby/object:Gem::Version
|
86
89
|
version: '0'
|
87
90
|
requirements: []
|
88
|
-
rubygems_version: 3.
|
91
|
+
rubygems_version: 3.7.1
|
89
92
|
specification_version: 4
|
90
93
|
summary: A different approach to organizing Rails app features.
|
91
94
|
test_files: []
|