feature_pack 0.8.0 → 0.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f70a5116de8367b0eecf7c72cbab4762b6c28ded77c2cfd7b0df4369e4d82431
4
- data.tar.gz: ca02d7cc7f78755908a3adec71641b8740f423cf7eab65d5b2f92c0b1755e861
3
+ metadata.gz: 102101eb87f4153fd1db6cdd46f15bf1776912773498a1f585db49e7a6be707d
4
+ data.tar.gz: a3492ed0b1add63f95a171964c2e688f9d94d351d18112c2961aabcb1c80b845
5
5
  SHA512:
6
- metadata.gz: 8b9ea87e71245fc6d56827d933b4ca32ebd6e1e1a8c144b1ba62ca4a005fce36244414a6c278b4f0ff5e263e73ebc0b71617b09f9fba16816804c4cad7ea3d79
7
- data.tar.gz: d135218c290abdd63c24c13f5e8944854e4bb186bb1db9982164ac7f52fe19a93e6c6f67e0d07f58d95baeeaf553a32fa021d9053f9513e0a0f40526ac43050c
6
+ metadata.gz: d959f7066efab1bb4371914ad61689384d4c6d9a25c4bbc57a0982f14c6cb694c7025aa5e81557790e52327e6112c6ef28cac5e36612abcfaf01f432d5fe01f1
7
+ data.tar.gz: f6e1c3c5bd1518732d146567446b4a36b4f4812605425c7ee476c0951685ab97f1ed4f4a87d91defbfb0b876b4428620a7fe28d3737a09b26142b2ad72671c9a
data/README.md CHANGED
@@ -1,91 +1,297 @@
1
1
  # Feature Pack Gem
2
- 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, which can be developed, tested, and maintained independently of each other.
3
2
 
