hot-glue 0.6.21.1 → 0.6.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +162 -3
- data/app/helpers/hot_glue/controller_helper.rb +45 -23
- data/lib/generators/hot_glue/fields/float_field.rb +9 -2
- data/lib/generators/hot_glue/fields/time_field.rb +1 -1
- data/lib/generators/hot_glue/layout/builder.rb +6 -3
- data/lib/generators/hot_glue/layout_strategy/bootstrap.rb +2 -0
- data/lib/generators/hot_glue/markup_templates/erb.rb +2 -3
- data/lib/generators/hot_glue/scaffold_generator.rb +76 -28
- data/lib/generators/hot_glue/templates/controller.rb.erb +7 -7
- data/lib/generators/hot_glue/templates/erb/_list.erb +8 -5
- data/lib/generators/hot_glue/templates/erb/_show.erb +1 -1
- data/lib/generators/hot_glue/templates/erb/edit.erb +12 -11
- data/lib/hot-glue.rb +11 -18
- data/lib/hotglue/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a92b0ee848c36d585691eefae1df23634e0814ad7d8e8b96a2d9dd7605c60d7
|
4
|
+
data.tar.gz: ecbd2c67fb4a1a3665870178da7c92b6a10a11d336a8b953fed1ca49042ffb18
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 045ef49863d103f17c889b6b0e8aa7a516faacb4b2b27a2b9bc5e6018d5113f89bfab29065d503bb277bb499afebcbf0592e57a3ef0ee1db61ca27ad2c5baa6a
|
7
|
+
data.tar.gz: 8b6eb8ad573fa9d58c46018c46c055b4af97bd6ec3f7eb597d572ec1195346eb9c6504a0ea0423fa44e6b52dae4e02194ff14bf554b22d566124b5671e7b461d
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -572,6 +572,37 @@ Then, finally the @charge will be loaded
|
|
572
572
|
This is "starfish access control" or "poor man's access control." It works when the current user has several things they can manage, and by extension can manage children of those things.
|
573
573
|
|
574
574
|
|
575
|
+
#### Example #3: Polymorphic Nesting
|
576
|
+
Use `(` and `)` to specify a non-standard upwards relationship from the child, as in the case of a polymorphic belongs_to
|
577
|
+
|
578
|
+
```
|
579
|
+
class Blast
|
580
|
+
has_many :rules, as: :ruleable
|
581
|
+
end
|
582
|
+
|
583
|
+
class Rule
|
584
|
+
# ruleable_id
|
585
|
+
# ruleable_type
|
586
|
+
|
587
|
+
belongs_to :ruleable, polymorphic: true
|
588
|
+
end
|
589
|
+
```
|
590
|
+
|
591
|
+
routes.rb
|
592
|
+
```
|
593
|
+
resources :blasts do
|
594
|
+
resources :rules
|
595
|
+
end
|
596
|
+
```
|
597
|
+
|
598
|
+
`rails generate hot_glue:scaffold Blast --downnest=rules`
|
599
|
+
|
600
|
+
`rails generate hot_glue:scaffold Rules --nested='blast(ruleable)'`
|
601
|
+
|
602
|
+
Notices the relationship from the parent to child is `rules` but from the child to parent, it is `ruleable` instead of `blast`
|
603
|
+
|
604
|
+
|
605
|
+
|
575
606
|
### `--auth=`
|
576
607
|
|
577
608
|
By default, it will be assumed you have a `current_user` for your user authentication. This will be treated as the "authentication root" for the "poor man's auth" explained above.
|
@@ -1277,6 +1308,45 @@ Can now be created with more space (wider) by adding a `+` to the end of the dow
|
|
1277
1308
|
The 'Abcs' portal will display as 5 bootstrap columns instead of the typical 4. (You may use multiple ++ to keep making it wider but the inverse with minus is not supported
|
1278
1309
|
|
1279
1310
|
|
1311
|
+
Polymorphic Downnesting
|
1312
|
+
|
1313
|
+
Here, a `Blast` `has_many :rules, as: :ruleable`
|
1314
|
+
|
1315
|
+
The child object is named `Rule` but it can belong to a Blast or an Agent. (Agent also has a similar has_many for Rules)
|
1316
|
+
|
1317
|
+
`belongs_to :ruleable, polymorphic: true`
|
1318
|
+
|
1319
|
+
We build the blast & agent controllers like so:
|
1320
|
+
|
1321
|
+
bin/rails generate hot_glue:scaffold Blast --downnest='blast_rules(rules)'
|
1322
|
+
bin/rails generate hot_glue:scaffold Agent --downnest='agent_rules(rules)'
|
1323
|
+
|
1324
|
+
Notice that the relationship name is `rules` (not blast_rules), so what goes before the parenthesis is the controller name (with prefix)
|
1325
|
+
What goes inside the controller name is the real relationship name.
|
1326
|
+
|
1327
|
+
For the children, we can't build one controller for the Rule, instead we build one for the `AgentRules` and another for the `BlastRules`
|
1328
|
+
|
1329
|
+
bin/rails generate hot_glue:scaffold Rule --nested='blast(ruleable)' --controller-prefix='Blast'
|
1330
|
+
bin/rails generate hot_glue:scaffold Rule --nested='agent(ruleable)' --controller-prefix='Agent'
|
1331
|
+
|
1332
|
+
(I realize building one child controller for each type of polymorph is tedius, but this is the best solution I could come up with.)
|
1333
|
+
|
1334
|
+
As these are children, what goes into the `--netsed` setting inside the parentheses is the polymorphic name specified by `as:` when declaring the `belongs_to`
|
1335
|
+
|
1336
|
+
routes.rb
|
1337
|
+
|
1338
|
+
```
|
1339
|
+
resources :agents do
|
1340
|
+
resources :agent_rules
|
1341
|
+
end
|
1342
|
+
|
1343
|
+
resources :blasts do
|
1344
|
+
resources :blast_rules
|
1345
|
+
end
|
1346
|
+
```
|
1347
|
+
|
1348
|
+
|
1349
|
+
|
1280
1350
|
### `--record-scope=`
|
1281
1351
|
|
1282
1352
|
Record scope allows you to apply a model based scope for the controller being generated.
|
@@ -1374,8 +1444,6 @@ called _after authorization_ but _before saving the new record_
|
|
1374
1444
|
(which creates the record, or fails validation).
|
1375
1445
|
Here you can do things like set default values, or modify the params before the record is saved.
|
1376
1446
|
|
1377
|
-
#### `--code-after-create=`
|
1378
|
-
is called after the record is saved (and thus has an id in the case of the create action).
|
1379
1447
|
|
1380
1448
|
#### `--code-before-update=`
|
1381
1449
|
is called in the `update` action _before_ it is saved.
|
@@ -1383,9 +1451,54 @@ is called in the `update` action _before_ it is saved.
|
|
1383
1451
|
#### `--code-after-update=`
|
1384
1452
|
is called in the `update` action _after_ it is saved.
|
1385
1453
|
|
1454
|
+
|
1455
|
+
#### `--code-after-create=`
|
1456
|
+
is called after a new record is saved (and thus has an id).
|
1457
|
+
|
1458
|
+
Here is where you will call operations that depend on the record having an id, like building child table records.
|
1459
|
+
|
1460
|
+
Notice that the next option is inserted in both `new` and `create`, making it the more suitable choice for setting default values.
|
1461
|
+
|
1462
|
+
|
1386
1463
|
#### `--code-after-new=`
|
1387
|
-
is called in the
|
1464
|
+
is called in both the `new` and `create` actions _directly after the .new() call_
|
1465
|
+
|
1466
|
+
This is a good place to set your created_by user id, like so
|
1467
|
+
|
1468
|
+
`--code-after-new='@email_template.created_by_user = current_user'`
|
1469
|
+
|
1470
|
+
|
1471
|
+
|
1472
|
+
```
|
1473
|
+
def new
|
1474
|
+
@email_template = EmailTemplate.new(crusade: crusade)
|
1475
|
+
@email_template.created_by_user = current_user // <--- YOUR CUSTOM CODE via --code-after-new
|
1476
|
+
|
1477
|
+
authorize @email_template
|
1478
|
+
@action = 'new'
|
1479
|
+
...
|
1480
|
+
```
|
1388
1481
|
|
1482
|
+
Notice that a separate hook for code-after-create is also available, but that happens after the save
|
1483
|
+
|
1484
|
+
Using both together `--code-after-new='@email_template.created_by_user = current_user' --code-after-create='@email_template.do_something'`
|
1485
|
+
|
1486
|
+
|
1487
|
+
|
1488
|
+
```
|
1489
|
+
def create
|
1490
|
+
...
|
1491
|
+
@email_template = EmailTemplate.new(modified_params)
|
1492
|
+
@email_template.created_by_user = current_user // <--- YOUR CUSTOM CODE via --code-after-new
|
1493
|
+
|
1494
|
+
authorize @email_template
|
1495
|
+
|
1496
|
+
if @email_template.save
|
1497
|
+
@email_template.do_something // <--- YOUR CUSTOM CODE via --code-after-create
|
1498
|
+
flash[:notice] = "Successfully created #{@email_template.name}"
|
1499
|
+
account.reload
|
1500
|
+
...
|
1501
|
+
```
|
1389
1502
|
|
1390
1503
|
|
1391
1504
|
|
@@ -2033,6 +2146,52 @@ These automatic pickups for partials are detected at build time. This means that
|
|
2033
2146
|
# VERSION HISTORY
|
2034
2147
|
|
2035
2148
|
|
2149
|
+
#### 2025-07-28 v0.6.22
|
2150
|
+
|
2151
|
+
`--phantom-create-params`
|
2152
|
+
These parameters get added in the strong parameters safelist for the create action
|
2153
|
+
|
2154
|
+
You'll probably wnat to use this along with --code-after-create to check that phanton param
|
2155
|
+
|
2156
|
+
TODO: you have to tap these away yourself
|
2157
|
+
TODO: can they be tapped away automatically if not using a factory
|
2158
|
+
|
2159
|
+
`--phantom-update-params`
|
2160
|
+
These parameters get added in the strong parameters safelist for the update action
|
2161
|
+
|
2162
|
+
`--controller-prefix`
|
2163
|
+
|
2164
|
+
Prefix the controller name, and the cooresponding route & path structure, with this prefix.
|
2165
|
+
For example, using `--controller-prefix='Open'` on a Document build will produce a controller
|
2166
|
+
|
2167
|
+
`OpenDocumentsController`
|
2168
|
+
|
2169
|
+
The controller will still treat the `Document` model as the thing it is building, just a different style of Document named with the prefix.
|
2170
|
+
|
2171
|
+
(To make this meaningful, you'll want to add a `--record-scope` or in some other way differentiate this controller based on its descriptive prefix)
|
2172
|
+
|
2173
|
+
• Polymorphic Downnesting
|
2174
|
+
|
2175
|
+
- `--nested` and `--downnest` can now both accept a param pass in parentheses `(`..`)` to use with polymorphism
|
2176
|
+
|
2177
|
+
See "Polymorphic downnesting" in the downnesting section for an example.
|
2178
|
+
|
2179
|
+
• Magic buttons no longer take up 2 bootstrap columns for each button
|
2180
|
+
|
2181
|
+
• Adds auto-disable to all Delete buttons; use with a `delete_able?` method on the model
|
2182
|
+
|
2183
|
+
• Removes more vestiges of optionalized nesting (which I had implemented 3 years ago!)
|
2184
|
+
|
2185
|
+
I no longer like optionalized nesting, and recommend against it.
|
2186
|
+
|
2187
|
+
Nesting should always be part of the structure, and every route should operate firmly in its nest path.
|
2188
|
+
|
2189
|
+
Use new controller-prefix to make on-off exceptions or with polymorphic children.
|
2190
|
+
|
2191
|
+
• Fixes for localized datetime & time inputs
|
2192
|
+
|
2193
|
+
|
2194
|
+
|
2036
2195
|
#### 2025-07-05 v0.6.21
|
2037
2196
|
•Now use new code insertion `--code-after-new` for code that happens directly after `.new()` call. use semicolon (`;`) to create linebreaks; no reason why the factories should insert flash messages
|
2038
2197
|
|
@@ -9,7 +9,7 @@ module HotGlue
|
|
9
9
|
current_timezone
|
10
10
|
|
11
11
|
args = args.merge({class: 'form-control',
|
12
|
-
|
12
|
+
type: 'datetime-local' })
|
13
13
|
|
14
14
|
if !value.nil?
|
15
15
|
args[:value] = date_to_current_timezone(value, current_timezone) + timezonize(current_timezone)
|
@@ -23,8 +23,8 @@ module HotGlue
|
|
23
23
|
def date_field_localized(form_object, field_name, value, **args)
|
24
24
|
|
25
25
|
form_object.text_field(field_name, args.merge({class: 'form-control',
|
26
|
-
|
27
|
-
|
26
|
+
type: 'date',
|
27
|
+
value: value }))
|
28
28
|
end
|
29
29
|
|
30
30
|
def time_field_localized(form_object, field_name, value, **args )
|
@@ -69,18 +69,23 @@ module HotGlue
|
|
69
69
|
end
|
70
70
|
|
71
71
|
def is_dst_now?
|
72
|
-
Time
|
73
|
-
|
74
|
-
|
72
|
+
ActiveSupport::TimeZone['Eastern Time (US & Canada)'].now.dst?
|
73
|
+
end
|
74
|
+
|
75
|
+
def format_timezone_offset(hour, minute)
|
76
|
+
sign = hour < 0 ? "-" : "+"
|
77
|
+
hour_abs = hour.abs.to_s.rjust(2, '0')
|
78
|
+
minute_str = minute.to_s.rjust(2, '0')
|
79
|
+
"#{sign}#{hour_abs}#{minute_str}"
|
75
80
|
end
|
76
81
|
|
77
82
|
def modify_date_inputs_on_params(modified_params, current_user_object = nil, field_list = {})
|
78
83
|
|
79
84
|
use_timezone = if current_user_object.try(:timezone)
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
85
|
+
(ActiveSupport::TimeZone[current_user_object.timezone])
|
86
|
+
else
|
87
|
+
Time.zone
|
88
|
+
end
|
84
89
|
|
85
90
|
|
86
91
|
uses_dst = (current_user_object.try(:locale_uses_dst)) || false
|
@@ -93,13 +98,28 @@ module HotGlue
|
|
93
98
|
field_list.include?(k.to_sym)
|
94
99
|
end
|
95
100
|
|
96
|
-
parsables = {
|
97
|
-
|
101
|
+
parsables = {
|
102
|
+
datetime: "%Y-%m-%d %H:%M %z",
|
103
|
+
time: "%H:%M %z"
|
104
|
+
}
|
105
|
+
|
98
106
|
|
99
107
|
if include_me && params[k].present?
|
100
108
|
if use_timezone
|
109
|
+
natural_offset = use_timezone.formatted_offset
|
110
|
+
hour = natural_offset.split(":").first.to_i
|
111
|
+
min = natural_offset.split(":").last.to_i
|
112
|
+
|
113
|
+
hour = hour + 1 if uses_dst && is_dst_now?
|
114
|
+
|
115
|
+
use_offset = format_timezone_offset(hour, min)
|
116
|
+
parse_date = "#{params[k].gsub("T", " ")} #{use_offset}"
|
117
|
+
|
118
|
+
|
119
|
+
Rails.logger.info("use_offset: #{use_offset}")
|
120
|
+
|
121
|
+
Rails.logger.info("parse_date: #{parse_date}")
|
101
122
|
|
102
|
-
parse_date = "#{params[k].gsub("T", " ")} #{use_timezone.formatted_offset}"
|
103
123
|
# note: as according to https://stackoverflow.com/questions/20111413/html5-datetime-local-control-how-to-hide-seconds
|
104
124
|
# there is no way to set the seconds to 00 in the datetime-local input field
|
105
125
|
# as I have implemented a "seconds don't matter" solution,
|
@@ -112,7 +132,12 @@ module HotGlue
|
|
112
132
|
else
|
113
133
|
parsed_time = Time.strptime(parse_date, parsables[field_list[k.to_sym]])
|
114
134
|
end
|
115
|
-
|
135
|
+
Rails.logger.info "parsed_time #{parsed_time}"
|
136
|
+
Rails.logger.info "Timezone: #{use_timezone.name}"
|
137
|
+
Rails.logger.info "Offset: #{use_timezone.formatted_offset}"
|
138
|
+
Rails.logger.info "DST? #{uses_dst} | is_dst_now? => #{is_dst_now?}"
|
139
|
+
Rails.logger.info "Final offset used: #{use_offset}"
|
140
|
+
|
116
141
|
params[k] = parsed_time
|
117
142
|
end
|
118
143
|
end
|
@@ -224,12 +249,12 @@ module HotGlue
|
|
224
249
|
when 'is_at_exactly'
|
225
250
|
["EXTRACT(HOUR FROM #{field}) = ?
|
226
251
|
AND EXTRACT(MINUTE FROM #{field}) = ? ", search_start.split(":")[0], search_start.split(":")[1]]
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
252
|
+
# when 'is_at_or_after'
|
253
|
+
# ["#{field} = ? OR #{field} > ?", search_start, search_start]
|
254
|
+
# when "is_before_or_at"
|
255
|
+
# ["#{field} = ? OR #{field} < ?", search_end, search_end]
|
256
|
+
# when "is_between"
|
257
|
+
# ["#{field} BETWEEN ? AND ?", search_start, search_end]
|
233
258
|
end
|
234
259
|
end
|
235
260
|
end
|
@@ -260,8 +285,5 @@ module HotGlue
|
|
260
285
|
|
261
286
|
private
|
262
287
|
|
263
|
-
# def server_timezone_offset # returns integer of hours to add/subtract from UTC
|
264
|
-
# Time.now.in_time_zone(Rails.application.config.time_zone).strftime("%z").to_i/100
|
265
|
-
# end
|
266
288
|
end
|
267
289
|
end
|
@@ -21,14 +21,21 @@ class FloatField < Field
|
|
21
21
|
# end
|
22
22
|
|
23
23
|
def search_field_output
|
24
|
-
""
|
24
|
+
" <div>" +
|
25
|
+
"\n <%= f.select 'q[0][#{name}_match]', options_for_select([['', ''], ['=', '='], " +
|
26
|
+
"\n ['≥', '≥'], ['>', '>'], " +
|
27
|
+
"\n ['≤', '≤'], ['<', '<']], @q[\'0\']['#{name}_match'] ), {} ," +
|
28
|
+
"\n { class: 'form-control match' } %>"+
|
29
|
+
"\n <%= f.text_field 'q[0][#{name}_search]', {value: @q[\'0\'][:#{name}_search], autocomplete: 'off', size: 4, class: 'form-control', type: 'number'} %>" +
|
30
|
+
"\n </div>"
|
25
31
|
end
|
26
32
|
|
27
33
|
|
28
34
|
def where_query_statement
|
35
|
+
".where(\"#{name} \#{#{name}_query }\")"
|
29
36
|
end
|
30
37
|
|
31
38
|
def load_all_query_statement
|
32
|
-
|
39
|
+
"#{name}_query = integer_query_constructor(@q['0'][:#{name}_match], @q['0'][:#{name}_search])"
|
33
40
|
end
|
34
41
|
end
|
@@ -5,7 +5,7 @@ class TimeField < Field
|
|
5
5
|
end
|
6
6
|
|
7
7
|
def form_field_output
|
8
|
-
"<%= time_field_localized(f, :#{name}, #{singular}.#{name}, label: '#{ name.to_s.humanize }') %>"
|
8
|
+
"<%= time_field_localized(f, :#{name}, #{singular}.#{name}&.in_time_zone(current_user.timezone)&.strftime('%H:%M'), label: '#{ name.to_s.humanize }') %>"
|
9
9
|
end
|
10
10
|
|
11
11
|
def line_field_output
|
@@ -60,16 +60,19 @@ module HotGlue
|
|
60
60
|
unless @big_edit
|
61
61
|
# how_many_downnest = downnest_object.size
|
62
62
|
if(!stacked_downnesting)
|
63
|
-
bootstrap_columns = bootstrap_columns - (downnest_object.
|
63
|
+
bootstrap_columns = bootstrap_columns - (downnest_object.size * 4)
|
64
64
|
else
|
65
65
|
bootstrap_columns = bootstrap_columns - 4
|
66
66
|
end
|
67
67
|
|
68
68
|
# downnest_children_width = []
|
69
|
-
|
70
|
-
|
69
|
+
|
70
|
+
|
71
|
+
@downnest_object.each do |child, data|
|
72
|
+
layout_object[:portals][child] = {size: data[:extra_size] + 4}
|
71
73
|
end
|
72
74
|
end
|
75
|
+
|
73
76
|
available_columns = (bootstrap_columns / bootstrap_column_width).floor
|
74
77
|
|
75
78
|
# when set to 2, turns the 12-column grid into a 6-column grid
|
@@ -34,10 +34,12 @@ class LayoutStrategy::Bootstrap < LayoutStrategy::Base
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def column_width
|
37
|
+
|
37
38
|
builder.layout_object[:columns][:size_each]
|
38
39
|
end
|
39
40
|
|
40
41
|
def downnest_portal_column_width(downnest)
|
42
|
+
|
41
43
|
"col-sm-#{ builder.layout_object[:portals][downnest][:size] }"
|
42
44
|
end
|
43
45
|
|
@@ -195,11 +195,11 @@ module HotGlue
|
|
195
195
|
end
|
196
196
|
|
197
197
|
|
198
|
-
the_output = add_spaces_each_line( "\n <
|
198
|
+
the_output = add_spaces_each_line( "\n <div #{@tinymce_stimulus_controller}class='<%= \"alert alert-danger\" if #{singular}.errors.details.keys.include?(:#{field_error_name}) %>' #{data_attr} >\n" +
|
199
199
|
add_spaces_each_line( (form_labels_position == 'before' ? (the_label || "") + "<br />\n" : "") +
|
200
200
|
+ field_result +
|
201
201
|
(form_labels_position == 'after' ? ( columns_map[col].newline_after_field? ? "<br />\n" : "") + (the_label || "") : "") , 4) +
|
202
|
-
"\n </
|
202
|
+
"\n </div>\n ", 2)
|
203
203
|
|
204
204
|
|
205
205
|
if hidden_create.include?(col.to_sym) || hidden_update.include?(col.to_sym)
|
@@ -244,7 +244,6 @@ module HotGlue
|
|
244
244
|
def all_line_fields(layout_strategy:,
|
245
245
|
perc_width:)
|
246
246
|
|
247
|
-
|
248
247
|
inline_list_labels = @inline_list_labels || 'omit'
|
249
248
|
columns = layout_object[:columns][:container]
|
250
249
|
|
@@ -30,7 +30,8 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
30
30
|
:self_auth, :namespace_value, :record_scope, :related_sets,
|
31
31
|
:search_clear_button, :search_autosearch, :include_object_names,
|
32
32
|
:stimmify, :stimmify_camel, :hidden_create, :hidden_update,
|
33
|
-
:invisible_create, :invisible_update
|
33
|
+
:invisible_create, :invisible_update, :phantom_create_params,
|
34
|
+
:phantom_update_params
|
34
35
|
# important: using an attr_accessor called :namespace indirectly causes a conflict with Rails class_name method
|
35
36
|
# so we use namespace_value instead
|
36
37
|
|
@@ -113,7 +114,9 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
113
114
|
class_option :new_button_position, type: :string, default: 'above'
|
114
115
|
class_option :downnest_shows_headings, type: :boolean, default: nil
|
115
116
|
class_option :stimmify, type: :string, default: nil
|
116
|
-
|
117
|
+
class_option :phantom_create_params, type: :string, default: nil
|
118
|
+
class_option :phantom_update_params, type: :string, default: nil
|
119
|
+
class_option :controller_prefix, type: :string, default: nil
|
117
120
|
|
118
121
|
# SEARCH OPTIONS
|
119
122
|
class_option :search, default: nil # set or predicate
|
@@ -170,7 +173,7 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
170
173
|
get_default_from_config(key: :bootstrap_column_width) || 2
|
171
174
|
|
172
175
|
|
173
|
-
|
176
|
+
@controller_prefix = options['controller_prefix']
|
174
177
|
@default_boolean_display = get_default_from_config(key: :default_boolean_display)
|
175
178
|
if options['layout']
|
176
179
|
layout = options['layout']
|
@@ -195,18 +198,25 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
195
198
|
end
|
196
199
|
|
197
200
|
args = meta_args[0]
|
198
|
-
@singular =
|
201
|
+
@singular = args.first.tableize.singularize # should be in form hello_world
|
199
202
|
|
200
203
|
if @singular.include?("/")
|
201
204
|
@singular = @singular.split("/").last
|
202
205
|
end
|
203
206
|
|
204
|
-
@plural = options['plural'] ||
|
207
|
+
@plural = (options['plural'] || args.first.tableize.singularize.pluralize) # respects what you set in inflections.rb, to override, use plural option
|
208
|
+
|
209
|
+
puts "SINGULAR: #{@singular}"
|
210
|
+
puts "PLURAL: #{@plural}"
|
211
|
+
|
212
|
+
|
205
213
|
@namespace = options['namespace'] || nil
|
206
214
|
@namespace_value = @namespace
|
207
|
-
use_controller_name =
|
208
|
-
|
209
|
-
|
215
|
+
use_controller_name = plural.titleize.gsub(" ", "")
|
216
|
+
|
217
|
+
|
218
|
+
@controller_build_name = ((@namespace.titleize.gsub(" ", "") + "::" if @namespace) || "") + (@controller_prefix ? @controller_prefix.titleize : "") + use_controller_name + "Controller"
|
219
|
+
@controller_build_folder = (@controller_prefix ? @controller_prefix.downcase + "_" : "") + use_controller_name.underscore
|
210
220
|
@controller_build_folder_singular = singular
|
211
221
|
|
212
222
|
@auth = options['auth'] || "current_user"
|
@@ -387,6 +397,9 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
387
397
|
|
388
398
|
@no_nav_menu = options['no_nav_menu']
|
389
399
|
|
400
|
+
@phantom_create_params = options['phantom_create_params'] || ""
|
401
|
+
@phantom_update_params = options['phantom_update_params'] || ""
|
402
|
+
|
390
403
|
if get_default_from_config(key: :pundit_default)
|
391
404
|
raise "please note the config setting `pundit_default` has been renamed `pundit`. please update your hot_glue.yml file"
|
392
405
|
end
|
@@ -409,11 +422,31 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
409
422
|
|
410
423
|
@downnest_children = [] # TODO: defactor @downnest_children in favor of downnest_object
|
411
424
|
@downnest_object = {}
|
425
|
+
|
412
426
|
if @downnest
|
413
|
-
@downnest_children = @downnest.split(",")
|
414
|
-
|
427
|
+
@downnest_children = @downnest.split(",")
|
428
|
+
|
429
|
+
@downnest_children.each do |child|
|
430
|
+
if child.include?("(")
|
431
|
+
child =~ /(.*)\((.*)\)/
|
432
|
+
child_name, polymorph_as = $1, $2
|
433
|
+
else
|
434
|
+
child_name = child
|
435
|
+
end
|
436
|
+
extra_size = child_name.count("+")
|
437
|
+
|
438
|
+
child_name.gsub!("+","")
|
439
|
+
|
440
|
+
|
441
|
+
@downnest_object[child] = {
|
442
|
+
name: child_name,
|
443
|
+
extra_size: extra_size,
|
444
|
+
polymorph_as: polymorph_as
|
445
|
+
}
|
446
|
+
end
|
415
447
|
end
|
416
448
|
|
449
|
+
|
417
450
|
@include_object_names = options['include_object_names'] || get_default_from_config(key: :include_object_names)
|
418
451
|
|
419
452
|
|
@@ -458,12 +491,17 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
458
491
|
|
459
492
|
if !@nested.nil?
|
460
493
|
@nested_set = @nested.split("/").collect { |arg|
|
461
|
-
|
462
|
-
|
494
|
+
if arg.include?("(")
|
495
|
+
arg =~ /(.*)\((.*)\)/
|
496
|
+
singular, polymorph_as = $1, $2
|
497
|
+
else
|
498
|
+
singular = arg
|
499
|
+
end
|
500
|
+
|
463
501
|
{
|
464
|
-
singular:
|
465
|
-
plural:
|
466
|
-
|
502
|
+
singular: singular,
|
503
|
+
plural: singular.pluralize,
|
504
|
+
polymorph_as: polymorph_as
|
467
505
|
}
|
468
506
|
}
|
469
507
|
puts "NESTING: #{@nested_set}"
|
@@ -489,6 +527,8 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
489
527
|
|
490
528
|
# OBJECT OWNERSHIP & NESTING
|
491
529
|
@reference_name = HotGlue.derrive_reference_name(singular_class)
|
530
|
+
|
531
|
+
|
492
532
|
if @auth && @self_auth
|
493
533
|
@object_owner_sym = @auth.gsub("current_", "").to_sym
|
494
534
|
@object_owner_eval = @auth
|
@@ -506,10 +546,11 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
506
546
|
@object_owner_eval = @auth
|
507
547
|
else
|
508
548
|
if @nested_set.any?
|
509
|
-
|
510
|
-
@
|
511
|
-
|
512
|
-
@
|
549
|
+
|
550
|
+
@object_owner_sym = (@nested_set.last[:polymorph_as] || @nested_set.last[:singular]).to_sym
|
551
|
+
|
552
|
+
@object_owner_eval = "#{( @nested_set.last[:singular])}"
|
553
|
+
@object_owner_name = (@nested_set.last[:polymorph_as] || @nested_set.last[:singular])
|
513
554
|
else
|
514
555
|
@object_owner_sym = nil
|
515
556
|
@object_owner_eval = ""
|
@@ -534,7 +575,7 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
534
575
|
@code_after_update = options['code_after_update']
|
535
576
|
@code_after_new = options['code_after_new']
|
536
577
|
|
537
|
-
buttons_width = ((!@no_edit && 1) || 0) + ((!@no_delete && 1) || 0) + @magic_buttons.
|
578
|
+
buttons_width = ((!@no_edit && 1) || 0) + ((!@no_delete && 1) || 0) + (@magic_buttons.any? ? 1 : 0)
|
538
579
|
|
539
580
|
|
540
581
|
# alt_lookups_entry =
|
@@ -665,6 +706,7 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
665
706
|
builder = HotGlue::Layout::Builder.new(generator: self,
|
666
707
|
include_setting: options['include'],
|
667
708
|
buttons_width: buttons_width)
|
709
|
+
|
668
710
|
@layout_object = builder.construct
|
669
711
|
|
670
712
|
|
@@ -1195,7 +1237,8 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
1195
1237
|
|
1196
1238
|
def form_path_edit_helper
|
1197
1239
|
HotGlue.optionalized_ternary(namespace: @namespace,
|
1198
|
-
target:
|
1240
|
+
target: @singular,
|
1241
|
+
prefix: (@controller_prefix ? @controller_prefix.downcase + "_" : ""),
|
1199
1242
|
nested_set: @nested_set,
|
1200
1243
|
with_params: false,
|
1201
1244
|
put_form: true,
|
@@ -1204,6 +1247,7 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
1204
1247
|
|
1205
1248
|
def delete_path_helper
|
1206
1249
|
HotGlue.optionalized_ternary(namespace: @namespace,
|
1250
|
+
prefix: (@controller_prefix ? @controller_prefix.downcase + "_" : ""),
|
1207
1251
|
target: @singular,
|
1208
1252
|
nested_set: @nested_set,
|
1209
1253
|
with_params: false,
|
@@ -1212,6 +1256,7 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
1212
1256
|
|
1213
1257
|
def edit_path_helper
|
1214
1258
|
HotGlue.optionalized_ternary(namespace: @namespace,
|
1259
|
+
prefix: (@controller_prefix ? @controller_prefix.downcase + "_" : ""),
|
1215
1260
|
target: @singular,
|
1216
1261
|
nested_set: @nested_set,
|
1217
1262
|
modifier: "edit_",
|
@@ -1219,6 +1264,16 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
1219
1264
|
put_form: true)
|
1220
1265
|
end
|
1221
1266
|
|
1267
|
+
def new_path_name
|
1268
|
+
HotGlue.optionalized_ternary(namespace: @namespace,
|
1269
|
+
target: singular,
|
1270
|
+
prefix: (@controller_prefix ? @controller_prefix.downcase + "_" : ""),
|
1271
|
+
nested_set: @nested_set,
|
1272
|
+
modifier: "new_",
|
1273
|
+
with_params: false)
|
1274
|
+
end
|
1275
|
+
|
1276
|
+
|
1222
1277
|
def path_arity
|
1223
1278
|
res = ""
|
1224
1279
|
if @nested_set.any? && @nested
|
@@ -1239,13 +1294,6 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
1239
1294
|
"#{@namespace + "/" if @namespace}#{@controller_build_folder}/list"
|
1240
1295
|
end
|
1241
1296
|
|
1242
|
-
def new_path_name
|
1243
|
-
HotGlue.optionalized_ternary(namespace: @namespace,
|
1244
|
-
target: singular,
|
1245
|
-
nested_set: @nested_set,
|
1246
|
-
modifier: "new_",
|
1247
|
-
with_params: false)
|
1248
|
-
end
|
1249
1297
|
|
1250
1298
|
def nested_assignments
|
1251
1299
|
return "" if @nested_set.none?
|
@@ -116,6 +116,7 @@ class <%= controller_class_name %> < <%= controller_descends_from %>
|
|
116
116
|
|
117
117
|
<%= controller_attachment_orig_filename_pickup_syntax %>
|
118
118
|
<%= creation_syntax %>
|
119
|
+
<%= @code_after_new ? @code_after_new.gsub(";","\n") + "\n" : "" %>
|
119
120
|
|
120
121
|
<% if @pundit %><% @related_sets.each do |key, related_set| %>
|
121
122
|
check_<%= related_set[:association_ids_method].to_s %>_permissions(modified_params, :create)<% end %><% end %>
|
@@ -144,14 +145,13 @@ class <%= controller_class_name %> < <%= controller_descends_from %>
|
|
144
145
|
rescue Pundit::NotAuthorizedError => e
|
145
146
|
flash[:alert] = "Not authorized."
|
146
147
|
@<%= singular %>.errors.add(:base, e.message)
|
147
|
-
<% unless @display_edit_after_create %>render :create, status: :unprocessable_entity<% else %>redirect_to <%=
|
148
|
+
<% unless @display_edit_after_create %>render :create, status: :unprocessable_entity<% else %>redirect_to <%= HotGlue.optionalized_ternary(namespace: @namespace,
|
148
149
|
top_level: true,
|
149
|
-
target: @
|
150
|
+
target: @plural,
|
150
151
|
nested_set: @nested_set,
|
151
|
-
modifier: 'edit_',
|
152
152
|
with_params: true,
|
153
|
-
instance_last_item:
|
154
|
-
put_form:
|
153
|
+
instance_last_item: false,
|
154
|
+
put_form: false).gsub("(#{singular}", "(@#{singular}") %><% end %>
|
155
155
|
|
156
156
|
<% end %>
|
157
157
|
end
|
@@ -287,13 +287,13 @@ class <%= controller_class_name %> < <%= controller_descends_from %>
|
|
287
287
|
end<% end %><% end %>
|
288
288
|
|
289
289
|
def <%=singular_name%>_params
|
290
|
-
fields = <%= ((fields_filtered_for_strong_params - @show_only) + @magic_buttons.collect{|x| "__#{x}"}).collect{|sym| ":#{sym}"}.join(", ") %><%= ", " + @related_sets.collect{|key, rs| "#{rs[:association_ids_method]}: []"}.join(", ") if @related_sets.any? %><%= ", " + @alt_lookups.collect{|k,v| ":__lookup_#{v[:assoc].downcase}_#{v[:lookup_as]}" }.join(", ") if @alt_lookups.any? %>
|
290
|
+
fields = <%= ((fields_filtered_for_strong_params - @show_only) + @magic_buttons.collect{|x| "__#{x}"} + @phantom_create_params.split(",")).collect{|sym| ":#{sym}"}.join(", ") %><%= ", " + @related_sets.collect{|key, rs| "#{rs[:association_ids_method]}: []"}.join(", ") if @related_sets.any? %><%= ", " + @alt_lookups.collect{|k,v| ":__lookup_#{v[:assoc].downcase}_#{v[:lookup_as]}" }.join(", ") if @alt_lookups.any? %>
|
291
291
|
params.require(:<%= testing_name %>).permit(fields)
|
292
292
|
end<% if @update_show_only %>
|
293
293
|
|
294
294
|
<% unless @no_edit %>
|
295
295
|
def update_<%=singular_name%>_params
|
296
|
-
fields = <%= ((fields_filtered_for_strong_params - @update_show_only) + @magic_buttons.collect{|x| "__#{x}"}).collect{|sym| ":#{sym}"}.join(", ") %><%= ", " + @related_sets.collect{|key, rs| "#{rs[:association_ids_method]}: []"}.join(", ") if @related_sets.any? %><%= ", " + @alt_lookups.collect{|k,v| ":__lookup_#{v[:assoc].downcase}_#{v[:lookup_as]}" }.join(", ") if @alt_lookups.any? %>
|
296
|
+
fields = <%= ((fields_filtered_for_strong_params - @update_show_only) + @magic_buttons.collect{|x| "__#{x}"} + @phantom_update_params.split(",")).collect{|sym| ":#{sym}"}.join(", ") %><%= ", " + @related_sets.collect{|key, rs| "#{rs[:association_ids_method]}: []"}.join(", ") if @related_sets.any? %><%= ", " + @alt_lookups.collect{|k,v| ":__lookup_#{v[:assoc].downcase}_#{v[:lookup_as]}" }.join(", ") if @alt_lookups.any? %>
|
297
297
|
<%= (fields_filtered_for_strong_params - @update_show_only).collect{|col|
|
298
298
|
# TODO : fields not on show only also not invisible should be checked here
|
299
299
|
# for _able? methods and added only when able
|
@@ -19,19 +19,22 @@
|
|
19
19
|
<% if @downnest_object.any? && !@big_edit %>
|
20
20
|
<% if !@stacked_downnesting %>
|
21
21
|
<%= @layout_strategy.downnest_column_style %>
|
22
|
-
|
23
|
-
|
22
|
+
|
23
|
+
<% @downnest_object.each_with_index do |data,i| %>
|
24
|
+
<% downnest = data[1] %>
|
25
|
+
<div class=" scaffold-col-heading <%= @layout_strategy.downnest_portal_column_width(downnest[:name]) %> <%= @layout_strategy.downnest_column_style %>">
|
24
26
|
<strong>
|
25
|
-
<%= downnest.titleize %>
|
27
|
+
<%= downnest[:name].titleize %>
|
26
28
|
</strong>
|
27
29
|
</div>
|
28
30
|
<% end %>
|
29
31
|
<% else %>
|
30
32
|
<div class=" scaffold-col-heading <%= @layout_strategy.downnest_portal_stacked_column_width %> <%= @layout_strategy.downnest_column_style %>">
|
31
33
|
<%= @layout_strategy.downnest_column_style %>
|
32
|
-
<% @downnest_object.
|
34
|
+
<% @downnest_object.each_with_index do |data,i| %>
|
35
|
+
<% downnest = data[1] %>
|
33
36
|
<strong>
|
34
|
-
<%= downnest.titleize %>
|
37
|
+
<%= downnest[:name].titleize %>
|
35
38
|
</strong>
|
36
39
|
<% end %>
|
37
40
|
</div>
|
@@ -39,7 +39,7 @@
|
|
39
39
|
|
40
40
|
<% if destroy_action %>
|
41
41
|
<\%= form_with url: <%= delete_path_helper %>, html: {data: {'<%= @ujs_syntax ? 'confirm' : 'turbo-confirm' %>': "Are you sure you want to delete #{ <%= @singular + "." + display_class %> }?" }, style: "display: inline-block;"}, method: :delete do |f| %>
|
42
|
-
<\%= f.submit "Delete".html_safe, class: "delete-<%= singular %>-button btn btn-primary btn-sm" %>
|
42
|
+
<\%= f.submit "Delete".html_safe, class: "delete-<%= singular %>-button btn btn-primary btn-sm", disabled: (<%= @singular %>.respond_to?(:delete_able?) && ! <%= @singular %>.delete_able? ) %>
|
43
43
|
<\% end %>
|
44
44
|
<% end %>
|
45
45
|
|
@@ -15,27 +15,28 @@
|
|
15
15
|
<% if @downnest_children.any? && @big_edit %>
|
16
16
|
<div class="container" data-controller="bootstrap-tabbed-nav">
|
17
17
|
<ul class="nav nav-tabs" id="<%= singular + "_downnest_portals" %>" role="tablist">
|
18
|
-
<% @downnest_object.each_with_index do |
|
19
|
-
<%
|
18
|
+
<% @downnest_object.each_with_index do |set, index| %>
|
19
|
+
<% key = set[0]; downnest = set[1] %>
|
20
20
|
<li class="nav-item" role="presentation">
|
21
|
-
<button class="nav-link <%= "active" if index==0 %>" id="<%= downnest %>-tab" data-bs-toggle="tab" data-bs-target="#<%= downnest %>-portal" type="button" role="tab" aria-controls="home" aria-selected="true">
|
22
|
-
<%= downnest.titlecase.pluralize %>
|
21
|
+
<button class="nav-link <%= "active" if index==0 %>" id="<%= downnest[:name] %>-tab" data-bs-toggle="tab" data-bs-target="#<%= downnest[:name] %>-portal" type="button" role="tab" aria-controls="home" aria-selected="true">
|
22
|
+
<%= downnest[:name].titlecase.pluralize %>
|
23
23
|
</button>
|
24
24
|
</li>
|
25
25
|
<% end %>
|
26
26
|
</ul>
|
27
27
|
|
28
28
|
<div class="tab-content" id="myTabContent">
|
29
|
-
<% @downnest_object.each_with_index do |
|
30
|
-
<%
|
31
|
-
<div class="tab-pane fade <%= "show active" if index==0 %>" id="<%= downnest %>-portal" role="tabpanel" aria-labelledby="<%= downnest %>-tab">
|
32
|
-
|
33
|
-
<%
|
29
|
+
<% @downnest_object.each_with_index do |set, index| %>
|
30
|
+
<% key = set[0]; downnest = set[1] %>
|
31
|
+
<div class="tab-pane fade <%= "show active" if index==0 %>" id="<%= downnest[:name] %>-portal" role="tabpanel" aria-labelledby="<%= downnest[:name] %>-tab">
|
32
|
+
|
33
|
+
<% downnest_object = eval("#{singular_class}.reflect_on_association(:#{downnest[:polymorph_as] || downnest[:name]})") %>
|
34
|
+
<% if downnest_object.nil?; raise "no relationship for downnested portal `#{downnest[:name]}` found on `#{singular_class}`; please check relationship for has_many :#{downnest[:name]} or use ( .. ) to work with polymorphism"; end; %>
|
34
35
|
<% downnest_class = downnest_object.class_name %>
|
35
|
-
<% downnest_object_name =
|
36
|
+
<% downnest_object_name = downnest[:name] %>
|
36
37
|
<% downnest_style = @layout_strategy.downnest_style %>
|
37
38
|
|
38
|
-
<\%= render partial: "<%= namespace_with_trailing_dash %><%= downnest_object_name %>/list", locals: {<%= @singular %>: @<%= @singular %>, <%=
|
39
|
+
<\%= render partial: "<%= namespace_with_trailing_dash %><%= downnest_object_name %>/list", locals: {<%= @singular %>: @<%= @singular %>, <%= downnest[:polymorph_as] || downnest[:name] %>: @<%= @singular %>.<%= downnest[:polymorph_as] || downnest[:name] %><% if @nested_set.any? %>, <%= @nested_set.collect{|x| "#{x[:singular]}: @#{x[:singular]}"}.join(", ") %>, nested_for: "<%= @nested_set.collect{|x| "#{x[:singular]}-" + "\#{" + "@#{x[:singular]}.id}"}.join("__") %>__<%= singular %>-#{@<%= @singular %>.id}" <% end %> } \%>
|
39
40
|
</div>
|
40
41
|
<% end %>
|
41
42
|
</div>
|
data/lib/hot-glue.rb
CHANGED
@@ -22,18 +22,10 @@ module HotGlue
|
|
22
22
|
end
|
23
23
|
|
24
24
|
|
25
|
-
def self.construct_downnest_object(input)
|
26
|
-
res = input.split(",").map { |child|
|
27
|
-
child_name = child.gsub("+","")
|
28
|
-
extra_size = child.count("+")
|
29
|
-
{child_name => 4+extra_size}
|
30
|
-
}
|
31
|
-
Hash[*res.collect{|hash| hash.collect{|key,value| [key,value].flatten}.flatten}.flatten]
|
32
|
-
end
|
33
|
-
|
34
25
|
def self.optionalized_ternary(namespace: nil,
|
35
26
|
target:,
|
36
27
|
nested_set:,
|
28
|
+
prefix: nil, # is this used
|
37
29
|
modifier: "",
|
38
30
|
with_params: false,
|
39
31
|
top_level: false,
|
@@ -71,15 +63,16 @@ module HotGlue
|
|
71
63
|
put_form: put_form,
|
72
64
|
nested_set: [nonoptional, *rest_of_nest])
|
73
65
|
|
74
|
-
is_missing_path = HotGlue.optionalized_ternary(
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
66
|
+
# is_missing_path = HotGlue.optionalized_ternary(
|
67
|
+
# namespace: namespace,
|
68
|
+
# target: target,
|
69
|
+
# modifier: modifier,
|
70
|
+
# top_level: top_level,
|
71
|
+
# with_params: with_params,
|
72
|
+
# put_form: put_form,
|
73
|
+
# nested_set: rest_of_nest )
|
74
|
+
#
|
75
|
+
return "#{is_present_path}"
|
83
76
|
end
|
84
77
|
end
|
85
78
|
|
data/lib/hotglue/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hot-glue
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.22
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jason Fleetwood-Boldt
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-07-
|
11
|
+
date: 2025-07-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|