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 +7 -0
- data/.rspec +4 -0
- data/.rubocop.yml +27 -0
- data/.ruby-version +1 -0
- data/.simplecov +41 -0
- data/LICENSE.txt +21 -0
- data/README.md +271 -0
- data/Rakefile +8 -0
- data/lib/gbc/trestle/menu_helper.rb +76 -0
- data/lib/gbc_trestle_modifier/version.rb +5 -0
- data/lib/gbc_trestle_modifier.rb +15 -0
- data/lib/generators/gbc/trestle/resource_generator.rb +137 -0
- data/lib/generators/gbc/trestle/templates/template_admin.rb.erb +42 -0
- data/lib/generators/gbc/trestle/templates/template_collection.rb.erb +14 -0
- data/lib/generators/gbc/trestle/templates/template_controller.rb.erb +12 -0
- data/lib/generators/gbc/trestle/templates/template_form.rb.erb +18 -0
- data/lib/generators/gbc/trestle/templates/template_routes.rb.erb +15 -0
- data/lib/generators/gbc/trestle/templates/template_scopes.rb.erb +15 -0
- data/lib/generators/gbc/trestle/templates/template_search.rb.erb +24 -0
- data/lib/generators/gbc/trestle/templates/template_table.rb.erb +49 -0
- data/railtie.rb +10 -0
- data/sig/gbc_trestle_resource_generator.rbs +4 -0
- metadata +91 -0
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
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,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,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,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
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: []
|