4
- ## Code and Folder Structure
3
+ [![Gem Version](https://badge.fury.io/rb/feature_pack.svg)](https://badge.fury.io/rb/feature_pack)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
 
6
- ### Group
7
- A group is a collection of related features. Groups are represented as directories in the `app/feature_packs` folder. Each group contains a `_group_space` directory that holds group-level views and JavaScript files. Group directories follow this naming pattern:
6
+ 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.
8
7
 
9
- #### Naming Convention
10
- Sample: `group_departments-100100_human_resources`
8
+ ## Installation
11
9
 
12
- `group_` prefix is followed by the group identification (required)
13
- `departments-100100` between `group_` and class name, exists only for organization purposes. The bounds are `group_` and next underscore `_`
14
- `human_resources` class name of the group (required)
10
+ Add this line to your application's Gemfile:
15
11
 
16
- #### Note worthy
17
- Group name is the same as the class name. Its used internally in the backend code.
18
- The 'Name' within the manifest file (Group.manifest[:name]) is the name to be shown in the user interface.
12
+ ```ruby
13
+ gem 'feature_pack'
14
+ ```
19
15
 
20
- The `_group_space` directory contains:
16
+ And then execute:
21
17
 
22
- - `views/` - Views of the group
23
- #### Common Files in _group_space/views
18
+ ```bash
19
+ bundle install
20
+ ```
24
21
 
25
- The `_group_space/views` directory typically contains these common files:
22
+ ## Setup
26
23
 
24
+ Add to your `config/application.rb`:
25
+
26
+ ```ruby
27
+ # After Bundler.require
28
+ require 'feature_pack'
29
+ FeaturePack.setup
27
30
  ```
28
- _group_space/views/
29
- ├── index.html.slim # Default view for the group
30
- └── partials/
31
- └── _header.html.slim # Base header template for the group
32
- └── _footer.html.slim # Base footer template for the group
33
- ```
34
- Can have more views and partials, depending on the defined on `controller.rb` but these are the most common ones.
35
31
 
36
- - `javascript/` - Group-level JavaScript modules shared across features
37
- - `controller.rb` - Base controller class for the group's features
38
- - `routes.rb` - The routes files come with the default `index` action/view.
32
+ ## Concepts
33
+
34
+ ### Groups
35
+ Groups are collections of related features. They provide:
36
+ - Shared layouts and partials
37
+ - Common base controller functionality
38
+ - Namespace organization
39
+ - Shared JavaScript modules
40
+
41
+ ### Features
42
+ Features are individual functionalities within a group. They provide:
43
+ - Independent controllers and views
44
+ - Isolated routes
45
+ - Feature-specific JavaScript
46
+ - Complete MVC structure
47
+
48
+ ## Usage
39
49
 
40
- #### How implement a new Group
50
+ ### Creating a New Group
51
+
52
+ ```bash
53
+ rails generate feature_pack:add_group human_resources
54
+ ```
55
+
56
+ This creates the following structure:
41
57
  ```
42
- rails generate feature_pack:add_gruop <group_name>
58
+ app/feature_packs/
59
+ └── group_YYMMDD_human_resources/
60
+ └── _group_space/
61
+ ├── controller.rb
62
+ ├── manifest.yaml
63
+ ├── routes.rb
64
+ ├── javascript/
65
+ └── views/
66
+ ├── index.html.slim
67
+ └── partials/
68
+ ├── _header.html.slim
69
+ └── _footer.html.slim
43
70
  ```
44
71
 
45
- ### Feature
46
- A feature is a single feature that can be added to a group. Feature naming patter is the same of group, but without the `group_` prefix.
72
+ ### Creating a New Feature
47
73
 
48
- #### Feature Routes
49
- Every feature has a default route, which is the `index` action/view. If the feature has more than the default `index` action/view, the routes are defined in the `routes.rb` file.
74
+ ```bash
75
+ rails generate feature_pack:add_feature human_resources/employees
76
+ ```
50
77
 
51
- #### How implement a new feature
78
+ This creates:
52
79
  ```
53
- rails generate feature_pack:add_feature <group_name>/<feature_name>
80
+ app/feature_packs/
81
+ └── group_YYMMDD_human_resources/
82
+ └── feature_YYMMDD_employees/
83
+ ├── controller.rb
84
+ ├── manifest.yaml
85
+ ├── routes.rb
86
+ ├── doc/
87
+ │ └── readme.md
88
+ ├── javascript/
89
+ ├── queries/
90
+ └── views/
91
+ ├── index.html.slim
92
+ └── partials/
93
+ ├── _header.html.slim
94
+ └── _footer.html.slim
54
95
  ```
55
96
 
56
- #### Helpers
97
+ ## Code Structure
98
+
99
+ ### Naming Convention
100
+
101
+ #### Groups
102
+ Format: `group_<id>_<name>`
103
+ - `group_` - Required prefix
104
+ - `<id>` - Unique identifier (typically YYMMDD format)
105
+ - `<name>` - Group name in snake_case
106
+
107
+ Example: `group_241209_human_resources`
108
+
109
+ #### Features
110
+ Format: `feature_<id>_<name>`
111
+ - `feature_` - Required prefix
112
+ - `<id>` - Unique identifier (typically YYMMDD format)
113
+ - `<name>` - Feature name in snake_case
57
114
 
115
+ Example: `feature_241209_employees`
116
+
117
+ ### Directory Structure
118
+
119
+ #### Group Space (`_group_space`)
120
+ Contains group-level resources:
121
+ - `controller.rb` - Base controller for all features in the group
122
+ - `manifest.yaml` - Group configuration
123
+ - `routes.rb` - Group-level routes
124
+ - `views/` - Shared views and layouts
125
+ - `javascript/` - Shared JavaScript modules
126
+
127
+ #### Feature Directory
128
+ Contains feature-specific resources:
129
+ - `controller.rb` - Feature controller
130
+ - `manifest.yaml` - Feature configuration
131
+ - `routes.rb` - Feature routes
132
+ - `views/` - Feature views
133
+ - `javascript/` - Feature-specific JavaScript
134
+ - `queries/` - Database queries
135
+ - `doc/` - Feature documentation
136
+
137
+ ## Controllers
138
+
139
+ ### Group Controller
140
+ ```ruby
141
+ class FeaturePack::HumanResourcesController < FeaturePack::GroupController
142
+ # Group-wide functionality
143
+ end
144
+ ```
145
+
146
+ ### Feature Controller
58
147
  ```ruby
59
- # Application Helper
60
- def feature_pack_group_path(group_name, *params) = send("feature_pack_#{group_name}_path".to_sym, *params)
61
- def feature_pack_path(group_name, feature_name, *params) = send("feature_pack_#{group_name}_#{feature_name}_path".to_sym, *params)
148
+ class FeaturePack::HumanResources::EmployeesController < FeaturePack::HumanResourcesController
149
+ def index
150
+ # Feature-specific logic
151
+ end
152
+ end
62
153
  ```
63
154
 
155
+ ## Routes
156
+
157
+ Routes are automatically configured based on manifest files:
158
+
159
+ ```yaml
160
+ # Group manifest.yaml
161
+ url: /hr
162
+ name: Human Resources
163
+
164
+ # Feature manifest.yaml
165
+ url: /employees
166
+ name: Employees Management
167
+ ```
168
+
169
+ This generates routes like:
170
+ - `/hr` - Group index
171
+ - `/hr/employees` - Feature index
172
+
64
173
  ## Helpers
65
174
 
66
- ### Using the `feature_pack_group_path` and `feature_pack_path` Helpers
175
+ ### Path Helpers
176
+
177
+ ```ruby
178
+ # Group path
179
+ feature_pack_group_path(:human_resources)
180
+ # => "/hr"
181
+
182
+ # Feature path
183
+ feature_pack_path(:human_resources, :employees)
184
+ # => "/hr/employees"
185
+
186
+ # With parameters
187
+ feature_pack_path(:human_resources, :employees, id: 1)
188
+ # => "/hr/employees?id=1"
189
+ ```
190
+
191
+ ### Controller Variables
192
+
193
+ Available in controllers and views:
194
+ - `@group` - Current group object
195
+ - `@feature` - Current feature object (not available in group controller)
196
+
197
+ ## View Hierarchy
198
+
199
+ Views are resolved in the following order:
200
+ 1. Feature-specific views
201
+ 2. Group-shared views
202
+ 3. Application default views
203
+
204
+ ### Partials
205
+ Header and footer partials follow a fallback pattern:
206
+ 1. Feature's `views/partials/_header.html.slim`
207
+ 2. Group's `views/partials/_header.html.slim`
208
+ 3. Application's default header
209
+
210
+ ## JavaScript Integration
211
+
212
+ JavaScript files are automatically discovered and can be referenced:
213
+
214
+ ```ruby
215
+ # In views
216
+ javascript_include_tag @group.javascript_module('shared')
217
+ javascript_include_tag @feature.javascript_module('employees')
218
+ ```
219
+
220
+ ## Advanced Configuration
221
+
222
+ ### Manifest Files
223
+
224
+ Group manifest (`_group_space/manifest.yaml`):
225
+ ```yaml
226
+ url: /hr
227
+ name: Human Resources
228
+ const_aliases:
229
+ - employee_model: Employee
230
+ - department_model: Department
231
+ ```
232
+
233
+ Feature manifest:
234
+ ```yaml
235
+ url: /employees
236
+ name: Employees Management
237
+ const_aliases:
238
+ - service: EmployeeService
239
+ ```
240
+
241
+ ### Const Aliases
242
+
243
+ Access aliased constants:
244
+ ```ruby
245
+ # In controllers
246
+ @group.employee_model # => Employee
247
+ @feature.service # => FeaturePack::HumanResources::Employees::EmployeeService
248
+ ```
249
+
250
+ ## Best Practices
251
+
252
+ 1. **Group Organization**: Group related features that share common functionality
253
+ 2. **Naming**: Use descriptive snake_case names for groups and features
254
+ 3. **Isolation**: Keep features independent and loosely coupled
255
+ 4. **Shared Resources**: Place common code in group space
256
+ 5. **Documentation**: Document each feature in its `doc/readme.md`
257
+
258
+ ## Troubleshooting
259
+
260
+ ### Common Issues
261
+
262
+ 1. **Group/Feature Not Found**
263
+ - Ensure proper naming convention
264
+ - Run `FeaturePack.setup` after adding new groups/features
265
+ - Check manifest files exist
266
+
267
+ 2. **Routes Not Working**
268
+ - Verify manifest.yaml has correct URL configuration
269
+ - Check routes.rb files exist
270
+ - Restart Rails server after changes
271
+
272
+ 3. **Views Not Rendering**
273
+ - Check view file extensions (.html.slim, .html.erb, etc.)
274
+ - Verify view paths in controller
275
+ - Check for typos in view names
276
+
277
+ ## Contributing
278
+
279
+ 1. Fork it
280
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
281
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
282
+ 4. Push to the branch (`git push origin my-new-feature`)
283
+ 5. Create new Pull Request
284
+
285
+ ## License
67
286
 
68
- The `feature_pack_group_path` and `feature_pack_path` helpers are used to generate URLs for specific groups and features within the feature package system.
287
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
69
288
 
70
- - `feature_pack_group_path(group, *params)`: Generates the path for a specific group. The `group` parameter should be an object representing the desired group. Additional parameters can be passed to specify more details in the URL.
71
-
72
- **Usage example:**
73
- ```ruby
74
- # Assuming `group` is a valid group name in symbol
75
- group_url = feature_pack_group_path(:group_name)
76
- ```
289
+ ## Author
77
290
 
78
- - `feature_pack_path(group, feature, *params)`: Generates the path for a specific feature within a group. The `group` and `feature` parameters should be symbols of group and feature name, respectively. Additional parameters can be passed to specify more details in the URL.
79
-
80
- **Usage example:**
81
- ```ruby
82
- # Assuming `group` and `feature` are valid objects
83
- feature_url = feature_pack_path(:my_group, :my_feature)
84
- ```
291
+ Gedean Dias - gedean.dias@gmail.com
85
292
 
86
- These helpers are useful for maintaining consistency and clarity when generating URLs within the application, ensuring that routes are correctly constructed based on the provided group and feature names.
293
+ ## Links
87
294
 
88
- ## Variables
89
- Current group and feature are available in the controller (so as in the views too) as `@group` and `@feature` variables.
90
- @feature variable is not available in the group controller.
91
- @group variable is available in the group controller and in the its features controllers.
295
+ - [GitHub Repository](https://github.com/gedean/feature_pack)
296
+ - [RubyGems](https://rubygems.org/gems/feature_pack)
297
+ - [Bug Reports](https://github.com/gedean/feature_pack/issues)
@@ -1,44 +1,58 @@
1
+ # Base controller for all feature controllers
2
+ # Handles the setup of features, views, and layouts
1
3
  class FeaturePack::Controller < ApplicationController
2
4
  prepend_before_action :setup_feature
3
5
 
6
+ # Default index action
4
7
  def index; end
5
8
 
6
9
  private
7
10
 
11
+ # Main setup method that configures the feature environment
8
12
  def setup_feature
9
13
  set_group_and_feature
10
14
  set_view_lookup_context_prefix
11
15
  set_layout_paths
12
16
  end
13
17
 
18
+ # Extracts and sets the group and feature from the controller path
14
19
  def set_group_and_feature
15
- group_name, feature_name = params['controller'].delete_prefix('feature_pack/').split('/').map(&:to_sym)
16
- @group = FeaturePack.group group_name
17
- @feature = FeaturePack.feature group_name, feature_name
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?
18
30
  end
19
31
 
32
+ # Configures the view lookup path to include feature-specific views
20
33
  def set_view_lookup_context_prefix
21
34
  return if lookup_context.prefixes.include?(@feature.views_relative_path)
22
35
 
23
36
  lookup_context.prefixes.prepend(@feature.views_relative_path)
24
37
  end
25
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)
26
44
  def set_layout_paths
27
- # Header/Footer Lookup order
28
- #
29
- # - Feature dir/_partials, if not exists
30
- # - Fallback to Group, if not exists
31
- # - Fallback to Application's default header/footer
32
-
33
45
  feature_partials_path = @feature.views_relative_path.join('partials')
34
46
  group_partials_path = @feature.group.views_path.concat('/partials')
35
47
 
48
+ # Set header layout
36
49
  if template_exists?('header', feature_partials_path, true)
37
50
  @header_layout_path = @feature.view('partials/header')
38
51
  elsif template_exists?('header', group_partials_path, true)
39
52
  @header_layout_path = @feature.group.view('partials/header')
40
53
  end
41
54
 
55
+ # Set footer layout
42
56
  if template_exists?('footer', feature_partials_path, true)
43
57
  @footer_layout_path = @feature.view('partials/footer')
44
58
  elsif template_exists?('footer', group_partials_path, true)
@@ -1,6 +1,7 @@
1
1
  module FeaturePack::Error
2
2
  class NoDataError < StandardError
3
- end
3
+ end
4
+
4
5
  class NoGroup < StandardError
5
- end
6
+ end
6
7
  end
@@ -1,14 +1,25 @@
1
+ # Routes configuration for FeaturePack
2
+ # This file is loaded by Rails routes to set up all group and feature routes
3
+
1
4
  FeaturePack.groups.each do |group|
5
+ # Configure group-level routes
2
6
  scope group.manifest[:url], as: group.name do
3
- raise "Group '#{group.name}' routes file not found in #{group.metadata_path}" if group.routes_file.nil?
7
+ if group.routes_file.nil?
8
+ raise FeaturePack::Error::NoDataError,
9
+ "Group '#{group.name}' routes file not found in #{group.metadata_path}"
10
+ end
4
11
 
5
12
  draw(group.routes_file)
6
13
  end
7
14
 
15
+ # Configure feature-level routes within the group namespace
8
16
  namespace group.name, path: group.manifest[:url] do
9
17
  group.features.each do |feature|
10
18
  scope feature.manifest[:url], as: feature.name do
11
- raise "Feature '#{feature.name}' routes file not found in #{feature.routes_file}" if feature.routes_file.nil?
19
+ if feature.routes_file.nil?
20
+ raise FeaturePack::Error::NoDataError,
21
+ "Feature '#{feature.name}' routes file not found in #{feature.routes_file_path}"
22
+ end
12
23
 
13
24
  draw(feature.routes_file)
14
25
  end
@@ -1,33 +1,45 @@
1
+ # Base controller for all group controllers
2
+ # Handles the setup of groups and their views
1
3
  class FeaturePack::GroupController < ApplicationController
2
4
  prepend_before_action :setup_group
5
+
6
+ # Default index action
3
7
  def index; end
4
8
 
5
9
  private
6
10
 
11
+ # Main setup method that configures the group environment
7
12
  def setup_group
8
13
  set_group
9
14
  set_view_lookup_context_prefix
10
15
  set_layout_paths
11
16
  end
12
17
 
18
+ # Extracts and sets the group from the controller path
13
19
  def set_group
14
20
  group_name = params[:controller].split('/')[1].to_sym
15
21
  @group = FeaturePack.group(group_name)
22
+
23
+ raise FeaturePack::Error::NoGroup, "Group '#{group_name}' not found" if @group.nil?
16
24
  end
17
25
 
26
+ # Configures the view lookup path to include group-specific views
18
27
  def set_view_lookup_context_prefix
19
28
  return if lookup_context.prefixes.include?(@group.views_path)
20
29
 
21
30
  lookup_context.prefixes.prepend(@group.views_path)
22
31
  end
23
32
 
33
+ # Sets up header and footer layout paths for the group
24
34
  def set_layout_paths
25
- patials_path = @group.views_path.concat('/partials')
35
+ partials_path = @group.views_path.concat('/partials')
26
36
 
27
- @header_layout_path = @group.view('partials/header') if template_exists?('header', patials_path, true)
37
+ if template_exists?('header', partials_path, true)
38
+ @header_layout_path = @group.view('partials/header')
39
+ end
28
40
 
29
- return unless template_exists?('footer', patials_path, true)
30
-
31
- @footer_layout_path = @group.view('partials/footer')
41
+ if template_exists?('footer', partials_path, true)
42
+ @footer_layout_path = @group.view('partials/footer')
43
+ end
32
44
  end
33
45
  end
data/lib/feature_pack.rb CHANGED
@@ -1,12 +1,18 @@
1
1
  require 'active_support/all'
2
2
 
3
+ # FeaturePack module provides a way to organize Rails applications into
4
+ # groups and features, enabling better code organization and isolation
3
5
  module FeaturePack
6
+ # Pattern constants for identifying groups and features
4
7
  GROUP_ID_PATTERN = /^group_.*?_/.freeze
5
8
  FEATURE_ID_PATTERN = /^feature_.*?_/.freeze
9
+
10
+ # Directory and file name constants
6
11
  GROUP_SPACE_DIRECTORY = '_group_space'.freeze
7
12
  MANIFEST_FILE_NAME = 'manifest.yaml'.freeze
8
13
  CONTROLLER_FILE_NAME = 'controller.rb'.freeze
9
14
 
15
+ # Attribute readers that will be dynamically defined
10
16
  ATTR_READERS = %i[
11
17
  path
12
18
  features_path
@@ -17,41 +23,113 @@ module FeaturePack
17
23
  javascript_files_paths
18
24
  ].freeze
19
25
 
20
- def self.setup()
21
- raise 'FeaturePack already setup!' if defined?(@@setup_executed_flag)
26
+ class << self
27
+ # Sets up the FeaturePack system
28
+ # This method should be called once during Rails initialization
29
+ def setup
30
+ raise 'FeaturePack already setup!' if defined?(@@setup_executed_flag)
22
31
 
23
- @@path = Pathname.new(__dir__)
24
- load @@path.join('feature_pack/error.rb')
32
+ initialize_paths
33
+ load_dependencies
34
+ discover_groups
35
+ discover_features
36
+ finalize_setup
37
+ end
25
38
 
26
- @@features_path = Pathname.new(Rails.root.join('app/feature_packs'))
27
- raise "Invalid features_path: '#{@@features_path}'" if @@features_path.nil?
28
- raise "Inexistent features_path: '#{@@features_path}'" unless Dir.exist?(@@features_path)
39
+ # Finds a group by name
40
+ # @param group_name [Symbol] The name of the group
41
+ # @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
29
45
 
30
- @@groups_controllers_paths = []
31
- @@features_controllers_paths = []
46
+ # Finds a feature within a group
47
+ # @param group_name [Symbol] The name of the group
48
+ # @param feature_name [Symbol] The name of the feature
49
+ # @return [OpenStruct, nil] The feature object or nil if not found
50
+ def feature(group_name, feature_name)
51
+ requested_group = group(group_name)
52
+ return nil if requested_group.nil?
53
+
54
+ requested_group.feature(feature_name)
55
+ end
32
56
 
33
- @@ignored_paths = Dir.glob("#{@@features_path}/[!]*/")
57
+ private
34
58
 
35
- @@javascript_files_paths = Dir.glob("#{@@features_path}/[!_]*/**/*.js")
36
- .map { |js_path| js_path.sub(/^#{Regexp.escape(@@features_path.to_s)}\//, '') }.to_a
59
+ def initialize_paths
60
+ @@path = Pathname.new(__dir__)
61
+ @@features_path = Pathname.new(Rails.root.join('app/feature_packs'))
62
+
63
+ validate_features_path!
64
+
65
+ @@groups_controllers_paths = []
66
+ @@features_controllers_paths = []
67
+ @@ignored_paths = Dir.glob("#{@@features_path}/[!]*/")
68
+ @@javascript_files_paths = discover_javascript_files
69
+ end
37
70
 
38
- ATTR_READERS.each { |attr| define_singleton_method(attr) { class_variable_get("@@#{attr}") } }
71
+ def load_dependencies
72
+ load @@path.join('feature_pack/error.rb')
73
+ end
39
74
 
40
- @@ignored_paths << @@path.join('feature_pack/feature_pack_routes.rb')
75
+ def validate_features_path!
76
+ raise "Invalid features_path: '#{@@features_path}'" if @@features_path.nil?
77
+ raise "Features path does not exist: '#{@@features_path}'" unless Dir.exist?(@@features_path)
78
+ end
41
79
 
42
- # raise "No Groups found in: '#{@@features_path}'" if Dir.glob("#{@@features_path}/[!_]*/").empty?
80
+ def discover_javascript_files
81
+ Dir.glob("#{@@features_path}/[!_]*/**/*.js")
82
+ .map { |js_path| js_path.sub(/^#{Regexp.escape(@@features_path.to_s)}\//, '') }
83
+ .to_a
84
+ end
43
85
 
44
- @@groups = Dir.glob("#{@@features_path}/[!_]*/").map do |group_path|
86
+ def finalize_setup
87
+ ATTR_READERS.each { |attr| define_singleton_method(attr) { class_variable_get("@@#{attr}") } }
88
+ @@ignored_paths << @@path.join('feature_pack/feature_pack_routes.rb')
89
+ @@setup_executed_flag = true
90
+ end
91
+
92
+ def discover_groups
93
+ @@groups = Dir.glob("#{@@features_path}/[!_]*/").map do |group_path|
94
+ build_group(group_path)
95
+ end
96
+ end
97
+
98
+ def build_group(group_path)
45
99
  relative_path = Pathname.new(group_path)
46
100
  base_path = File.basename(group_path, File::SEPARATOR)
101
+
102
+ validate_group_id!(base_path)
103
+
104
+ routes_file = find_group_routes_file(group_path, base_path)
105
+ @@groups_controllers_paths << File.join(group_path, GROUP_SPACE_DIRECTORY, CONTROLLER_FILE_NAME)
106
+
107
+ group = create_group_struct(base_path, group_path, relative_path, routes_file)
108
+ setup_group_aliases(group)
109
+ define_group_methods(group)
110
+
111
+ group
112
+ end
47
113
 
48
- # On route draw call, the extension is ignored
49
- routes_file = File.exist?(File.join(group_path, GROUP_SPACE_DIRECTORY, 'routes.rb')) ? File.join(base_path, GROUP_SPACE_DIRECTORY, 'routes') : nil
114
+ def validate_group_id!(base_path)
115
+ if base_path.scan(GROUP_ID_PATTERN).empty?
116
+ raise "Group '#{base_path}' does not have a valid ID. Expected format: group_<id>_<name>"
117
+ end
118
+ end
50
119
 
51
- @@groups_controllers_paths << File.join(group_path, GROUP_SPACE_DIRECTORY, CONTROLLER_FILE_NAME)
120
+ def find_group_routes_file(group_path, base_path)
121
+ routes_path = File.join(group_path, GROUP_SPACE_DIRECTORY, 'routes.rb')
122
+ File.exist?(routes_path) ? File.join(base_path, GROUP_SPACE_DIRECTORY, 'routes') : nil
123
+ end
52
124
 
53
- raise "Group '#{base_path}' does not have a valid ID" if base_path.scan(GROUP_ID_PATTERN).empty?
54
- group = OpenStruct.new(
125
+ def create_group_struct(base_path, group_path, relative_path, routes_file)
126
+ manifest_path = File.join(group_path, GROUP_SPACE_DIRECTORY, MANIFEST_FILE_NAME)
127
+
128
+ unless File.exist?(manifest_path)
129
+ raise "Manifest file not found for group '#{base_path}' at #{manifest_path}"
130
+ end
131
+
132
+ OpenStruct.new(
55
133
  id: base_path.scan(GROUP_ID_PATTERN).first.delete_suffix('_'),
56
134
  name: base_path.gsub(GROUP_ID_PATTERN, '').to_sym,
57
135
  metadata_path: @@features_path.join(group_path, GROUP_SPACE_DIRECTORY),
@@ -59,82 +137,139 @@ module FeaturePack
59
137
  base_dir: File.basename(relative_path, File::SEPARATOR),
60
138
  routes_file: routes_file,
61
139
  features: [],
62
- manifest: YAML.load_file(File.join(group_path, GROUP_SPACE_DIRECTORY, MANIFEST_FILE_NAME)).deep_symbolize_keys
140
+ manifest: load_manifest(manifest_path)
63
141
  )
142
+ end
143
+
144
+ def load_manifest(manifest_path)
145
+ YAML.load_file(manifest_path).deep_symbolize_keys
146
+ rescue => e
147
+ raise "Failed to load manifest at #{manifest_path}: #{e.message}"
148
+ end
64
149
 
150
+ def setup_group_aliases(group)
65
151
  group.manifest.fetch(:const_aliases, []).each do |alias_data|
66
152
  alias_method_name, alias_const_name = alias_data.first
67
- group.define_singleton_method(alias_method_name) { "FeaturePack::#{group.name.name.camelize}::#{alias_const_name}".constantize }
153
+ group.define_singleton_method(alias_method_name) do
154
+ "FeaturePack::#{group.name.to_s.camelize}::#{alias_const_name}".constantize
155
+ end
68
156
  end
157
+ end
69
158
 
70
- def group.feature(feature_name) = features.find { |p| p.name.eql?(feature_name) }
71
- def group.views_path = "#{base_dir}/#{GROUP_SPACE_DIRECTORY}/views"
72
- def group.view(view_name) = "#{base_dir}/#{GROUP_SPACE_DIRECTORY}/views/#{view_name}"
73
- def group.javascript_module(javascript_file_name) = "#{base_dir}/#{GROUP_SPACE_DIRECTORY}/javascript/#{javascript_file_name}"
74
-
75
- group
159
+ def define_group_methods(group)
160
+ def group.feature(feature_name)
161
+ features.find { |p| p.name.eql?(feature_name) }
162
+ end
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
76
175
  end
77
176
 
78
- @@groups.each do |group|
79
- Dir.glob("#{group.relative_path}[!_]*/").each do |feature_path|
80
- absolute_path = @@features_path.join(feature_path)
81
- relative_path = Pathname.new(feature_path)
82
- base_path = File.basename(feature_path, File::SEPARATOR)
83
-
84
- feature_name = base_path.gsub(FEATURE_ID_PATTERN, '').to_sym
85
-
86
- routes_file_path = relative_path.join('routes.rb')
87
-
88
- # The custom routes file loads before the Rails default routes,
89
- # leading to errors like NoMethodError for 'scope'.
90
- # Ignoring them is required to prevent these issues.
91
- @@ignored_paths << routes_file_path
92
-
93
- # Due to Zeiwerk rules, Controllers have special load process
94
- @@features_controllers_paths << relative_path.join(CONTROLLER_FILE_NAME)
95
-
96
- @@ignored_paths << relative_path.join(CONTROLLER_FILE_NAME)
97
-
98
- raise "Resource '#{relative_path}' does not have a valid ID" if base_path.scan(FEATURE_ID_PATTERN).empty?
99
- feature_sub_path = relative_path.sub(/^#{Regexp.escape(@@features_path.to_s)}\//, '')
100
- feature = OpenStruct.new(
101
- id: base_path.scan(FEATURE_ID_PATTERN).first.delete_suffix('_'),
102
- name: feature_name,
103
- group: group,
104
- absolute_path: absolute_path,
105
- relative_path: relative_path,
106
- sub_path: feature_sub_path,
107
- routes_file_path: routes_file_path,
108
- routes_file: feature_sub_path.join('routes'),
109
- # controller_path: relative_path.join('controller'),
110
- views_absolute_path: absolute_path.join('views'),
111
- views_relative_path: relative_path.sub(/^#{Regexp.escape(@@features_path.to_s)}\//, '').join('views'),
112
- javascript_relative_path: relative_path.sub(/^#{Regexp.escape(@@features_path.to_s)}\//, '').join('javascript'),
113
- manifest: YAML.load_file(File.join(feature_path, MANIFEST_FILE_NAME)).deep_symbolize_keys
114
- )
115
-
116
- def feature.class_name = "FeaturePack::#{group.name.name.camelize}::#{name.name.camelize}"
117
- def feature.namespace = class_name.constantize
118
-
119
- feature.manifest.fetch(:const_aliases, []).each do |alias_data|
120
- alias_method_name, alias_const_name = alias_data.first
121
- feature.define_singleton_method(alias_method_name) { "#{class_name}::#{alias_const_name}".constantize }
177
+ def discover_features
178
+ @@groups.each do |group|
179
+ Dir.glob("#{group.relative_path}[!_]*/").each do |feature_path|
180
+ build_feature(group, feature_path)
122
181
  end
182
+ end
183
+ end
123
184
 
124
- def feature.view(view_name) = "#{views_relative_path}/#{view_name}"
125
- def feature.javascript_module(javascript_file_name) = "#{javascript_relative_path}/#{javascript_file_name}"
185
+ def build_feature(group, feature_path)
186
+ absolute_path = @@features_path.join(feature_path)
187
+ relative_path = Pathname.new(feature_path)
188
+ base_path = File.basename(feature_path, File::SEPARATOR)
189
+
190
+ validate_feature_id!(base_path, relative_path)
191
+
192
+ feature_name = base_path.gsub(FEATURE_ID_PATTERN, '').to_sym
193
+ routes_file_path = relative_path.join('routes.rb')
194
+
195
+ setup_feature_paths(relative_path, routes_file_path)
196
+
197
+ feature = create_feature_struct(
198
+ base_path, feature_name, group, absolute_path,
199
+ relative_path, routes_file_path, feature_path
200
+ )
201
+
202
+ define_feature_methods(feature)
203
+ setup_feature_aliases(feature)
204
+
205
+ group.features << feature
206
+ end
126
207
 
127
- group.features << feature
208
+ def validate_feature_id!(base_path, relative_path)
209
+ if base_path.scan(FEATURE_ID_PATTERN).empty?
210
+ raise "Feature '#{relative_path}' does not have a valid ID. Expected format: feature_<id>_<name>"
128
211
  end
129
212
  end
130
213
 
131
- @@setup_executed_flag = true
132
- end
214
+ def setup_feature_paths(relative_path, routes_file_path)
215
+ # Custom routes file loads before Rails default routes
216
+ @@ignored_paths << routes_file_path
217
+
218
+ # Controllers have special load process due to Zeitwerk
219
+ controller_path = relative_path.join(CONTROLLER_FILE_NAME)
220
+ @@features_controllers_paths << controller_path
221
+ @@ignored_paths << controller_path
222
+ end
223
+
224
+ 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(@@features_path.to_s)}\//, '')
226
+ manifest_path = File.join(feature_path, MANIFEST_FILE_NAME)
227
+
228
+ unless File.exist?(manifest_path)
229
+ raise "Manifest file not found for feature '#{feature_name}' at #{manifest_path}"
230
+ end
231
+
232
+ OpenStruct.new(
233
+ id: base_path.scan(FEATURE_ID_PATTERN).first.delete_suffix('_'),
234
+ name: feature_name,
235
+ group: group,
236
+ absolute_path: absolute_path,
237
+ relative_path: relative_path,
238
+ sub_path: feature_sub_path,
239
+ routes_file_path: routes_file_path,
240
+ routes_file: feature_sub_path.join('routes'),
241
+ views_absolute_path: absolute_path.join('views'),
242
+ views_relative_path: relative_path.sub(/^#{Regexp.escape(@@features_path.to_s)}\//, '').join('views'),
243
+ javascript_relative_path: relative_path.sub(/^#{Regexp.escape(@@features_path.to_s)}\//, '').join('javascript'),
244
+ manifest: load_manifest(manifest_path)
245
+ )
246
+ end
133
247
 
134
- def self.group(group_name) = @@groups.find { |g| g.name.eql?(group_name) }
135
- def self.feature(group_name, feature_name)
136
- requested_group = group(group_name)
137
- return nil if requested_group.nil?
138
- requested_group.feature(feature_name)
248
+ def define_feature_methods(feature)
249
+ def feature.class_name
250
+ "FeaturePack::#{group.name.to_s.camelize}::#{name.to_s.camelize}"
251
+ end
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
264
+ end
265
+
266
+ def setup_feature_aliases(feature)
267
+ feature.manifest.fetch(:const_aliases, []).each do |alias_data|
268
+ alias_method_name, alias_const_name = alias_data.first
269
+ feature.define_singleton_method(alias_method_name) do
270
+ "#{class_name}::#{alias_const_name}".constantize
271
+ end
272
+ end
273
+ end
139
274
  end
140
275
  end
@@ -1,34 +1,81 @@
1
1
  require 'rails/generators/base'
2
2
  require 'rails/generators/named_base'
3
3
 
4
- class FeaturePack::AddFeatureGenerator < Rails::Generators::NamedBase
5
- desc 'Adds a new Feature'
6
- source_root File.expand_path('templates', __dir__)
4
+ module FeaturePack
5
+ # Generator for creating new features within a group
6
+ # Usage: rails generate feature_pack:add_feature GROUP_NAME/FEATURE_NAME
7
+ class AddFeatureGenerator < Rails::Generators::NamedBase
8
+ desc 'Creates a new Feature within an existing Group'
9
+ source_root File.expand_path('templates', __dir__)
7
10
 
8
- argument :name, type: :string, required: true, desc: 'The name (sneak case) of the group to add'
11
+ argument :name, type: :string, required: true, desc: 'The group/feature name (snake_case) format: group_name/feature_name'
9
12
 
10
- def add_feature
11
- raise "Feature name couldn't have more than one bar '/'" if name.count('/') > 1
13
+ def add_feature
14
+ validate_feature_name!
15
+ parse_names
16
+ check_group_existence!
17
+ check_feature_existence!
18
+
19
+ @feature_id = generate_feature_id
20
+ @feature_dir = @group.relative_path.join("feature_#{@feature_id}_#{@feature_name}")
21
+
22
+ create_feature_files
23
+
24
+ say "Feature '#{@feature_name}' created successfully in group '#{@group_name}'!", :green
25
+ say "Location: #{@feature_dir}", :green
26
+ end
12
27
 
13
- @group_name, @feature_name = name.split('/')
14
- @group = FeaturePack.group(@group_name.to_sym)
15
- @group_class_name = @group_name.camelcase
16
- @feature_class_name = @feature_name.camelcase
28
+ private
17
29
 
18
- raise "Group '#{@group_name}' doesn't exist. First, create it." if @group.nil?
30
+ def validate_feature_name!
31
+ if name.count('/') != 1
32
+ raise Thor::Error, "Feature name must be in format: group_name/feature_name"
33
+ end
34
+ end
19
35
 
20
- raise "Feature '#{@group_class_name}::#{@feature_class_name}' already Exist." if FeaturePack.feature(@group_name.to_sym, @feature_name.to_sym).present?
36
+ def parse_names
37
+ @group_name, @feature_name = name.split('/')
38
+
39
+ unless @group_name.match?(/^[a-z_]+$/) && @feature_name.match?(/^[a-z_]+$/)
40
+ raise Thor::Error, "Group and feature names must be in snake_case format"
41
+ end
42
+
43
+ @group_class_name = @group_name.camelcase
44
+ @feature_class_name = @feature_name.camelcase
45
+ end
21
46
 
22
- @feature_id = @feature_name.gsub('_', '-') + '-' + '999'
23
- @feature_dir = @group.relative_path.join("feature_#{@feature_id}_#{@feature_name}")
47
+ def check_group_existence!
48
+ @group = FeaturePack.group(@group_name.to_sym)
49
+
50
+ if @group.nil?
51
+ raise Thor::Error, "Group '#{@group_name}' doesn't exist. Create it first with: rails generate feature_pack:add_group #{@group_name}"
52
+ end
53
+ end
24
54
 
25
- template './controller.rb.tt', @feature_dir.join('controller.rb')
26
- template './manifest.yaml.tt', @feature_dir.join('manifest.yaml')
27
- template './routes.rb.tt', @feature_dir.join('routes.rb')
28
- template './views/index.html.slim.tt', @feature_dir.join('views/index.html.slim')
29
- template './views/partials/_header.html.slim.tt', @feature_dir.join('views/partials/_header.html.slim')
30
- template './views/partials/_footer.html.slim.tt', @feature_dir.join('views/partials/_footer.html.slim')
31
- template './doc/readme.md.tt', @feature_dir.join('doc/readme.md')
32
- create_file @feature_dir.join('queries', '.gitkeep')
55
+ def check_feature_existence!
56
+ if FeaturePack.feature(@group_name.to_sym, @feature_name.to_sym).present?
57
+ raise Thor::Error, "Feature '#{@feature_name}' already exists in group '#{@group_name}'"
58
+ end
59
+ end
60
+
61
+ def generate_feature_id
62
+ # Generate a unique ID based on timestamp
63
+ # Format: YYMMDD (can't contain underscores)
64
+ Time.now.strftime('%y%m%d')
65
+ end
66
+
67
+ def create_feature_files
68
+ template './controller.rb.tt', @feature_dir.join('controller.rb')
69
+ template './manifest.yaml.tt', @feature_dir.join('manifest.yaml')
70
+ template './routes.rb.tt', @feature_dir.join('routes.rb')
71
+ template './views/index.html.slim.tt', @feature_dir.join('views/index.html.slim')
72
+ template './views/partials/_header.html.slim.tt', @feature_dir.join('views/partials/_header.html.slim')
73
+ template './views/partials/_footer.html.slim.tt', @feature_dir.join('views/partials/_footer.html.slim')
74
+ template './doc/readme.md.tt', @feature_dir.join('doc/readme.md')
75
+
76
+ # Create directories
77
+ empty_directory @feature_dir.join('queries')
78
+ empty_directory @feature_dir.join('javascript')
79
+ end
33
80
  end
34
81
  end
@@ -1,3 +1,30 @@
1
- class FeaturePack::<%= @group_class_name %>::<%= @feature_class_name %>Controller < FeaturePack::Controller
2
- # index defined in parent controller
1
+ # Feature controller for <%= @feature_class_name %>
2
+ class FeaturePack::<%= @group_class_name %>::<%= @feature_class_name %>Controller < FeaturePack::<%= @group_class_name %>Controller
3
+ # The 'index' action is already defined in the parent controller
4
+ # Add your feature-specific actions and logic here
5
+
6
+ # Example actions:
7
+ # def show
8
+ # @item = find_item(params[:id])
9
+ # end
10
+
11
+ # def new
12
+ # @item = Item.new
13
+ # end
14
+
15
+ # def create
16
+ # @item = Item.new(item_params)
17
+ # if @item.save
18
+ # redirect_to feature_pack_path(:<%= @group_name %>, :<%= @feature_name %>)
19
+ # else
20
+ # render :new
21
+ # end
22
+ # end
23
+
24
+ private
25
+
26
+ # Add feature-specific private methods here
27
+ # def item_params
28
+ # params.require(:item).permit(:name, :description)
29
+ # end
3
30
  end
@@ -1,16 +1,17 @@
1
- :name: Feature Friendly Name
2
- :description:
3
- :type: :alert
4
- :url: <%= @feature_name.gsub('_', '-') %>
5
- :version: 1.0.0
6
- #const_aliases:
7
- # - :params_class: 'Processor::Params'
8
- :access_policy:
9
- :admin: '*'
10
- :coordenacao_aps: '*'
11
- :coordenacao_atividade_fisica: '*'
12
- :coordenacao_saude_bucal:
13
- - :lista_equipes_saude_bucal
14
- :professional:
15
- - :whatsapp_text_alert
16
- - :whatsapp_protected_pdf
1
+ # Feature manifest configuration
2
+ name: <%= @feature_class_name.humanize %>
3
+ description: Description of <%= @feature_name.humanize %> feature
4
+ url: /<%= @feature_name.gsub('_', '-') %>
5
+ version: 1.0.0
6
+
7
+ # Optional: Define constant aliases for this feature
8
+ # const_aliases:
9
+ # - service: <%= @feature_class_name %>Service
10
+ # - repository: <%= @feature_class_name %>Repository
11
+
12
+ # Optional: Define access policies
13
+ # access_policy:
14
+ # admin: '*'
15
+ # user:
16
+ # - index
17
+ # - show
@@ -1,29 +1,66 @@
1
1
  require 'rails/generators/base'
2
2
  require 'rails/generators/named_base'
3
3
 
4
- class FeaturePack::AddGroupGenerator < Rails::Generators::NamedBase
5
- desc 'Adds a new Feature Group'
6
- source_root File.expand_path('templates', __dir__)
7
-
8
- argument :name, type: :string, required: true, desc: 'The name (sneak case) of the group to add'
9
-
10
- def create_feature_group
11
- raise "Group name couldn't have '/'" if name.include?('/')
12
- raise "Group '#{name}' already exists" if FeaturePack.group(name.to_sym).present?
13
-
14
- @class_name = name.camelcase
15
-
16
- # id can't contain underline '_'
17
- group_id = name.gsub('_', '-') + '-' + '999'
18
- group_dir = FeaturePack.features_path.join("group_#{group_id}_#{name}")
19
-
20
- template './_group_space/controller.rb.tt', group_dir.join('_group_space', 'controller.rb')
21
- template './_group_space/manifest.yaml.tt', group_dir.join('_group_space', 'manifest.yaml')
22
- template './_group_space/routes.rb.tt', group_dir.join('_group_space', 'routes.rb')
23
- template './_group_space/views/index.html.slim.tt', group_dir.join('_group_space', 'views/index.html.slim')
24
- template './_group_space/views/partials/_header.html.slim.tt',
25
- group_dir.join('_group_space', 'views/partials/_header.html.slim')
26
- template './_group_space/views/partials/_footer.html.slim.tt',
27
- group_dir.join('_group_space', 'views/partials/_footer.html.slim')
4
+ module FeaturePack
5
+ # Generator for creating new feature groups
6
+ # Usage: rails generate feature_pack:add_group GROUP_NAME
7
+ class AddGroupGenerator < Rails::Generators::NamedBase
8
+ desc 'Creates a new Feature Group with the standard directory structure'
9
+ source_root File.expand_path('templates', __dir__)
10
+
11
+ argument :name, type: :string, required: true, desc: 'The name (snake_case) of the group to add'
12
+
13
+ def create_feature_group
14
+ validate_group_name!
15
+ check_group_existence!
16
+
17
+ @class_name = name.camelcase
18
+ @group_id = generate_group_id
19
+
20
+ group_dir = FeaturePack.features_path.join("group_#{@group_id}_#{name}")
21
+
22
+ create_group_files(group_dir)
23
+
24
+ say "Group '#{name}' created successfully!", :green
25
+ say "Location: #{group_dir}", :green
26
+ end
27
+
28
+ private
29
+
30
+ def validate_group_name!
31
+ if name.include?('/')
32
+ raise Thor::Error, "Group name cannot contain '/'. Use snake_case format."
33
+ end
34
+
35
+ unless name.match?(/^[a-z_]+$/)
36
+ raise Thor::Error, "Group name must be in snake_case format (lowercase letters and underscores only)."
37
+ end
38
+ end
39
+
40
+ def check_group_existence!
41
+ if FeaturePack.group(name.to_sym).present?
42
+ raise Thor::Error, "Group '#{name}' already exists"
43
+ end
44
+ end
45
+
46
+ def generate_group_id
47
+ # Generate a unique ID based on timestamp
48
+ # Format: YYMMDD (can't contain underscores)
49
+ Time.now.strftime('%y%m%d')
50
+ end
51
+
52
+ def create_group_files(group_dir)
53
+ template './_group_space/controller.rb.tt', group_dir.join('_group_space', 'controller.rb')
54
+ template './_group_space/manifest.yaml.tt', group_dir.join('_group_space', 'manifest.yaml')
55
+ template './_group_space/routes.rb.tt', group_dir.join('_group_space', 'routes.rb')
56
+ template './_group_space/views/index.html.slim.tt', group_dir.join('_group_space', 'views/index.html.slim')
57
+ template './_group_space/views/partials/_header.html.slim.tt',
58
+ group_dir.join('_group_space', 'views/partials/_header.html.slim')
59
+ template './_group_space/views/partials/_footer.html.slim.tt',
60
+ group_dir.join('_group_space', 'views/partials/_footer.html.slim')
61
+
62
+ # Create javascript directory
63
+ empty_directory group_dir.join('_group_space', 'javascript')
64
+ end
28
65
  end
29
66
  end
@@ -1,3 +1,13 @@
1
- class FeaturePack::<%= class_name %>Controller < FeaturePack::GroupController
2
- # 'index' already defined in RootGroupController
1
+ # Group controller for <%= @class_name %>
2
+ class FeaturePack::<%= @class_name %>Controller < FeaturePack::GroupController
3
+ # The 'index' action is already defined in the parent GroupController
4
+ # Add any group-wide functionality here that will be inherited by all features
5
+
6
+ # Example:
7
+ # before_action :authenticate_user!
8
+ # before_action :check_permissions
9
+
10
+ private
11
+
12
+ # Add shared private methods for this group here
3
13
  end
@@ -1,2 +1,8 @@
1
- name: Group Friendly Name Here
2
- url: <%= name.gsub('_', '-') %>
1
+ # Group manifest configuration
2
+ name: <%= @class_name.humanize %>
3
+ url: /<%= name.gsub('_', '-') %>
4
+
5
+ # Optional: Define constant aliases for this group
6
+ # const_aliases:
7
+ # - model_name: ModelClass
8
+ # - service_name: ServiceClass
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: feature_pack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gedean Dias
@@ -39,7 +39,6 @@ extensions: []
39
39
  extra_rdoc_files: []
40
40
  files:
41
41
  - README.md
42
- - doc/changelog.md
43
42
  - doc/feature_pack.md
44
43
  - lib/feature_pack.rb
45
44
  - lib/feature_pack/api/controller.rb
@@ -86,7 +85,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
86
85
  - !ruby/object:Gem::Version
87
86
  version: '0'
88
87
  requirements: []
89
- rubygems_version: 3.6.3
88
+ rubygems_version: 3.6.9
90
89
  specification_version: 4
91
90
  summary: A different approach to organizing Rails app features.
92
91
  test_files: []
data/doc/changelog.md DELETED
@@ -1,2 +0,0 @@
1
- ## [0.8.0] - 2025-02-09
2
- - Melhoria no gemspec: uso de variável 'spec', resumo aprimorado, descrição formatada com HEREDOC, substituição de Dir[] por Dir.glob e adição de metadata.