hot-glue 0.6.0.1 → 0.6.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/FUNDING.yml +1 -1
- data/Gemfile.lock +5 -5
- data/README.md +128 -11
- data/lib/generators/hot_glue/field_factory.rb +7 -1
- data/lib/generators/hot_glue/fields/association_field.rb +2 -3
- data/lib/generators/hot_glue/fields/attachment_field.rb +2 -3
- data/lib/generators/hot_glue/fields/field.rb +9 -3
- data/lib/generators/hot_glue/fields/related_set_field.rb +60 -0
- data/lib/generators/hot_glue/markup_templates/erb.rb +4 -3
- data/lib/generators/hot_glue/scaffold_generator.rb +77 -14
- data/lib/generators/hot_glue/templates/controller.rb.erb +54 -23
- data/lib/generators/hot_glue/templates/erb/_edit.erb +3 -1
- data/lib/generators/hot_glue/templates/erb/_new_form.erb +4 -1
- data/lib/generators/hot_glue/templates/typeahead_controller.rb.erb +1 -0
- data/lib/generators/hot_glue/typeahead_generator.rb +5 -1
- data/lib/hotglue/version.rb +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c984f13c5f9639c6cea9837c4266cf0e4e529e9adef1dffea257551ebd80f8cd
|
4
|
+
data.tar.gz: ba87601c2e04556604a7900880f054fd67b95ee78c9113668664ffb591c0601f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f9b1228beb6639f671f2138c0b62a891060da18089e23cb1a9f0518b77b0002e2c38018b18df49525f0dcf6b2715c6ed3c4c415bca8d203c8afad0c5113fad88
|
7
|
+
data.tar.gz: 34e37814fe29eb58097331279107b1b58a4e9dbb4f3f9887c0e5c6b095d484c8a893034d3dafeed2ffbd5350a3cbd7542ee7dba4ffbd0a149b04a4b6a936ab8c
|
data/.github/FUNDING.yml
CHANGED
@@ -1 +1 @@
|
|
1
|
-
custom: ["https://twitter.com/HotGlueForRails", "https://school.
|
1
|
+
custom: ["https://twitter.com/HotGlueForRails", "https://school.jfbcodes.com/8188?utm_source=github.com&utm_campaign=github_hot_glue_repo_funding_link"]
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
hot-glue (0.6.
|
4
|
+
hot-glue (0.6.1)
|
5
5
|
ffaker (~> 2.16)
|
6
6
|
kaminari (~> 1.2)
|
7
7
|
rails (> 5.1)
|
@@ -90,7 +90,7 @@ GEM
|
|
90
90
|
xpath (~> 3.2)
|
91
91
|
concurrent-ruby (1.1.10)
|
92
92
|
crass (1.0.6)
|
93
|
-
date (3.3.
|
93
|
+
date (3.3.4)
|
94
94
|
devise (4.8.1)
|
95
95
|
bcrypt (~> 3.0)
|
96
96
|
orm_adapter (~> 0.1)
|
@@ -139,12 +139,12 @@ GEM
|
|
139
139
|
mini_mime (1.1.2)
|
140
140
|
mini_portile2 (2.8.4)
|
141
141
|
minitest (5.16.3)
|
142
|
-
net-imap (0.4.
|
142
|
+
net-imap (0.4.5)
|
143
143
|
date
|
144
144
|
net-protocol
|
145
145
|
net-pop (0.1.2)
|
146
146
|
net-protocol
|
147
|
-
net-protocol (0.2.
|
147
|
+
net-protocol (0.2.2)
|
148
148
|
timeout
|
149
149
|
net-smtp (0.4.0)
|
150
150
|
net-protocol
|
@@ -235,7 +235,7 @@ GEM
|
|
235
235
|
stimulus-rails (1.1.1)
|
236
236
|
railties (>= 6.0.0)
|
237
237
|
thor (1.2.1)
|
238
|
-
timeout (0.4.
|
238
|
+
timeout (0.4.1)
|
239
239
|
turbo-rails (1.3.2)
|
240
240
|
actionpack (>= 6.0.0)
|
241
241
|
activejob (>= 6.0.0)
|
data/README.md
CHANGED
@@ -38,7 +38,7 @@ Other than the opinionated differences and additional features, Hot Glue produce
|
|
38
38
|
|
39
39
|
# Get Hot Glue
|
40
40
|
|
41
|
-
## [GET THE COURSE TODAY](https://school.
|
41
|
+
## [GET THE COURSE TODAY](https://school.jfbcodes.com/8188/?utm_source=github.com&utm_campaign=github_hot_glue_readme_page) **only $60 USD!**
|
42
42
|
|
43
43
|
|
44
44
|
---
|
@@ -522,7 +522,7 @@ The short form looks like this. It presumes there is a 'pets' association from `
|
|
522
522
|
|
523
523
|
(The long form equivalent of this would be `--hawk=pet_id{current_user.pets}`)
|
524
524
|
|
525
|
-
This is covered in [Example #3 in the Hot Glue Tutorial](https://school.
|
525
|
+
This is covered in [Example #3 in the Hot Glue Tutorial](https://school.jfbcodes.com/8188)
|
526
526
|
|
527
527
|
To hawk to a scope that is not the currently authenticated user, use the long form with `{...}`
|
528
528
|
to specify the scope. Be sure to note to add the association name itself, like `users`:
|
@@ -532,7 +532,7 @@ to specify the scope. Be sure to note to add the association name itself, like `
|
|
532
532
|
This would hawk the Appointment's `user_id` key to any users who are within the scope of the
|
533
533
|
current_user's has_many association (so, for any other "my" family, would be `current_user.family.users`).
|
534
534
|
|
535
|
-
This is covered in [Example #4 in the Hot Glue Tutorial](https://school.
|
535
|
+
This is covered in [Example #4 in the Hot Glue Tutorial](https://school.jfbcodes.com/8188)
|
536
536
|
|
537
537
|
|
538
538
|
### `--plural=`
|
@@ -681,8 +681,9 @@ Notice that each modifiers can be used with specific field types.
|
|
681
681
|
| (truthy label)\|(falsy label) | specify a binary switch with a pipe (\|) character if the value is truthy, it will display as "truthy label" if the value is falsy, it will display as "falsy label" | booleans, datetimes, dates, times | | |
|
682
682
|
| partials | applies to enums only, you must have a partial whose name matches each enum type | enums only | | |
|
683
683
|
| tinymce | applies to text fields only, be sure to setup TineMCE globally | text fields only | | |
|
684
|
+
| typeahead | turns a foreign key (only) into a searchable typeahead field | foreign keys only | | |
|
684
685
|
|
685
|
-
Except for "(truthy label)" and "(falsy label)" which
|
686
|
+
Except for "(truthy label)" and "(falsy label)" which use the special syntax, use the modifier _exactly_ as it is named.
|
686
687
|
|
687
688
|
### `--pundit`
|
688
689
|
If you enable Pundit, your controllers will look for a Policy that matches the name of the thing being built.
|
@@ -690,11 +691,14 @@ If you enable Pundit, your controllers will look for a Policy that matches the n
|
|
690
691
|
**Realtime Field-level Access**
|
691
692
|
Hot Glue gives you automatic field level access control if you create `*_able?` methods for each field you'd like to control on your Pundit policy.
|
692
693
|
|
693
|
-
(Although this is not what Pundit recommends for field level access control, it has been implemented this way to provide field-by-field user feedback to the user shown in red just like Rails validation errors are currently shown.
|
694
|
+
(Although this is not what Pundit recommends for field level access control, it has been implemented this way to provide field-by-field user feedback to the user shown in red just like Rails validation errors are currently shown. A user having access to input text into a field they shouldn't have access to is *only hypothetical*, because the Hot Glue will correctly show the the user connect edit was view-only anyway, making unallowed entry only something that could be achieved through a mistake or hacking. Nevertheless, rest assured that if there was a input mistake-- like a user having a field editable when it shouldn't be, if you implement your backend policy correctly with an `update?` method guards against the disallowed input, your user will show an error message and the record will not be updated.)
|
695
|
+
|
696
|
+
The `*_able?` method should return true or false depending on whether or not the field can be edited. No distinction is made between the `new` and `edit` contexts. You may check if the record is new using `new_record?`.
|
697
|
+
|
698
|
+
|
699
|
+
**The `*_able?` method is used by Hot Glue only on the new and edit actions. You must incorporate it into the policy's `update?` method as in the example, or else no guard will check prevent the user doesn't pass a value to input it anyway.**
|
694
700
|
|
695
|
-
The `*_able?` method should return true or false depending on whether or not the field can be edited. (No distinction is made between the different contexts. You may check if the record is new using `new_record?`.
|
696
701
|
|
697
|
-
The `*_able?` method is used by Hot Glue only on the new and edit actions, but you should incorporate it into the policy's `update?` method as in the example, which will extend other operations since Hot Glue will use the policy to authorize the action
|
698
702
|
Add one `*_able?` method to the policy for each field you want to allow field-level access control.
|
699
703
|
|
700
704
|
Replace `*` with the name of the field you want under access control. Remember to include `_id` for foreign keys. You do not need it for any field you don't want under access control.
|
@@ -851,7 +855,7 @@ This means you can be a somewhat lazy about your bang methods, but keep in mind
|
|
851
855
|
|
852
856
|
Finally, you can raise an ActiveRecord error which will also get passed to the user in the flash alert area.
|
853
857
|
|
854
|
-
For more information see Example 5 in the Tutorial
|
858
|
+
For more information see [Example 5 in the Tutorial](https://school.jfbcodes.com/8188)
|
855
859
|
|
856
860
|
|
857
861
|
### `--downnest`
|
@@ -963,6 +967,25 @@ This happens using two interconnected mechanisms:
|
|
963
967
|
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.
|
964
968
|
|
965
969
|
|
970
|
+
### `--related-sets`
|
971
|
+
|
972
|
+
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.
|
973
|
+
|
974
|
+
Consider the classic example of three tables: users, user_roles, and roles
|
975
|
+
|
976
|
+
User `has_many :user_roles`; UserRole `belongs_to :user` and `belongs_to :role`; and Role `has_many :user_roles` and `has_many :user, through: :user_roles`
|
977
|
+
|
978
|
+
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.)
|
979
|
+
|
980
|
+
```
|
981
|
+
rails generate hot_glue:scaffold User --related-sets=roles --include=email,roles --gd
|
982
|
+
```
|
983
|
+
|
984
|
+
Note this leaves open a privileged escalation attack (a security vulnerability).
|
985
|
+
|
986
|
+
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)
|
987
|
+
|
988
|
+
|
966
989
|
## "Thing" Label
|
967
990
|
|
968
991
|
Note that on a per model basis, you can also globally omit the label or set a unique label value using
|
@@ -1028,6 +1051,20 @@ Use `before` to make the labels come before or `after` to make them come after.
|
|
1028
1051
|
Omits the heading of column names that appears above the 1st row of data.
|
1029
1052
|
|
1030
1053
|
|
1054
|
+
|
1055
|
+
`--code-before-create`
|
1056
|
+
`--code-after-create`
|
1057
|
+
`--code-before-update`
|
1058
|
+
`--code-after-update`
|
1059
|
+
|
1060
|
+
Insert some code into the `create` action or `update` actions.
|
1061
|
+
The **before code** is called _after authorization_ but _before saving_ (which creates the record, or fails validation).
|
1062
|
+
The **after create** code is called after the record is saved (and thus has an id in the case of the create action).
|
1063
|
+
Both should be wrapped in quotation marks when specified in the command line, and use semicolons to separate multiple lines of code.
|
1064
|
+
(Notice the funky indentation of the lines in the generated code. Adjust you input to get the indentation correct.)
|
1065
|
+
|
1066
|
+
|
1067
|
+
|
1031
1068
|
## Special Features
|
1032
1069
|
|
1033
1070
|
### `--attachments`
|
@@ -1325,6 +1362,10 @@ Child portals have the headings omitted automatically (there is a heading identi
|
|
1325
1362
|
- Enum - displayed as a drop-down list (defined the enum values on your model).
|
1326
1363
|
- For Rails 6 see https://jasonfleetwoodboldt.com/courses/stepping-up-rails/enumerated-types-in-rails-and-postgres/
|
1327
1364
|
- You must specify the enum definition both in your model and also in your database migration for both Rails 6 + Rails 7
|
1365
|
+
- You can modify an enum so that instead of a drop down list, it displays a partial view that you must build. See the [v0.5.23 Release notes](https://github.com/hot-glue-for-rails/hot-glue#2023-10-01---v0523) for details.
|
1366
|
+
|
1367
|
+
|
1368
|
+
|
1328
1369
|
|
1329
1370
|
# Note about enums
|
1330
1371
|
|
@@ -1369,6 +1410,8 @@ def self.status_labels
|
|
1369
1410
|
|
1370
1411
|
Now, your labels will show up on the front-end as defined in the `_labels` ("Is currently pending", etc) instead of the database-values.
|
1371
1412
|
|
1413
|
+
You can modify an enum so that instead of a drop down list, it displays a partial view that you must build. See the [v0.5.23 Release notes](https://github.com/hot-glue-for-rails/hot-glue#2023-10-01---v0523) for details.
|
1414
|
+
|
1372
1415
|
### Validation Magic
|
1373
1416
|
|
1374
1417
|
Use ActiveRecord validations or hooks to validate your data. Hot Glue will automatically display the errors in the UI.
|
@@ -1406,7 +1449,7 @@ You will do these three things:
|
|
1406
1449
|
2. When generating a scaffold you want to make a typeahead association, use `--modify='parent_id{typeahead}'` where `parent_id` is the foreign key
|
1407
1450
|
`bin/rails generate hot_glue:scaffold Book --include=title,author_id --modify='author_id{typeahead}'`
|
1408
1451
|
3. Within each namespace, you will generate a special typeahead controller (it exists for the associated object to be searched on
|
1409
|
-
`bin/rails generate hot_glue:
|
1452
|
+
`bin/rails generate hot_glue:typeahead Author`
|
1410
1453
|
This will create a controller for `AuthorsTypeaheadController` that will allow text search against any *string* field on the `Author` model.
|
1411
1454
|
This special generator takes flags `--namespace` as the normal generator and also `--search-by` to let you specify the list of fields you want to search by.
|
1412
1455
|
|
@@ -1471,8 +1514,82 @@ For example, to display the field `my_story` on the object `Thing`, you'd genera
|
|
1471
1514
|
bin/rails generate Thing --include=my_story --modify='my_story{tinymce}'
|
1472
1515
|
```
|
1473
1516
|
|
1517
|
+
### Pickup Partial Includes for `_edit` and `_new` Screens
|
1518
|
+
|
1519
|
+
If you have a partial already in the view folder called `_edit_within_form.html.erb`, it with get included within the edit form.
|
1520
|
+
If you have a partial already in the view folder called `_new_within_form.html.erb`, it with get included within the new form.
|
1521
|
+
For these, you can use any of the objects by local variable name or the special `f` local variable to access the form itself.
|
1522
|
+
|
1523
|
+
These partials are good for including extra functionality in the form, like interactive widgets or hidden fields.
|
1524
|
+
If you have a partial already in the view folder called `_edit_after_form.html.erb`, it with get included **_after_** the edit form.
|
1525
|
+
If you have a partial already in the view folder called `_new_after_form.html.erb`, it with get included **_after_** the new form.
|
1526
|
+
You can use any of the objects by local variable name (but you cannot use the form object `f` because it is not in scope.)
|
1527
|
+
|
1528
|
+
The `within` partials should do operations within the form (like hidden fields), and the `after` partials should do entirely unrelated operations, like a different form entirely.
|
1529
|
+
|
1530
|
+
These automatic pickups for partials are detected at buildtime. This means that if you add these partials later, you must rebuild your scaffold.
|
1531
|
+
|
1532
|
+
|
1533
|
+
|
1534
|
+
|
1535
|
+
|
1474
1536
|
# VERSION HISTORY
|
1475
1537
|
|
1538
|
+
#### 2023-12-02 - v0.6.2
|
1539
|
+
|
1540
|
+
• Fixes to typeahead when using Pundit.
|
1541
|
+
|
1542
|
+
• New Code Hooks: Add Custom Code Before/After the Update or Create Actions
|
1543
|
+
|
1544
|
+
`--code-before-create`
|
1545
|
+
`--code-after-create`
|
1546
|
+
`--code-before-update`
|
1547
|
+
`--code-after-update`
|
1548
|
+
|
1549
|
+
|
1550
|
+
Insert some code into the `create` action or `update` actions.
|
1551
|
+
|
1552
|
+
The **before code** is called _after authorization_ but _before saving_ (which creates the record, or fails validation).
|
1553
|
+
|
1554
|
+
The **after create** code is called after the record is saved (and thus has an id in the case of the create action).
|
1555
|
+
|
1556
|
+
Both should be wrapped in quotation marks when specified in the command line, and use semicolons to separate multiple lines of code.
|
1557
|
+
(Notice the funky indentation of the lines in the generated code. Adjust you input to get the indentation correct.)
|
1558
|
+
|
1559
|
+
• New Automatic Pickup Partial Includes for `_edit` and `_new` Screens
|
1560
|
+
|
1561
|
+
See "Pickup Partial Includes for `_edit` and `_new` Screens" above
|
1562
|
+
|
1563
|
+
|
1564
|
+
|
1565
|
+
#### 2023-11-21 - v0.6.1 - `--related-sets`
|
1566
|
+
|
1567
|
+
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.
|
1568
|
+
|
1569
|
+
Consider the classic example of three tables: users, user_roles, and roles
|
1570
|
+
|
1571
|
+
User `has_many :user_roles`; UserRole `belongs_to :user` and `belongs_to :role`; and Role `has_many :user_roles` and `has_many :user, through: :user_roles`
|
1572
|
+
|
1573
|
+
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.)
|
1574
|
+
|
1575
|
+
```
|
1576
|
+
rails generate hot_glue:scaffold User --related-sets=roles --include=email,roles --gd
|
1577
|
+
```
|
1578
|
+
|
1579
|
+
Note that when making a scaffold like this, you may leave open a privileged escalation attack (a security vulnerability).
|
1580
|
+
|
1581
|
+
To fix this, you'll need to use Pundit with special syntax designed for this purpose.
|
1582
|
+
|
1583
|
+
For a complete solution, please see Example 17 in the [Hot Glue Tutorial](https://school.jfbcodes.com/8188).
|
1584
|
+
|
1585
|
+
Without Pundit, due to a quirk in how this code works with ActiveRecord, all update operates to the related sets table are permitted (and go through), even if the update operation otherwise fails validation for the fields on the object. (ActiveRecord doesn't seem to have a way to validate the related sets directly.)
|
1586
|
+
|
1587
|
+
In this case, your update actions may update the relate sets table but fail to update the current object.
|
1588
|
+
|
1589
|
+
Using this feature with Pundit will fix this problem, and it is achieved with a (hacky) implementation that performs a pre-check for each related set against the Pundit policy.
|
1590
|
+
|
1591
|
+
#### v0.6.0.1 - small tweaks to typeahead
|
1592
|
+
|
1476
1593
|
#### 2023-11-03 - v0.6.0
|
1477
1594
|
|
1478
1595
|
Typeahead Associations
|
@@ -1862,8 +1979,8 @@ License check has been removed (Hot Glue is now free to use for hobbyists and in
|
|
1862
1979
|
#### 2022-03-23 - v0.5.2 - Hawked Foreign Keys
|
1863
1980
|
|
1864
1981
|
- You can now protect your foreign keys from malicious input and also restrict the scope of drop downs to show only records with the specified access control restriction.
|
1865
|
-
- [Example #3](https://school.
|
1866
|
-
- [Example #4](https://school.
|
1982
|
+
- [Example #3](https://school.jfbcodes.com/8188) in the Hot Glue Tutorial shows you how to use the hawk to limit the scope to the logged in user.
|
1983
|
+
- [Example #4](https://school.jfbcodes.com/8188) in the Hot Glue Tutorial shows how to hawk to a non-usual scope, the inverse of the current user's belongs_to (that is, hawk the scope to X where current_user `belongs_to :x`)
|
1867
1984
|
|
1868
1985
|
|
1869
1986
|
#### 2022-03-12 - v0.5.1 - Inline List Labels
|
@@ -10,6 +10,8 @@ require_relative "fields/text_field"
|
|
10
10
|
require_relative "fields/time_field"
|
11
11
|
require_relative "fields/uuid_field"
|
12
12
|
require_relative "fields/attachment_field"
|
13
|
+
require_relative "fields/related_set_field"
|
14
|
+
|
13
15
|
|
14
16
|
|
15
17
|
class FieldFactory
|
@@ -42,8 +44,11 @@ class FieldFactory
|
|
42
44
|
EnumField
|
43
45
|
when :attachment
|
44
46
|
AttachmentField
|
47
|
+
when :related_set
|
48
|
+
RelatedSetField
|
45
49
|
end
|
46
50
|
@class_name = class_name
|
51
|
+
|
47
52
|
@field = field_class.new(name: name,
|
48
53
|
layout_strategy: generator.layout_strategy,
|
49
54
|
form_placeholder_labels: generator.form_placeholder_labels,
|
@@ -60,6 +65,7 @@ class FieldFactory
|
|
60
65
|
modify_as: generator.modify_as[name.to_sym] || nil,
|
61
66
|
display_as: generator.display_as[name.to_sym] || nil,
|
62
67
|
default_boolean_display: generator.default_boolean_display,
|
63
|
-
namespace: generator.namespace_value
|
68
|
+
namespace: generator.namespace_value,
|
69
|
+
pundit: generator.pundit )
|
64
70
|
end
|
65
71
|
end
|
@@ -10,7 +10,7 @@ class AssociationField < Field
|
|
10
10
|
update_show_only: ,
|
11
11
|
hawk_keys: , auth: , sample_file_path:, ownership_field: ,
|
12
12
|
attachment_data: nil , layout_strategy: , form_placeholder_labels: nil,
|
13
|
-
form_labels_position:, modify_as: , self_auth: , namespace:
|
13
|
+
form_labels_position:, modify_as: , self_auth: , namespace:, pundit: )
|
14
14
|
super
|
15
15
|
|
16
16
|
@assoc_model = eval("#{class_name}.reflect_on_association(:#{assoc})")
|
@@ -78,9 +78,8 @@ class AssociationField < Field
|
|
78
78
|
def form_field_output
|
79
79
|
assoc_name = name.to_s.gsub("_id","")
|
80
80
|
assoc = eval("#{class_name}.reflect_on_association(:#{assoc_name})")
|
81
|
-
|
82
81
|
if modify_as && modify_as[:typeahead]
|
83
|
-
search_url = "#{namespace ? namespace + "_" : ""}#{assoc.
|
82
|
+
search_url = "#{namespace ? namespace + "_" : ""}#{assoc.class_name.downcase.pluralize}_typeahead_index_url"
|
84
83
|
"<div class='typeahead typeahead--#{assoc.name}_id'
|
85
84
|
data-controller='typeahead'
|
86
85
|
data-typeahead-url-value='<%= #{search_url} %>'
|
@@ -1,10 +1,9 @@
|
|
1
1
|
class AttachmentField < Field
|
2
2
|
attr_accessor :attachment_data
|
3
3
|
def initialize(name:, class_name:, default_boolean_display: ,
|
4
|
-
display_as:,
|
5
|
-
singular:, update_show_only:, hawk_keys:, auth:,
|
4
|
+
display_as:, singular:, update_show_only:, hawk_keys:, auth:,
|
6
5
|
sample_file_path: nil, attachment_data:, ownership_field:, layout_strategy: ,
|
7
|
-
form_placeholder_labels: , form_labels_position:, modify_as:, self_auth: , namespace:
|
6
|
+
form_placeholder_labels: , form_labels_position:, modify_as:, self_auth: , namespace:, pundit: )
|
8
7
|
super
|
9
8
|
|
10
9
|
@attachment_data = attachment_data
|
@@ -5,7 +5,7 @@ class Field
|
|
5
5
|
:hawk_keys, :layout_strategy, :limit, :modify_as, :name, :object, :sample_file_path,
|
6
6
|
:self_auth,
|
7
7
|
:singular_class, :singular, :sql_type, :ownership_field,
|
8
|
-
:update_show_only, :namespace
|
8
|
+
:update_show_only, :namespace, :pundit
|
9
9
|
|
10
10
|
def initialize(
|
11
11
|
auth: ,
|
@@ -24,7 +24,8 @@ class Field
|
|
24
24
|
singular: ,
|
25
25
|
update_show_only:,
|
26
26
|
self_auth:,
|
27
|
-
namespace
|
27
|
+
namespace:,
|
28
|
+
pundit:
|
28
29
|
)
|
29
30
|
@name = name
|
30
31
|
@layout_strategy = layout_strategy
|
@@ -40,13 +41,14 @@ class Field
|
|
40
41
|
@form_labels_position = form_labels_position
|
41
42
|
@modify_as = modify_as
|
42
43
|
@display_as = display_as
|
44
|
+
@pundit = pundit
|
43
45
|
|
44
46
|
@self_auth = self_auth
|
45
47
|
@default_boolean_display = default_boolean_display
|
46
48
|
@namespace = namespace
|
47
49
|
|
48
50
|
# TODO: remove knowledge of subclasses from Field
|
49
|
-
unless self.class == AttachmentField
|
51
|
+
unless self.class == AttachmentField || self.class == RelatedSetField
|
50
52
|
@sql_type = eval("#{class_name}.columns_hash['#{name}']").sql_type
|
51
53
|
@limit = eval("#{class_name}.columns_hash['#{name}']").limit
|
52
54
|
end
|
@@ -56,6 +58,10 @@ class Field
|
|
56
58
|
@name
|
57
59
|
end
|
58
60
|
|
61
|
+
def form_field_output
|
62
|
+
raise "superclass must implement"
|
63
|
+
end
|
64
|
+
|
59
65
|
def field_error_name
|
60
66
|
name
|
61
67
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
class RelatedSetField < Field
|
2
|
+
|
3
|
+
attr_accessor :assoc_name, :assoc_class, :assoc
|
4
|
+
|
5
|
+
def initialize( class_name: , default_boolean_display:, display_as: ,
|
6
|
+
name: , singular: ,
|
7
|
+
update_show_only: ,
|
8
|
+
hawk_keys: , auth: , sample_file_path:, ownership_field: ,
|
9
|
+
attachment_data: nil , layout_strategy: , form_placeholder_labels: nil,
|
10
|
+
form_labels_position:, modify_as: , self_auth: , namespace:, pundit:)
|
11
|
+
super
|
12
|
+
|
13
|
+
@related_set_model = eval("#{class_name}.reflect_on_association(:#{name})")
|
14
|
+
|
15
|
+
if @related_set_model.nil?
|
16
|
+
raise "You specified a related set #{name} but there is no association on #{singular_class} for #{name}; please add a `has_and_belongs_to_many :#{name}` OR a `has_many :#{name}, through: ...` to the #{singular_class} model"
|
17
|
+
|
18
|
+
# exit_message = "*** Oops: The model #{class_name} is missing an association for :#{assoc_name} or the model #{assoc_name.titlecase} doesn't exist. TODO: Please implement a model for #{assoc_name.titlecase}; or add to #{class_name} `belongs_to :#{assoc_name}`. To make a controller that can read all records, specify with --god."
|
19
|
+
puts exit_message
|
20
|
+
raise(HotGlue::Error, exit_message)
|
21
|
+
end
|
22
|
+
|
23
|
+
@assoc_class = eval(@related_set_model.try(:class_name))
|
24
|
+
|
25
|
+
name_list = [:name, :to_label, :full_name, :display_name, :email]
|
26
|
+
|
27
|
+
if assoc_class && name_list.collect{ |field|
|
28
|
+
assoc_class.respond_to?(field.to_s) || assoc_class.instance_methods.include?(field)
|
29
|
+
}.none?
|
30
|
+
exit_message = "Oops: Missing a label for `#{assoc_class}`. Can't find any column to use as the display label for the #{@assoc_name} association on the #{class_name} model. TODO: Please implement just one of: 1) name, 2) to_label, 3) full_name, 4) display_name 5) email. You can implement any of these directly on your`#{assoc_class}` model (can be database fields or model methods) or alias them to field you want to use as your display label. Then RERUN THIS GENERATOR. (Field used will be chosen based on rank here.)"
|
31
|
+
raise(HotGlue::Error, exit_message)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
def form_field_output
|
38
|
+
disabled_syntax = +""
|
39
|
+
if pundit
|
40
|
+
disabled_syntax << ", {disabled: ! #{class_name}Policy.new(#{auth}, @#{singular}).role_ids_able?}"
|
41
|
+
end
|
42
|
+
" <%= f.collection_check_boxes :#{association_ids_method}, #{association_class_name}.all, :id, :label, {}#{disabled_syntax} do |m| %>
|
43
|
+
<%= m.check_box %> <%= m.label %><br />
|
44
|
+
<% end %>"
|
45
|
+
end
|
46
|
+
|
47
|
+
def association_ids_method
|
48
|
+
eval("#{class_name}.reflect_on_association(:#{name})").class_name.underscore + "_ids"
|
49
|
+
end
|
50
|
+
|
51
|
+
def association_class_name
|
52
|
+
eval("#{class_name}.reflect_on_association(:#{name})").class_name
|
53
|
+
end
|
54
|
+
|
55
|
+
def viewable_output
|
56
|
+
"<%= #{singular}.#{name}.collect(&:label).join(\", \") %>"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
@@ -8,7 +8,7 @@ module HotGlue
|
|
8
8
|
:inline_list_labels, :layout_object,
|
9
9
|
:columns, :col_identifier, :singular,
|
10
10
|
:form_placeholder_labels, :hawk_keys, :update_show_only,
|
11
|
-
:attachments, :show_only, :columns_map, :pundit
|
11
|
+
:attachments, :show_only, :columns_map, :pundit, :related_sets
|
12
12
|
|
13
13
|
|
14
14
|
def initialize(singular:, singular_class: ,
|
@@ -17,7 +17,7 @@ module HotGlue
|
|
17
17
|
ownership_field: , form_labels_position: ,
|
18
18
|
inline_list_labels: ,
|
19
19
|
form_placeholder_labels:, hawk_keys: ,
|
20
|
-
update_show_only:, attachments: , columns_map:, pundit:
|
20
|
+
update_show_only:, attachments: , columns_map:, pundit:, related_sets: )
|
21
21
|
|
22
22
|
@singular = singular
|
23
23
|
@singular_class = singular_class
|
@@ -39,6 +39,7 @@ module HotGlue
|
|
39
39
|
@hawk_keys = hawk_keys
|
40
40
|
@update_show_only = update_show_only
|
41
41
|
@attachments = attachments
|
42
|
+
@related_sets = related_sets
|
42
43
|
end
|
43
44
|
|
44
45
|
def add_spaces_each_line(text, num_spaces)
|
@@ -150,7 +151,7 @@ module HotGlue
|
|
150
151
|
|
151
152
|
"<div class='#{col_identifier} #{singular}--#{column.join("-")}'#{style_with_flex_basis}> " +
|
152
153
|
column.map { |col|
|
153
|
-
if eval("#{singular_class}.columns_hash['#{col}']").nil? && !attachments.keys.include?(col)
|
154
|
+
if eval("#{singular_class}.columns_hash['#{col}']").nil? && !attachments.keys.include?(col) && !related_sets.include?(col)
|
154
155
|
raise "Can't find column '#{col}' on #{singular_class}, are you sure that is the column name?"
|
155
156
|
end
|
156
157
|
field_output = columns_map[col].line_field_output
|
@@ -23,7 +23,7 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
23
23
|
:nest_with, :path, :plural, :sample_file_path, :show_only_data, :singular,
|
24
24
|
:singular_class, :smart_layout, :stacked_downnesting, :update_show_only, :ownership_field,
|
25
25
|
:layout_strategy, :form_placeholder_labels, :form_labels_position, :pundit,
|
26
|
-
:self_auth, :namespace_value
|
26
|
+
:self_auth, :namespace_value, :related_sets
|
27
27
|
# important: using an attr_accessor called :namespace indirectly causes a conflict with Rails class_name method
|
28
28
|
# so we use namespace_value instead
|
29
29
|
|
@@ -89,6 +89,11 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
89
89
|
class_option :modify, default: {}
|
90
90
|
class_option :display_as, default: {}
|
91
91
|
class_option :pundit, default: nil
|
92
|
+
class_option :related_sets, default: ''
|
93
|
+
class_option :code_before_create, default: nil
|
94
|
+
class_option :code_after_create, default: nil
|
95
|
+
class_option :code_before_update, default: nil
|
96
|
+
class_option :code_after_update, default: nil
|
92
97
|
|
93
98
|
def initialize(*meta_args)
|
94
99
|
super
|
@@ -320,7 +325,7 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
320
325
|
end
|
321
326
|
|
322
327
|
if @god
|
323
|
-
@auth = nil
|
328
|
+
# @auth = nil
|
324
329
|
end
|
325
330
|
# when in self auth, the object is the same as the authenticated object
|
326
331
|
|
@@ -370,6 +375,24 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
370
375
|
puts "NESTING: #{@nested_set}"
|
371
376
|
end
|
372
377
|
|
378
|
+
# related_sets
|
379
|
+
related_set_input = options['related_sets'].split(",")
|
380
|
+
@related_sets = {}
|
381
|
+
related_set_input.each do |setting|
|
382
|
+
name = setting.to_sym
|
383
|
+
association_ids_method = eval("#{singular_class}.reflect_on_association(:#{setting.to_sym})").class_name.underscore + "_ids"
|
384
|
+
class_name = eval("#{singular_class}.reflect_on_association(:#{setting.to_sym})").class_name
|
385
|
+
|
386
|
+
@related_sets[setting.to_sym] = { name: setting.to_sym,
|
387
|
+
association_ids_method: association_ids_method,
|
388
|
+
class_name: class_name }
|
389
|
+
end
|
390
|
+
|
391
|
+
if @related_sets.any?
|
392
|
+
puts "RELATED SETS: #{@related_sets}"
|
393
|
+
|
394
|
+
end
|
395
|
+
|
373
396
|
# OBJECT OWNERSHIP & NESTING
|
374
397
|
@reference_name = HotGlue.derrive_reference_name(singular_class)
|
375
398
|
if @auth && @self_auth
|
@@ -407,15 +430,23 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
407
430
|
@no_field_form = true
|
408
431
|
end
|
409
432
|
|
433
|
+
@code_before_create = options['code_before_create']
|
434
|
+
@code_after_create = options['code_after_create']
|
435
|
+
@code_before_update = options['code_before_update']
|
436
|
+
@code_after_update = options['code_after_update']
|
437
|
+
|
410
438
|
buttons_width = ((!@no_edit && 1) || 0) + ((!@no_delete && 1) || 0) + @magic_buttons.count
|
411
439
|
|
440
|
+
|
441
|
+
|
442
|
+
|
412
443
|
# build a new polymorphic object
|
413
444
|
@associations = []
|
414
445
|
@columns_map = {}
|
415
446
|
@columns.each do |col|
|
416
|
-
if !(@the_object.columns_hash.keys.include?(col.to_s) || @attachments.keys.include?(col))
|
417
|
-
|
418
|
-
end
|
447
|
+
# if !(@the_object.columns_hash.keys.include?(col.to_s) || @attachments.keys.include?(col))
|
448
|
+
# raise "couldn't find #{col} in either field list or attachments list"
|
449
|
+
# end
|
419
450
|
|
420
451
|
if col.to_s.starts_with?("_")
|
421
452
|
@show_only << col
|
@@ -425,6 +456,10 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
425
456
|
type = @the_object.columns_hash[col.to_s].type
|
426
457
|
elsif @attachments.keys.include?(col)
|
427
458
|
type = :attachment
|
459
|
+
elsif @related_sets.keys.include?(col)
|
460
|
+
type = :related_set
|
461
|
+
else
|
462
|
+
raise "couldn't find #{col} in either field list, attachments, or related sets"
|
428
463
|
end
|
429
464
|
this_column_object = FieldFactory.new(name: col.to_s,
|
430
465
|
generator: self,
|
@@ -440,7 +475,7 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
440
475
|
if field.is_a?(AssociationField)
|
441
476
|
if @modify_as && @modify_as[key] && @modify_as[key][:typeahead]
|
442
477
|
assoc_name = field.assoc_name
|
443
|
-
file_path = "app/controllers/#{namespace ?
|
478
|
+
file_path = "app/controllers/#{@namespace ? @namespace + "/" : ""}#{assoc_name.pluralize}_typeahead_controller.rb"
|
444
479
|
|
445
480
|
if ! File.exist?(file_path)
|
446
481
|
|
@@ -455,6 +490,8 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
455
490
|
end
|
456
491
|
end
|
457
492
|
|
493
|
+
|
494
|
+
|
458
495
|
# create the template object
|
459
496
|
if @markup == "erb"
|
460
497
|
@template_builder = HotGlue::ErbTemplate.new(
|
@@ -473,6 +510,7 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
473
510
|
attachments: @attachments,
|
474
511
|
columns_map: @columns_map,
|
475
512
|
pundit: @pundit,
|
513
|
+
related_sets: @related_sets
|
476
514
|
)
|
477
515
|
elsif @markup == "slim"
|
478
516
|
raise(HotGlue::Error, "SLIM IS NOT IMPLEMENTED")
|
@@ -607,6 +645,7 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
607
645
|
end
|
608
646
|
|
609
647
|
def identify_object_owner
|
648
|
+
return if @god
|
610
649
|
auth_assoc = @auth && @auth.gsub("current_", "")
|
611
650
|
|
612
651
|
if @object_owner_sym && !@self_auth
|
@@ -657,6 +696,16 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
657
696
|
|
658
697
|
check_if_sample_file_is_present
|
659
698
|
end
|
699
|
+
|
700
|
+
if @related_sets.any?
|
701
|
+
if !@pundit
|
702
|
+
puts "********************\nWARNING: You are using --related-sets without using Pundit. This makes the set fully accessible. Use Pundit to prevent a privileged escalation vulnerability\n********************\n"
|
703
|
+
end
|
704
|
+
@related_sets.each do |key, related_set|
|
705
|
+
@columns << related_set[:name] if !@columns.include?(related_set[:name])
|
706
|
+
puts "Adding related set :#{related_set[:name]} as-a-column"
|
707
|
+
end
|
708
|
+
end
|
660
709
|
end
|
661
710
|
|
662
711
|
def check_if_sample_file_is_present
|
@@ -676,16 +725,25 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
676
725
|
puts ""
|
677
726
|
end
|
678
727
|
|
679
|
-
def
|
680
|
-
@columns
|
728
|
+
def fields_filtered_for_strong_params
|
729
|
+
@columns - @related_sets.collect{|key, set| set[:name]}
|
681
730
|
end
|
682
731
|
|
683
732
|
def creation_syntax
|
684
733
|
if @factory_creation == ''
|
685
|
-
"@#{singular } = #{ class_name }.
|
734
|
+
"@#{singular } = #{ class_name }.new(modified_params)"
|
686
735
|
else
|
687
|
-
|
688
|
-
|
736
|
+
res = +"begin
|
737
|
+
#{@factory_creation}
|
738
|
+
"
|
739
|
+
res << "\n" + "@#{singular} = factory.#{singular}" unless res.include?("@#{singular} = factory.#{singular}")
|
740
|
+
res << "flash[:notice] = \"Successfully created \#{@#{singular}.name}\" unless @#{singular}.new_record?
|
741
|
+
rescue ActiveRecord::RecordInvalid
|
742
|
+
@#{singular} = factory.#{singular}
|
743
|
+
flash[:alert] = \"Oops, your #{singular} could not be created. #{@hawk_alarm}\"
|
744
|
+
@action = 'new'
|
745
|
+
end"
|
746
|
+
res
|
689
747
|
end
|
690
748
|
end
|
691
749
|
|
@@ -703,6 +761,7 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
703
761
|
|
704
762
|
def copy_controller_and_spec_files
|
705
763
|
@default_colspan = @columns.size
|
764
|
+
|
706
765
|
unless @specs_only || @no_controller
|
707
766
|
template "controller.rb.erb", File.join("#{filepath_prefix}app/controllers#{namespace_with_dash}", "#{@controller_build_folder}_controller.rb")
|
708
767
|
if @namespace
|
@@ -969,7 +1028,7 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
969
1028
|
end
|
970
1029
|
|
971
1030
|
def object_scope
|
972
|
-
if @auth
|
1031
|
+
if @auth && !@god
|
973
1032
|
if @nested_set.none?
|
974
1033
|
@auth + ".#{plural}"
|
975
1034
|
else
|
@@ -1046,6 +1105,10 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
1046
1105
|
|
1047
1106
|
def copy_view_files
|
1048
1107
|
return if @specs_only
|
1108
|
+
@edit_within_form_partial = File.exist?("#{filepath_prefix}app/views#{namespace_with_dash}/#{@controller_build_folder}/_edit_within_form.html.#{@markup}")
|
1109
|
+
@edit_after_form_partial = File.exist?("#{filepath_prefix}app/views#{namespace_with_dash}/#{@controller_build_folder}/_edit_within_form.html.#{@markup}")
|
1110
|
+
@new_within_form_partial = File.exist?("#{filepath_prefix}app/views#{namespace_with_dash}/#{@controller_build_folder}/_new_within_form.html.#{@markup}")
|
1111
|
+
@new_after_form_partial = File.exist?("#{filepath_prefix}app/views#{namespace_with_dash}/#{@controller_build_folder}/_new_within_form.html.#{@markup}")
|
1049
1112
|
|
1050
1113
|
if @no_controller
|
1051
1114
|
File.write("#{Rails.root}/app/views/#{namespace_with_trailing_dash}/#{plural}/REGENERATE.md", regenerate_me_code)
|
@@ -1296,7 +1359,7 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
1296
1359
|
|
1297
1360
|
def n_plus_one_includes
|
1298
1361
|
if @associations.any? || @attachments.any?
|
1299
|
-
".includes(" + (@associations.map { |x| x } + @attachments.collect { |k, v| "#{k}_attachment" }).map { |x| ":#{x.to_s}" }.join(", ") + ")"
|
1362
|
+
".includes(" + (@associations.map { |x| x } + @attachments.collect { |k, v| "#{k}_attachment" } ).map { |x| ":#{x.to_s}" }.join(", ") + ")"
|
1300
1363
|
else
|
1301
1364
|
""
|
1302
1365
|
end
|
@@ -1342,7 +1405,7 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
1342
1405
|
end
|
1343
1406
|
|
1344
1407
|
def any_datetime_fields?
|
1345
|
-
(@columns - @attachments.keys.collect(&:to_sym)).collect { |col| eval("#{singular_class}.columns_hash['#{col}']").type }.include?(:datetime)
|
1408
|
+
(@columns - @attachments.keys.collect(&:to_sym) - @related_sets.keys ).collect { |col| eval("#{singular_class}.columns_hash['#{col}']").type }.include?(:datetime)
|
1346
1409
|
end
|
1347
1410
|
|
1348
1411
|
def post_action_parental_updates
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class <%= controller_class_name %> < <%= controller_descends_from %>
|
2
4
|
# regenerate this controller with
|
3
5
|
<% if defined?(RuboCop) %># rubocop:disable Layout/LineLength
|
@@ -20,7 +22,7 @@ class <%= controller_class_name %> < <%= controller_descends_from %>
|
|
20
22
|
|
21
23
|
nest_chain << arg %>
|
22
24
|
before_action :<%= arg[:singular] %><%= ", if: -> { params.include?(:#{arg[:singular]}_id) }" if arg[:optional] %><% } %><% end %>
|
23
|
-
before_action :load_<%= singular_name %>, only: [
|
25
|
+
before_action :load_<%= singular_name %>, only: %i[show edit update destroy]
|
24
26
|
after_action -> { flash.discard }, if: -> { request.format.symbol == :turbo_stream }
|
25
27
|
<% if @nested_set.any? %>def <%= @nested_set[0][:singular] %><% if @god
|
26
28
|
next_object = nil
|
@@ -72,7 +74,7 @@ class <%= controller_class_name %> < <%= controller_descends_from %>
|
|
72
74
|
|
73
75
|
<% end %>def load_all_<%= plural %><% if @pundit %>
|
74
76
|
@<%= plural_name %> = policy_scope(<%= object_scope %>).page(params[:page])<%= n_plus_one_includes %><%= ".per(per)" if @paginate_per_page_selector %>
|
75
|
-
|
77
|
+
<% else %> <% if !@self_auth %>
|
76
78
|
@<%= plural_name %> = <%= object_scope.gsub("@",'') %><%= n_plus_one_includes %>.page(params[:page])<%= ".per(per)" if @paginate_per_page_selector %><%= " if params.include?(:#{ @nested_set.last[:singular]}_id)" if @nested_set.any? && @nested_set[0] && @nested_set[0][:optional] %><% if @nested_set[0] && @nested_set[0][:optional] %>
|
77
79
|
@<%= plural_name %> = <%= class_name %>.all<% end %><% else %>
|
78
80
|
@<%= plural_name %> = <%= class_name %>.where(id: <%= auth_object.gsub("@",'') %>.id)<%= n_plus_one_includes %>.page(params[:page])<%= ".per(per)" if @paginate_per_page_selector %><% end %>
|
@@ -80,63 +82,71 @@ class <%= controller_class_name %> < <%= controller_descends_from %>
|
|
80
82
|
end
|
81
83
|
|
82
84
|
def index
|
83
|
-
load_all_<%= plural %><% if @pundit %>
|
85
|
+
load_all_<%= plural %><% if @pundit %><% if @pundit %>
|
86
|
+
authorize @<%= plural_name %><% end %>
|
84
87
|
rescue Pundit::NotAuthorizedError
|
85
|
-
flash[:alert] =
|
86
|
-
render
|
88
|
+
flash[:alert] = 'You are not authorized to perform this action.'
|
89
|
+
render 'layouts/error'<% end %>
|
87
90
|
end
|
88
91
|
|
89
92
|
<% if create_action %> def new<% if @object_owner_sym %>
|
90
|
-
@<%= singular_name %> = <% if @pundit %>policy_scope(<% end %><%= class_name %><% if @pundit %>)<% end %>.new
|
93
|
+
@<%= singular_name %> = <% if @pundit %>policy_scope(<% end %><%= class_name %><% if @pundit %>)<% end %>.new<% if eval("#{class_name}.reflect_on_association(:#{@object_owner_sym})").class == ActiveRecord::Reflection::BelongsToReflection %>(<%= @object_owner_sym %>: <%= @object_owner_eval %>)<% end %><% elsif @object_owner_optional && any_nested? %>
|
91
94
|
@<%= singular_name %> = <% if @pundit %>policy_scope(<% end %><%= class_name %><% if @pundit %>)<% end %>.new({}.merge(<%= @nested_set.last[:singular] %> ? {<%= @object_owner_sym %>: <%= @object_owner_eval %>} : {}))<% else %>
|
92
95
|
@<%= singular_name %> = <% if @pundit %>policy_scope(<% end %><%= class_name %><% if @pundit %>)<% end %>.new(<% if any_nested? %><%= @object_owner_sym %>: <%= @object_owner_eval %><% end %>)<% end %>
|
93
96
|
<% if @pundit %>authorize @<%= singular_name %><% end %><% if @pundit %>
|
94
|
-
@action =
|
97
|
+
@action = 'new'
|
95
98
|
rescue Pundit::NotAuthorizedError
|
96
|
-
flash[:alert] =
|
99
|
+
flash[:alert] = 'You are not authorized to perform this action.'
|
100
|
+
load_all_users
|
97
101
|
render :index<% end %>
|
98
102
|
end
|
99
103
|
|
100
104
|
def create
|
101
105
|
modified_params = modify_date_inputs_on_params(<%= singular_name %>_params.dup, <%= current_user_object %>, <%= datetime_fields_list %>) <% if @object_owner_sym && eval("#{class_name}.reflect_on_association(:#{@object_owner_sym})").class == ActiveRecord::Reflection::BelongsToReflection %>
|
102
106
|
modified_params = modified_params.merge(<%= @object_owner_sym %>: <%= @object_owner_eval %>) <% elsif @object_owner_optional && any_nested? %>
|
103
|
-
modified_params = modified_params.merge(<%= @object_owner_name %> ? {<%= @object_owner_sym %>: <%= @object_owner_eval %>} : {}) <% elsif !@object_owner_eval.empty? %>
|
107
|
+
modified_params = modified_params.merge(<%= @object_owner_name %> ? {<%= @object_owner_sym %>: <%= @object_owner_eval %>} : {}) <% elsif !@object_owner_eval.empty? && !@god %>
|
104
108
|
modified_params = modified_params.merge(<%= @object_owner_eval %>) <% end %>
|
105
109
|
|
106
110
|
<% if @hawk_keys.any? %>
|
107
111
|
modified_params = hawk_params({<%= hawk_to_ruby %>}, modified_params)<% end %>
|
108
|
-
|
112
|
+
<%= controller_attachment_orig_filename_pickup_syntax %>
|
109
113
|
<%= creation_syntax %>
|
110
|
-
|
111
|
-
|
114
|
+
<% if @pundit %><% @related_sets.each do |key, related_set| %>
|
115
|
+
check_<%= related_set[:association_ids_method].to_s %>_permissions(modified_params, :create)<% end %><% end %>
|
116
|
+
<% if @pundit %>authorize @<%= singular %><% end %>
|
117
|
+
<%= @code_before_create ? "\n " + @code_before_create.gsub(";", "\n") : "" %>
|
118
|
+
if @<%= singular_name %>.save<%= @code_after_create ? ("\n " + @code_after_create.gsub(";", "\n")) : ""%>
|
112
119
|
flash[:notice] = "Successfully created #{@<%= singular %>.<%= display_class %>}"
|
113
120
|
<%= post_action_parental_updates %>
|
114
121
|
load_all_<%= plural %>
|
115
122
|
render :create
|
116
123
|
else
|
117
124
|
flash[:alert] = "Oops, your <%= singular_name %> could not be created. #{@hawk_alarm}"
|
118
|
-
@action =
|
125
|
+
@action = 'new'
|
119
126
|
render :create, status: :unprocessable_entity
|
120
127
|
end<% if @pundit %>
|
121
128
|
rescue Pundit::NotAuthorizedError
|
122
|
-
flash[:alert]
|
129
|
+
flash[:alert] << 'Creating organization not authorized. '
|
130
|
+
flash[:alert] << @<%= singular %>.errors.collect{|k| "#{k.attribute} #{k.message}"}.join(" ")
|
131
|
+
flash[:alert] << flash[:notice]
|
123
132
|
render :index <% end %>
|
124
133
|
end
|
125
134
|
|
126
135
|
<% end %>
|
127
136
|
<% unless @no_edit %>
|
128
137
|
def show
|
138
|
+
<% if @pundit %>authorize @<%= singular %><% end %>
|
129
139
|
redirect_to <%= HotGlue.optionalized_ternary(namespace: @namespace,
|
130
140
|
target: @singular,
|
131
141
|
nested_set: @nested_set,
|
132
|
-
modifier:
|
142
|
+
modifier: 'edit_',
|
133
143
|
with_params: true,
|
134
144
|
put_form: true).gsub("(#{singular}", "(@#{singular}") %>
|
135
145
|
end
|
136
146
|
|
137
147
|
def edit<% if @pundit %>
|
138
148
|
authorize @<%= singular_name %><% end %>
|
139
|
-
@action =
|
149
|
+
@action = 'edit'
|
140
150
|
render :edit<% if @pundit %>
|
141
151
|
rescue Pundit::NotAuthorizedError
|
142
152
|
flash[:alert] = "Editing #{@<%= singular %>.<%= display_class %>} not authorized."
|
@@ -152,27 +162,33 @@ class <%= controller_class_name %> < <%= controller_descends_from %>
|
|
152
162
|
flash[:notice] << "<% singular %> <%= button.titlecase %>."
|
153
163
|
end
|
154
164
|
<% end %>
|
165
|
+
|
155
166
|
modified_params = modify_date_inputs_on_params(<% if @update_show_only %>update_<% end %><%= singular_name %>_params.dup<%= controller_update_params_tap_away_magic_buttons %>, <%= current_user_object %>, <%= datetime_fields_list %>) <% if @object_owner_sym && eval("#{class_name}.reflect_on_association(:#{@object_owner_sym})").class == ActiveRecord::Reflection::BelongsToReflection %>
|
156
167
|
modified_params = modified_params.merge(<%= @object_owner_sym %>: <%= @object_owner_eval %>) <% elsif @object_owner_optional && any_nested? %>
|
157
|
-
modified_params = modified_params.merge(<%= @object_owner_name %> ? {<%= @object_owner_sym %>: <%= @object_owner_eval %>} : {}) <% elsif ! @object_owner_eval.empty? && !@self_auth%>
|
168
|
+
modified_params = modified_params.merge(<%= @object_owner_name %> ? {<%= @object_owner_sym %>: <%= @object_owner_eval %>} : {}) <% elsif ! @object_owner_eval.empty? && !@self_auth && ! @god%>
|
158
169
|
modified_params = modified_params.merge(<%= @object_owner_eval %>) <% end %>
|
170
|
+
<% if @pundit %><% @related_sets.each do |key, related_set| %>
|
171
|
+
check_<%= related_set[:association_ids_method].to_s %>_permissions(modified_params, :update)<% end %><% end %>
|
159
172
|
|
160
173
|
<% if @hawk_keys.any? %> modified_params = hawk_params({<%= hawk_to_ruby %>}, modified_params)<% end %>
|
161
|
-
|
174
|
+
<%= controller_attachment_orig_filename_pickup_syntax %>
|
162
175
|
<% if @pundit %>
|
163
176
|
if @<%= singular_name %>.attributes = modified_params
|
164
|
-
|
165
|
-
|
177
|
+
authorize @<%= singular_name %>
|
178
|
+
<%= @code_before_update ? "\n " + @code_before_update.gsub(";", "\n") : "" %>
|
179
|
+
@<%= singular_name %>.save
|
166
180
|
<% else %>
|
181
|
+
<%= @code_before_update ? "\n " + @code_before_update.gsub(";", "\n") : "" %>
|
167
182
|
if @<%= singular_name %>.update(modified_params)
|
168
183
|
<% end %>
|
184
|
+
<%= @code_after_update ? "\n " + @code_after_update.gsub(";", "\n") : "" %>
|
169
185
|
<% if @display_list_after_update %> load_all_<%= plural %><% end %>
|
170
186
|
flash[:notice] << "Saved #{@<%= singular %>.<%= display_class %>}"
|
171
187
|
flash[:alert] = @hawk_alarm if @hawk_alarm
|
172
188
|
render :update
|
173
189
|
else
|
174
190
|
flash[:alert] = "<%= singular_name.titlecase %> could not be saved. #{@hawk_alarm}"
|
175
|
-
@action =
|
191
|
+
@action = 'edit'
|
176
192
|
render :update, status: :unprocessable_entity
|
177
193
|
end<% if @pundit %>
|
178
194
|
rescue Pundit::NotAuthorizedError
|
@@ -194,12 +210,27 @@ class <%= controller_class_name %> < <%= controller_descends_from %>
|
|
194
210
|
render :update<% end %>
|
195
211
|
end<% end %>
|
196
212
|
|
213
|
+
<% if @pundit %><% @related_sets.each do |key, rs| %>
|
214
|
+
def check_<%= rs[:association_ids_method] %>_permissions(modified_params, action)
|
215
|
+
if modified_params[:<%= rs[:association_ids_method] %>].present? &&
|
216
|
+
modified_params[:<%= rs[:association_ids_method] %>] != @<%= singular %>.<%= rs[:association_ids_method] %>
|
217
|
+
# authorize the <%= rs[:association_ids_method] %> change using special modified_relations: {
|
218
|
+
# <%= rs[:association_ids_method] %>: modified_params[:<%= rs[:association_ids_method] %>>]} syntax for Pundit
|
219
|
+
if ! <%= singular_class %>Policy.new(current_user, @<%= singular %>,
|
220
|
+
modified_relations: {role_ids: modified_params[:<%= rs[:association_ids_method] %>]
|
221
|
+
}).method("#{action}?".to_sym).call
|
222
|
+
authorize @<%= singular %>, "#{action}?".to_sym
|
223
|
+
raise Pundit::NotAuthorizedError, message: @<%= singular %>.errors.collect{|k| "#{k.attribute} #{k.message}"}.join(" ")
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end<% end %><% end %>
|
227
|
+
|
197
228
|
def <%=singular_name%>_params
|
198
|
-
params.require(:<%= testing_name %>).permit(<%= (
|
229
|
+
params.require(:<%= testing_name %>).permit(<%= ((fields_filtered_for_strong_params - @show_only ) + @magic_buttons.collect{|x| "__#{x}"}).collect{|sym| ":#{sym}"}.join(", ") %><%= ", " + @related_sets.collect{|key, rs| "#{rs[:association_ids_method]}: []"}.join(", ") if @related_sets.any? %>)
|
199
230
|
end<% if @update_show_only %>
|
200
231
|
|
201
232
|
def update_<%=singular_name%>_params
|
202
|
-
params.require(:<%= testing_name %>).permit(<%= (
|
233
|
+
params.require(:<%= testing_name %>).permit(<%= ((fields_filtered_for_strong_params - @update_show_only) + @magic_buttons.collect{|x| "__#{x}"}).collect{|sym| ":#{sym}"}.join(", ") %><%= ", " + @related_sets.collect{|key, rs| "#{rs[:association_ids_method]}: []"}.join(", ") if @related_sets.any? %>)
|
203
234
|
end<% end %>
|
204
235
|
|
205
236
|
def namespace
|
@@ -5,7 +5,9 @@
|
|
5
5
|
<\% end %>
|
6
6
|
<h2>Editing <\%= <%= singular %>.<%= display_class %> %></h2>
|
7
7
|
<\%= form_with model: <%= singular %>, url: <%= form_path_edit_helper %> do |f| %>
|
8
|
-
|
8
|
+
<\%= render partial: "<%= namespace_with_trailing_dash + @controller_build_folder + "/" %>form", locals: {:<%= singular %> => <%= singular %>, f: f}<%= @nested_set.collect{|arg| ".merge(#{arg[:singular]} ? {#{arg[:singular]}: #{arg[:singular]}} : {})" }.join %> \%>
|
9
|
+
<% if @edit_within_form_partial %><\%= render partial: "edit_within_form", locals: {f: f, <%= singular %>: <%= singular %>}<%= @nested_set.collect{|arg| ".merge(#{arg[:singular]} ? {#{arg[:singular]}: #{arg[:singular]}} : {})" }.join %> %><% end %>
|
9
10
|
<\% end %>
|
11
|
+
<% if @edit_within_form_partial %><\%= render partial: "edit_after_form", locals: {<%= singular %>: <%= singular %>}<%= @nested_set.collect{|arg| ".merge(#{arg[:singular]} ? {#{arg[:singular]}: #{arg[:singular]}} : {})" }.join %> %><% end %>
|
10
12
|
</div>
|
11
13
|
<\% end %>
|
@@ -5,5 +5,8 @@
|
|
5
5
|
<\%= form_with model: <%= singular %>, url: <%= form_path_new_helper %>, method: :post do |f| \%>
|
6
6
|
<\%= render partial: "<%= namespace_with_slash + @controller_build_folder %>/form",
|
7
7
|
locals: { <%= singular %>: <%= singular %>, f: f}<%= @nested_set.collect{|arg| ".merge(defined?(#{arg[:singular]}) ? {#{arg[:singular]}: #{arg[:singular]}}: {})" }.join %> \%>
|
8
|
-
|
8
|
+
|
9
|
+
<% if @new_within_form_partial %><\%= render partial: "new_within_form", locals: {f: f, <%= singular %>: <%= singular %>}<%= @nested_set.collect{|arg| ".merge(#{arg[:singular]} ? {#{arg[:singular]}: #{arg[:singular]}} : {})" }.join %> %><% end %>
|
10
|
+
<\% end %>
|
11
|
+
<% if @new_after_form_partial %><\%= render partial: "new_after_form", locals: {<%= singular %>: <%= singular %>}<%= @nested_set.collect{|arg| ".merge(#{arg[:singular]} ? {#{arg[:singular]}: #{arg[:singular]}} : {})" }.join %> %><% end %>
|
9
12
|
<\% end %>
|
@@ -5,6 +5,7 @@ class <%= ((@namespace.titleize.gsub(" ", "") + "::" if @namespace) || "") + @pl
|
|
5
5
|
# rubocop:enable Layout/LineLength <% end %>
|
6
6
|
|
7
7
|
def index
|
8
|
+
<% if @pundit %>authorize <%= @class_name %>, :typeahead? <% end %>
|
8
9
|
query = params[:query]
|
9
10
|
@typeahead_identifier = params[:typeahead_identifier]
|
10
11
|
@<%= @plural %> = <%= @singular.titleize.gsub(" ", "") %>.where("<%= @search_by.collect{|search| "LOWER(#{search}) LIKE ?" }.join(" OR ") %>", <%= @search_by.collect{|search| "\"%\#{query.downcase}%\"" }.join(", ") %>).limit(10)
|
@@ -1,9 +1,12 @@
|
|
1
|
+
require_relative './default_config_loader'
|
2
|
+
|
1
3
|
module HotGlue
|
2
4
|
class TypeaheadGenerator < Rails::Generators::Base
|
3
5
|
source_root File.expand_path('templates', __dir__)
|
4
6
|
class_option :namespace, type: :string, default: nil
|
5
7
|
class_option :search_by, type: :string, default: nil
|
6
8
|
|
9
|
+
include DefaultConfigLoader
|
7
10
|
def filepath_prefix
|
8
11
|
# todo: inject the context
|
9
12
|
'spec/dummy/' if $INTERNAL_SPECS
|
@@ -19,9 +22,10 @@ module HotGlue
|
|
19
22
|
puts message
|
20
23
|
raise(HotGlue::Error, message)
|
21
24
|
end
|
25
|
+
@pundit = get_default_from_config(key: :pundit_default)
|
22
26
|
|
23
27
|
@singular = args.first.tableize.singularize
|
24
|
-
|
28
|
+
@class_name = args.first
|
25
29
|
@plural = args.first.tableize.pluralize
|
26
30
|
@namespace = options['namespace']
|
27
31
|
|
data/lib/hotglue/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hot-glue
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jason Fleetwood-Boldt
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-12-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -54,7 +54,7 @@ dependencies:
|
|
54
54
|
version: '2.16'
|
55
55
|
description: Simple, plug & play Rails scaffold building companion for Turbo-Rails
|
56
56
|
and Hotwire
|
57
|
-
email:
|
57
|
+
email: hello@jfbcodes.com
|
58
58
|
executables: []
|
59
59
|
extensions: []
|
60
60
|
extra_rdoc_files: []
|
@@ -90,6 +90,7 @@ files:
|
|
90
90
|
- lib/generators/hot_glue/fields/field.rb
|
91
91
|
- lib/generators/hot_glue/fields/float_field.rb
|
92
92
|
- lib/generators/hot_glue/fields/integer_field.rb
|
93
|
+
- lib/generators/hot_glue/fields/related_set_field.rb
|
93
94
|
- lib/generators/hot_glue/fields/string_field.rb
|
94
95
|
- lib/generators/hot_glue/fields/text_field.rb
|
95
96
|
- lib/generators/hot_glue/fields/time_field.rb
|
@@ -174,5 +175,5 @@ requirements: []
|
|
174
175
|
rubygems_version: 3.4.10
|
175
176
|
signing_key:
|
176
177
|
specification_version: 4
|
177
|
-
summary: A gem to build
|
178
|
+
summary: A gem to build Turbo Rails scaffolding.
|
178
179
|
test_files: []
|