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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8ffc84538112521ab7f15e7c0d234de7ffc5e5231e49a6ffb3cb3fd77c4f0545
4
- data.tar.gz: f9e7c8e6b171aace305d5fae08ee24d08f6304c0127e5044579e8d3c30cc6ca6
3
+ metadata.gz: c984f13c5f9639c6cea9837c4266cf0e4e529e9adef1dffea257551ebd80f8cd
4
+ data.tar.gz: ba87601c2e04556604a7900880f054fd67b95ee78c9113668664ffb591c0601f
5
5
  SHA512:
6
- metadata.gz: 53da97a3e0c570c41594ae47ed50149a5519e512d1677c38dc70fb118e69893d67165b7f7de89e0e8f56325f766934225f810c52fba501c9b9a1a71943cdc365
7
- data.tar.gz: f6fd036dd5abeb33724927a6a25e2fab560f553f4df23cf186438f09036e68f147aef50ce356b7c9c3858cd3fbe81c02bc9649b46076562da3a3cf43f0cf0530
6
+ metadata.gz: f9b1228beb6639f671f2138c0b62a891060da18089e23cb1a9f0518b77b0002e2c38018b18df49525f0dcf6b2715c6ed3c4c415bca8d203c8afad0c5113fad88
7
+ data.tar.gz: 34e37814fe29eb58097331279107b1b58a4e9dbb4f3f9887c0e5c6b095d484c8a893034d3dafeed2ffbd5350a3cbd7542ee7dba4ffbd0a149b04a4b6a936ab8c
data/.github/FUNDING.yml CHANGED
@@ -1 +1 @@
1
- custom: ["https://twitter.com/HotGlueForRails", "https://school.jasonfleetwoodboldt.com/8188?utm_source=github.com&utm_campaign=github_hot_glue_repo_funding_link"]
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.0)
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.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.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.1)
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.0)
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.jasonfleetwoodboldt.com/8188/?utm_source=github.com&utm_campaign=github_hot_glue_readme_page) **only $60 USD!**
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.jasonfleetwoodboldt.com/8188)
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.jasonfleetwoodboldt.com/8188)
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 represent the labels you should specify separated by the pipe character (|), use the modifier exactly as shown.
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. This is is only hypothetical, because the interface correctly shows the field as viewable or editable anyway, making bad 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, the backend policy would guard against the disallowed input and show an error message.)
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:typehead Author`
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.jasonfleetwoodboldt.com/8188) in the Hot Glue Tutorial shows you how to use the hawk to limit the scope to the logged in user.
1866
- - [Example #4](https://school.jasonfleetwoodboldt.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`)
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.plural_name}_typeahead_index_url"
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
- raise "couldn't find #{col} in either field list or attachments list"
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 ? namspace + "/" : ""}#{assoc_name.pluralize}_typeahead_controller.rb"
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 fields_filtered_for_email_lookups
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 }.create(modified_params)"
734
+ "@#{singular } = #{ class_name }.new(modified_params)"
686
735
  else
687
- "#{@factory_creation}\n" +
688
- " @#{singular } = factory.#{singular}"
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: [:show, :edit, :update, :destroy]
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
- authorize @<%= plural_name %>.all<% else %> <% if !@self_auth %>
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] = "You are not authorized to perform this action."
86
- render "layouts/error"<% end %>
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(<% 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? %>
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 = "new"
97
+ @action = 'new'
95
98
  rescue Pundit::NotAuthorizedError
96
- flash[:alert] = "You are not authorized to perform this action."
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
- <%= controller_attachment_orig_filename_pickup_syntax %>
112
+ <%= controller_attachment_orig_filename_pickup_syntax %>
109
113
  <%= creation_syntax %>
110
-
111
- if @<%= singular_name %>.save
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 = "new"
125
+ @action = 'new'
119
126
  render :create, status: :unprocessable_entity
120
127
  end<% if @pundit %>
121
128
  rescue Pundit::NotAuthorizedError
122
- flash[:alert] = "Creating <%= singular %> not authorized. #{@<%= singular %>.errors.collect{|k| "#{k.attribute} #{k.message}"}.join(" ")} #{flash[:notice]} "
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: "edit_",
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 = "edit"
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
- <%= controller_attachment_orig_filename_pickup_syntax %>
174
+ <%= controller_attachment_orig_filename_pickup_syntax %>
162
175
  <% if @pundit %>
163
176
  if @<%= singular_name %>.attributes = modified_params
164
- authorize @<%= singular_name %>
165
- @<%= singular_name %>.save
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 = "edit"
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(<%= (fields_filtered_for_email_lookups - @show_only ) + @magic_buttons.collect{|x| "__#{x}".to_sym }%>)
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(<%= (fields_filtered_for_email_lookups - @update_show_only) + @magic_buttons.collect{|x| "__#{x}".to_sym }%>)
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
- <\%= 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 %> \%>
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
- <\% end %>
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
 
@@ -1,5 +1,5 @@
1
1
  module HotGlue
2
2
  class Version
3
- CURRENT = '0.6.0.1'
3
+ CURRENT = '0.6.2'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hot-glue
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0.1
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-11 00:00:00.000000000 Z
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: code@jasonfb.net
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 Tubro Rails scaffolding.
178
+ summary: A gem to build Turbo Rails scaffolding.
178
179
  test_files: []