hot-glue 0.6.23 → 0.6.25
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 +640 -596
- data/lib/generators/hot_glue/fields/association_field.rb +1 -0
- data/lib/generators/hot_glue/fields/related_set_field.rb +30 -10
- data/lib/generators/hot_glue/scaffold_generator.rb +22 -5
- data/lib/generators/hot_glue/templates/controller.rb.erb +3 -4
- data/lib/generators/hot_glue/templates/typeahead_controller.rb.erb +1 -1
- data/lib/generators/hot_glue/typeahead_generator.rb +4 -0
- data/lib/hotglue/version.rb +1 -1
- metadata +2 -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:
|
742
748
|
|
743
|
-
|
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.
|
744
938
|
|
745
|
-
You may not specify both include and exclude.
|
746
939
|
|
747
|
-
Include setting is affected by both specified grouping mode and smart layouts, explained below.
|
748
940
|
|
749
941
|
|
750
|
-
|
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)
|
966
|
+
|
967
|
+
### `--no-nav-menu`
|
968
|
+
|
969
|
+
To suppress this behavior, add `--no-nav-menu` to the build command and the _nav template will not be touched.
|
970
|
+
|
971
|
+
|
972
|
+
|
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
|
|
@@ -809,14 +1032,28 @@ The layout builder takes the number of columns remaining and then distributes th
|
|
809
1032
|
|
810
1033
|
A D G
|
811
1034
|
|
812
|
-
B E H
|
1035
|
+
B E H
|
1036
|
+
|
1037
|
+
C F I
|
1038
|
+
|
1039
|
+
This is what would happen if 9 fields, specified in the order A,B,C,D,E,F,G,H,I, were distributed across 3 columns.
|
1040
|
+
|
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.)
|
1042
|
+
|
1043
|
+
|
1044
|
+
### `--new-button-position` (above, below; default: above)
|
1045
|
+
Show the new button above or below the list.
|
813
1046
|
|
814
|
-
C F I
|
815
1047
|
|
816
|
-
This is what would happen if 9 fields, specified in the order A,B,C,D,E,F,G,H,I, were distributed across 3 columns.
|
817
1048
|
|
818
|
-
|
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.
|
819
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`
|
820
1057
|
|
821
1058
|
|
822
1059
|
### `--modify=field1{...},field2{...}`
|
@@ -949,7 +1186,55 @@ When building a non-Gd controller with a `--alt-foreign-key-lookup`, if you don'
|
|
949
1186
|
|
950
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.
|
951
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
|
+
|
952
1236
|
|
1237
|
+
## Access Control & Field Visibility Features
|
953
1238
|
|
954
1239
|
### `--pundit`
|
955
1240
|
If you enable Pundit, your controllers will look for a Policy that matches the name of the thing being built.
|
@@ -1030,9 +1315,6 @@ If provided, the output code looks something like (in this example, showing the
|
|
1030
1315
|
raise Pundit::NotAuthorizedError if ! UniqueInvoicePolicy.edit?
|
1031
1316
|
```
|
1032
1317
|
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1036
1318
|
### `--show-only=`
|
1037
1319
|
(separate field names by COMMA)
|
1038
1320
|
|
@@ -1136,302 +1418,124 @@ In the `wrappers` folder, I am using a special sticky partial `_edit_within_form
|
|
1136
1418
|
```
|
1137
1419
|
|
1138
1420
|
|
1139
|
-
Then, create a `app/javascript/controllers/wrapper_form_controller.js` file with the following code:
|
1140
|
-
|
1141
|
-
```javascript
|
1142
|
-
|
1143
|
-
|
1144
|
-
import { Controller } from "@hotwired/stimulus"
|
1145
|
-
|
1146
|
-
import {basicSetup} from "codemirror"
|
1147
|
-
import {EditorView} from "@codemirror/view"
|
1148
|
-
|
1149
|
-
// Connects to data-controller="wrapper-form"
|
1150
|
-
export default class extends Controller {
|
1151
|
-
static targets = ['rawSource', 'name', 'nameWrapper', 'editor'];
|
1152
|
-
|
1153
|
-
connect() {
|
1154
|
-
console.log("WrapperFormController connected")
|
1155
|
-
this.account_id = this.element.dataset['accountId']
|
1156
|
-
this.crusade_id = this.element.dataset['crusadeId']
|
1157
|
-
this.wrapper_id = this.element.dataset['wrapperId']
|
1158
|
-
|
1159
|
-
const view = new EditorView({
|
1160
|
-
doc: this.rawSourceTarget.value,
|
1161
|
-
parent: this.editorTarget,
|
1162
|
-
extensions: [basicSetup]
|
1163
|
-
})
|
1164
|
-
|
1165
|
-
this.view = view;
|
1166
|
-
this.element.addEventListener('submit', this.formSubmit.bind(this))
|
1167
|
-
// this.previewButtonTarget.addEventListener('click', this.previewClick.bind(this))
|
1168
|
-
}
|
1169
|
-
|
1170
|
-
formSubmit(event) {
|
1171
|
-
this.rawSourceTarget.value = this.view.state.doc.toString();
|
1172
|
-
}
|
1173
|
-
}
|
1174
|
-
```
|
1175
|
-
|
1176
|
-
Notice we are also using `--stimmify` to decorate the form with a Stimulus controller.
|
1177
|
-
|
1178
|
-
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.
|
1179
|
-
|
1180
|
-
### `--invisible=`
|
1181
|
-
### `--create-invisible=`
|
1182
|
-
### `--update-invisible=`
|
1183
|
-
(two lists are maintained: create and update. any fields on the unnamed invisible list will be invisible on both create and update actions)
|
1184
|
-
|
1185
|
-
If a field is on the invisible list, the policy will be checked for a `_able?` method.
|
1186
|
-
If this method returns true, displayed like a normal editable field.
|
1187
|
-
|
1188
|
-
If the policy doesn't allow editing, this field will be made invisible: completely removed from the form.
|
1189
|
-
|
1190
|
-
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.
|
1191
|
-
|
1192
|
-
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=`.)
|
1193
|
-
|
1194
|
-
|
1195
|
-
Like show-only, note special behavior with pundit.
|
1196
|
-
|
1197
|
-
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,
|
1198
|
-
|
1199
|
-
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.
|
1200
|
-
|
1201
|
-
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.
|
1202
|
-
|
1203
|
-
| | | Pundit | | |
|
1204
|
-
|----------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---|---|
|
1205
|
-
| 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. | | |
|
1206
|
-
| 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. | | |
|
1207
|
-
| 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. | | |
|
1208
|
-
| 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. | | |
|
1209
|
-
| Hidden | Not displayed or updatable if the field respond false to _able? or the Policy doesn't allow. | Unrelated to pundit Policy | | |
|
1210
|
-
| | | | | |
|
1211
|
-
|
1212
|
-
|
1213
|
-
Pundit calls the policy for every action, including the index action. In these cases it passes an association instead of a single record.
|
1214
|
-
|
1215
|
-
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.
|
1216
|
-
|
1217
|
-
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.
|
1218
|
-
|
1219
|
-
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).
|
1220
|
-
|
1221
|
-
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)`)
|
1222
|
-
|
1223
|
-
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.
|
1224
|
-
|
1225
|
-
|
1226
|
-
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.
|
1227
|
-
|
1228
|
-
```
|
1229
|
-
class ThingPolicy < ApplicationPolicy
|
1230
|
-
attr_reader :user, :thing
|
1231
|
-
|
1232
|
-
def initialize(user, thing)
|
1233
|
-
@user = user
|
1234
|
-
@thing = thing
|
1235
|
-
end
|
1236
|
-
|
1237
|
-
def ccc_able?
|
1238
|
-
if thing.is_a?(Thing) # a thing is not a Thing when it is an active relation of many things
|
1239
|
-
!!thing.bbb # show or hide ccc based on whether or not bbb is true
|
1240
|
-
else
|
1241
|
-
current_user.is_admin? # show or hide the column heading for ccc based on whether or not the current user is an admin
|
1242
|
-
end
|
1243
|
-
end
|
1244
|
-
# more policy method here ...
|
1245
|
-
end
|
1246
|
-
```
|
1247
|
-
Here, for all CRUD actions, the object is a thing, and so the editablility of ccc is dependent on bbb being true.
|
1248
|
-
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.
|
1249
|
-
|
1250
|
-
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.
|
1251
|
-
|
1252
|
-
|
1253
|
-
|
1254
|
-
### `--ujs_syntax=true` (Default is set automatically based on whether you have turbo-rails installed)
|
1255
|
-
|
1256
|
-
If you are pre-Turbo (UJS), your delete buttons will come out like this:
|
1257
|
-
`data: {'confirm': 'Are you sure you want to delete....?'}`
|
1258
|
-
|
1259
|
-
If you are Turbo (Rails 7 or Rails 6 with proactive Turbo-Rails install), your delete button will be:
|
1260
|
-
`data: {'turbo-confirm': 'Are you sure you want to delete....?'}`
|
1261
|
-
|
1262
|
-
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.
|
1263
|
-
|
1264
|
-
**WARNING**: If you created a new Rails app since October 2021 and you have the yanked turbo-rails Gems on your local machine,
|
1265
|
-
you will have some bugs with the delete buttons and also not be on the latest version of turbo-rails.
|
1266
|
-
|
1267
|
-
Make sure to uninstall the yanked 7.1.0 and 7.1.1 from your machine with `gem uninstall turbo-rails`
|
1268
|
-
and also fix any Rails apps created since October 2021 by fixing the Gemfile. Details here:
|
1269
|
-
https://stackoverflow.com/questions/70671324/new-rails-7-turbo-app-doesnt-show-the-data-turbo-confirm-alert-messages-dont-f
|
1270
|
-
|
1271
|
-
|
1272
|
-
### `--magic-buttons=`
|
1273
|
-
If you pass a list of magic buttons (separated by commas), they will appear in the button area on your list.
|
1274
|
-
|
1275
|
-
It will be assumed there will be corresponding bang methods on your models.
|
1276
|
-
|
1277
|
-
The bang (`!`) methods can respond in one of four ways:
|
1278
|
-
|
1279
|
-
• With true, in which case a generic success message will be shown in the flash notice (“Approved” or “Rejected” in this case)
|
1280
|
-
|
1281
|
-
• With false, in which case a generic error message will be shown in the flash alert (“Could not approve…”)
|
1282
|
-
|
1283
|
-
• With a string, which will be assumed to be a “success” case, and will be passed to the front-end in the alert notice.
|
1284
|
-
|
1285
|
-
• Raise an ActiveRecord exception
|
1286
|
-
|
1287
|
-
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.
|
1288
|
-
|
1289
|
-
Finally, you can raise an ActiveRecord error which will also get passed to the user in the flash alert area.
|
1290
|
-
|
1291
|
-
For more information see [Example 6 in the Tutorial](https://school.jfbcodes.com/8188)
|
1292
|
-
|
1293
|
-
You can also define methods on your model that have the same name as the button with `_able?` at the end.
|
1294
|
-
(Prior to v0.6.20, these methods expected names with `able?` but no underscore.)
|
1295
|
-
|
1296
|
-
The button will be display as disabled if the method returns false.
|
1297
|
-
|
1298
|
-
|
1299
|
-
### `--downnest=`
|
1300
|
-
|
1301
|
-
Automatically create subviews down your object tree. This should be the name of a has_many relationship based from the current object.
|
1302
|
-
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.
|
1303
|
-
|
1304
|
-
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).
|
1305
|
-
|
1306
|
-
Can now be created with more space (wider) by adding a `+` to the end of the downnest name
|
1307
|
-
- e.g. `--downnest=abc+,xyz`
|
1308
|
-
|
1309
|
-
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
|
1310
|
-
|
1311
|
-
|
1312
|
-
Polymorphic Downnesting
|
1313
|
-
|
1314
|
-
Here, a `Blast` `has_many :rules, as: :ruleable`
|
1315
|
-
|
1316
|
-
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)
|
1317
|
-
|
1318
|
-
`belongs_to :ruleable, polymorphic: true`
|
1319
|
-
|
1320
|
-
We build the blast & agent controllers like so:
|
1321
|
-
|
1322
|
-
bin/rails generate hot_glue:scaffold Blast --downnest='blast_rules(rules)'
|
1323
|
-
bin/rails generate hot_glue:scaffold Agent --downnest='agent_rules(rules)'
|
1324
|
-
|
1325
|
-
Notice that the relationship name is `rules` (not blast_rules), so what goes before the parenthesis is the controller name (with prefix)
|
1326
|
-
What goes inside the controller name is the real relationship name.
|
1327
|
-
|
1328
|
-
For the children, we can't build one controller for the Rule, instead we build one for the `AgentRules` and another for the `BlastRules`
|
1329
|
-
|
1330
|
-
bin/rails generate hot_glue:scaffold Rule --nested='blast(ruleable)' --controller-prefix='Blast'
|
1331
|
-
bin/rails generate hot_glue:scaffold Rule --nested='agent(ruleable)' --controller-prefix='Agent'
|
1332
|
-
|
1333
|
-
(I realize building one child controller for each type of polymorph is tedius, but this is the best solution I could come up with.)
|
1334
|
-
|
1335
|
-
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`
|
1336
|
-
|
1337
|
-
routes.rb
|
1338
|
-
|
1339
|
-
```
|
1340
|
-
resources :agents do
|
1341
|
-
resources :agent_rules
|
1342
|
-
end
|
1421
|
+
Then, create a `app/javascript/controllers/wrapper_form_controller.js` file with the following code:
|
1343
1422
|
|
1344
|
-
|
1345
|
-
resources :blast_rules
|
1346
|
-
end
|
1347
|
-
```
|
1423
|
+
```javascript
|
1348
1424
|
|
1349
1425
|
|
1426
|
+
import { Controller } from "@hotwired/stimulus"
|
1350
1427
|
|
1351
|
-
|
1428
|
+
import {basicSetup} from "codemirror"
|
1429
|
+
import {EditorView} from "@codemirror/view"
|
1352
1430
|
|
1353
|
-
|
1354
|
-
|
1431
|
+
// Connects to data-controller="wrapper-form"
|
1432
|
+
export default class extends Controller {
|
1433
|
+
static targets = ['rawSource', 'name', 'nameWrapper', 'editor'];
|
1355
1434
|
|
1356
|
-
|
1435
|
+
connect() {
|
1436
|
+
console.log("WrapperFormController connected")
|
1437
|
+
this.account_id = this.element.dataset['accountId']
|
1438
|
+
this.crusade_id = this.element.dataset['crusadeId']
|
1439
|
+
this.wrapper_id = this.element.dataset['wrapperId']
|
1357
1440
|
|
1358
|
-
|
1441
|
+
const view = new EditorView({
|
1442
|
+
doc: this.rawSourceTarget.value,
|
1443
|
+
parent: this.editorTarget,
|
1444
|
+
extensions: [basicSetup]
|
1445
|
+
})
|
1359
1446
|
|
1360
|
-
|
1447
|
+
this.view = view;
|
1448
|
+
this.element.addEventListener('submit', this.formSubmit.bind(this))
|
1449
|
+
// this.previewButtonTarget.addEventListener('click', this.previewClick.bind(this))
|
1450
|
+
}
|
1361
1451
|
|
1452
|
+
formSubmit(event) {
|
1453
|
+
this.rawSourceTarget.value = this.view.state.doc.toString();
|
1454
|
+
}
|
1455
|
+
}
|
1362
1456
|
```
|
1363
|
-
scope :is_open, -> {where(state == 'open')}
|
1364
|
-
```
|
1365
|
-
|
1366
|
-
Now all records displayed through the generated controller
|
1367
1457
|
|
1458
|
+
Notice we are also using `--stimmify` to decorate the form with a Stimulus controller.
|
1368
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.
|
1369
1461
|
|
1370
|
-
### `--related-sets=`
|
1371
1462
|
|
1372
|
-
|
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)
|
1373
1467
|
|
1374
|
-
|
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.
|
1375
1470
|
|
1376
|
-
|
1471
|
+
If the policy doesn't allow editing, this field will be made invisible: completely removed from the form.
|
1377
1472
|
|
1378
|
-
|
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.
|
1379
1474
|
|
1380
|
-
|
1381
|
-
rails generate hot_glue:scaffold User --related-sets=roles --include=email,roles --gd
|
1382
|
-
```
|
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=`.)
|
1383
1476
|
|
1384
|
-
Note this leaves open a privileged escalation attack (a security vulnerability).
|
1385
1477
|
|
1386
|
-
|
1478
|
+
Like show-only, note special behavior with pundit.
|
1387
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,
|
1388
1481
|
|
1389
|
-
|
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.
|
1390
1483
|
|
1391
|
-
|
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.
|
1392
1485
|
|
1393
|
-
|
1394
|
-
|
1395
|
-
|
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
|
+
| | | | | |
|
1396
1494
|
|
1397
|
-
### `--list-label-heading=`
|
1398
|
-
The plural of the list of things at the top of the list.
|
1399
|
-
If not, a specification that exists as `@@tabel_label_plural` from the Model will be used.
|
1400
|
-
If this does not exist, the UPCASE (all-uppercase) version of the model name.
|
1401
1495
|
|
1402
|
-
|
1403
|
-
Overrides the button on the list that the user clicks onto to create a new record.
|
1404
|
-
(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.
|
1405
1497
|
|
1406
|
-
|
1407
|
-
The text at the top of the new form that appears when the new input entry is displayed.
|
1408
|
-
(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.
|
1409
1499
|
|
1410
|
-
|
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.
|
1411
1501
|
|
1412
|
-
|
1413
|
-
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).
|
1414
1503
|
|
1415
|
-
|
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)`)
|
1416
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.
|
1417
1507
|
|
1418
|
-
### `--form-placeholder-labels=` (default: false)
|
1419
1508
|
|
1420
|
-
|
1421
|
-
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.
|
1422
1510
|
|
1423
|
-
|
1511
|
+
```
|
1512
|
+
class ThingPolicy < ApplicationPolicy
|
1513
|
+
attr_reader :user, :thing
|
1424
1514
|
|
1425
|
-
|
1515
|
+
def initialize(user, thing)
|
1516
|
+
@user = user
|
1517
|
+
@thing = thing
|
1518
|
+
end
|
1426
1519
|
|
1427
|
-
|
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.
|
1428
1532
|
|
1429
|
-
|
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.
|
1430
1534
|
|
1431
|
-
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.
|
1432
1535
|
|
1433
1536
|
|
1434
|
-
|
1537
|
+
## Code Insertion Features
|
1538
|
+
'
|
1435
1539
|
Insert some code into the `new`, `create` or `update` action actions.
|
1436
1540
|
|
1437
1541
|
Wrapped in quotation marks when specified in the command line, and use semicolons to separate multiple lines of code.
|
@@ -1485,7 +1589,6 @@ Notice that a separate hook for code-after-create is also available, but that ha
|
|
1485
1589
|
Using both together `--code-after-new='@email_template.created_by_user = current_user' --code-after-create='@email_template.do_something'`
|
1486
1590
|
|
1487
1591
|
|
1488
|
-
|
1489
1592
|
```
|
1490
1593
|
def create
|
1491
1594
|
...
|
@@ -1500,13 +1603,13 @@ def create
|
|
1500
1603
|
account.reload
|
1501
1604
|
...
|
1502
1605
|
```
|
1606
|
+
TODO: build a solution for inserting code only in the `new` action but NOT the create action
|
1503
1607
|
|
1504
1608
|
|
1505
1609
|
|
1506
|
-
|
1507
|
-
|
1610
|
+
## Searching
|
1508
1611
|
|
1509
|
-
|
1612
|
+
### `--search=` (options: simple, set, false predicate, default: false)
|
1510
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.
|
1511
1614
|
Within the set, the search query is **_combinative_** ("and"), so records matching all criteria are shown as the **result set.**
|
1512
1615
|
For date pickers and time pickers, you need the additional Stimulus.
|
@@ -1559,69 +1662,60 @@ NOT IMPLEMENTED YET
|
|
1559
1662
|
TODO: implement me
|
1560
1663
|
|
1561
1664
|
|
1562
|
-
### `--attachments=`
|
1563
|
-
|
1564
|
-
#### ActiveStorage Quick Setup
|
1565
|
-
(For complete docs, refer to https://guides.rubyonrails.org/active_storage_overview.html)
|
1566
1665
|
|
1567
|
-
`
|
1568
|
-
(for videos `brew install ffmpeg`)
|
1666
|
+
### `--stimmify` or `--stimmify=xyz`
|
1569
1667
|
|
1570
|
-
|
1571
|
-
bundle add image_processing
|
1572
|
-
./bin/rails active_storage:install
|
1573
|
-
./bin/rails db:migrate
|
1574
|
-
```
|
1575
|
-
|
1576
|
-
Generate an images model:
|
1577
|
-
|
1578
|
-
`./bin/rails generate model Images name:string`
|
1668
|
+
Automatically build the new and edit form with `data-controller='xyz'` to attach stimulus
|
1579
1669
|
|
1580
|
-
|
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`
|
1581
1671
|
|
1582
|
-
```
|
1583
|
-
has_one_attached :avatar do |attachable|
|
1584
|
-
attachable.variant :thumb, resize_to_limit: [100, 100]
|
1585
|
-
end
|
1586
|
-
```
|
1587
|
-
Generate a Hot Glue scaffold with the attachment avatar appended to the field list (the shorthand syntax)
|
1588
1672
|
|
1589
|
-
|
1673
|
+
`@singular.gsub("_", "-") + "-form"`
|
1590
1674
|
|
1591
|
-
(
|
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)
|
1592
1677
|
|
1593
|
-
#### Caveats:
|
1594
|
-
• If thumbnails aren't showing up, make sure you have
|
1595
1678
|
|
1596
|
-
|
1597
|
-
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
|
1598
|
-
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
|
1599
1680
|
|
1600
|
-
|
1681
|
+
```
|
1682
|
+
<%= form_with model: thing,
|
1683
|
+
url: things_path,
|
1684
|
+
html: {
|
1685
|
+
'data-controller': "thing-form"
|
1686
|
+
}
|
1687
|
+
%>
|
1688
|
+
...
|
1689
|
+
```
|
1601
1690
|
|
1602
|
-
|
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
|
+
```
|
1603
1701
|
|
1604
|
-
|
1702
|
+
Note that your fields also appended with `data-thing-target=abc` and also `data-thing-target=abcWrapper`
|
1605
1703
|
|
1606
|
-
|
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/
|
1607
1706
|
|
1608
|
-
|
1707
|
+
**You must make the stimulus controller yourself!!**
|
1609
1708
|
|
1610
|
-
|
1709
|
+
Create them with:
|
1611
1710
|
|
1612
|
-
|
1711
|
+
`rails g stimulus thing_form`
|
1613
1712
|
|
1614
|
-
|
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.
|
1615
1714
|
|
1616
|
-
|
1617
|
-
```
|
1618
|
-
has_one_attached :avatar do |attachable|
|
1619
|
-
attachable.variant :thumbnail, resize_to_limit: [100, 100]
|
1620
|
-
end
|
1621
|
-
```
|
1715
|
+
Instead, use the generator and the registry will be updated for you automatically.
|
1622
1716
|
|
1623
|
-
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.
|
1624
1717
|
|
1718
|
+
## Attachments
|
1625
1719
|
|
1626
1720
|
#### `--attachments=` Long form syntax with 1st and 2nd parameters
|
1627
1721
|
|
@@ -1635,7 +1729,7 @@ Note: You must have a string field called `orig_filename`. It does not need to b
|
|
1635
1729
|
|
1636
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.
|
1637
1731
|
|
1638
|
-
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.
|
1639
1733
|
|
1640
1734
|
#### `--attachments=` Long form syntax with 1st, 2nd, and 3rd parameters
|
1641
1735
|
|
@@ -1691,16 +1785,16 @@ config.active_storage.service = :amazon
|
|
1691
1785
|
|
1692
1786
|
|
1693
1787
|
#### For Direct Upload Support
|
1694
|
-
1.
|
1788
|
+
1.
|
1695
1789
|
```
|
1696
1790
|
yarn add @rails/activestorage
|
1697
1791
|
```
|
1698
1792
|
|
1699
|
-
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
|
1700
1794
|
|
1701
1795
|
- Start sidekiq with `bundle exec sidekiq` while you are testing
|
1702
1796
|
|
1703
|
-
3. Install ActiveStorage JS using:
|
1797
|
+
3. Install ActiveStorage JS using:
|
1704
1798
|
|
1705
1799
|
`./bin/rails generate hot_glue:direct_upload_install`
|
1706
1800
|
|
@@ -1727,7 +1821,7 @@ This will 1) copy the dropzone_controller.js file into your app and 2) add the d
|
|
1727
1821
|
|
1728
1822
|
### Attach Stimulus JS Controllers to Your Forms with `--stimmify` or `--stimmify=xyz`
|
1729
1823
|
|
1730
|
-
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.
|
1731
1825
|
|
1732
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`
|
1733
1827
|
|
@@ -1775,91 +1869,13 @@ https://jasonfleetwoodboldt.com/courses/rails-7-crash-course/rails-7-stimulus-js
|
|
1775
1869
|
|
1776
1870
|
|
1777
1871
|
|
1778
|
-
|
1779
|
-
|
1780
|
-
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.
|
1781
|
-
|
1782
|
-
You may use semi-colons to separate multiple lines of code.
|
1783
|
-
|
1784
|
-
For example, a user Factory might be called like so:
|
1785
|
-
|
1786
|
-
`./bin/rails generate hot_glue:scaffold User --factory-creation={factory = UserFactory.new(params: user_params)} --gd`
|
1787
|
-
|
1788
|
-
(Note we are relying on the `user_params` method provided by the controller.)
|
1789
|
-
|
1790
|
-
You must do one of two things:
|
1791
|
-
|
1792
|
-
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.)
|
1793
|
-
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
|
1794
|
-
|
1795
|
-
(The code example above is the option for #2 because it does not contain `@user =`)
|
1796
|
-
|
1797
|
-
If using number #2, Hot Glue will append this to the code specified:
|
1798
|
-
```
|
1799
|
-
@user = factory.user
|
1800
|
-
```
|
1801
|
-
|
1802
|
-
|
1803
|
-
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.)
|
1804
|
-
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.)
|
1805
|
-
|
1806
|
-
```
|
1807
|
-
class UserFactory
|
1808
|
-
attr_reader :user
|
1809
|
-
attr_accessor :email
|
1810
|
-
|
1811
|
-
def initialize(params: {})
|
1812
|
-
user = User.find_or_create_by(email: params[:email])
|
1813
|
-
|
1814
|
-
user.update(params)
|
1815
|
-
if user.new_record?
|
1816
|
-
# do special new user logic here, like sending an email
|
1817
|
-
end
|
1818
|
-
end
|
1819
|
-
end
|
1820
|
-
```
|
1821
|
-
|
1822
|
-
|
1823
|
-
be sure your factory code creates a local variable that follows this name
|
1824
|
-
|
1825
|
-
**<downcase association name>**_factory.<downcase association name>
|
1826
|
-
|
1827
|
-
Thus, your factory object must have a method of the same name as the factory being created which returns the thing that got created.
|
1828
|
-
(It can do the creation either on instantiation or when calling that method)
|
1829
|
-
|
1830
|
-
For example, assuming the example from above, we are going to do the lookup ourselves inside of our own `AgentFactory` object.)
|
1831
|
-
|
1832
|
-
```
|
1833
|
-
factory = AgentFactory.new(find_or_create_by_email: agent_company_params[:__lookup_email],
|
1834
|
-
params: modified_params)
|
1835
|
-
```
|
1836
|
-
|
1837
|
-
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.
|
1838
|
-
However, two special variables are in scope which you can use in your calling code.
|
1839
|
-
|
1840
|
-
`*_params` (where * is the name of the thing you are building)
|
1841
|
-
`modified_params`
|
1842
|
-
|
1843
|
-
Either one must be received by your factory for your factory to create data based off the inputted data.
|
1844
|
-
|
1845
|
-
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.
|
1846
|
-
|
1847
|
-
Always:
|
1848
|
-
• In your factory calling code, assign the variable `factory = ` (do not use a different variable name),
|
1849
|
-
• Write a factory object with a `new` method that received the paramters you are specifying in your calling code,
|
1850
|
-
• Be sure your factory has an _instance method_ a method with the **same name** of the built object, which hot glue will call next:
|
1851
|
-
|
1852
|
-
`@agent = factory.agent`
|
1853
|
-
|
1854
|
-
Don't include this last line in your factory code.
|
1855
|
-
|
1856
|
-
|
1857
|
-
# SPECIAL FEATURES
|
1872
|
+
# SPECIAL FEATURES DISCUSSION
|
1873
|
+
This section discusses features that don't correspond to a single option or flag.
|
1858
1874
|
|
1859
1875
|
|
1860
1876
|
## "Thing" Label
|
1861
1877
|
|
1862
|
-
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
|
1863
1879
|
`@@table_label_singular` and `@@table_label_plural` on your model objects.
|
1864
1880
|
|
1865
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.
|
@@ -2026,6 +2042,11 @@ has_many :users, through: :account_users
|
|
2026
2042
|
```
|
2027
2043
|
|
2028
2044
|
|
2045
|
+
Notice that from the table of things referencing the typeahead thing,
|
2046
|
+
the `--modify` takes a 3rd parameter in `[...]` to specify the nested set if the typeahead itself is nested.
|
2047
|
+
|
2048
|
+
As with this example, this means that the object referencing the typeahead object must be in the same namespace, but does not need to be at the same nest level.
|
2049
|
+
|
2029
2050
|
`bin/rails generate hot_glue:scaffold Member --auth='current_user' --auth-identifier='user' --auth-identifier=user --modify='user_id{typeahead}[account]'`
|
2030
2051
|
|
2031
2052
|
in our routes.rb file, we have
|
@@ -2145,15 +2166,35 @@ These automatic pickups for partials are detected at build time. This means that
|
|
2145
2166
|
|
2146
2167
|
|
2147
2168
|
# VERSION HISTORY
|
2169
|
+
#### 2025-08-22 - v0.6.24
|
2170
|
+
`--related-sets` fixes issue with related sets due to ruby syntax
|
2171
|
+
|
2172
|
+
• 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
|
2173
|
+
(like the --hawk, with the need to explicitly call `--hawk`) using `[`...`]`
|
2174
|
+
Both parameters are optional. If the label field `{...}` is unspecified, it will default to `label`.
|
2175
|
+
If the 2nd parameter `[...]` (hawk) is unspecified, the widget will display all records in the related table, using the base class + `.all`
|
2176
|
+
If the 1st parameter `{...}` is left off, still use square braces `[...]` for the hawk.
|
2177
|
+
|
2178
|
+
|
2179
|
+
Example:
|
2180
|
+
`rails generate hot_glue:scaffold User --nested=company --related-sets=roles{name}[company.roles] --include=email,roles --gd`
|
2181
|
+
|
2182
|
+
This shows the related set of `roles` using the field named `name` on the role object to display its name.
|
2183
|
+
|
2184
|
+
Only displays the roles associated with the current company via the `company.roles` association.
|
2185
|
+
|
2186
|
+
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.
|
2187
|
+
|
2148
2188
|
|
2149
2189
|
#### 2025-08-15 - v.0.6.23
|
2150
2190
|
|
2191
|
+
|
2151
2192
|
• Lazy Lists: Important Breaking Change
|
2152
2193
|
|
2153
2194
|
All downnested portals now use Turbo's last list feature (frame with `src:` to load the subview via a separate request).
|
2154
2195
|
The user sees "Loading" in the box as it is loading. (See `--downnest` and `--nested` sections.)
|
2155
2196
|
|
2156
|
-
Unfortunately, this is a partially breaking change in that a parent & child should be rebuilt together on this version.
|
2197
|
+
Unfortunately, this is a partially breaking change in that a parent & child should be rebuilt together on this version.
|
2157
2198
|
|
2158
2199
|
Whereas before the parent's edit template included the list and passed it the records to render immediately (in the same request)
|
2159
2200
|
|
@@ -2189,11 +2230,11 @@ Just remember you must rebuild the parent if you rebuild a child, and you must r
|
|
2189
2230
|
|
2190
2231
|
• Modify now has an `include_blank` option to add a blank option for associations
|
2191
2232
|
|
2233
|
+
(Assuming the thing you are building has a `person_id` and `belongs_to :person, optional: true`)
|
2192
2234
|
|
2193
|
-
`--modify
|
2194
|
-
|
2195
|
-
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.
|
2235
|
+
`--modify=person_id{include_blank}`
|
2196
2236
|
|
2237
|
+
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.
|
2197
2238
|
|
2198
2239
|
• Fixes cancel button problems related to subviews (no longer necessary to load the edit of the parent in the lazy paradigm)
|
2199
2240
|
|
@@ -2285,7 +2326,7 @@ probably want about 3 or 4 columns. When you have more than 5 it's difficult to
|
|
2285
2326
|
#### 2025-06-10 v0.6.19
|
2286
2327
|
|
2287
2328
|
• Fixes magic button output behavior to correctly show the string returned by the bang menthod
|
2288
|
-
• Fixes internal syntax of
|
2329
|
+
• Fixes internal syntax of modify for modified fields; note default is now 'Yes' and 'No' for radio buttons
|
2289
2330
|
• fixes destroy behavior with explicit .destroy! and ActiveRecord::RecordNotDestroyed; documents previously undocumented 4th parameter for attachment input
|
2290
2331
|
|
2291
2332
|
|
@@ -2322,7 +2363,10 @@ Automatically build the new and edit form with `data-controller='xyz'` to attach
|
|
2322
2363
|
|
2323
2364
|
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`
|
2324
2365
|
|
2325
|
-
(
|
2366
|
+
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
|
2367
|
+
|
2368
|
+
|
2369
|
+
For example, `rails g hot_glue:scaffold Thing --stimmy` generates a form that looks like.
|
2326
2370
|
|
2327
2371
|
```
|
2328
2372
|
<%= form_with model: thing,
|
@@ -2337,7 +2381,7 @@ If you use the shorthand (specify no `=`) your stimulus controller's name will b
|
|
2337
2381
|
|
2338
2382
|
Note that your fields also appended with `data-thing-target=abc` and also `data-thing-target=abcWrapper`
|
2339
2383
|
|
2340
|
-
|
2384
|
+
|
2341
2385
|
|
2342
2386
|
For a crash course on Stimulus, see
|
2343
2387
|
https://jasonfleetwoodboldt.com/courses/rails-7-crash-course/rails-7-stimulus-js-basics-with-importmap-rails/
|