gbc_trestle_modifier 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: fb8167df866980d3daff08fecbf94507b43b6b516cf58044ad638206a281e27c
4
+ data.tar.gz: 83616a2e73528ce8f979d14ec9f7252b1da6b5a4061dbd84c1bcba0290f2f566
5
+ SHA512:
6
+ metadata.gz: cf93c14cb231e399359c33ee84e0e2afae0e2486577f48da4f913ee79930dfd0e58e18974550c35f417bb22610183886dd09957461363d6e0c0766753780a67b
7
+ data.tar.gz: 776e3ea1d96b761877e10674510a32434feab5e28a4cee7695617c78902bda7725066759320d98cbadcb6a1b68e6e4c73ca3521daffe61c54ec7739de2bbcb91
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ --format documentation
2
+ --color
3
+ --order rand
4
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,27 @@
1
+ require:
2
+ - rubocop-rspec
3
+ AllCops:
4
+ TargetRubyVersion: 3.0
5
+ SuggestExtensions: false
6
+ NewCops: disable
7
+
8
+ Style/StringLiterals:
9
+ EnforcedStyle: double_quotes
10
+
11
+ Style/StringLiteralsInInterpolation:
12
+ EnforcedStyle: double_quotes
13
+
14
+ Style/Documentation:
15
+ Enabled: false
16
+
17
+ Style/FrozenStringLiteralComment:
18
+ Enabled: false
19
+
20
+ RSpec/VerifiedDoubles:
21
+ Enabled: false
22
+
23
+ RSpec/ExampleLength:
24
+ Max: 35
25
+
26
+ RSpec/MultipleExpectations:
27
+ Enabled: false
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-3.3.3
data/.simplecov ADDED
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ # First require the LCOV formatter
4
+ require "simplecov-lcov"
5
+
6
+ # Configure SimpleCov LCOV formatter
7
+ SimpleCov::Formatter::LcovFormatter.config do |c|
8
+ c.report_with_single_file = true
9
+ c.single_report_path = "coverage/lcov.info"
10
+ c.lcov_file_name = "lcov.info"
11
+ c.output_directory = "coverage"
12
+ end
13
+
14
+ # Print a message to confirm .simplecov is loaded
15
+ puts "Loading .simplecov configuration file..."
16
+
17
+ # Start SimpleCov with appropriate configuration
18
+ # Note: Use 'rails' preset only if this is a Rails gem, otherwise use default
19
+ SimpleCov.start do
20
+ # Don't get coverage on the test files themselves
21
+ add_filter "/spec/"
22
+
23
+ # You can add additional filters here if needed
24
+ # add_filter '/config/'
25
+
26
+ # You can also add groups
27
+ add_group "Lib", "lib"
28
+
29
+ # Enable branch coverage
30
+ enable_coverage :branch
31
+
32
+ # Set minimum coverage thresholds (adjust as needed)
33
+ minimum_coverage 90
34
+ minimum_coverage_by_file 80
35
+
36
+ # Use LCOV formatter for CI integration
37
+ formatter SimpleCov::Formatter::MultiFormatter.new([
38
+ SimpleCov::Formatter::HTMLFormatter,
39
+ SimpleCov::Formatter::LcovFormatter
40
+ ])
41
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Gregory Brown
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,271 @@
1
+ # GbcTrestleModifier
2
+
3
+ ## Disclaimer
4
+
5
+ Before you start using this generator, a disclaimer.
6
+
7
+ This is an extremely opinionated way of doing things in Trestle. It is the way I organize my projects using Trestle because I find it easier to work with.
8
+
9
+ I plan on adding new features to it over time, but for now, I am not planning on major updates or anything along those lines.
10
+
11
+ ## Introduction
12
+
13
+ [Trestle](https://trestle.io/) is a great tool for rapidly prototyping projects. I use it in loads of my projects. There are a couple of things that kind of tick me off, though:
14
+
15
+ ### Large resource files
16
+
17
+ Trestle resources tend to become large files in the `app/admin` folder due to the way they are written.
18
+ I find it hard to read/maintain them as one big file, so I split them up into smaller files and created a generator to ensure that they always follow a standard.
19
+
20
+ ### Complex menu administration
21
+
22
+ Another pet peeve is the menu handling. Handling menu items in each resource quickly becomes a nightmare. Ordering them requires a lot of manual work.
23
+
24
+ To keep things simpler, inspired by the work from the crowd at [WinterCMS](https://wintercms.com),
25
+ I created a `menu.yml` file that is used to manage the menu. I also created a helper that simplifies the placement of the menu.
26
+
27
+ ## Splitting up Resources
28
+
29
+ ### Using the generator
30
+
31
+ ```bash
32
+ rails generate gbc:trestle:resource my_resource [ModelName]
33
+ ```
34
+
35
+ You basically run this at the root of your Rails project and the generator will create all the necessary files for you to start customizing.
36
+
37
+ #### my_resource
38
+
39
+ This is the admin name. The convention is that you name it as the pluralized version of your model, so if you have a model called `Category`, you would name it `categories`.
40
+ This is the name of the Trestle resource.
41
+
42
+ #### ModelName - optional
43
+
44
+ If you are going to call your resource something else, like `manage_categories` (which I personally like to do), then you'll need to pass along the base model that this resource works with.
45
+
46
+ To illustrate:
47
+
48
+ ```bash
49
+ rails generate gbc:trestle:resource manage_categories Category
50
+ ```
51
+
52
+ This will generate a functioning resource called `manage_categories` that uses `Category` as its base model.
53
+
54
+ ### Basic understanding
55
+
56
+ To keep things easy to read in Admin, I create a folder with the resource name and create files for: Controller, Form, Table, and Routes.
57
+ Each file works pretty much the same way they do in a Trestle resource.
58
+
59
+ Where in a Trestle resource you'd have something like this:
60
+
61
+ ```ruby
62
+ Trestle.resource(:my_resource) do
63
+ # ...
64
+ table do
65
+ column :name
66
+ column :created_at, align: :center
67
+ actions
68
+ end
69
+ # ...
70
+ ```
71
+
72
+ Your `my_resource/table.rb` would have something like:
73
+
74
+ ```ruby
75
+ module TargetSites
76
+ class Table
77
+ def initialize(base)
78
+ @base = base
79
+ end
80
+
81
+ def render
82
+ @base.table do # <-- this is the table method for our resource
83
+ column :name
84
+ column :created_at, align: :center
85
+ actions
86
+ end
87
+ end
88
+ end
89
+ end
90
+ ```
91
+
92
+ Basically, everything you would do in your `table do` block in your resource, you now do in your `@base.table do` block inside your `my_resource/table.rb`.
93
+
94
+ ### Tables, Forms, and Routes
95
+
96
+ All follow the same pattern. Again, I am not planning on reinventing the way Trestle works; I am just making my life easier.
97
+ When you have complex tables, complex forms, and the need for several controllers and routes in a resource, I find working in separate files much easier.
98
+
99
+ ### Controllers - the exception
100
+
101
+ Controllers follow a slightly different model. Instead of instantiating a class, we just need to define our methods in the module.
102
+
103
+ Let's say I want to override the `index` method on my resource.
104
+
105
+ In a regular Trestle resource, we'd do:
106
+
107
+ ```ruby
108
+ controller do
109
+ def index
110
+ render json: 'Overwrite index for example'
111
+ end
112
+ end
113
+ ```
114
+
115
+ With the split-up files, you'd edit your `my_resource/controller.rb` like this:
116
+
117
+ ```ruby
118
+ module MyResource
119
+ module Controller
120
+ def index
121
+ render json: 'Overwrite index for example'
122
+ end
123
+ end
124
+ end
125
+ ```
126
+
127
+ ### How does this all work?
128
+
129
+ Basically, I inject into the resource file the loaders for each of these files.
130
+
131
+ ```ruby
132
+ # frozen_string_literal: true
133
+
134
+ Trestle.resource(:my_resource) do
135
+ menu do
136
+ item :my_resource, icon: "fa fa-star", badge: "FIX MENU.YML"
137
+ end
138
+
139
+ # All your configurations are in folder app/admin/my_resource
140
+ #
141
+ # DO NOT CHANGE THINGS FROM HERE ON
142
+ # BLOCK 1
143
+ resource_dir = File.expand_path("my_resource", __dir__)
144
+ components = %w[table form controller routes]
145
+
146
+ components.each do |component|
147
+ file_path = File.join(resource_dir, "#{component}.rb")
148
+ if File.exist?(file_path)
149
+ require_relative file_path
150
+ class_name_segment = component.camelize # Converts 'table' to 'Table'
151
+ full_class_name = "MyResource::#{class_name_segment}"
152
+
153
+ if (klass = full_class_name.safe_constantize) && klass.respond_to?(:new) && klass.instance_methods.include?(:render)
154
+ klass.new(self).render
155
+ end
156
+ end
157
+ end
158
+
159
+ # Call methods into controller
160
+ # BLOCK 2
161
+ controller do
162
+ include MyResource::Controller
163
+ end
164
+ end
165
+ ```
166
+
167
+ In **BLOCK 1** — which you should never need to edit:
168
+
169
+ We load the files from the folder.
170
+ If they have a `render` method (which they should), we call it, passing in our current resource — essentially injecting our table/form/controller/etc. functionality into our resource.
171
+
172
+ In **BLOCK 2** — we are including the methods into our controller definition.
173
+
174
+ ## Easy menu administration
175
+
176
+ ### The problem
177
+
178
+ Menus in Trestle are great, but they quickly become a nightmare if you are moving things around.
179
+ This is in part due to the atomic nature of how we declare them. In each resource, you specify which group, which position (and so on) this menu item must be in.
180
+
181
+ When you have dozens of menus, adding a new group or changing the order of a menu item requires you to painstakingly go through all the resources and adjust menu entries.
182
+
183
+ ### The solution
184
+
185
+ Rather than declaring the menus in each resource, I created a helper and a YAML file that allow you to centralize menu administration. (This was inspired by the work from the crowd at [WinterCMS](https://wintercms.org)).
186
+
187
+ #### The `menu.yml`
188
+
189
+ Central to menu administration — this is where you structure your menu.
190
+
191
+ ```yaml
192
+ group1:
193
+ label: 'Group 1'
194
+ priority: 1
195
+ items:
196
+ item1:
197
+ url: '/admin/admin1'
198
+ label: 'Group 1 - Menu Item 1'
199
+ icon: 'fa-speak'
200
+ priority: 1
201
+ item2:
202
+ url: '/admin/admin2'
203
+ label: 'Group 1 - Menu Item 2'
204
+ icon: 'fa-home'
205
+ priority: 2
206
+ target: '_blank'
207
+ badge:
208
+ text: 'Externo'
209
+ type: 'warning'
210
+ group2:
211
+ label: 'Group 2'
212
+ priority: 2
213
+ items:
214
+ item1:
215
+ url: '/admin/admin3'
216
+ label: 'Group 2 - Menu Item 1'
217
+ icon: 'fa-speak'
218
+ priority: 1
219
+ item2:
220
+ url: '/admin/admin4'
221
+ label: 'Group 2 - Menu Item 2'
222
+ icon: 'fa-home'
223
+ priority: 2
224
+ target: '_blank'
225
+ badge:
226
+ text: 'Externo'
227
+ type: 'warning'
228
+ ```
229
+
230
+ OK — I agree that looks complex, but it's pretty straightforward when you look at it.
231
+
232
+ - I have two groups: `group1` and `group2`
233
+ - Each group has two `items`
234
+ - Groups have a `priority` (which determines the order of the groups — lower comes first)
235
+ - Items also have a `priority` — this orders the items inside the group
236
+
237
+ The rest of the parameters are pretty much the same as the parameters you can pass into a Trestle menu and menu item.
238
+
239
+ #### The helper
240
+
241
+ Now instead of declaring your menu item directly in your resource, you just say what YAML item this resource points to:
242
+
243
+ ```ruby
244
+ menu do
245
+ Trestle::MenuHelper.new(
246
+ self,
247
+ "group1",
248
+ "item2"
249
+ ).render_menu
250
+ end
251
+ ```
252
+
253
+ This would render `item2` of `group1`.
254
+
255
+ ### What does this allow?
256
+
257
+ Well, now that you are no longer declaring positions and groups directly in your resources, most of your changes to menu ordering will happen in your `menu.yml` file.
258
+
259
+ For example, if you wish to make `item2` of `group1` the first item in the list, all you have to do is change the priorities in the YAML file.
260
+
261
+ ## In the future
262
+
263
+ I plan on adding more separation — splitting Scopes, Collections, and Search into their own files. I haven't yet, because most of my resources have small, simple declarations that are easy to manage.
264
+
265
+ ## Contributing
266
+
267
+ Bug reports and pull requests are welcome on GitHub at https://github.com/GregoryBrownConsultancy/gbc_trestle_modifier.
268
+
269
+ ## License
270
+
271
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rubocop/rake_task"
5
+
6
+ RuboCop::RakeTask.new
7
+
8
+ task default: :rubocop
@@ -0,0 +1,76 @@
1
+ module Gbc
2
+ module Trestle
3
+ class MenuHelper
4
+ def initialize(base, group, item)
5
+ @base = base
6
+ @group = group
7
+ @item = item
8
+ end
9
+
10
+ attr_accessor :base, :group, :item
11
+
12
+ def render_menu
13
+ config = load_config
14
+ group_obj = config[group]
15
+ item_obj = group_obj["items"][item]
16
+
17
+ base.item(
18
+ item, item_obj["url"],
19
+ priority: item_priority(group_obj, item_obj),
20
+ label: safe(item_obj["label"]), icon: icon(item_obj),
21
+ target: target(item_obj), badge: badge(item_obj),
22
+ group: safe(group_obj["label"])
23
+ )
24
+ end
25
+
26
+ private
27
+
28
+ def safe(text)
29
+ text.to_s.html_safe
30
+ end
31
+ def load_config
32
+ app_root = find_app_root
33
+ YAML.load_file(File.join(app_root, "app", "admin", "menu.yml"))
34
+ end
35
+
36
+ def item_priority(group_obj, item_obj)
37
+ ((group_obj["priority"].to_i || 0) * 100) + (item_obj["priority"].to_i || 1)
38
+ end
39
+
40
+ def target(item_obj)
41
+ item_obj["target"] || "_self"
42
+ end
43
+
44
+ def icon(item_obj)
45
+ "fa #{item_obj["icon"]}"
46
+ end
47
+
48
+ def badge(item_obj)
49
+ return "" unless item_obj["badge"]
50
+
51
+ { text: item_obj["badge"]["text"], class: "badge-#{item_obj["badge"]["type"]}" }
52
+ end
53
+
54
+ def find_app_root
55
+ # Option 1: Using Bundler.root (most common and reliable for Rails apps)
56
+ if defined?(Bundler) && Bundler.root
57
+ return Bundler.root.to_s
58
+ end
59
+
60
+ # Option 2: Fallback heuristic (less reliable, but can be used if Bundler.root isn't available)
61
+ # This attempts to find a common Rails file (like config/application.rb)
62
+ # by traversing up the directory tree from the current working directory.
63
+ current_dir = Dir.pwd
64
+ while current_dir != '/' && current_dir != File.dirname(current_dir)
65
+ if File.exist?(File.join(current_dir, 'config', 'application.rb')) ||
66
+ File.exist?(File.join(current_dir, 'Gemfile'))
67
+ return current_dir
68
+ end
69
+ current_dir = File.dirname(current_dir)
70
+ end
71
+
72
+ nil # Application root not found
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GbcTrestleResourceGenerator
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "gbc_trestle_modifier/version"
4
+ require "gbc/trestle/menu_helper"
5
+ require "rails/railtie"
6
+
7
+ # :nocov:
8
+ module GbcTrestleResourceGenerator
9
+ class Railtie < Rails::Railtie
10
+ generators do
11
+ require_relative "generators/gbc/trestle/resource_generator"
12
+ end
13
+ end
14
+ end
15
+ # :nocov:
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require_relative "model_inspector"
5
+ module Gbc
6
+ module Trestle
7
+ class ResourceGenerator < Rails::Generators::Base
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ argument :name, type: :string,
11
+ desc: "The name for the Trestle admin resource (e.g., Product, UserGroup)."
12
+
13
+ argument :model, type: :string, required: false,
14
+ desc: "The associated model name (optional, e.g., Product, Item)."
15
+
16
+ # Description displayed when running `rails generate custom:trestle --help`.
17
+ desc "Generates a Trestle admin folder and files for a given resource."
18
+ def start
19
+ say_status "building", "Building new Trestle resource"
20
+ end
21
+
22
+ def info1
23
+ say_status "building", "Building supporting files"
24
+ end
25
+
26
+ def create_admin_folder
27
+ # `empty_directory` ensures the directory exists and is empty if it was there before.
28
+ empty_directory admin_folder_path
29
+ # `say_status` provides feedback to the user in the console.
30
+ end
31
+
32
+ def create_table_template
33
+ # `copy_file` copies a file from the source_root to the destination path.
34
+ # No ERB processing is done here.
35
+ if model.present?
36
+ puts "Info: Attempting to fetch attributes for model: #{model}"
37
+ model_class = Gbc::ModelInspector.find_active_record_model(model)
38
+ puts "Info: Found model class: #{model_class}"
39
+ if model_class
40
+ puts "Info: Attempting to fetch attributes for model: #{model}"
41
+ @model_database_attributes = Gbc::ModelInspector.get_model_database_attributes(model_class)
42
+ end
43
+ end
44
+ template "template_table.rb.erb", "#{admin_folder_path}/table.rb"
45
+ end
46
+
47
+ def create_form_template
48
+ # `copy_file` copies a file from the source_root to the destination path.
49
+ # No ERB processing is done here.
50
+ template "template_form.rb.erb", "#{admin_folder_path}/form.rb"
51
+ end
52
+
53
+ def create_routes_template
54
+ # `copy_file` copies a file from the source_root to the destination path.
55
+ # No ERB processing is done here.
56
+ template "template_routes.rb.erb", "#{admin_folder_path}/routes.rb"
57
+ end
58
+
59
+ def create_collection_template
60
+ # `copy_file` copies a file from the source_root to the destination path.
61
+ # No ERB processing is done here.
62
+ template "template_collection.rb.erb", "#{admin_folder_path}/collection.rb"
63
+ end
64
+
65
+ def create_scopes_template
66
+ # `copy_file` copies a file from the source_root to the destination path.
67
+ # No ERB processing is done here.
68
+ template "template_scopes.rb.erb", "#{admin_folder_path}/scopes.rb"
69
+ end
70
+
71
+ def create_search_template
72
+ # `copy_file` copies a file from the source_root to the destination path.
73
+ # No ERB processing is done here.
74
+ template "template_search.rb.erb", "#{admin_folder_path}/search.rb"
75
+ end
76
+
77
+ def create_controller_template
78
+ # `copy_file` copies a file from the source_root to the destination path.
79
+ # No ERB processing is done here.
80
+ template "template_controller.rb.erb", "#{admin_folder_path}/controller.rb"
81
+ end
82
+
83
+ def info2
84
+ say_status "Finishing", "Creating the admin file to tie things up"
85
+ end
86
+
87
+ # Process and copy the main admin template file.
88
+ def process_admin_template
89
+ # `template` processes an ERB file and copies the result.
90
+ # Variables defined in this generator class (like `file_name`, `model_name_snake_cased`)
91
+ # are accessible within the ERB template.
92
+ template "template_admin.rb.erb", "#{admin_root_path}/#{file_name}_admin.rb"
93
+ end
94
+
95
+ private
96
+
97
+ # --- Helper Methods ---
98
+
99
+ # Determines the snake_cased name of the resource, used for folder and file names.
100
+ # Example: "Product" -> "product", "UserGroup" -> "user_group"
101
+ def file_name
102
+ name.underscore
103
+ end
104
+
105
+ def file_name_classified
106
+ name.classify
107
+ end
108
+
109
+ def admin_root_path
110
+ "app/admin"
111
+ end
112
+
113
+ # Constructs the full path for the admin resource's folder.
114
+ # Example: "app/admin/product"
115
+ def admin_folder_path
116
+ "#{admin_root_path}/#{file_name}"
117
+ end
118
+
119
+ # Returns the snake_cased version of the provided model name, if present.
120
+ # Example: "Order" -> "order", nil -> nil
121
+ def model_name_snake_cased
122
+ model.present? ? model.underscore : nil
123
+ end
124
+
125
+ # Returns the classified (camel-cased) version of the provided model name, if present.
126
+ # Used for the `model` option in Trestle.resource.
127
+ # Example: "order" -> "Order", nil -> nil
128
+ def model_name_classified
129
+ model.present? ? model.classify : nil
130
+ end
131
+
132
+ def model_definition
133
+ model.present? ? ", model: #{model_name_classified}" : ""
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal
2
+
3
+ Trestle.resource(:<%= file_name%><%= model_definition %>) do
4
+ menu do
5
+ # Look at menu.yml to register new menu items
6
+ # Once you have setup a menu item in the yaml:
7
+ # REMOVE THIS LINE
8
+ item :<%= file_name %>, icon: "fa fa-star", badge: "FIX MENU.YML"
9
+
10
+ # and uncomment this with the correct values
11
+ #
12
+ # Trestle::MenuHelper.new(
13
+ # self,
14
+ # "GROUP_IN_YAML",
15
+ # "_ENTRY_IN_YAML"
16
+ # ).render_menu
17
+ end
18
+
19
+ # All your configurations are in folder <%= admin_folder_path %>
20
+ #
21
+ # DO NOT CHANGE THINGS FROM HERE ON
22
+ resource_dir = File.expand_path("<%= file_name %>", __dir__)
23
+ components = %w[table form controller collection scopes search routes]
24
+
25
+ components.each do |component|
26
+ file_path = File.join(resource_dir, "#{component}.rb")
27
+ if File.exist?(file_path)
28
+ require_relative file_path
29
+ class_name_segment = component.camelize # Converts 'table' to 'Table'
30
+ full_class_name = "<%= file_name_classified %>::#{class_name_segment}"
31
+
32
+ if (klass = full_class_name.safe_constantize) && klass.respond_to?(:new) && klass.instance_methods.include?(:render)
33
+ klass.new(self).render
34
+ end
35
+
36
+ end
37
+ end
38
+ # call methods into controller
39
+ controller do
40
+ include <%= file_name_classified %>::Controller
41
+ end
42
+ end
@@ -0,0 +1,14 @@
1
+ module <%= file_name_classified %>
2
+ class Collection
3
+ def initialize(base)
4
+ @base = base
5
+ end
6
+ def render
7
+ # @base.collection do
8
+ # # Example of selecting distinct records
9
+ # # sorted by created_at
10
+ # model.distinct.order(created_at: :desc)
11
+ # end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,12 @@
1
+ module <%= file_name_classified %>
2
+ module Controller
3
+ # Add your controller methods here
4
+ # def index
5
+ # render json: 'Overwrite index for example'
6
+ # end
7
+ #
8
+ # def ajax_process
9
+ # # ... do something ajaxy
10
+ # end
11
+ end
12
+ end
@@ -0,0 +1,18 @@
1
+ module <%= file_name_classified %>
2
+ class Form
3
+ def initialize(base)
4
+ @base = base
5
+ end
6
+ def render
7
+ @base.form do
8
+ col(sm: 12) { "Edit file <%= admin_folder_path %>/form.rb" }
9
+ # Fill out you form normally here
10
+ #
11
+ # col(sm: 12) do
12
+ # text_field(:name)
13
+ # end
14
+ # ...
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ module <%= file_name_classified %>
2
+ class Routes
3
+ def initialize(base)
4
+ @base = base
5
+ end
6
+ def render
7
+ @base.routes do
8
+ # Fill out you table normally here
9
+ #
10
+ # post :some_endpoint, on: :collection
11
+ # get :another_endpoint, on: :collection, params: { document: :string }
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module <%= file_name_classified %>
2
+ class Scopes
3
+ def initialize(base)
4
+ @base = base
5
+ end
6
+ def render
7
+ @base.scopes do
8
+ # Fill out you table normally here
9
+ #
10
+ # scope :all, label: 'Show All', default: true
11
+ # scope :expired, label: 'Show expired'
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,24 @@
1
+ module <%= file_name_classified %>
2
+ class Search
3
+ def initialize(base)
4
+ @base = base
5
+ end
6
+
7
+ def render()
8
+ # Search example
9
+ #
10
+ # @base.search do |query|
11
+ # if query
12
+ # collection
13
+ # .where(
14
+ # %(table_name.slug ILIKE :query OR
15
+ # table_name.name ILIKE :query),
16
+ # query: "%#{query}%"
17
+ # )
18
+ # else
19
+ # collection
20
+ # end
21
+ # end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,49 @@
1
+ module <%= file_name_classified %>
2
+ class Table
3
+ def initialize(base)
4
+ @base = base
5
+ end
6
+ def render
7
+ default_table
8
+ # named_table
9
+ end
10
+
11
+ def default_table
12
+ @base.table do
13
+ column :TODO do |_x|
14
+ "Edit file <%= admin_folder_path %>%}/form.rb to adjust columns"
15
+ end
16
+ <% if @model_attributes.present? %>
17
+ # DYNAMICALLY CREATED!
18
+ <% @model_attributes.each do |name, type| %>
19
+ column <%= name%>
20
+ <% end %>
21
+ <% end %>
22
+ # Fill out you table normally here
23
+ #
24
+ # column :name
25
+ # column :email
26
+ # ...
27
+ # column :created_at, align: :center
28
+ # actions
29
+ end
30
+ end
31
+
32
+ # named tabled can be used in other resources
33
+ #
34
+ # def named_table
35
+ # @base.table :named_table, collection: collection do
36
+ # column :TODO do |_x|
37
+ # "Edit file <%= admin_folder_path %>%}/form.rb to adjust columns"
38
+ # end
39
+ # # Fill out you table normally here
40
+ # #
41
+ # # column :name
42
+ # # column :email
43
+ # # ...
44
+ # # column :created_at, align: :center
45
+ # # actions
46
+ # end
47
+ # end
48
+ end
49
+ end
data/railtie.rb ADDED
@@ -0,0 +1,10 @@
1
+ # lib/gbc_trestle_modifier/railtie.rb
2
+ require "rails/railtie"
3
+
4
+ module GbcTrestleResourceGenerator
5
+ class Railtie < Rails::Railtie
6
+ generators do
7
+ require_relative "../generators/gbc/gbc_trestle_modifier"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,4 @@
1
+ module GbcTrestleResourceGenerator
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gbc_trestle_modifier
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Gregory Brown
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-08-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: trestle
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: " Treste is a great tool for rapid prototyping projects. I use it
28
+ in loads\n of my projects. There are a couple of things that kind of tick me
29
+ off though;\n Trestle resources tend to become large files in the app/admin folder
30
+ due to the\n way they are written. I find it hard to read/maintain them as a
31
+ big file, so I \n split them up into smaller files and created a generator to
32
+ ensure that they \n always follow a standard. Another pet peeve is the menu handling.
33
+ Handling menu itmes\n in each resource quickly becomes a nightmare. Ordering
34
+ them requires a lot of \n manual work. To keep things simpler, inspired by the
35
+ work from the crowd at WinterCMS,\n I created a menu.yml file that is used to
36
+ manage the menu. I also created a helper that\n simplifies the placing of the
37
+ menu. \n"
38
+ email:
39
+ - greg@gregorybrown.com.br
40
+ executables: []
41
+ extensions: []
42
+ extra_rdoc_files: []
43
+ files:
44
+ - ".rspec"
45
+ - ".rubocop.yml"
46
+ - ".ruby-version"
47
+ - ".simplecov"
48
+ - LICENSE.txt
49
+ - README.md
50
+ - Rakefile
51
+ - lib/gbc/trestle/menu_helper.rb
52
+ - lib/gbc_trestle_modifier.rb
53
+ - lib/gbc_trestle_modifier/version.rb
54
+ - lib/generators/gbc/trestle/resource_generator.rb
55
+ - lib/generators/gbc/trestle/templates/template_admin.rb.erb
56
+ - lib/generators/gbc/trestle/templates/template_collection.rb.erb
57
+ - lib/generators/gbc/trestle/templates/template_controller.rb.erb
58
+ - lib/generators/gbc/trestle/templates/template_form.rb.erb
59
+ - lib/generators/gbc/trestle/templates/template_routes.rb.erb
60
+ - lib/generators/gbc/trestle/templates/template_scopes.rb.erb
61
+ - lib/generators/gbc/trestle/templates/template_search.rb.erb
62
+ - lib/generators/gbc/trestle/templates/template_table.rb.erb
63
+ - railtie.rb
64
+ - sig/gbc_trestle_resource_generator.rbs
65
+ homepage: https://github.com/GregoryBrownConsultancy/gbc_trestle_modifier
66
+ licenses:
67
+ - MIT
68
+ metadata:
69
+ homepage_uri: https://github.com/GregoryBrownConsultancy/gbc_trestle_modifier
70
+ rubygems_mfa_required: 'true'
71
+ source_code_uri: https://github.com/GregoryBrownConsultancy/gbc_trestle_modifier
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: 3.0.0
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ requirements: []
87
+ rubygems_version: 3.5.11
88
+ signing_key:
89
+ specification_version: 4
90
+ summary: Adds a custom generators and better menu handling for Trestle
91
+ test_files: []