hot-glue 0.6.21.2 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f79c85d7b5413fe8f12306c5b46de1eedd68c6373ffbab5512a1a57ac22c0e30
4
- data.tar.gz: d151d9357396d201e1b8a25b59146b7f065b260d38b8f456ece7d798d2b09320
3
+ metadata.gz: 0a92b0ee848c36d585691eefae1df23634e0814ad7d8e8b96a2d9dd7605c60d7
4
+ data.tar.gz: ecbd2c67fb4a1a3665870178da7c92b6a10a11d336a8b953fed1ca49042ffb18
5
5
  SHA512:
6
- metadata.gz: 0e9df1507995d80cc0efc229c29bca7e6e1d52e1a6be0870cbcee4d9cc42cfec85ddf2dd3ec8b710162eff4d800a04f847dcdbd8580220277642026a7e5dbc45
7
- data.tar.gz: 0271cd11a4848ef7044ac0310cf9064fff0defef82a2e4237c8caf34bb8e660b618f2da3c1f3565e6317b69c10aa3e6bc3e24366a088c4be5e756d6d25cada7e
6
+ metadata.gz: 045ef49863d103f17c889b6b0e8aa7a516faacb4b2b27a2b9bc5e6018d5113f89bfab29065d503bb277bb499afebcbf0592e57a3ef0ee1db61ca27ad2c5baa6a
7
+ data.tar.gz: 8b6eb8ad573fa9d58c46018c46c055b4af97bd6ec3f7eb597d572ec1195346eb9c6504a0ea0423fa44e6b52dae4e02194ff14bf554b22d566124b5671e7b461d
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- hot-glue (0.6.21.1)
4
+ hot-glue (0.6.21.2)
5
5
  ffaker (~> 2.16)
6
6
  kaminari (~> 1.2)
7
7
  rails (> 5.1)
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 `new` after the .new() call
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
- type: 'datetime-local' })
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
- type: 'date',
27
- value: value }))
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.now.utc.month > 3 && Time.now.month < 11 ||
73
- (Time.now.utc.month == 3 && Time.now.day >= (14 - Time.now.utc.wday)) ||
74
- (Time.now.utc.month == 11 && Time.now.utc.day < (7 - Time.now.utc.wday))
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
- (ActiveSupport::TimeZone[current_user_object.timezone])
81
- else
82
- Time.zone
83
- end
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,18 +98,28 @@ module HotGlue
93
98
  field_list.include?(k.to_sym)
94
99
  end
95
100
 
96
- parsables = {datetime: "%Y-%m-%d %H:%M %z",
97
- time: "%H:%M %z"}
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
101
109
  natural_offset = use_timezone.formatted_offset
102
- hour = natural_offset.split(":").first
103
- min = natural_offset.split(":").last
104
- hour = hour.to_i - 1 if uses_dst && is_dst_now?
110
+ hour = natural_offset.split(":").first.to_i
111
+ min = natural_offset.split(":").last.to_i
105
112
 
106
- use_offset = "#{hour}:#{min}"
113
+ hour = hour + 1 if uses_dst && is_dst_now?
114
+
115
+ use_offset = format_timezone_offset(hour, min)
107
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}")
122
+
108
123
  # note: as according to https://stackoverflow.com/questions/20111413/html5-datetime-local-control-how-to-hide-seconds
109
124
  # there is no way to set the seconds to 00 in the datetime-local input field
110
125
  # as I have implemented a "seconds don't matter" solution,
@@ -117,6 +132,11 @@ module HotGlue
117
132
  else
118
133
  parsed_time = Time.strptime(parse_date, parsables[field_list[k.to_sym]])
119
134
  end
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}"
120
140
 
121
141
  params[k] = parsed_time
122
142
  end
@@ -229,12 +249,12 @@ module HotGlue
229
249
  when 'is_at_exactly'
