Feature Pack Gem
Feature Pack organizes and sets up the architecture of micro-applications within a Ruby on Rails application, enabling the segregation of code, management, and isolation of functionalities. Features can be developed, tested, and maintained independently of each other.
Installation
Add this line to your application's Gemfile:
gem 'feature_pack'
And then execute:
bundle install
Setup
Add to your config/application.rb
:
# After Bundler.require
require 'feature_pack'
FeaturePack.setup
Concepts
Groups
Groups are collections of related features. They provide:
- Shared layouts and partials
- Common base controller functionality
- Namespace organization
- Shared JavaScript modules
Features
Features are individual functionalities within a group. They provide:
- Independent controllers and views
- Isolated routes
- Feature-specific JavaScript
- Complete MVC structure
Usage
Creating a New Group
rails generate feature_pack:add_group human_resources
This creates the following structure:
app/feature_packs/
└── group_YYMMDD_human_resources/
└── _group_space/
├── controller.rb
├── manifest.yaml
├── routes.rb
├── javascript/
└── views/
├── index.html.slim
└── partials/
├── _header.html.slim
└── _footer.html.slim
Creating a New Feature
rails generate feature_pack:add_feature human_resources/employees
This creates:
app/feature_packs/
└── group_YYMMDD_human_resources/
└── feature_YYMMDD_employees/
├── controller.rb
├── manifest.yaml
├── routes.rb
├── doc/
│ └── readme.md
├── javascript/
├── queries/
└── views/
├── index.html.slim
└── partials/
├── _header.html.slim
└── _footer.html.slim
Code Structure
Naming Convention
Groups
Format: group_<id>_<name>
-
group_
- Required prefix -
<id>
- Unique identifier (typically YYMMDD format) -
<name>
- Group name in snake_case
Example: group_241209_human_resources
Features
Format: feature_<id>_<name>
-
feature_
- Required prefix -
<id>
- Unique identifier (typically YYMMDD format) -
<name>
- Feature name in snake_case
Example: feature_241209_employees
Directory Structure
Group Space (_group_space
)
Contains group-level resources:
-
controller.rb
- Base controller for all features in the group -
manifest.yaml
- Group configuration -
routes.rb
- Group-level routes -
views/
- Shared views and layouts -
javascript/
- Shared JavaScript modules
Feature Directory
Contains feature-specific resources:
-
controller.rb
- Feature controller -
manifest.yaml
- Feature configuration -
routes.rb
- Feature routes -
views/
- Feature views -
javascript/
- Feature-specific JavaScript -
queries/
- Database queries -
doc/
- Feature documentation
Controllers
Group Controller
class FeaturePack::HumanResourcesController < FeaturePack::GroupController
# Group-wide functionality
end
Feature Controller
class FeaturePack::HumanResources::EmployeesController < FeaturePack::HumanResourcesController
def index
# Feature-specific logic
end
end
Routes
Routes are automatically configured based on manifest files:
# Group manifest.yaml
url: /hr
name: Human Resources
# Feature manifest.yaml
url: /employees
name: Employees Management
This generates routes like:
-
/hr
- Group index -
/hr/employees
- Feature index
Helpers
Path Helpers
# Group path
feature_pack_group_path(:human_resources)
# => "/hr"
# Feature path
feature_pack_path(:human_resources, :employees)
# => "/hr/employees"
# With parameters
feature_pack_path(:human_resources, :employees, id: 1)
# => "/hr/employees?id=1"
Controller Variables
Available in controllers and views:
-
@group
- Current group object -
@feature
- Current feature object (not available in group controller)
View Hierarchy
Views are resolved in the following order:
- Feature-specific views
- Group-shared views
- Application default views
Partials
Header and footer partials follow a fallback pattern:
- Feature's
views/partials/_header.html.slim
- Group's
views/partials/_header.html.slim
- Application's default header
JavaScript Integration
JavaScript files are automatically discovered and can be referenced:
# In views
javascript_include_tag @group.javascript_module('shared')
javascript_include_tag @feature.javascript_module('employees')
Advanced Configuration
Manifest Files
Group manifest (_group_space/manifest.yaml
):
url: /hr
name: Human Resources
const_aliases:
- employee_model: Employee
- department_model: Department
Feature manifest:
url: /employees
name: Employees Management
const_aliases:
- service: EmployeeService
Const Aliases
Access aliased constants:
# In controllers
@group.employee_model # => Employee
@feature.service # => FeaturePack::HumanResources::Employees::EmployeeService
Hooks
after_initialize Hook
O FeaturePack suporta hooks after_initialize
que permitem executar código customizado após o carregamento de grupos e features.
Como Funciona
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.
Localização dos Arquivos
-
Para grupos:
app/feature_packs/[nome_do_grupo]/_group_space/__after_initialize.rb
-
Para features:
app/feature_packs/[nome_do_grupo]/[nome_da_feature]/__after_initialize.rb
Contexto de Execução
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
.
Exemplos de Uso
Hook para grupo:
# app/feature_packs/group_241209_human_resources/_group_space/__after_initialize.rb
# Registrar o grupo em um sistema de auditoria
Rails.logger.info "Grupo #{name} carregado com #{features.size} features"
# Configurar permissões globais do grupo
features.each do |feature|
Rails.logger.info " - Feature #{feature.name} disponível em #{feature.manifest[:url]}"
end
# Carregar configurações específicas do grupo
config_file = File.join(absolute_path, '_group_space', 'config.yml')
if File.exist?(config_file)
@config = YAML.load_file(config_file)
end
Hook para feature:
# app/feature_packs/group_241209_human_resources/feature_241209_employees/__after_initialize.rb
# Registrar rotas dinâmicas
Rails.logger.info "Feature #{name} inicializada no grupo #{group.name}"
# Verificar dependências
required_gems = %w[devise cancancan]
required_gems.each do |gem_name|
unless Gem.loaded_specs.key?(gem_name)
Rails.logger.warn "Feature #{name} requer a gem #{gem_name}"
end
end
# Registrar a feature em um sistema de métricas
StatsD.increment("features.#{group.name}.#{name}.loaded") if defined?(StatsD)
# Configurar cache específico da feature
Rails.cache.write("feature:#{group.name}:#{name}:loaded_at", Time.current)
Best Practices
- Group Organization: Group related features that share common functionality
- Naming: Use descriptive snake_case names for groups and features
- Isolation: Keep features independent and loosely coupled
- Shared Resources: Place common code in group space
-
Documentation: Document each feature in its
doc/readme.md
Troubleshooting
Common Issues
-
Group/Feature Not Found
- Ensure proper naming convention
- Run
FeaturePack.setup
after adding new groups/features - Check manifest files exist
-
Routes Not Working
- Verify manifest.yaml has correct URL configuration
- Check routes.rb files exist
- Restart Rails server after changes
-
Views Not Rendering
- Check view file extensions (.html.slim, .html.erb, etc.)
- Verify view paths in controller
- Check for typos in view names
Contributing
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request
License
The gem is available as open source under the terms of the MIT License.
Author
Gedean Dias - gedean.dias@gmail.com