hot-glue 0.6.22 → 0.6.24
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 +668 -573
- data/lib/generators/hot_glue/fields/association_field.rb +5 -1
- data/lib/generators/hot_glue/fields/related_set_field.rb +30 -10
- data/lib/generators/hot_glue/scaffold_generator.rb +35 -6
- data/lib/generators/hot_glue/templates/controller.rb.erb +5 -5
- data/lib/generators/hot_glue/templates/erb/_form.erb +1 -2
- data/lib/generators/hot_glue/templates/erb/_lazy_list.erb +7 -0
- data/lib/generators/hot_glue/templates/erb/_show.erb +1 -3
- data/lib/generators/hot_glue/templates/erb/create.turbo_stream.erb +1 -1
- data/lib/generators/hot_glue/templates/erb/edit.erb +2 -1
- data/lib/generators/hot_glue/templates/erb/index.erb +2 -1
- data/lib/hot-glue.rb +19 -19
- data/lib/hotglue/version.rb +1 -1
- metadata +3 -2
data/README.md
CHANGED
@@ -321,16 +321,7 @@ TitleCase class name of the thing you want to build a scaffolding for.
|
|
321
321
|
|
322
322
|
(note: Your `Thing` object must `belong_to` an authenticated `User` or alternatively you must create a Gd controller, see below.)
|
323
323
|
|
324
|
-
|
325
|
-
## FLAGS (Options with no values)
|
326
|
-
These options (flags) also uses `--` syntax but do not take any values. (Notice no equal sign.) Everything is assumed (default) to be false unless specified.
|
327
|
-
|
328
|
-
|
329
|
-
### `--stacked-downnesting`
|
330
|
-
|
331
|
-
This puts the downnested portals on top of one another (stacked top to bottom) instead of side-by-side (left to right). This is useful if you have a lot of downnested portals and you want to keep the page from getting too wide.
|
332
|
-
|
333
|
-
|
324
|
+
About these docs: The options (take an argument) flags (do not take an argument) have been merged together. Flags will be listed with no trailing `=`
|
334
325
|
|
335
326
|
### `--god` or `--gd`
|
336
327
|
|
@@ -346,167 +337,103 @@ end
|
|
346
337
|
|
347
338
|
```
|
348
339
|
|
349
|
-
### `--
|
350
|
-
You can specify this either as builder flag or as a config setting (in `config/hot_glue.yml`)
|
351
|
-
Use `font-awesome` for Font Awesome or `none` for no icons.
|
352
|
-
|
353
|
-
|
354
|
-
### `--specs-only`
|
355
|
-
|
356
|
-
Produces ONLY the controller spec file, nothing else.
|
357
|
-
|
358
|
-
|
359
|
-
### `--no-specs`
|
360
|
-
|
361
|
-
Produces all the files except the spec file.
|
362
|
-
|
363
|
-
|
364
|
-
### `--no-paginate` (default: false)
|
365
|
-
|
366
|
-
Omits pagination. (All list views have pagination by default.)
|
367
|
-
|
368
|
-
### `--paginate-per-page-selector` (default: false)
|
369
|
-
|
370
|
-
Show a small drop-down below the list to let the user choose 10, 25, or 100 results per page.
|
371
|
-
|
372
|
-
|
373
|
-
### `--no-list`
|
374
|
-
|
375
|
-
Omits list action. Only makes sense to use this if want to create a view where you only want the create button or to navigate to the update screen alternative ways. (The new/create still appears, as well the edit, update & destroy actions are still created even though there is no natural way to navigate to them.)
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
### `--no-create`
|
380
|
-
|
381
|
-
Omits new & create actions.
|
382
|
-
|
383
|
-
### `--no-delete`
|
384
|
-
|
385
|
-
Omits delete button & destroy action.
|
386
|
-
|
387
|
-
### `--no-controller`
|
388
|
-
|
389
|
-
Omits controller.
|
390
|
-
|
391
|
-
### `--no-list`
|
392
|
-
|
393
|
-
Omits list views.
|
394
|
-
|
395
|
-
`--new-button-position` (above, below; default: above)
|
396
|
-
Show the new button above or below the list.
|
397
|
-
|
398
|
-
`--downnest-shows-headings` (default: false)
|
399
|
-
Show headings above downnested portals.
|
400
|
-
|
340
|
+
### `--namespace=`
|
401
341
|
|
402
|
-
|
342
|
+
pass `--namespace=` as an option to denote a namespace to apply to the Rails path helpers
|
403
343
|
|
404
|
-
If you do not want inline editing of your list items but instead want to fallback to full-page style behavior for your edit views, use `--big-edit`.
|
405
344
|
|
406
|
-
|
345
|
+
`./bin/rails generate hot_glue:scaffold Thing --namespace=dashboard`
|
407
346
|
|
408
|
-
|
347
|
+
This produces several views at `app/views/dashboard/things/` and a controller at`app/controllers/dashboard/things_controller.rb`
|
409
348
|
|
410
|
-
|
349
|
+
The controller looks like so:
|
411
350
|
|
412
|
-
|
351
|
+
```
|
352
|
+
class Dashboard::ThingsController < ApplicationController
|
353
|
+
before_action :authenticate_user!
|
354
|
+
before_action :load_thing, only: [:show, :edit, :update, :destroy]
|
355
|
+
def load_thing
|
356
|
+
@thing = current_user.things.find(params[:id])
|
357
|
+
end
|
358
|
+
...
|
359
|
+
end
|
413
360
|
|
361
|
+
```
|
414
362
|
|
415
|
-
### `--
|
363
|
+
### `--auth=`
|
416
364
|
|
417
|
-
|
365
|
+
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.
|
418
366
|
|
419
|
-
|
367
|
+
The poor man's auth presumes that object graphs have only one natural way to traverse them (that is, one primary way to traverse them), and that all relationships infer that a set of things or their descendants are granted access to "me" for reading, writing, updating, and deleting.
|
420
368
|
|
421
|
-
|
369
|
+
Of course this is a sloppy way to do access control, and can easily leave open endpoints your real users shouldn't have access to.
|
422
370
|
|
423
|
-
|
371
|
+
When you display anything built with the scaffolding, Hot Glue assumes the `current_user` will have `has_many` association that matches the pluralized name of the scaffold. In the case of nesting, we will automatically find the nested objects first, then continue down the nest chain to find the target object. This is how Hot Glue assumes all object are 'anchored' to the logged-in user. (As explained in the `--nested` section.)
|
424
372
|
|
425
|
-
If
|
373
|
+
If you use Devise, you probably already have a `current_user` method available in your controllers. If you don't use Devise, you can implement it in your ApplicationController.
|
426
374
|
|
427
|
-
|
375
|
+
If you use a different object other than "User" for authentication, override using the `auth` option.
|
428
376
|
|
429
|
-
|
377
|
+
`./bin/rails generate hot_glue:scaffold Thing --auth=current_account`
|
430
378
|
|
431
|
-
|
379
|
+
You will note that in this example it is presumed that the Account object will have an association for `things`
|
432
380
|
|
433
|
-
|
381
|
+
It is also presumed that when viewing their own dashboard of things, the user will want to see ALL of their associated things.
|
434
382
|
|
435
|
-
|
383
|
+
If you supply nesting (see below), your nest chain will automatically begin with your auth root object (see nesting)
|
436
384
|
|
437
385
|
|
438
|
-
### `--
|
439
|
-
Omits list LABEL itself above the list. (Do not confuse with the list heading which contains the field labels.)
|
386
|
+
### `--auth_identifier=`
|
440
387
|
|
441
|
-
|
388
|
+
Your controller will call a method authenticate_ (AUTH IDENTIFIER) bang, like:
|
442
389
|
|
390
|
+
`authenticate_user!`
|
443
391
|
|
444
|
-
|
392
|
+
Before all of the controller actions. If you leave this blank, it will default to using the variable name supplied by auth with "current_" stripped away.
|
393
|
+
(This is setup for devise.)
|
445
394
|
|
446
|
-
|
395
|
+
Be sure to implement the following method in your ApplicationController or some other method. Here's a quick example using Devise. You will note in the code below, user_signed_in? is implemented when you add Devise methods to your User table.
|
447
396
|
|
448
|
-
|
397
|
+
As well, the `after_sign_in_path_for(user)` here is a hook for Devise also that provides you with after login redirect to the page where the user first intended to go.
|
449
398
|
|
450
|
-
|
399
|
+
```
|
400
|
+
def authenticate_user!
|
401
|
+
if ! user_signed_in?
|
402
|
+
session['user_return_to'] = request.path
|
403
|
+
redirect_to new_user_registration_path
|
404
|
+
end
|
405
|
+
end
|
451
406
|
|
452
|
-
|
407
|
+
def after_sign_in_path_for(user)
|
408
|
+
session['user_return_to'] || account_url(user)
|
409
|
+
end
|
410
|
+
```
|
453
411
|
|
454
|
-
Can also be specified globally in `config/hot_glue.yml`
|
455
412
|
|
456
|
-
|
457
|
-
At the namespace level, you can have a file called `_nav.html.erb` to create tabbed bootstrap nav
|
413
|
+
The default (do not pass `auth_identifier=`) will match the `auth` (So if you use 'account' as the auth, `authenticate_account!` will get invoked from your generated controller; the default is always 'user', so you can leave both auth and auth_identifier off if you want 'user')
|
458
414
|
|
459
|
-
To create the file for the first time (at each namespace), start by running
|
460
|
-
```
|
461
|
-
bin/rails generate hot_glue:nav_template --namespace=xyz
|
462
|
-
```
|
463
415
|
|
464
|
-
|
416
|
+
`./bin/rails generate hot_glue:scaffold Thing --auth=current_account --auth_identifier=login`
|
465
417
|
|
418
|
+
|
419
|
+
In this example, the controller produced with:
|
466
420
|
```
|
467
|
-
|
468
|
-
</ul>
|
421
|
+
before_action :authenticate_login!
|
469
422
|
```
|
470
|
-
|
471
|
-
Once the file is present, any further builds in this namespace will:
|
472
|
-
|
473
|
-
1) Append to this `_nav.html.erb` file, adding a tab for the new built scaffold
|
474
|
-
2) On the list view of the scaffold being built, it will include a render to the _nav partial, passing the name of the currently-viewed thing as the local variable `nav` (this is how the nav template knows which tab to make active).
|
423
|
+
However, the object graph anchors would continue to start from current_account. That is,
|
475
424
|
```
|
476
|
-
|
425
|
+
@thing = current_account.things.find(params[:id])
|
477
426
|
```
|
478
|
-
(In this example `owner/` is the namespace and `things` is the name of the scaffold being built)
|
479
|
-
|
480
|
-
To suppress this behavior, add `--no-nav-menu` to the build command and the _nav template will not be touched.
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
## Options With Arguments
|
485
|
-
|
486
|
-
All options begin with two dashes (`--`) and a followed by an `=` and a value
|
487
|
-
|
488
|
-
### `--namespace=`
|
489
|
-
|
490
|
-
pass `--namespace=` as an option to denote a namespace to apply to the Rails path helpers
|
491
|
-
|
492
427
|
|
493
|
-
|
428
|
+
Use empty string to **turn this method off**:
|
429
|
+
|
430
|
+
`./bin/rails generate hot_glue:scaffold Thing --auth=current_account --auth_identifier=''`
|
494
431
|
|
495
|
-
|
432
|
+
In this case a controller would be generated that would have NO before_action to authenticate the account, but it would still treat the current_account as the auth root for the purpose of loading the objects.
|
496
433
|
|
497
|
-
|
434
|
+
Please note that this example would produce non-functional code, so you would need to manually fix your controllers to make sure `current_account` is available to the controller.
|
498
435
|
|
499
|
-
```
|
500
|
-
class Dashboard::ThingsController < ApplicationController
|
501
|
-
before_action :authenticate_user!
|
502
|
-
before_action :load_thing, only: [:show, :edit, :update, :destroy]
|
503
|
-
def load_thing
|
504
|
-
@thing = current_user.things.find(params[:id])
|
505
|
-
end
|
506
|
-
...
|
507
|
-
end
|
508
436
|
|
509
|
-
```
|
510
437
|
|
511
438
|
### `--nested=`
|
512
439
|
|
@@ -569,7 +496,7 @@ Then, finally the @charge will be loaded
|
|
569
496
|
|
570
497
|
`@charge = @line.charges.find(params[:id])`
|
571
498
|
|
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.
|
499
|
+
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
500
|
|
574
501
|
|
575
502
|
#### Example #3: Polymorphic Nesting
|
@@ -603,83 +530,139 @@ Notices the relationship from the parent to child is `rules` but from the child
|
|
603
530
|
|
604
531
|
|
605
532
|
|
606
|
-
### `--
|
533
|
+
### `--downnest=`
|
607
534
|
|
608
|
-
|
535
|
+
Automatically create subviews down your object tree. This should be the name of a has_many relationship based from the current object.
|
536
|
+
You will need to build scaffolding with the same name for the related object as well. On the list view, the object you are currently building will be built with a sub-view list of the objects related from the given line.
|
609
537
|
|
610
|
-
The
|
538
|
+
The downnested child table (not to be confused with this object's `--nested` setting, where you are specifying this object's _parents_) is called a **child portal**. When you create a record in the child portal, the related record is automatically set to be owned by its parent (as specified by `--nested`). For an example, see the [v0.4.7 release notes](https://github.com/jasonfb/hot-glue/releases/tag/v0.4.7).
|
611
539
|
|
612
|
-
|
540
|
+
Can now be created with more space (wider) by adding a `+` to the end of the downnest name
|
541
|
+
- e.g. `--downnest=abc+,xyz`
|
613
542
|
|
614
|
-
|
543
|
+
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
|
615
544
|
|
616
|
-
If you
|
545
|
+
If you are nesting from a controller that uses big edit, your child portals do not display on the list page.
|
617
546
|
|
618
|
-
|
547
|
+
Instead, they display on the edit page, and they display below the record using a Bootstrap tab nav that is automatically built for you.
|
619
548
|
|
620
|
-
`./bin/rails generate hot_glue:scaffold Thing --auth=current_account`
|
621
549
|
|
622
|
-
You will note that in this example it is presumed that the Account object will have an association for `things`
|
623
550
|
|
624
|
-
|
551
|
+
#### Polymorphic Downnesting
|
625
552
|
|
626
|
-
|
553
|
+
Here, a `Blast` `has_many :rules, as: :ruleable`
|
627
554
|
|
555
|
+
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)
|
628
556
|
|
629
|
-
|
557
|
+
`belongs_to :ruleable, polymorphic: true`
|
630
558
|
|
631
|
-
|
559
|
+
We build the blast & agent controllers like so:
|
632
560
|
|
633
|
-
|
561
|
+
bin/rails generate hot_glue:scaffold Blast --downnest='blast_rules(rules)'
|
562
|
+
bin/rails generate hot_glue:scaffold Agent --downnest='agent_rules(rules)'
|
634
563
|
|
635
|
-
|
636
|
-
|
564
|
+
Notice that the relationship name is `rules` (not blast_rules), so what goes before the parenthesis is the controller name (with prefix)
|
565
|
+
What goes inside the controller name is the real relationship name.
|
637
566
|
|
638
|
-
|
567
|
+
For the children, we can't build one controller for the Rule, instead we build one for the `AgentRules` and another for the `BlastRules`
|
639
568
|
|
640
|
-
|
569
|
+
bin/rails generate hot_glue:scaffold Rule --nested='blast(ruleable)' --controller-prefix='Blast'
|
570
|
+
bin/rails generate hot_glue:scaffold Rule --nested='agent(ruleable)' --controller-prefix='Agent'
|
571
|
+
|
572
|
+
(I realize building one child controller for each type of polymorph is tedius, but this is the best solution I could come up with.)
|
573
|
+
|
574
|
+
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`
|
575
|
+
|
576
|
+
routes.rb
|
641
577
|
|
642
578
|
```
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
redirect_to new_user_registration_path
|
647
|
-
end
|
648
|
-
end
|
579
|
+
resources :agents do
|
580
|
+
resources :agent_rules
|
581
|
+
end
|
649
582
|
|
650
|
-
|
651
|
-
|
652
|
-
|
583
|
+
resources :blasts do
|
584
|
+
resources :blast_rules
|
585
|
+
end
|
653
586
|
```
|
654
587
|
|
588
|
+
### `--stacked-downnesting`
|
655
589
|
|
656
|
-
|
590
|
+
This puts the downnested portals on top of one another (stacked top to bottom) instead of side-by-side (left to right). This is useful if you have a lot of downnested portals and you want to keep the page from getting too wide.
|
657
591
|
|
658
592
|
|
659
|
-
`./bin/rails generate hot_glue:scaffold Thing --auth=current_account --auth_identifier=login`
|
660
593
|
|
661
|
-
|
662
|
-
|
663
|
-
```
|
664
|
-
before_action :authenticate_login!
|
665
|
-
```
|
666
|
-
However, the object graph anchors would continue to start from current_account. That is,
|
667
|
-
```
|
668
|
-
@thing = current_account.things.find(params[:id])
|
669
|
-
```
|
594
|
+
### `--downnest-shows-headings` (default: false)
|
595
|
+
Show headings (the label of the list) above downnested portals.
|
670
596
|
|
671
|
-
Use empty string to **turn this method off**:
|
672
|
-
|
673
|
-
`./bin/rails generate hot_glue:scaffold Thing --auth=current_account --auth_identifier=''`
|
674
597
|
|
675
|
-
In this case a controller would be generated that would have NO before_action to authenticate the account, but it would still treat the current_account as the auth root for the purpose of loading the objects.
|
676
598
|
|
677
|
-
|
599
|
+
|
600
|
+
### `--record-scope=`
|
601
|
+
|
602
|
+
Record scope allows you to apply a model based scope for the controller being generated.
|
603
|
+
This is applied on top of all other scopes, searches, and modifiers applied to the built controller.
|
604
|
+
|
605
|
+
`bin/rails :generate hot_glue:scaffold Order --record-scope='.is_open'`
|
606
|
+
|
607
|
+
Be sure to use single quotes (`'`) and don't forget the dot (`.`) before your scope(s).
|
608
|
+
|
609
|
+
Make sure your Order model has a scope `is_open`, like so:
|
610
|
+
|
611
|
+
```
|
612
|
+
scope :is_open, -> {where(state == 'open')}
|
613
|
+
```
|
614
|
+
|
615
|
+
Now all records displayed through the generated controller_
|
616
|
+
|
617
|
+
|
618
|
+
### `--big-edit`
|
619
|
+
|
620
|
+
If you do not want inline editing of your list items but instead want to fallback to full-page style behavior for your edit views, use `--big-edit`.
|
621
|
+
|
622
|
+
The user will be taken to a full-screen edit page instead of an edit-in-place interaction.
|
623
|
+
|
624
|
+
When using `--big-edit`, any downnested portals will be displayed on the edit page instead of on the list page.
|
625
|
+
|
626
|
+
Big edit makes all edit and magic button operations happen using `'data-turbo': false`, fully reloading the page and submitting HTML requests instead of TURBO_STREAM requests.
|
627
|
+
|
628
|
+
Likewise, the controller's `update` action always redirects instead of using Turbo.
|
629
|
+
|
630
|
+
|
631
|
+
### `--include=`
|
632
|
+
Separate field names by COMMA
|
633
|
+
|
634
|
+
If you specify an include list, it will be treated as a whitelist: no fields will be included unless specified on the include list.
|
635
|
+
|
636
|
+
`./bin/rails generate hot_glue:scaffold Account --include=first_name,last_name,company_name,created_at,kyc_verified_at`
|
637
|
+
|
638
|
+
You may not specify both include and exclude.
|
639
|
+
|
640
|
+
Include setting is affected by both specified grouping mode and smart layouts, explained below.
|
641
|
+
|
642
|
+
|
643
|
+
### `--exclude=`
|
644
|
+
(separate field names by COMMA)
|
645
|
+
|
646
|
+
By default, all fields are included unless they are on the default exclude list. (The default exclude list is `id`, `created_at`, `updated_at`, `encrypted_password`, `reset_password_token`, `reset_password_sent_at`, `remember_created_at`, `confirmation_token`, `confirmed_at`, `confirmation_sent_at`, `unconfirmed_email`.)
|
647
|
+
|
648
|
+
If you specify any exclude list, those excluded **and** the default exclude list will be excluded. (If you need any of the fields on the default exclude list, you must use `--include` instead.)
|
649
|
+
|
650
|
+
|
651
|
+
`./bin/rails generate hot_glue:scaffold Account --exclude=password`
|
652
|
+
|
653
|
+
|
654
|
+
### `--display-list-after-update`
|
655
|
+
|
656
|
+
After an update-in-place normally only the edit view is swapped out for the show view of the record you just edited.
|
657
|
+
|
658
|
+
Sometimes you might want to redisplay the entire list after you make an update (for example, if your action removes that record from the result set).
|
659
|
+
|
660
|
+
To do this, use flag `--display-list-after-update`. The update will behave like delete and re-fetch all the records in the result and tell Turbo to swap out the entire list.
|
678
661
|
|
679
662
|
|
680
663
|
### `--hawk=`
|
681
664
|
|
682
|
-
Hawk a foreign key that is not the object's owner to within a specified scope.
|
665
|
+
Hawk a foreign key that is not the object's owner to within a specified scope.
|
683
666
|
|
684
667
|
Assuming a Pet belong_to a :human, when building an Appointments scaffold,
|
685
668
|
you can hawk the `pet_id` to the current human's pets. (Whoever is the authentication object.)
|
@@ -693,16 +676,32 @@ The short form looks like this. It presumes there is a 'pets' association from `
|
|
693
676
|
|
694
677
|
This is covered in [Example #3 in the Hot Glue Tutorial](https://school.jfbcodes.com/8188)
|
695
678
|
|
696
|
-
To hawk to a scope that is not the currently authenticated user, use the long form with `{...}`
|
697
|
-
to specify the scope. Be sure to note to add the association name itself, like `users`:
|
679
|
+
To hawk to a scope that is not the currently authenticated user, use the long form with `{...}`
|
680
|
+
to specify the scope. Be sure to note to add the association name itself, like `users`:
|
698
681
|
|
699
682
|
`--hawk=user_id{current_user.family.users}`
|
700
683
|
|
701
|
-
This would hawk the Appointment's `user_id` key to any users who are within the scope of the
|
702
|
-
current_user's has_many association (so, for any other "my" family, would be `current_user.family.users`).
|
684
|
+
This would hawk the Appointment's `user_id` key to any users who are within the scope of the
|
685
|
+
current_user's has_many association (so, for any other "my" family, would be `current_user.family.users`).
|
703
686
|
|
704
687
|
This is covered in [Example #4 in the Hot Glue Tutorial](https://school.jfbcodes.com/8188)
|
705
688
|
|
689
|
+
### `--with-turbo-streams`
|
690
|
+
|
691
|
+
If and only if you specify `--with-turbo-streams`, your views will contain `turbo_stream_from` directives. Whereas your views will always contain `turbo_frame_tags` (whether or not this flag is specified) and will use the Turbo stream replacement mechanism for non-idempotent actions (create & update). This flag just brings the magic of live-reload to the scaffold interfaces themselves.
|
692
|
+
|
693
|
+
**_To test_**: Open the same interface in two separate browser windows. Make an edit in one window and watch your edit appear in the other window instantly.
|
694
|
+
|
695
|
+
This happens using two interconnected mechanisms:
|
696
|
+
|
697
|
+
1) by default, all Hot Glue scaffold is wrapped in `turbo_frame_tag`s. The id of these tags is your namespace + the Rails dom_id(...). That means all Hot Glue scaffold is namespaced to the namespaces you use and won't collide with other turbo_frame_tag you might be using elsewhere
|
698
|
+
|
699
|
+
2) by appending **model callbacks**, we can automatically broadcast updates to the users who are using the Hot Glue scaffold. The model callbacks (after_update_commit and after_destroy_commit) get appended automatically to the top of your model file. Each model callback targets the scaffold being built (so just this scaffold), using its namespace, and renders the line partial (or destroys the content in the case of delete) from the scaffolding.
|
700
|
+
|
701
|
+
please note that *creating* and *deleting* do not yet have a full & complete implementation: Your pages won't re-render the pages being viewed cross-peer (that is, between two users using the app at the same time) if the insertion or deletion causes the pagination to be off for another user.
|
702
|
+
|
703
|
+
|
704
|
+
|
706
705
|
|
707
706
|
### `--plural=`
|
708
707
|
|
@@ -712,7 +711,7 @@ Only use for non-standard plurlizations, and be sure to pass it as TitleCase (as
|
|
712
711
|
An better alternative is to define the non-standard plurlizations globally in your app, which Hot Glue will respect.
|
713
712
|
|
714
713
|
Make a file at `config/initializers/inflections.rb`
|
715
|
-
|
714
|
+
|
716
715
|
Add new inflection rules using the following format:
|
717
716
|
```
|
718
717
|
ActiveSupport::Inflector.inflections do |inflect|
|
@@ -722,32 +721,256 @@ end
|
|
722
721
|
```
|
723
722
|
|
724
723
|
|
724
|
+
### `--ujs_syntax=true` (Default is set automatically based on whether you have turbo-rails installed)
|
725
725
|
|
726
|
+
If you are pre-Turbo (UJS), your delete buttons will come out like this:
|
727
|
+
`data: {'confirm': 'Are you sure you want to delete....?'}`
|
726
728
|
|
727
|
-
|
728
|
-
|
729
|
+
If you are Turbo (Rails 7 or Rails 6 with proactive Turbo-Rails install), your delete button will be:
|
730
|
+
`data: {'turbo-confirm': 'Are you sure you want to delete....?'}`
|
729
731
|
|
730
|
-
|
732
|
+
If you specify the flag, you preference will be used. If you leave the flag off, Hot Glue will detect the presence of Turbo-Rails in your app.
|
731
733
|
|
732
|
-
If you
|
734
|
+
**WARNING**: If you created a new Rails app since October 2021 and you have the yanked turbo-rails Gems on your local machine,
|
735
|
+
you will have some bugs with the delete buttons and also not be on the latest version of turbo-rails.
|
733
736
|
|
737
|
+
Make sure to uninstall the yanked 7.1.0 and 7.1.1 from your machine with `gem uninstall turbo-rails`
|
738
|
+
and also fix any Rails apps created since October 2021 by fixing the Gemfile. Details here:
|
739
|
+
https://stackoverflow.com/questions/70671324/new-rails-7-turbo-app-doesnt-show-the-data-turbo-confirm-alert-messages-dont-f
|
734
740
|
|
735
|
-
`./bin/rails generate hot_glue:scaffold Account --exclude=password`
|
736
741
|
|
742
|
+
### `--magic-buttons=`
|
743
|
+
If you pass a list of magic buttons (separated by commas), they will appear in the button area on your list.
|
737
744
|
|
738
|
-
|
739
|
-
Separate field names by COMMA
|
745
|
+
It will be assumed there will be corresponding bang methods on your models.
|
740
746
|
|
741
|
-
|
747
|
+
The bang (`!`) methods can respond in one of four ways:
|
748
|
+
|
749
|
+
• With true, in which case a generic success message will be shown in the flash notice (“Approved” or “Rejected” in this case)
|
750
|
+
|
751
|
+
• With false, in which case a generic error message will be shown in the flash alert (“Could not approve…”)
|
752
|
+
|
753
|
+
• With a string, which will be assumed to be a “success” case, and will be passed to the front-end in the alert notice.
|
754
|
+
|
755
|
+
• Raise an ActiveRecord exception
|
756
|
+
|
757
|
+
This means you can be a somewhat lazy about your bang methods, but keep in mind the truth operator compares boolean true NOT any object is truth. So your return object must either be actually true (boolean), or an object that is string or string-like (responds to .to_s). Want to just say it didn’t work? Return false. Want to just say it was OK? Return true. Want to say it was successful but provide a more detailed response? Return a string.
|
758
|
+
|
759
|
+
Finally, you can raise an ActiveRecord error which will also get passed to the user in the flash alert area.
|
760
|
+
|
761
|
+
For more information see [Example 6 in the Tutorial](https://school.jfbcodes.com/8188)
|
762
|
+
|
763
|
+
You can also define methods on your model that have the same name as the button with `_able?` at the end.
|
764
|
+
(Prior to v0.6.20, these methods expected names with `able?` but no underscore.)
|
765
|
+
|
766
|
+
The button will be display as disabled if the method returns false.
|
767
|
+
|
768
|
+
|
769
|
+
|
770
|
+
### `--related-sets=`
|
771
|
+
|
772
|
+
Used to show a checkbox set of related records. The relationship should be a `has_and_belongs_to_many` or a `has_many through:` from the object being built.
|
773
|
+
|
774
|
+
Consider the classic example of three tables: users, user_roles, and roles
|
775
|
+
|
776
|
+
User `has_many :user_roles` and `has_many :roles, through: :user_roles`
|
777
|
+
UserRole `belongs_to :user` and `belongs_to :role`
|
778
|
+
and Role `has_many :user_roles` and `has_many :user, through: :user_roles`
|
779
|
+
|
780
|
+
We'll generate a scaffold to edit the users table. A checkbox set of related roles will also appear to allow editing of roles. (In this example, the only field to be edited is the email field.)
|
781
|
+
|
782
|
+
```
|
783
|
+
rails generate hot_glue:scaffold User --related-sets=roles --include=email,roles --gd
|
784
|
+
```
|
785
|
+
|
786
|
+
Note this leaves open a privileged escalation attack (a security vulnerability).
|
787
|
+
|
788
|
+
To fix this, you'll need to use Pundit with special syntax designed for this purpose. Please see [Example #17 in the Hot Glue Tutorial](https://school.jfbcodes.com/8188)
|
789
|
+
|
790
|
+
• Remember you model should have `accepts_nested_attributes_for :roles, allow_destroy: true`
|
791
|
+
|
792
|
+
• If you are using an --include list (not auto-detect or smart layout), be sure to treat the tags as-if it was one field on your layout and insert it according to where you want it.
|
793
|
+
|
794
|
+
• Each related set can take two additional parameters: specify the label to use as the related label using curly braces `{`...`}`, and specify any hawk scope to be applied to the displayed list of associated objects
|
795
|
+
(like the --hawk, with the need to explicitly call `--hawk`) using `[`...`]`
|
796
|
+
Both parameters are optional. If the label field is unspecified, it will default to `label`.
|
797
|
+
If the 2nd parameter is unspecified, it will display all records in the related table, with the base class + `.all`
|
798
|
+
|
799
|
+
If the 1st parameter is left off, still use square braces `[...]` for the hawk.
|
800
|
+
|
801
|
+
Example:
|
802
|
+
`rails generate hot_glue:scaffold User --nested=company --related-sets=roles{name}[company.roles] --include=email,roles --gd`
|
803
|
+
|
804
|
+
This shows the related set of `roles` using the field named `name` on the role object to display its name. Only the roles associated with the current company via the `company.roles` association.
|
805
|
+
Notice that here `company` must be in scope, which can either be supplied by you in the base class, or in this example we have nested User within Company (so its nest path would be different than the example above), which would put `company` in the scope of the build.
|
806
|
+
|
807
|
+
|
808
|
+
### `--factory-creation={ ... }`
|
809
|
+
|
810
|
+
The code you specify inside of `{` and `}` will be used to generate a new object. The factory should instantiate with any arguments (I suggest Ruby keyword arguments) and must provide a method that is the name of the thing.
|
811
|
+
|
812
|
+
You may use semi-colons to separate multiple lines of code.
|
813
|
+
|
814
|
+
For example, a user Factory might be called like so:
|
815
|
+
|
816
|
+
`./bin/rails generate hot_glue:scaffold User --factory-creation={factory = UserFactory.new(params: user_params)} --gd`
|
817
|
+
|
818
|
+
(Note we are relying on the `user_params` method provided by the controller.)
|
819
|
+
|
820
|
+
You must do one of two things:
|
821
|
+
|
822
|
+
1) In the code you specify, set an instance variable `@user` to be the newly created thing. (Your code should contain something like `@thing = ` to trigger this option.)
|
823
|
+
2) Make a local variable called `factory` **and** have a method of the name of the object (`user`) on a local variable called `factory` that your code created
|
824
|
+
|
825
|
+
(The code example above is the option for #2 because it does not contain `@user =`)
|
826
|
+
|
827
|
+
If using number #2, Hot Glue will append this to the code specified:
|
828
|
+
```
|
829
|
+
@user = factory.user
|
830
|
+
```
|
831
|
+
|
832
|
+
|
833
|
+
Here's a sample UserFactory that will create a new user only if one with a matching email address doesn't exist. (Otherwise, it will update the existing record.)
|
834
|
+
Your initialize method can take any params you need it to, and using this pattern your business logic is applied consistently throughout your app. (You must, of course, use your Factory everywhere else in your app too.)
|
835
|
+
|
836
|
+
```
|
837
|
+
class UserFactory
|
838
|
+
attr_reader :user
|
839
|
+
attr_accessor :email
|
840
|
+
|
841
|
+
def initialize(params: {})
|
842
|
+
user = User.find_or_create_by(email: params[:email])
|
843
|
+
|
844
|
+
user.update(params)
|
845
|
+
if user.new_record?
|
846
|
+
# do special new user logic here, like sending an email
|
847
|
+
end
|
848
|
+
end
|
849
|
+
end
|
850
|
+
```
|
851
|
+
|
852
|
+
|
853
|
+
be sure your factory code creates a local variable that follows this name
|
854
|
+
|
855
|
+
**<downcase association name>**_factory.<downcase association name>
|
856
|
+
|
857
|
+
Thus, your factory object must have a method of the same name as the factory being created which returns the thing that got created.
|
858
|
+
(It can do the creation either on instantiation or when calling that method)
|
859
|
+
|
860
|
+
For example, assuming the example from above, we are going to do the lookup ourselves inside of our own `AgentFactory` object.)
|
861
|
+
|
862
|
+
```
|
863
|
+
factory = AgentFactory.new(find_or_create_by_email: agent_company_params[:__lookup_email],
|
864
|
+
params: modified_params)
|
865
|
+
```
|
866
|
+
|
867
|
+
Here the new AgentFactory will receive any variables by keyword argument, and since you're specifying the calling code here, Hot Glue does not dictate your factory's setup.
|
868
|
+
However, two special variables are in scope which you can use in your calling code.
|
869
|
+
|
870
|
+
`*_params` (where * is the name of the thing you are building)
|
871
|
+
`modified_params`
|
872
|
+
|
873
|
+
Either one must be received by your factory for your factory to create data based off the inputted data.
|
874
|
+
|
875
|
+
Remember, `*_params` has the input params passed only the through the sanitizer, and modified_params has it passed through the timezone aware mechanism and other Hot Glue-specific defaults.
|
876
|
+
|
877
|
+
Always:
|
878
|
+
• In your factory calling code, assign the variable `factory = ` (do not use a different variable name),
|
879
|
+
• Write a factory object with a `new` method that received the paramters you are specifying in your calling code,
|
880
|
+
• Be sure your factory has an _instance method_ a method with the **same name** of the built object, which hot glue will call next:
|
881
|
+
|
882
|
+
`@agent = factory.agent`
|
883
|
+
|
884
|
+
Don't include this last line in your factory code.
|
885
|
+
|
886
|
+
|
887
|
+
|
888
|
+
## On/off switches
|
889
|
+
|
890
|
+
### `--specs-only`
|
891
|
+
|
892
|
+
Produces ONLY the controller spec file, nothing else.
|
893
|
+
|
894
|
+
### `--no-specs`
|
895
|
+
|
896
|
+
Produces all the files except the spec file.
|
897
|
+
|
898
|
+
### `--no-paginate` (default: false)
|
899
|
+
|
900
|
+
Omits pagination. (All list views have pagination by default.)
|
901
|
+
|
902
|
+
### `--paginate-per-page-selector` (default: false)
|
903
|
+
|
904
|
+
Show a small drop-down below the list to let the user choose 10, 25, or 100 results per page.
|
905
|
+
|
906
|
+
|
907
|
+
### `--no-list`
|
908
|
+
|
909
|
+
Omits list action. Only makes sense to use this if want to create a view where you only want the create button or to navigate to the update screen alternative ways. (The new/create still appears, as well the edit, update & destroy actions are still created even though there is no natural way to navigate to them.)
|
910
|
+
|
911
|
+
### `--no-create`
|
912
|
+
|
913
|
+
Omits new & create actions.
|
914
|
+
|
915
|
+
### `--no-delete`
|
916
|
+
|
917
|
+
Omits delete button & destroy action.
|
918
|
+
|
919
|
+
### `--no-controller`
|
920
|
+
|
921
|
+
Omits controller.
|
922
|
+
|
923
|
+
### `--no-list`
|
924
|
+
|
925
|
+
Omits list views.
|
926
|
+
|
927
|
+
|
928
|
+
|
929
|
+
### `--no-list-label`
|
930
|
+
Omits list LABEL itself above the list. (Do not confuse with the list heading which contains the field labels.)
|
931
|
+
|
932
|
+
Note that list labels may be automatically omitted on downnested scaffolds.
|
933
|
+
|
934
|
+
|
935
|
+
### `--no-list-heading`
|
936
|
+
|
937
|
+
Omits the heading of column names that appears above the 1st row of data.
|
938
|
+
|
939
|
+
|
940
|
+
|
941
|
+
|
942
|
+
|
943
|
+
## Nav Templates and `--no-nav-menu`
|
944
|
+
At the namespace level, you can have a file called `_nav.html.erb` to create tabbed bootstrap nav
|
945
|
+
|
946
|
+
To create the file for the first time (at each namespace), start by running
|
947
|
+
```
|
948
|
+
bin/rails generate hot_glue:nav_template --namespace=xyz
|
949
|
+
```
|
950
|
+
|
951
|
+
This will append the file `_nav.html.erb` to the views folder at `views/xyz`. To begin, this file contains only the following:
|
952
|
+
|
953
|
+
```
|
954
|
+
<ul class='nav nav-tabs'>
|
955
|
+
</ul>
|
956
|
+
```
|
957
|
+
|
958
|
+
Once the file is present, any further builds in this namespace will:
|
959
|
+
|
960
|
+
1) Append to this `_nav.html.erb` file, adding a tab for the new built scaffold
|
961
|
+
2) On the list view of the scaffold being built, it will include a render to the _nav partial, passing the name of the currently-viewed thing as the local variable `nav` (this is how the nav template knows which tab to make active).
|
962
|
+
```
|
963
|
+
<%= render partial: "owner/nav", locals: {nav: "things"} %>
|
964
|
+
```
|
965
|
+
(In this example `owner/` is the namespace and `things` is the name of the scaffold being built)
|
742
966
|
|
743
|
-
|
967
|
+
### `--no-nav-menu`
|
744
968
|
|
745
|
-
|
969
|
+
To suppress this behavior, add `--no-nav-menu` to the build command and the _nav template will not be touched.
|
746
970
|
|
747
|
-
Include setting is affected by both specified grouping mode and smart layouts, explained below.
|
748
971
|
|
749
972
|
|
750
|
-
|
973
|
+
## Layout & Manipulation features
|
751
974
|
|
752
975
|
To specify grouped columns, separate COLUMNS by a COLON, then separate fields with commas. Specified groupings work like smart layouts (see below), except you drive which groupings make up the columns.
|
753
976
|
|
@@ -818,6 +1041,20 @@ This is what would happen if 9 fields, specified in the order A,B,C,D,E,F,G,H,I,
|
|
818
1041
|
(If you had a number of fields that wasn't easily divisible by the number of columns, it would leave the final column one or a few fields short of the others.)
|
819
1042
|
|
820
1043
|
|
1044
|
+
### `--new-button-position` (above, below; default: above)
|
1045
|
+
Show the new button above or below the list.
|
1046
|
+
|
1047
|
+
|
1048
|
+
|
1049
|
+
### `--button-icons` (default is no icons)
|
1050
|
+
You can specify this either as builder flag or as a config setting (in `config/hot_glue.yml`)
|
1051
|
+
Use `font-awesome` for Font Awesome or `none` for no icons.
|
1052
|
+
|
1053
|
+
### `--include-object-names`
|
1054
|
+
When you are "Editing X" we specify that X is a ___ (author, book, room, etc)
|
1055
|
+
e.g. "Editing author Edgar Allan Poe" vs "Editing Edgar Allan Poe"
|
1056
|
+
Can also be specified globally in `config/hot_glue.yml`
|
1057
|
+
|
821
1058
|
|
822
1059
|
### `--modify=field1{...},field2{...}`
|
823
1060
|
|
@@ -852,7 +1089,8 @@ Notice that each modifiers can be used with specific field types.
|
|
852
1089
|
| tinymce | applies to text fields only, be sure to setup TineMCE globally | text fields only | | |
|
853
1090
|
| typeahead | turns a foreign key (only) into a searchable typeahead field | foreign keys only | | |
|
854
1091
|
| timezone | turns a string (varchar) into a drop down of timezones | foreign keys only | | |
|
855
|
-
|
|
1092
|
+
| include_blank | special modifier for association fields, adds include_blank to the created dropdown | |
|
1093
|
+
| none | special modifier for using badges |
|
856
1094
|
|
857
1095
|
Except for "(truthy label)" and "(falsy label)" which use the special syntax, use the modifier _exactly_ as it is named.
|
858
1096
|
|
@@ -948,7 +1186,55 @@ When building a non-Gd controller with a `--alt-foreign-key-lookup`, if you don'
|
|
948
1186
|
|
949
1187
|
To fix, either hawk the field or use with a factory creation pattern. This is because the associated object is on your graph but Hot Glue doesn't know how to securly load it without knowing its relationship to the current user. Use the `--hawk` mechanism to specify that relationship, and the lookup mechanism will integrate nicely.
|
950
1188
|
|
1189
|
+
## Labels
|
1190
|
+
|
1191
|
+
### `--label=`
|
1192
|
+
|
1193
|
+
The general name of the thing, will be applied as "New ___" for the new button & form. Will be *pluralized* for list label heading, so if the word has a non-standard pluralization, be sure to specify it in `config/inflictions.rb`
|
1194
|
+
|
1195
|
+
If you specify anything explicitly, it will be used.
|
1196
|
+
If not, a specification that exists as `@@tabel_label_singular` from the Model will be used.
|
1197
|
+
If this does not exist, the Titleized (capitalized) version of the model name.
|
1198
|
+
|
1199
|
+
### `--list-label-heading=`
|
1200
|
+
The plural of the list of things at the top of the list.
|
1201
|
+
If not, a specification that exists as `@@tabel_label_plural` from the Model will be used.
|
1202
|
+
If this does not exist, the UPCASE (all-uppercase) version of the model name.
|
1203
|
+
|
1204
|
+
### `--new-button-label=`
|
1205
|
+
Overrides the button on the list that the user clicks onto to create a new record.
|
1206
|
+
(Default is to follow the same rules described in the `--label` option but with the word "New" prepended.)
|
1207
|
+
|
1208
|
+
### `--new-form-heading=`
|
1209
|
+
The text at the top of the new form that appears when the new input entry is displayed.
|
1210
|
+
(Default follows the same rules described in the `--label` option but with the word "New" prepended.)
|
1211
|
+
|
1212
|
+
|
1213
|
+
|
1214
|
+
### `--form-labels-position=` (default: `after`; options are **before**, **after**, and **omit**)
|
1215
|
+
By default form labels appear after the form inputs. To make them appear before or omit them, use this flag.
|
1216
|
+
|
1217
|
+
See also `--form-placeholder-labels` to use placeolder labels.
|
1218
|
+
|
1219
|
+
|
1220
|
+
### `--form-placeholder-labels=` (default: false)
|
1221
|
+
|
1222
|
+
When set to true, fields, numbers, and text areas will have placeholder labels.
|
1223
|
+
Will not apply to dates, times, datetimes, dropdowns (enums + foreign keys), or booleans.
|
1224
|
+
|
1225
|
+
See also setting `--form-labels-position` to control position or omit normal labels.
|
1226
|
+
|
1227
|
+
### `--inline-list-labels=` (before, after, omit; default: omit)
|
1228
|
+
|
1229
|
+
Determines if field label will appear on the LIST VIEW. Note that because Hot Glue has no separate show route or page, this affects the `_show` template which is rendered as a partial from the LIST view.
|
1230
|
+
|
1231
|
+
Because the labels are already in the heading, this is `omit` by default. (Use with `--no-list-heading` to omit the labels in the list heading.)
|
1232
|
+
|
1233
|
+
Use `before` to make the labels come before or `after` to make them come after. See Version 0.5.1 release notes for an example.
|
1234
|
+
|
1235
|
+
|
951
1236
|
|
1237
|
+
## Access Control & Field Visibility Features
|
952
1238
|
|
953
1239
|
### `--pundit`
|
954
1240
|
If you enable Pundit, your controllers will look for a Policy that matches the name of the thing being built.
|
@@ -1029,9 +1315,6 @@ If provided, the output code looks something like (in this example, showing the
|
|
1029
1315
|
raise Pundit::NotAuthorizedError if ! UniqueInvoicePolicy.edit?
|
1030
1316
|
```
|
1031
1317
|
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1035
1318
|
### `--show-only=`
|
1036
1319
|
(separate field names by COMMA)
|
1037
1320
|
|
@@ -1167,270 +1450,92 @@ export default class extends Controller {
|
|
1167
1450
|
}
|
1168
1451
|
|
1169
1452
|
formSubmit(event) {
|
1170
|
-
this.rawSourceTarget.value = this.view.state.doc.toString();
|
1171
|
-
}
|
1172
|
-
}
|
1173
|
-
```
|
1174
|
-
|
1175
|
-
Notice we are also using `--stimmify` to decorate the form with a Stimulus controller.
|
1176
|
-
|
1177
|
-
The code above uses Code Mirror to act as a code editor, which requires pulling the value off the hidden form element (putting it into the code mirror interface) and pushing it back into the hidden form element when the Submit button is clicked.
|
1178
|
-
|
1179
|
-
### `--invisible=`
|
1180
|
-
### `--create-invisible=`
|
1181
|
-
### `--update-invisible=`
|
1182
|
-
(two lists are maintained: create and update. any fields on the unnamed invisible list will be invisible on both create and update actions)
|
1183
|
-
|
1184
|
-
If a field is on the invisible list, the policy will be checked for a `_able?` method.
|
1185
|
-
If this method returns true, displayed like a normal editable field.
|
1186
|
-
|
1187
|
-
If the policy doesn't allow editing, this field will be made invisible: completely removed from the form.
|
1188
|
-
|
1189
|
-
Like show only, these will check for `*_able?` methods on the object or Policy and will only display the field if the method returns true.
|
1190
|
-
|
1191
|
-
It will also block the field from being updated on the backend, so don't use this if you want to create a hidden_field tag but still allow the controller to update it. (For that, see `--hidden=`.)
|
1192
|
-
|
1193
|
-
|
1194
|
-
Like show-only, note special behavior with pundit.
|
1195
|
-
|
1196
|
-
A field can be marked invisible and show-only, in which case the invisible rule take prescedence when the access control is denied (field removed from form) but th show-only rule takes prescedance when the access control is granted,
|
1197
|
-
|
1198
|
-
Hidden can be used with invisible. In this case, the access control will be applied to the field. When editable, the hidden field output will be used.
|
1199
|
-
|
1200
|
-
Must be used with Pundit. Without pundit, see other alternatives: hidden fields, show only fields, or just removing the field completely by using the exclude list or leaving it off the include list.
|
1201
|
-
|
1202
|
-
| | | Pundit | | |
|
1203
|
-
|----------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---|---|
|
1204
|
-
| fields not on any of these other lists | Included as editable on both the create + update actions or as viewable if Policy access control returns false | Without pundit, everything is editable. With pundit, the Policy is checked for an `_able?` method for each field at build time. When this method returns false, the field is viewable. | | |
|
1205
|
-
| Show only | Viewable only (non-editable) on both create + edit/update overriding whatever Policy returns. | Without pundit, this field is viewable only. Overrides whatever policy returns. | | |
|
1206
|
-
| Update Show only | Viewable only on the update screen or if Pundit doesn't allow editing | Same as above but apply only to the update screen. | | |
|
1207
|
-
| Invisible | Displayed in the HTML output and received by the controller for create or update action but shown as a hidden_field on the HTML, invisible to the user. You should use this if you want to construct your own form input or set the value via Javascript | Cannot be used without Pundit. With pundit, fields editable via the policy are editable on the screen (unless they are also show-only, in which case they are visible) and non-editable via the policy are made invisible (completely removed) from the screen. | | |
|
1208
|
-
| Hidden | Not displayed or updatable if the field respond false to _able? or the Policy doesn't allow. | Unrelated to pundit Policy | | |
|
1209
|
-
| | | | | |
|
1210
|
-
|
1211
|
-
|
1212
|
-
Pundit calls the policy for every action, including the index action. In these cases it passes an association instead of a single record.
|
1213
|
-
|
1214
|
-
Normally, field level access control that applies for show only and invisible is affects each record, in the list view, new page, or edit page.
|
1215
|
-
|
1216
|
-
For that reason, your `_able?` methods may check the individual records, except in the case of the `index` action for which the pundit policy has no individual record.
|
1217
|
-
|
1218
|
-
For these special cases, you might want to hide the entire column itself when the `_able?` return false on the association call (not in the context any of any record).
|
1219
|
-
|
1220
|
-
You'd want to do this for a global switch, unrelated to the record, to show or hide the column itself. When using invisible fields, the column headings are check against the policy's `_able?` fields, too, but since this called on the list, the entire set of objects being returned by the list is passed to the policy. (For example `policy(@things)`)
|
1221
|
-
|
1222
|
-
This makes it impossible to make your access control depend solely on the record itself, so can be used only for context-based access control that is applied to the column headings.
|
1223
|
-
|
1224
|
-
|
1225
|
-
In a case like this, you'll want the `_able?` method on the policy to know if the object is a record or many records.
|
1226
|
-
|
1227
|
-
```
|
1228
|
-
class ThingPolicy < ApplicationPolicy
|
1229
|
-
attr_reader :user, :thing
|
1230
|
-
|
1231
|
-
def initialize(user, thing)
|
1232
|
-
@user = user
|
1233
|
-
@thing = thing
|
1234
|
-
end
|
1235
|
-
|
1236
|
-
def ccc_able?
|
1237
|
-
if thing.is_a?(Thing) # a thing is not a Thing when it is an active relation of many things
|
1238
|
-
!!thing.bbb # show or hide ccc based on whether or not bbb is true
|
1239
|
-
else
|
1240
|
-
current_user.is_admin? # show or hide the column heading for ccc based on whether or not the current user is an admin
|
1241
|
-
end
|
1242
|
-
end
|
1243
|
-
# more policy method here ...
|
1244
|
-
end
|
1245
|
-
```
|
1246
|
-
Here, for all CRUD actions, the object is a thing, and so the editablility of ccc is dependent on bbb being true.
|
1247
|
-
If thing is not a Thing, then it is active relation (so this applies to the column headings) and we show it only to admins.
|
1248
|
-
|
1249
|
-
Remember, since the `_able?` methods are not otherwise called during Pundit's index cycle, this applies only to the list column headings and has no bearing on create, update, read, delete for which the access control can be anything you want that is available to the Poilcy.
|
1250
|
-
|
1251
|
-
|
1252
|
-
|
1253
|
-
### `--ujs_syntax=true` (Default is set automatically based on whether you have turbo-rails installed)
|
1254
|
-
|
1255
|
-
If you are pre-Turbo (UJS), your delete buttons will come out like this:
|
1256
|
-
`data: {'confirm': 'Are you sure you want to delete....?'}`
|
1257
|
-
|
1258
|
-
If you are Turbo (Rails 7 or Rails 6 with proactive Turbo-Rails install), your delete button will be:
|
1259
|
-
`data: {'turbo-confirm': 'Are you sure you want to delete....?'}`
|
1260
|
-
|
1261
|
-
If you specify the flag, you preference will be used. If you leave the flag off, Hot Glue will detect the presence of Turbo-Rails in your app.
|
1262
|
-
|
1263
|
-
**WARNING**: If you created a new Rails app since October 2021 and you have the yanked turbo-rails Gems on your local machine,
|
1264
|
-
you will have some bugs with the delete buttons and also not be on the latest version of turbo-rails.
|
1265
|
-
|
1266
|
-
Make sure to uninstall the yanked 7.1.0 and 7.1.1 from your machine with `gem uninstall turbo-rails`
|
1267
|
-
and also fix any Rails apps created since October 2021 by fixing the Gemfile. Details here:
|
1268
|
-
https://stackoverflow.com/questions/70671324/new-rails-7-turbo-app-doesnt-show-the-data-turbo-confirm-alert-messages-dont-f
|
1269
|
-
|
1270
|
-
|
1271
|
-
### `--magic-buttons=`
|
1272
|
-
If you pass a list of magic buttons (separated by commas), they will appear in the button area on your list.
|
1273
|
-
|
1274
|
-
It will be assumed there will be corresponding bang methods on your models.
|
1275
|
-
|
1276
|
-
The bang (`!`) methods can respond in one of four ways:
|
1277
|
-
|
1278
|
-
• With true, in which case a generic success message will be shown in the flash notice (“Approved” or “Rejected” in this case)
|
1279
|
-
|
1280
|
-
• With false, in which case a generic error message will be shown in the flash alert (“Could not approve…”)
|
1281
|
-
|
1282
|
-
• With a string, which will be assumed to be a “success” case, and will be passed to the front-end in the alert notice.
|
1283
|
-
|
1284
|
-
• Raise an ActiveRecord exception
|
1285
|
-
|
1286
|
-
This means you can be a somewhat lazy about your bang methods, but keep in mind the truth operator compares boolean true NOT any object is truth. So your return object must either be actually true (boolean), or an object that is string or string-like (responds to .to_s). Want to just say it didn’t work? Return false. Want to just say it was OK? Return true. Want to say it was successful but provide a more detailed response? Return a string.
|
1287
|
-
|
1288
|
-
Finally, you can raise an ActiveRecord error which will also get passed to the user in the flash alert area.
|
1289
|
-
|
1290
|
-
For more information see [Example 6 in the Tutorial](https://school.jfbcodes.com/8188)
|
1291
|
-
|
1292
|
-
You can also define methods on your model that have the same name as the button with `_able?` at the end.
|
1293
|
-
(Prior to v0.6.20, these methods expected names with `able?` but no underscore.)
|
1294
|
-
|
1295
|
-
The button will be display as disabled if the method returns false.
|
1296
|
-
|
1297
|
-
|
1298
|
-
### `--downnest=`
|
1299
|
-
|
1300
|
-
Automatically create subviews down your object tree. This should be the name of a has_many relationship based from the current object.
|
1301
|
-
You will need to build scaffolding with the same name for the related object as well. On the list view, the object you are currently building will be built with a sub-view list of the objects related from the given line.
|
1302
|
-
|
1303
|
-
The downnested child table (not to be confused with this object's `--nested` setting, where you are specifying this object's _parents_) is called a **child portal**. When you create a record in the child portal, the related record is automatically set to be owned by its parent (as specified by `--nested`). For an example, see the [v0.4.7 release notes](https://github.com/jasonfb/hot-glue/releases/tag/v0.4.7).
|
1304
|
-
|
1305
|
-
Can now be created with more space (wider) by adding a `+` to the end of the downnest name
|
1306
|
-
- e.g. `--downnest=abc+,xyz`
|
1307
|
-
|
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
|
1309
|
-
|
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
|
-
|
1350
|
-
### `--record-scope=`
|
1351
|
-
|
1352
|
-
Record scope allows you to apply a model based scope for the controller being generated.
|
1353
|
-
This is applied on top of all other scopes, searches, and modifiers applied to the built controller.
|
1354
|
-
|
1355
|
-
`bin/rails :generate hot_glue:scaffold Order --record-scope='.is_open'`
|
1356
|
-
|
1357
|
-
Be sure to use single quotes (`'`) and don't forget the dot (`.`) before your scope(s).
|
1358
|
-
|
1359
|
-
Make sure your Order model has a scope `is_open`, like so:
|
1360
|
-
|
1361
|
-
```
|
1362
|
-
scope :is_open, -> {where(state == 'open')}
|
1453
|
+
this.rawSourceTarget.value = this.view.state.doc.toString();
|
1454
|
+
}
|
1455
|
+
}
|
1363
1456
|
```
|
1364
1457
|
|
1365
|
-
|
1366
|
-
|
1458
|
+
Notice we are also using `--stimmify` to decorate the form with a Stimulus controller.
|
1367
1459
|
|
1460
|
+
The code above uses Code Mirror to act as a code editor, which requires pulling the value off the hidden form element (putting it into the code mirror interface) and pushing it back into the hidden form element when the Submit button is clicked.
|
1368
1461
|
|
1369
|
-
### `--related-sets=`
|
1370
1462
|
|
1371
|
-
|
1463
|
+
### `--invisible=`
|
1464
|
+
### `--create-invisible=`
|
1465
|
+
### `--update-invisible=`
|
1466
|
+
(two lists are maintained: create and update. any fields on the unnamed invisible list will be invisible on both create and update actions)
|
1372
1467
|
|
1373
|
-
|
1468
|
+
If a field is on the invisible list, the policy will be checked for a `_able?` method.
|
1469
|
+
If this method returns true, displayed like a normal editable field.
|
1374
1470
|
|
1375
|
-
|
1471
|
+
If the policy doesn't allow editing, this field will be made invisible: completely removed from the form.
|
1376
1472
|
|
1377
|
-
|
1473
|
+
Like show only, these will check for `*_able?` methods on the object or Policy and will only display the field if the method returns true.
|
1378
1474
|
|
1379
|
-
|
1380
|
-
rails generate hot_glue:scaffold User --related-sets=roles --include=email,roles --gd
|
1381
|
-
```
|
1475
|
+
It will also block the field from being updated on the backend, so don't use this if you want to create a hidden_field tag but still allow the controller to update it. (For that, see `--hidden=`.)
|
1382
1476
|
|
1383
|
-
Note this leaves open a privileged escalation attack (a security vulnerability).
|
1384
1477
|
|
1385
|
-
|
1478
|
+
Like show-only, note special behavior with pundit.
|
1386
1479
|
|
1480
|
+
A field can be marked invisible and show-only, in which case the invisible rule take prescedence when the access control is denied (field removed from form) but th show-only rule takes prescedance when the access control is granted,
|
1387
1481
|
|
1388
|
-
|
1482
|
+
Hidden can be used with invisible. In this case, the access control will be applied to the field. When editable, the hidden field output will be used.
|
1389
1483
|
|
1390
|
-
|
1484
|
+
Must be used with Pundit. Without pundit, see other alternatives: hidden fields, show only fields, or just removing the field completely by using the exclude list or leaving it off the include list.
|
1391
1485
|
|
1392
|
-
|
1393
|
-
|
1394
|
-
|
1486
|
+
| | | Pundit | | |
|
1487
|
+
|----------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---|---|
|
1488
|
+
| fields not on any of these other lists | Included as editable on both the create + update actions or as viewable if Policy access control returns false | Without pundit, everything is editable. With pundit, the Policy is checked for an `_able?` method for each field at build time. When this method returns false, the field is viewable. | | |
|
1489
|
+
| Show only | Viewable only (non-editable) on both create + edit/update overriding whatever Policy returns. | Without pundit, this field is viewable only. Overrides whatever policy returns. | | |
|
1490
|
+
| Update Show only | Viewable only on the update screen or if Pundit doesn't allow editing | Same as above but apply only to the update screen. | | |
|
1491
|
+
| Invisible | Displayed in the HTML output and received by the controller for create or update action but shown as a hidden_field on the HTML, invisible to the user. You should use this if you want to construct your own form input or set the value via Javascript | Cannot be used without Pundit. With pundit, fields editable via the policy are editable on the screen (unless they are also show-only, in which case they are visible) and non-editable via the policy are made invisible (completely removed) from the screen. | | |
|
1492
|
+
| Hidden | Not displayed or updatable if the field respond false to _able? or the Policy doesn't allow. | Unrelated to pundit Policy | | |
|
1493
|
+
| | | | | |
|
1395
1494
|
|
1396
|
-
### `--list-label-heading=`
|
1397
|
-
The plural of the list of things at the top of the list.
|
1398
|
-
If not, a specification that exists as `@@tabel_label_plural` from the Model will be used.
|
1399
|
-
If this does not exist, the UPCASE (all-uppercase) version of the model name.
|
1400
1495
|
|
1401
|
-
|
1402
|
-
Overrides the button on the list that the user clicks onto to create a new record.
|
1403
|
-
(Default is to follow the same rules described in the `--label` option but with the word "New" prepended.)
|
1496
|
+
Pundit calls the policy for every action, including the index action. In these cases it passes an association instead of a single record.
|
1404
1497
|
|
1405
|
-
|
1406
|
-
The text at the top of the new form that appears when the new input entry is displayed.
|
1407
|
-
(Default follows the same rules described in the `--label` option but with the word "New" prepended.)
|
1498
|
+
Normally, field level access control that applies for show only and invisible is affects each record, in the list view, new page, or edit page.
|
1408
1499
|
|
1409
|
-
|
1500
|
+
For that reason, your `_able?` methods may check the individual records, except in the case of the `index` action for which the pundit policy has no individual record.
|
1410
1501
|
|
1411
|
-
|
1412
|
-
By default form labels appear after the form inputs. To make them appear before or omit them, use this flag.
|
1502
|
+
For these special cases, you might want to hide the entire column itself when the `_able?` return false on the association call (not in the context any of any record).
|
1413
1503
|
|
1414
|
-
|
1504
|
+
You'd want to do this for a global switch, unrelated to the record, to show or hide the column itself. When using invisible fields, the column headings are check against the policy's `_able?` fields, too, but since this called on the list, the entire set of objects being returned by the list is passed to the policy. (For example `policy(@things)`)
|
1415
1505
|
|
1506
|
+
This makes it impossible to make your access control depend solely on the record itself, so can be used only for context-based access control that is applied to the column headings.
|
1416
1507
|
|
1417
|
-
### `--form-placeholder-labels=` (default: false)
|
1418
1508
|
|
1419
|
-
|
1420
|
-
Will not apply to dates, times, datetimes, dropdowns (enums + foreign keys), or booleans.
|
1509
|
+
In a case like this, you'll want the `_able?` method on the policy to know if the object is a record or many records.
|
1421
1510
|
|
1422
|
-
|
1511
|
+
```
|
1512
|
+
class ThingPolicy < ApplicationPolicy
|
1513
|
+
attr_reader :user, :thing
|
1423
1514
|
|
1424
|
-
|
1515
|
+
def initialize(user, thing)
|
1516
|
+
@user = user
|
1517
|
+
@thing = thing
|
1518
|
+
end
|
1425
1519
|
|
1426
|
-
|
1520
|
+
def ccc_able?
|
1521
|
+
if thing.is_a?(Thing) # a thing is not a Thing when it is an active relation of many things
|
1522
|
+
!!thing.bbb # show or hide ccc based on whether or not bbb is true
|
1523
|
+
else
|
1524
|
+
current_user.is_admin? # show or hide the column heading for ccc based on whether or not the current user is an admin
|
1525
|
+
end
|
1526
|
+
end
|
1527
|
+
# more policy method here ...
|
1528
|
+
end
|
1529
|
+
```
|
1530
|
+
Here, for all CRUD actions, the object is a thing, and so the editablility of ccc is dependent on bbb being true.
|
1531
|
+
If thing is not a Thing, then it is active relation (so this applies to the column headings) and we show it only to admins.
|
1427
1532
|
|
1428
|
-
|
1533
|
+
Remember, since the `_able?` methods are not otherwise called during Pundit's index cycle, this applies only to the list column headings and has no bearing on create, update, read, delete for which the access control can be anything you want that is available to the Poilcy.
|
1429
1534
|
|
1430
|
-
Use `before` to make the labels come before or `after` to make them come after. See Version 0.5.1 release notes for an example.
|
1431
1535
|
|
1432
1536
|
|
1433
|
-
|
1537
|
+
## Code Insertion Features
|
1538
|
+
'
|
1434
1539
|
Insert some code into the `new`, `create` or `update` action actions.
|
1435
1540
|
|
1436
1541
|
Wrapped in quotation marks when specified in the command line, and use semicolons to separate multiple lines of code.
|
@@ -1484,7 +1589,6 @@ Notice that a separate hook for code-after-create is also available, but that ha
|
|
1484
1589
|
Using both together `--code-after-new='@email_template.created_by_user = current_user' --code-after-create='@email_template.do_something'`
|
1485
1590
|
|
1486
1591
|
|
1487
|
-
|
1488
1592
|
```
|
1489
1593
|
def create
|
1490
1594
|
...
|
@@ -1499,13 +1603,13 @@ def create
|
|
1499
1603
|
account.reload
|
1500
1604
|
...
|
1501
1605
|
```
|
1606
|
+
TODO: build a solution for inserting code only in the `new` action but NOT the create action
|
1502
1607
|
|
1503
1608
|
|
1504
1609
|
|
1505
|
-
|
1506
|
-
|
1610
|
+
## Searching
|
1507
1611
|
|
1508
|
-
|
1612
|
+
### `--search=` (options: simple, set, false predicate, default: false)
|
1509
1613
|
If you specify `--search` to `set`, you will get a whole bar across the top of the list with search fields for each field.
|
1510
1614
|
Within the set, the search query is **_combinative_** ("and"), so records matching all criteria are shown as the **result set.**
|
1511
1615
|
For date pickers and time pickers, you need the additional Stimulus.
|
@@ -1558,69 +1662,60 @@ NOT IMPLEMENTED YET
|
|
1558
1662
|
TODO: implement me
|
1559
1663
|
|
1560
1664
|
|
1561
|
-
### `--attachments=`
|
1562
|
-
|
1563
|
-
#### ActiveStorage Quick Setup
|
1564
|
-
(For complete docs, refer to https://guides.rubyonrails.org/active_storage_overview.html)
|
1565
1665
|
|
1566
|
-
`
|
1567
|
-
(for videos `brew install ffmpeg`)
|
1666
|
+
### `--stimmify` or `--stimmify=xyz`
|
1568
1667
|
|
1569
|
-
|
1570
|
-
bundle add image_processing
|
1571
|
-
./bin/rails active_storage:install
|
1572
|
-
./bin/rails db:migrate
|
1573
|
-
```
|
1574
|
-
|
1575
|
-
Generate an images model:
|
1576
|
-
|
1577
|
-
`./bin/rails generate model Images name:string`
|
1668
|
+
Automatically build the new and edit form with `data-controller='xyz'` to attach stimulus
|
1578
1669
|
|
1579
|
-
|
1670
|
+
If you use the shorthand (specify no `=`) your stimulus controller's name will be inferred from the Singular form of the scaffolding being built, with dashes for underscores, and ending with `-form`
|
1580
1671
|
|
1581
|
-
```
|
1582
|
-
has_one_attached :avatar do |attachable|
|
1583
|
-
attachable.variant :thumb, resize_to_limit: [100, 100]
|
1584
|
-
end
|
1585
|
-
```
|
1586
|
-
Generate a Hot Glue scaffold with the attachment avatar appended to the field list (the shorthand syntax)
|
1587
1672
|
|
1588
|
-
|
1673
|
+
`@singular.gsub("_", "-") + "-form"`
|
1589
1674
|
|
1590
|
-
(
|
1675
|
+
(so a `BankAcccount` scaffold, with singular form `bank_account`, attaches Stimulus-to-html using
|
1676
|
+
`data-controller='bank-account-form'` using a Stimulis controller at `app/javascript/controllers/bank_account_form_controller.js` stimulus controller)
|
1591
1677
|
|
1592
|
-
#### Caveats:
|
1593
|
-
• If thumbnails aren't showing up, make sure you have
|
1594
1678
|
|
1595
|
-
|
1596
|
-
2) used an image that supports ActiveStorage "variable" mechanism. The supported types are png, gif, jpg, pjpeg, tiff, bmp, vnd.adobe.photoshop, vnd.microsoft.icon, webp. see https://stackoverflow.com/a/61971660/3163663
|
1597
|
-
To debug, make sure the object responds true to the variable? method.
|
1679
|
+
For example, `rails g hot_glue:scaffold Thing --stimmify` generates a form that looks like
|
1598
1680
|
|
1599
|
-
|
1681
|
+
```
|
1682
|
+
<%= form_with model: thing,
|
1683
|
+
url: things_path,
|
1684
|
+
html: {
|
1685
|
+
'data-controller': "thing-form"
|
1686
|
+
}
|
1687
|
+
%>
|
1688
|
+
...
|
1689
|
+
```
|
1600
1690
|
|
1601
|
-
|
1691
|
+
`rails g hot_glue:scaffold Thing --stimmify=xyz` generates a form that looks like
|
1692
|
+
```
|
1693
|
+
<%= form_with model: thing,
|
1694
|
+
url: things_path,
|
1695
|
+
html: {
|
1696
|
+
'data-controller': "xyz-form"
|
1697
|
+
}
|
1698
|
+
%>
|
1699
|
+
...
|
1700
|
+
```
|
1602
1701
|
|
1603
|
-
|
1702
|
+
Note that your fields also appended with `data-thing-target=abc` and also `data-thing-target=abcWrapper`
|
1604
1703
|
|
1605
|
-
|
1704
|
+
For a crash course on Stimulus, see
|
1705
|
+
https://jasonfleetwoodboldt.com/courses/rails-7-crash-course/rails-7-stimulus-js-basics-with-importmap-rails/
|
1606
1706
|
|
1607
|
-
|
1707
|
+
**You must make the stimulus controller yourself!!**
|
1608
1708
|
|
1609
|
-
|
1709
|
+
Create them with:
|
1610
1710
|
|
1611
|
-
|
1711
|
+
`rails g stimulus thing_form`
|
1612
1712
|
|
1613
|
-
|
1713
|
+
_Notice that Stimulus requires object registration when used in a Node environment._ For this reason, you should NOT create new files yourself by hand in the `app/javascript/controllers/` folder.
|
1614
1714
|
|
1615
|
-
|
1616
|
-
```
|
1617
|
-
has_one_attached :avatar do |attachable|
|
1618
|
-
attachable.variant :thumbnail, resize_to_limit: [100, 100]
|
1619
|
-
end
|
1620
|
-
```
|
1715
|
+
Instead, use the generator and the registry will be updated for you automatically.
|
1621
1716
|
|
1622
|
-
If using the long-form syntax with 1 parameter and Hot Glue does not find the specified variant declared in your attachment, it will stop and raise an error.
|
1623
1717
|
|
1718
|
+
## Attachments
|
1624
1719
|
|
1625
1720
|
#### `--attachments=` Long form syntax with 1st and 2nd parameters
|
1626
1721
|
|
@@ -1634,7 +1729,7 @@ Note: You must have a string field called `orig_filename`. It does not need to b
|
|
1634
1729
|
|
1635
1730
|
Note that the `orig_filename` is not part of the inputted parameters, it simply gets appended to the model **bypassing the Rails strong parameters mechanism**, which is why it is irrelevant if it is included in the field list and recommended that if you do include it, you make it show-only so as not to allow your users to edit or modify it.
|
1636
1731
|
|
1637
|
-
Note: The 1st and 2nd parameters may be left empty (use `||`) but the 3rd and 4th parameters must either be specified or the parameter must be left off.
|
1732
|
+
Note: The 1st and 2nd parameters may be left empty (use `||`) but the 3rd and 4th parameters must either be specified or the parameter must be left off.
|
1638
1733
|
|
1639
1734
|
#### `--attachments=` Long form syntax with 1st, 2nd, and 3rd parameters
|
1640
1735
|
|
@@ -1690,16 +1785,16 @@ config.active_storage.service = :amazon
|
|
1690
1785
|
|
1691
1786
|
|
1692
1787
|
#### For Direct Upload Support
|
1693
|
-
1.
|
1788
|
+
1.
|
1694
1789
|
```
|
1695
1790
|
yarn add @rails/activestorage
|
1696
1791
|
```
|
1697
1792
|
|
1698
|
-
2. You need a job runner like sidekiq or delayed_job. I recommend sidekiq. Make sure to have it
|
1793
|
+
2. You need a job runner like sidekiq or delayed_job. I recommend sidekiq. Make sure to have it
|
1699
1794
|
|
1700
1795
|
- Start sidekiq with `bundle exec sidekiq` while you are testing
|
1701
1796
|
|
1702
|
-
3. Install ActiveStorage JS using:
|
1797
|
+
3. Install ActiveStorage JS using:
|
1703
1798
|
|
1704
1799
|
`./bin/rails generate hot_glue:direct_upload_install`
|
1705
1800
|
|
@@ -1726,7 +1821,7 @@ This will 1) copy the dropzone_controller.js file into your app and 2) add the d
|
|
1726
1821
|
|
1727
1822
|
### Attach Stimulus JS Controllers to Your Forms with `--stimmify` or `--stimmify=xyz`
|
1728
1823
|
|
1729
|
-
Automatically build the new and edit form with `data-controller='xyz'` to attach cooresponding stimulus controllers.
|
1824
|
+
Automatically build the new and edit form with `data-controller='xyz'` to attach cooresponding stimulus controllers.
|
1730
1825
|
|
1731
1826
|
If you use the shorthand (specify no `=`) your stimulus controller's name will be inferred from the Singular form of the scaffolding beild built, with dashes for underscores, and ending with `-form`
|
1732
1827
|
|
@@ -1774,91 +1869,13 @@ https://jasonfleetwoodboldt.com/courses/rails-7-crash-course/rails-7-stimulus-js
|
|
1774
1869
|
|
1775
1870
|
|
1776
1871
|
|
1777
|
-
|
1778
|
-
|
1779
|
-
The code you specify inside of `{` and `}` will be used to generate a new object. The factory should instantiate with any arguments (I suggest Ruby keyword arguments) and must provide a method that is the name of the thing.
|
1780
|
-
|
1781
|
-
You may use semi-colons to separate multiple lines of code.
|
1782
|
-
|
1783
|
-
For example, a user Factory might be called like so:
|
1784
|
-
|
1785
|
-
`./bin/rails generate hot_glue:scaffold User --factory-creation={factory = UserFactory.new(params: user_params)} --gd`
|
1786
|
-
|
1787
|
-
(Note we are relying on the `user_params` method provided by the controller.)
|
1788
|
-
|
1789
|
-
You must do one of two things:
|
1790
|
-
|
1791
|
-
1) In the code you specify, set an instance variable `@user` to be the newly created thing. (Your code should contain something like `@thing = ` to trigger this option.)
|
1792
|
-
2) Make a local variable called `factory` **and** have a method of the name of the object (`user`) on a local variable called `factory` that your code created
|
1793
|
-
|
1794
|
-
(The code example above is the option for #2 because it does not contain `@user =`)
|
1795
|
-
|
1796
|
-
If using number #2, Hot Glue will append this to the code specified:
|
1797
|
-
```
|
1798
|
-
@user = factory.user
|
1799
|
-
```
|
1800
|
-
|
1801
|
-
|
1802
|
-
Here's a sample UserFactory that will create a new user only if one with a matching email address doesn't exist. (Otherwise, it will update the existing record.)
|
1803
|
-
Your initialize method can take any params you need it to, and using this pattern your business logic is applied consistently throughout your app. (You must, of course, use your Factory everywhere else in your app too.)
|
1804
|
-
|
1805
|
-
```
|
1806
|
-
class UserFactory
|
1807
|
-
attr_reader :user
|
1808
|
-
attr_accessor :email
|
1809
|
-
|
1810
|
-
def initialize(params: {})
|
1811
|
-
user = User.find_or_create_by(email: params[:email])
|
1812
|
-
|
1813
|
-
user.update(params)
|
1814
|
-
if user.new_record?
|
1815
|
-
# do special new user logic here, like sending an email
|
1816
|
-
end
|
1817
|
-
end
|
1818
|
-
end
|
1819
|
-
```
|
1820
|
-
|
1821
|
-
|
1822
|
-
be sure your factory code creates a local variable that follows this name
|
1823
|
-
|
1824
|
-
**<downcase association name>**_factory.<downcase association name>
|
1825
|
-
|
1826
|
-
Thus, your factory object must have a method of the same name as the factory being created which returns the thing that got created.
|
1827
|
-
(It can do the creation either on instantiation or when calling that method)
|
1828
|
-
|
1829
|
-
For example, assuming the example from above, we are going to do the lookup ourselves inside of our own `AgentFactory` object.)
|
1830
|
-
|
1831
|
-
```
|
1832
|
-
factory = AgentFactory.new(find_or_create_by_email: agent_company_params[:__lookup_email],
|
1833
|
-
params: modified_params)
|
1834
|
-
```
|
1835
|
-
|
1836
|
-
Here the new AgentFactory will receive any variables by keyword argument, and since you're specifying the calling code here, Hot Glue does not dictate your factory's setup.
|
1837
|
-
However, two special variables are in scope which you can use in your calling code.
|
1838
|
-
|
1839
|
-
`*_params` (where * is the name of the thing you are building)
|
1840
|
-
`modified_params`
|
1841
|
-
|
1842
|
-
Either one must be received by your factory for your factory to create data based off the inputted data.
|
1843
|
-
|
1844
|
-
Remember, `*_params` has the input params passed only the through the sanitizer, and modified_params has it passed through the timezone aware mechanism and other Hot Glue-specific defaults.
|
1845
|
-
|
1846
|
-
Always:
|
1847
|
-
• In your factory calling code, assign the variable `factory = ` (do not use a different variable name),
|
1848
|
-
• Write a factory object with a `new` method that received the paramters you are specifying in your calling code,
|
1849
|
-
• Be sure your factory has an _instance method_ a method with the **same name** of the built object, which hot glue will call next:
|
1850
|
-
|
1851
|
-
`@agent = factory.agent`
|
1852
|
-
|
1853
|
-
Don't include this last line in your factory code.
|
1854
|
-
|
1855
|
-
|
1856
|
-
# SPECIAL FEATURES
|
1872
|
+
# SPECIAL FEATURES DISCUSSION
|
1873
|
+
This section discusses features that don't correspond to a single option or flag.
|
1857
1874
|
|
1858
1875
|
|
1859
1876
|
## "Thing" Label
|
1860
1877
|
|
1861
|
-
Note that on a per
|
1878
|
+
Note that on a per-model basis, you can also globally omit the label or set a unique label value using
|
1862
1879
|
`@@table_label_singular` and `@@table_label_plural` on your model objects.
|
1863
1880
|
|
1864
1881
|
You have three options to specify labels explicitly with a string, and 1 option to specify a global name for which the words "Delete ___" and "New ___" will be added.
|
@@ -2144,16 +2161,96 @@ These automatic pickups for partials are detected at build time. This means that
|
|
2144
2161
|
|
2145
2162
|
|
2146
2163
|
# VERSION HISTORY
|
2164
|
+
#### 2025-08-22 - v0.6.24
|
2165
|
+
`--related-sets` fixes issue with related sets due to ruby syntax
|
2166
|
+
|
2167
|
+
• Each related set can take two additional parameters: specify the label to use as the related label using curly braces `{`...`}`, and specify any hawk scope to be applied to the displayed list of associated objects
|
2168
|
+
(like the --hawk, with the need to explicitly call `--hawk`) using `[`...`]`
|
2169
|
+
Both parameters are optional. If the label field `{...}` is unspecified, it will default to `label`.
|
2170
|
+
If the 2nd parameter `[...]` (hawk) is unspecified, the widget will display all records in the related table, using the base class + `.all`
|
2171
|
+
If the 1st parameter `{...}` is left off, still use square braces `[...]` for the hawk.
|
2172
|
+
|
2173
|
+
|
2174
|
+
Example:
|
2175
|
+
`rails generate hot_glue:scaffold User --nested=company --related-sets=roles{name}[company.roles] --include=email,roles --gd`
|
2176
|
+
|
2177
|
+
This shows the related set of `roles` using the field named `name` on the role object to display its name.
|
2178
|
+
|
2179
|
+
Only displays the roles associated with the current company via the `company.roles` association.
|
2180
|
+
|
2181
|
+
Notice that here `company` must be in scope, which can either be supplied by you in the base class, or in this example we have nested User within Company (so its nest path would be different than the example above), which would put `company` in the scope of the build.
|
2182
|
+
|
2183
|
+
|
2184
|
+
#### 2025-08-15 - v.0.6.23
|
2185
|
+
|
2186
|
+
|
2187
|
+
• Lazy Lists: Important Breaking Change
|
2188
|
+
|
2189
|
+
All downnested portals now use Turbo's last list feature (frame with `src:` to load the subview via a separate request).
|
2190
|
+
The user sees "Loading" in the box as it is loading. (See `--downnest` and `--nested` sections.)
|
2191
|
+
|
2192
|
+
Unfortunately, this is a partially breaking change in that a parent & child should be rebuilt together on this version.
|
2193
|
+
|
2194
|
+
Whereas before the parent's edit template included the list and passed it the records to render immediately (in the same request)
|
2195
|
+
|
2196
|
+
```
|
2197
|
+
<%= render partial: "agent_rules/list", locals: {agent: @agent, rules: @agent.rules} %>
|
2198
|
+
|
2199
|
+
```
|
2200
|
+
|
2201
|
+
Now, we will render a new partial (from the child's build folder) at `lazy_list` (when the parent is rendering its children)
|
2202
|
+
|
2203
|
+
```
|
2204
|
+
<%= render partial: "agent_rules/lazy_list", locals: {agent: @agent, ...
|
2205
|
+
```
|
2206
|
+
|
2207
|
+
The `lazy_list` itself contains a turbo frame tag with an `src` set to the URL where the list can be loaded, appened with __lazy=1 to let the child controller know not to render the full layout.
|
2208
|
+
|
2209
|
+
|
2210
|
+
```
|
2211
|
+
<%= tag.turbo_frame id: "__agents-list" + ((('__' + nested_for) if defined?(nested_for)) || ""),
|
2212
|
+
src: account_crusade_agents_path(account,crusade) + "?__lazy",
|
2213
|
+
loading: "lazy" do %>
|
2214
|
+
|
2215
|
+
```
|
2216
|
+
|
2217
|
+
In the downnested controller, the children will now suppress the layout when loaded lazy
|
2218
|
+
|
2219
|
+
```
|
2220
|
+
render layout: (params[:__lazy].present? ? false : true)
|
2221
|
+
```
|
2222
|
+
|
2223
|
+
Just remember you must rebuild the parent if you rebuild a child, and you must rebuild ALL the children of any parent that is rebuilt.
|
2224
|
+
|
2225
|
+
|
2226
|
+
• Modify now has an `include_blank` option to add a blank option for associations
|
2227
|
+
|
2228
|
+
(Assuming the thing you are building has a `person_id` and `belongs_to :person, optional: true`)
|
2229
|
+
|
2230
|
+
`--modify=person_id{include_blank}`
|
2231
|
+
|
2232
|
+
Make sure your `belongs_to` association has `optional: true` or else you will get validation errors when you try to save a record with an empty association. Without this setting (by default), drop-downs show a blank line when the association is *not set*, but when the association is *set* no blank line is shown, making it impossible to unset or clear out an association once it has already been set.
|
2233
|
+
|
2234
|
+
• Fixes cancel button problems related to subviews (no longer necessary to load the edit of the parent in the lazy paradigm)
|
2235
|
+
|
2236
|
+
• Fixes double-entry redisplay problem (second create action would be saved but not show up in UI) due to malformed nested_for
|
2237
|
+
|
2238
|
+
|
2147
2239
|
|
2148
2240
|
|
2149
2241
|
#### 2025-07-28 v0.6.22
|
2150
2242
|
|
2243
|
+
|
2244
|
+
|
2245
|
+
#### 2025-07-05 v0.6.21
|
2246
|
+
•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
|
2247
|
+
|
2151
2248
|
`--phantom-create-params`
|
2152
2249
|
These parameters get added in the strong parameters safelist for the create action
|
2153
2250
|
|
2154
2251
|
You'll probably wnat to use this along with --code-after-create to check that phanton param
|
2155
2252
|
|
2156
|
-
TODO: you have to tap these away yourself
|
2253
|
+
TODO: you have to tap these away yourself
|
2157
2254
|
TODO: can they be tapped away automatically if not using a factory
|
2158
2255
|
|
2159
2256
|
`--phantom-update-params`
|
@@ -2182,7 +2279,7 @@ See "Polymorphic downnesting" in the downnesting section for an example.
|
|
2182
2279
|
|
2183
2280
|
• Removes more vestiges of optionalized nesting (which I had implemented 3 years ago!)
|
2184
2281
|
|
2185
|
-
I no longer like optionalized nesting, and recommend against it.
|
2282
|
+
I no longer like optionalized nesting, and recommend against it.
|
2186
2283
|
|
2187
2284
|
Nesting should always be part of the structure, and every route should operate firmly in its nest path.
|
2188
2285
|
|
@@ -2190,11 +2287,6 @@ Use new controller-prefix to make on-off exceptions or with polymorphic children
|
|
2190
2287
|
|
2191
2288
|
• Fixes for localized datetime & time inputs
|
2192
2289
|
|
2193
|
-
|
2194
|
-
|
2195
|
-
#### 2025-07-05 v0.6.21
|
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
|
2197
|
-
|
2198
2290
|
• removes duplicitous flash messages in factory context
|
2199
2291
|
|
2200
2292
|
• adds documentation for `--code-before-create`, `--code-after-create`, `--code-before-update`, `--code-after-update` (these features were already implemented)
|
@@ -2229,7 +2321,7 @@ probably want about 3 or 4 columns. When you have more than 5 it's difficult to
|
|
2229
2321
|
#### 2025-06-10 v0.6.19
|
2230
2322
|
|
2231
2323
|
• Fixes magic button output behavior to correctly show the string returned by the bang menthod
|
2232
|
-
• Fixes internal syntax of
|
2324
|
+
• Fixes internal syntax of modify for modified fields; note default is now 'Yes' and 'No' for radio buttons
|
2233
2325
|
• fixes destroy behavior with explicit .destroy! and ActiveRecord::RecordNotDestroyed; documents previously undocumented 4th parameter for attachment input
|
2234
2326
|
|
2235
2327
|
|
@@ -2266,7 +2358,10 @@ Automatically build the new and edit form with `data-controller='xyz'` to attach
|
|
2266
2358
|
|
2267
2359
|
If you use the shorthand (specify no `=`) your stimulus controller's name will be inferred from the Singular form of the scaffolding beild built, with dashes for underscores, and ending with `-form`
|
2268
2360
|
|
2269
|
-
(
|
2361
|
+
Notice that under the hood we are doing this `@singular.gsub("_", "-") + "-form"` so a scaffold built for `bank_account` will become a `BankAccountForm` stimulus controller
|
2362
|
+
|
2363
|
+
|
2364
|
+
For example, `rails g hot_glue:scaffold Thing --stimmy` generates a form that looks like.
|
2270
2365
|
|
2271
2366
|
```
|
2272
2367
|
<%= form_with model: thing,
|
@@ -2281,7 +2376,7 @@ If you use the shorthand (specify no `=`) your stimulus controller's name will b
|
|
2281
2376
|
|
2282
2377
|
Note that your fields also appended with `data-thing-target=abc` and also `data-thing-target=abcWrapper`
|
2283
2378
|
|
2284
|
-
|
2379
|
+
|
2285
2380
|
|
2286
2381
|
For a crash course on Stimulus, see
|
2287
2382
|
https://jasonfleetwoodboldt.com/courses/rails-7-crash-course/rails-7-stimulus-js-basics-with-importmap-rails/
|