230
250
  ["EXTRACT(HOUR FROM #{field}) = ?
231
251
  AND EXTRACT(MINUTE FROM #{field}) = ? ", search_start.split(":")[0], search_start.split(":")[1]]
232
- # when 'is_at_or_after'
233
- # ["#{field} = ? OR #{field} > ?", search_start, search_start]
234
- # when "is_before_or_at"
235
- # ["#{field} = ? OR #{field} < ?", search_end, search_end]
236
- # when "is_between"
237
- # ["#{field} BETWEEN ? AND ?", search_start, search_end]
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]
238
258
  end
239
259
  end
240
260
  end
@@ -265,8 +285,5 @@ module HotGlue
265
285
 
266
286
  private
267
287
 
268
- # def server_timezone_offset # returns integer of hours to add/subtract from UTC
269
- # Time.now.in_time_zone(Rails.application.config.time_zone).strftime("%z").to_i/100
270
- # end
271
288
  end
272
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
- raise "Float field search not implemented"
39
+ "#{name}_query = integer_query_constructor(@q['0'][:#{name}_match], @q['0'][:#{name}_search])"
33
40
  end
34
41
  end
@@ -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.collect{|k,v| v}.sum)
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
- downnest_object.each do |child, size|
70
- layout_object[:portals][child] = {size: size}
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 <span #{@tinymce_stimulus_controller}class='<%= \"alert alert-danger\" if #{singular}.errors.details.keys.include?(:#{field_error_name}) %>' #{data_attr} >\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 </span>\n ", 2)
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 = args.first.tableize.singularize # should be in form hello_world
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'] || @singular.pluralize # respects what you set in inflections.rb, to override, use plural option
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 = plural.titleize.gsub(" ", "")
208
- @controller_build_name = ((@namespace.titleize.gsub(" ", "") + "::" if @namespace) || "") + use_controller_name + "Controller"
209
- @controller_build_folder = use_controller_name.underscore
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(",").map { |child| child.gsub("+", "") }
414
- @downnest_object = HotGlue.construct_downnest_object(@downnest)
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
- is_optional = arg.start_with?("~")
462
- arg.gsub!("~", "")
494
+ if arg.include?("(")
495
+ arg =~ /(.*)\((.*)\)/
496
+ singular, polymorph_as = $1, $2
497
+ else
498
+ singular = arg
499
+ end
500
+
463
501
  {
464
- singular: arg,
465
- plural: arg.pluralize,
466
- optional: is_optional
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
- @object_owner_sym = @nested_set.last[:singular].to_sym
510
- @object_owner_eval = "@#{@nested_set.last[:singular]}"
511
- @object_owner_name = @nested_set.last[:singular]
512
- @object_owner_optional = @nested_set.last[:optional]
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.count
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: @singular,
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 <%= HotGlue.optionalized_ternary(namespace: @namespace,
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: @singular,
150
+ target: @plural,
150
151
  nested_set: @nested_set,
151
- modifier: 'edit_',
152
152
  with_params: true,
153
- instance_last_item: true,
154
- put_form: true).gsub("(#{singular}", "(@#{singular}") %><% end %>
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
- <% @downnest_object.each do |downnest,i| %>
23
- <div class=" scaffold-col-heading <%= @layout_strategy.downnest_portal_column_width(downnest) %> <%= @layout_strategy.downnest_column_style %>">
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.each do |downnest,i| %>
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 |data,index| %>
19
- <% downnest = data[0] %>
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 |data, index| %>
30
- <% downnest = data[0] %>
31
- <div class="tab-pane fade <%= "show active" if index==0 %>" id="<%= downnest %>-portal" role="tabpanel" aria-labelledby="<%= downnest %>-tab">
32
- <% downnest_object = eval("#{singular_class}.reflect_on_association(:#{downnest})") %>
33
- <% if downnest_object.nil?; raise "no relationship for downnested portal `#{downnest}` found on `#{singular_class}`; please check relationship for has_many :#{downnest}"; end; %>
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 = eval("#{downnest_class}.table_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 %>, <%= downnest_object_name %>: @<%= @singular %>.<%= downnest %><% 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
+ <\%= 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
- namespace: namespace,
76
- target: target,
77
- modifier: modifier,
78
- top_level: top_level,
79
- with_params: with_params,
80
- put_form: put_form,
81
- nested_set: rest_of_nest )
82
- return "defined?(#{instance_sym + nested_set[0][:singular]}2) ? #{is_present_path} : #{is_missing_path}"
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
 
@@ -1,5 +1,5 @@
1
1
  module HotGlue
2
2
  class Version
3
- CURRENT = '0.6.21.2'
3
+ CURRENT = '0.6.22'
4
4
  end
5
5
  end
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.21.2
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-10 00:00:00.000000000 Z
11
+ date: 2025-07-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails