hot-glue 0.6.16 → 0.6.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '09ebf223206d1bf224f02aaafe2796774bfa6b3d61c6892c1b0144ec2a8dd0f3'
4
- data.tar.gz: 1fd04b5e1909ebcf2ecc9eeb5c4e212f7a6878d49c0fe918305e43e441cd73d3
3
+ metadata.gz: ddd7d67de32f74ce4e79fabcaaa216e9e5cfbbd84c4bb6d9ab53b6ba2dd57f00
4
+ data.tar.gz: 5e4e51cce50f7c7e3579bb09c452c5c0b02b4331870a22c9bd249281dab8cd7a
5
5
  SHA512:
6
- metadata.gz: c01e39699fe999a7024850b9d738d5e205b4e5abe8c39d3619d10efa74fbed2384ac6cadfa4010d52c0a9fdea3878ae599aa23002cd439d323701f4b8524c3eb
7
- data.tar.gz: 79c20e1f78772f79e396cecfc81990a70b3a0d17988962baebd6c2ea0b10eabedea59ebd8624f34d0ad7ce864d59bf7ad7be2afb8c712ef01f605ab06e272fce
6
+ metadata.gz: e250a3b6598e3e83312bad3b27d7da0f200563ab7e89a544f23aa46913eb1f23ea796854dcdf7d2939291ec8b8ce92fe245c629658982b4f47a71e20a19e1c5f
7
+ data.tar.gz: '08b99eba601f426decbcd5e2fe5daf6c9b121c1285e847d2f7b827da14f1c16a30bb20e2813770a68b20a623fe85584e1e9fe6784c86f42eb5d97c375715421d'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- hot-glue (0.6.15)
4
+ hot-glue (0.6.18)
5
5
  ffaker (~> 2.16)
6
6
  kaminari (~> 1.2)
7
7
  rails (> 5.1)
@@ -140,7 +140,7 @@ GEM
140
140
  mini_mime (1.1.2)
141
141
  mini_portile2 (2.8.4)
142
142
  minitest (5.16.3)
143
- net-imap (0.5.6)
143
+ net-imap (0.5.8)
144
144
  date
145
145
  net-protocol
146
146
  net-pop (0.1.2)
data/README.md CHANGED
@@ -680,15 +680,49 @@ For the `$` modifier only, if the field name ends with `_cents`, the modifier wi
680
680
 
681
681
  ### `--alt-foreign-key-lookup=`
682
682
 
683
- Use for a join table to specify that a field should be looked up by a different field
683
+ Use for a join table to specify that a field should be looked up by a different field. For example, when you want to lookup a user by a (complete) email address.
684
684
 
685
+ In these contexts, the lookup must be exact match to the text entered (no partial matches supported).
685
686
 
686
- `./bin/rails generate hot_glue:scaffold AccountUser --alt-foreign-key-lookup=user_id{email}`
687
+ Use `+` to map to the `find_or_create_by` method. (Without `+` it will use `find_by`.)
688
+
689
+ This is accomlished using a little magic of a lookup field called `__lookup_X_Y` passed in the form parameters.
690
+
691
+ The lookup field is used to look up the associated record, then deleted from the params.
692
+
693
+ #### When used in `--gd` mode
694
+ The lookup will use the base class and have access to all records.
695
+ `user = User.find_or_create_by!(email: params[:__lookup_email])`
696
+ `modified_params.tap { |hs| hs.delete(:__lookup_target_email)}`
697
+ `modified_params.merge!(user: user)`
698
+
699
+ Example 1
700
+ `./bin/rails generate hot_glue:scaffold AccountUser --gd --alt-foreign-key-lookup=user_id{email}`
687
701
 
688
702
  Here we are specifying that the `user_id` field should be looked up by the `email` field on the User table.
689
703
  If no existing user exists, we create one because we are using the `find_or_create_by!` method.
690
704
 
705
+ ### When used with a Hawk
706
+
707
+ A hawk applied to the same field will be enforced within this mechanism.
708
+
709
+ Example #2
710
+ `bin/rails generate hot_glue:scaffold Appointment --gd --alt-foreign-key-lookup='user_id{email}' --hawk='user_id{current_user.family}'`
711
+
712
+
713
+ Whether or not in Gd mode, the hawk is enforced by scoping the find to the hawk's scope
714
+ ```
715
+ user = current_user.family.users.find_by(email: appointment_params[:__lookup_user_email] )
716
+ modified_params.tap { |hs| hs.delete(:__lookup_user_email)}
717
+ @appointment = Appointment.new(modified_params.merge(user: user))
718
+ ```
719
+
720
+
721
+
722
+ ### With Factory
723
+
691
724
  Use with a factory pattern like this one:
725
+
692
726
  ```
693
727
  class AccountUserFactory
694
728
  attr_accessor :account_user
@@ -712,9 +746,17 @@ end
712
746
  this works with a factory creation syntax like so:
713
747
 
714
748
  ```
715
- --factory-creation='factory = AccountUserFactory.new(params: account_user_params, account: account)
749
+ rails generate hot_glue:scaffold AccountUser --alt-foreign-key-lookup=user_id{email} --factory-creation='factory = AccountUserFactory.new(params: account_user_params, account: account)
716
750
  ```
717
- *See the `--factory-creation` section.
751
+
752
+ In this example, the lookup is *not performed inside of the create action*, because it is assumed you will do so yourself inside of your factory.
753
+
754
+ As you see in the example above, params is passed to your factory and it is the `account_user_params` fromm the controller.
755
+
756
+ #### Warning:
757
+ When building a non-Gd controller with a `--alt-foreign-key-lookup`, if you don't also hawk the same field you will get an error.
758
+
759
+ To fix, either hawk the field or use with a factory creation pattern. This is because the associated object is on your graph but Hot Glue doesn't know how to securly load it without knowing its relationship to the current user. Use the `--hawk` mechanism to specify that relationship, and the lookup mechanism will integrate nicely.
718
760
 
719
761
 
720
762
 
@@ -868,6 +910,92 @@ Remember, if there's a corresponding `*_able?` method on the policy, it will be
868
910
  As shown in the method `name_able?` of the example ThingPolicy above, if this field on your policy returns true, the field will be editable. If it returns false, the field will be viewable (read-only).
869
911
 
870
912
 
913
+ ### `--hidden`
914
+
915
+ Separate list of fields.
916
+
917
+ These fields will be hidden from the form but will exist as hidden_field, and so the update will still work.
918
+
919
+
920
+ EXAMPLE:
921
+
922
+ ```
923
+ bin/rails generate hot_glue:scaffold Wrapper --namespace='account_dashboard' --no-nav-menu --big-edit --smart-layout --stimmify --hidden=raw_source
924
+ ```
925
+
926
+ In the `wrappers` folder, I am using a special sticky partial `_edit_within_form.html.erb`, which contains code preserved from build-to-build and included in the form:
927
+
928
+
929
+ ```
930
+ <div class="row" style="position: relative; width: 100%; overflow: auto;">
931
+ <div class="col-md-12">
932
+ <div id="wrapper__raw_source"
933
+ style="position: static">
934
+
935
+ <div id="wrapper__raw_source-toolbar">
936
+
937
+ </div>
938
+
939
+
940
+ <div cols="60"
941
+ data-wrapper-form-target="editor"
942
+ id="wrapper__raw_source-editor" >
943
+ </div>
944
+ </div>
945
+
946
+
947
+ </div>
948
+ </div>
949
+ <div class="col-md-2">
950
+ </div>
951
+ ```
952
+
953
+
954
+ Then, create a `app/javascript/controllers/wrapper_form_controller.js` file with the following code:
955
+
956
+ ```javascript
957
+
958
+
959
+ import { Controller } from "@hotwired/stimulus"
960
+
961
+ import {basicSetup} from "codemirror"
962
+ import {EditorView} from "@codemirror/view"
963
+
964
+ // Connects to data-controller="wrapper-form"
965
+ export default class extends Controller {
966
+ static targets = ['rawSource', 'name', 'nameWrapper', 'editor'];
967
+
968
+ connect() {
969
+ console.log("WrapperFormController connected")
970
+ this.account_id = this.element.dataset['accountId']
971
+ this.crusade_id = this.element.dataset['crusadeId']
972
+ this.wrapper_id = this.element.dataset['wrapperId']
973
+
974
+ const view = new EditorView({
975
+ doc: this.rawSourceTarget.value,
976
+ parent: this.editorTarget,
977
+ extensions: [basicSetup]
978
+ })
979
+
980
+ this.view = view;
981
+ this.element.addEventListener('submit', this.formSubmit.bind(this))
982
+ // this.previewButtonTarget.addEventListener('click', this.previewClick.bind(this))
983
+ }
984
+
985
+ formSubmit(event) {
986
+ this.rawSourceTarget.value = this.view.state.doc.toString();
987
+ }
988
+ }
989
+ ```
990
+
991
+ Notice we are also using `--stimmify` to decorate the form with a Stimulus controller.
992
+
993
+ The code above uses Code Mirror to act as a code editor, which requires pulling the value off the hidden form element (putting it into the code mirror interface) and pushing it back into the hidden form element when the Submit button is clicked.
994
+
995
+
996
+
997
+
998
+
871
999
  ### `--ujs_syntax=true` (Default is set automatically based on whether you have turbo-rails installed)
872
1000
 
873
1001
  If you are pre-Turbo (UJS), your delete buttons will come out like this:
@@ -1381,6 +1509,54 @@ Then run:
1381
1509
  This will 1) copy the dropzone_controller.js file into your app and 2) add the dropzone css into your app's application.css or application.bootstrap.css file.
1382
1510
 
1383
1511
 
1512
+ ### Attach Stimulus JS Controllers to Your Forms with `--stimmify` or `--stimmify=xyz`
1513
+
1514
+ Automatically build the new and edit form with `data-controller='xyz'` to attach cooresponding stimulus controllers.
1515
+
1516
+ If you use the shorthand (specify no `=`) your stimulus controller's name will be inferred from the Singular form of the scaffolding beild built, with dashes for underscores, and ending with `-form`
1517
+
1518
+ `@singular.gsub("_", "-") + "-form"`
1519
+
1520
+ (For example, `rails g hot_glue:scaffold Thing --stimmy` generates a form that looks like
1521
+
1522
+ ```
1523
+ <%= form_with model: thing,
1524
+ url: things_path(account,crusade,email_template),
1525
+ html: {
1526
+ 'data-controller': "thing-form"
1527
+ }
1528
+ %>
1529
+ ...
1530
+
1531
+ ```
1532
+
1533
+ Note that your fields also appended with `data-thing-target=abc` and also `data-thing-target=abcWrapper`
1534
+ (assuming `thing` is the scaffold being built and abc is the field name)
1535
+
1536
+
1537
+ Here, we are building a `thing` scaffold. The field `name` is decorated twice: once for the wrapper span and again for the specific form element itself.
1538
+ ```
1539
+ <span class="" data-thing-form-target="nameWrapper">
1540
+ <input value="asdfadf" autocomplete="off" size="40" class="form-control" type="" data-thing-form-target="name" name="thing[name]" id="thing_name">
1541
+
1542
+
1543
+ <label class="text-muted small form-text" for="">Name</label>
1544
+ </span>
1545
+ ```
1546
+
1547
+ Your stimulus controller will need two targets for each field:
1548
+
1549
+ ```
1550
+ static targets = ['name', 'nameWrapper'];
1551
+ ```
1552
+ You can interact with the wrapper for things like clicks or hovers, or to hide/show the entire box surrounding the form element.
1553
+
1554
+ Use the form field element itself to affect things like enabled or the value of the field.
1555
+
1556
+
1557
+ For a crash course on Stimulus, see
1558
+ https://jasonfleetwoodboldt.com/courses/rails-7-crash-course/rails-7-stimulus-js-basics-with-importmap-rails/
1559
+
1384
1560
 
1385
1561
 
1386
1562
  ### `--factory-creation={ ... }`
@@ -1768,6 +1944,61 @@ These automatic pickups for partials are detected at build time. This means that
1768
1944
 
1769
1945
  # VERSION HISTORY
1770
1946
 
1947
+ #### 2025-05-18 v0.6.18
1948
+ • Significant additions to `--alt-foreign-key-lookups` which can now work:
1949
+ - on its on, without needing a factory
1950
+ - with a factory
1951
+ - with our without the hawk
1952
+ - in Gd mode or, if not, use with `--factory-creation` or use with `--hawk`
1953
+
1954
+ See notes above for details.
1955
+
1956
+ • Adds Integer as serachable field
1957
+
1958
+ _ foo and bar are integers _
1959
+
1960
+ `rails g hot_glue:scaffold Thing --include=foo,bar --search=set --search-fields=foo,bar`
1961
+
1962
+
1963
+
1964
+ #### 2025-05-09 - v0.6.17
1965
+
1966
+
1967
+ • Adds Stimulus JS & `--stimmify` or `--stimmify=xyz`
1968
+
1969
+ Automatically build the new and edit form with `data-controller='xyz'` to attach stimulus
1970
+
1971
+ If you use the shorthand (specify no `=`) your stimulus controller's name will be inferred from the Singular form of the scaffolding beild built, with dashes for underscores, and ending with `-form`
1972
+
1973
+ (For example, `rails g hot_glue:scaffold Thing --stimmy` generates a form that looks like
1974
+
1975
+ ```
1976
+ <%= form_with model: thing,
1977
+ url: things_path(account,crusade,email_template),
1978
+ html: {
1979
+ 'data-controller': "thing-form"
1980
+ }
1981
+ %>
1982
+ ...
1983
+
1984
+ ```
1985
+
1986
+ Note that your fields also appended with `data-thing-target=abc` and also `data-thing-target=abcWrapper`
1987
+
1988
+ See section "Attach Stimulus JS Controllers to Your Forms with `--stimmify` or `--stimmify=xyz`"
1989
+
1990
+ For a crash course on Stimulus, see
1991
+ https://jasonfleetwoodboldt.com/courses/rails-7-crash-course/rails-7-stimulus-js-basics-with-importmap-rails/
1992
+
1993
+
1994
+ • Adds `--hidden` option
1995
+ Pass a list of fields, like include or show-only. This will make the field hidden on the form *but still updated via its submission*
1996
+
1997
+ • Show only fields associations are now safe-nil using `&` to not crash if the association doesn't exist
1998
+
1999
+
2000
+
2001
+
1771
2002
  #### 2025-03-31 v0.6.16
1772
2003
 
1773
2004
  • Bootstrap Tab Panes For Downnested Portals
@@ -143,6 +143,27 @@ module HotGlue
143
143
  end
144
144
  end
145
145
 
146
+ def integer_query_constructor(match, search)
147
+ if match.blank? || search.blank?
148
+ nil
149
+ else
150
+ case match
151
+ when '='
152
+ "= #{search.to_i}"
153
+ when '≥'
154
+ ">= #{search.to_i}"
155
+ when '>'
156
+ "> #{search.to_i}"
157
+ when '≤'
158
+ "<= #{search.to_i}"
159
+ when '<'
160
+ "< #{search.to_i}"
161
+ else
162
+ nil
163
+ end
164
+ end
165
+ end
166
+
146
167
  def enum_constructor(field_name, value, **args)
147
168
  return nil if value.blank?
148
169
  ["#{field_name} = ?", value]
@@ -53,25 +53,25 @@ class FieldFactory
53
53
  raise "Field type could be identified #{name} "
54
54
  end
55
55
 
56
- @field = field_class.new(name: name,
57
- layout_strategy: generator.layout_strategy,
58
- form_placeholder_labels: generator.form_placeholder_labels,
59
- form_labels_position: generator.form_labels_position,
60
- ownership_field: generator.ownership_field,
61
- hawk_keys: generator.hawk_keys,
62
- auth: generator.auth,
63
- class_name: generator.singular_class,
64
- alt_lookup: generator.alt_lookups[name] || nil,
65
- singular: generator.singular,
66
- self_auth: generator.self_auth,
67
- update_show_only: generator.update_show_only,
68
- attachment_data: generator.attachments[name.to_sym],
69
- sample_file_path: generator.sample_file_path,
70
- modify_as: generator.modify_as[name.to_sym] || nil,
71
- plural: generator.plural,
72
- display_as: generator.display_as[name.to_sym] || nil,
73
- default_boolean_display: generator.default_boolean_display,
74
- namespace: generator.namespace_value,
75
- pundit: generator.pundit )
56
+ @field = field_class.new(scaffold: generator, name: name)
57
+ # layout_strategy: generator.layout_strategy,
58
+ # form_placeholder_labels: generator.form_placeholder_labels,
59
+ # form_labels_position: generator.form_labels_position,
60
+ # ownership_field: generator.ownership_field,
61
+ # hawk_keys: generator.hawk_keys,
62
+ # auth: generator.auth,
63
+ # class_name: generator.singular_class,
64
+ # alt_lookup: generator.alt_lookups[name] || nil,
65
+ # singular: generator.singular,
66
+ # self_auth: generator.self_auth,
67
+ # update_show_only: generator.update_show_only,
68
+ # attachment_data: generator.attachments[name.to_sym],
69
+ # sample_file_path: generator.sample_file_path,
70
+ # modify_as: generator.modify_as[name.to_sym] || nil,
71
+ # plural: generator.plural,
72
+ # display_as: generator.display_as[name.to_sym] || nil,
73
+ # default_boolean_display: generator.default_boolean_display,
74
+ # namespace: generator.namespace_value,
75
+ # pundit: generator.pundit )
76
76
  end
77
77
  end
@@ -5,17 +5,8 @@ class AssociationField < Field
5
5
 
6
6
  attr_accessor :assoc_name, :assoc_class, :assoc, :alt_lookup
7
7
 
8
- def initialize( alt_lookup: ,
9
- class_name: ,
10
- default_boolean_display:, display_as: ,
11
- name: , singular: ,
12
- update_show_only: ,
13
- hawk_keys: , auth: , sample_file_path:, ownership_field: ,
14
- attachment_data: nil , layout_strategy: , form_placeholder_labels: nil,
15
- form_labels_position:, modify_as: , self_auth: , namespace:, pundit: , plural: )
8
+ def initialize(scaffold: , name: )
16
9
  super
17
-
18
-
19
10
  @assoc_model = eval("#{class_name}.reflect_on_association(:#{assoc})")
20
11
 
21
12
  if assoc_model.nil?
@@ -91,25 +82,23 @@ class AssociationField < Field
91
82
  display_column = HotGlue.derrive_reference_name(assoc_class_name)
92
83
 
93
84
 
94
- "<%= #{singular}.#{assoc_name}.#{display_column} %>"
85
+ "<%= #{singular}.#{assoc_name}&.#{display_column} %>"
95
86
  end
96
87
 
97
88
  def form_field_output
98
89
  assoc_name = name.to_s.gsub("_id","")
90
+
91
+
99
92
  assoc = eval("#{class_name}.reflect_on_association(:#{assoc_name})")
100
93
 
101
- if alt_lookup
102
- alt = alt_lookup[:lookup_as]
94
+ if alt_lookup.keys.include?(name)
103
95
  assoc_name = name.to_s.gsub("_id","")
104
96
  assoc = eval("#{class_name}.reflect_on_association(:#{assoc_name})")
105
97
 
106
- alt = alt_lookup[:lookup_as]
107
- "<%= f.text_field :__lookup_#{alt}, value: @#{singular}.#{assoc_name}.try(:#{alt}), placeholder: \"search by #{alt}\" %>"
108
-
109
- # if modify_as
110
- # modified_display_output
111
- # else
112
- # end
98
+ lookup_field = alt_lookup[name][:lookup_as]
99
+ assoc = alt_lookup[name][:assoc].downcase
100
+ parts = name.split('_')
101
+ "<%= f.text_field :__lookup_#{assoc}_#{lookup_field}, value: @#{singular}.#{assoc_name}&.#{lookup_field}, placeholder: \"#{lookup_field}\" " + (stimmify ? ", 'data-#{@stimmify}-target': '#{camelcase_name}' " : "") + "%>"
113
102
  elsif modify_as && modify_as[:typeahead]
114
103
  search_url = "#{namespace ? namespace + "_" : ""}" +
115
104
  modify_as[:nested].join("_") + ( modify_as[:nested].any? ? "_" : "") +
@@ -156,8 +145,17 @@ class AssociationField < Field
156
145
  end
157
146
 
158
147
 
148
+ if @stimmify
149
+ col_target = HotGlue.to_camel_case(name.to_s.gsub("_", " "))
150
+ data_attr = ", data: {'#{@stimmify}-target': '#{col_target}'} "
151
+ els
152
+ data_attr = ""
153
+ end
154
+
155
+
156
+
159
157
  (is_owner ? "<% unless @#{assoc_name} %>\n" : "") +
160
- " <%= f.collection_select(:#{name}, #{hawked_association}, :id, :#{display_column}, {prompt: true, selected: #{singular}.#{name} }, class: 'form-control') %>\n" +
158
+ " <%= f.collection_select(:#{name}, #{hawked_association}, :id, :#{display_column}, { prompt: true, selected: #{singular}.#{name} }, class: 'form-control'#{data_attr}) %>\n" +
161
159
  (is_owner ? "<% else %>\n <%= @#{assoc_name}.#{display_column} %>" : "") +
162
160
  (is_owner ? "\n<% end %>" : "")
163
161
  end
@@ -188,11 +186,9 @@ class AssociationField < Field
188
186
 
189
187
  if assoc_class
190
188
  display_column = HotGlue.derrive_reference_name(assoc_class.to_s)
191
-
192
189
  "<%= #{singular}.#{assoc}.try(:#{display_column}) || '<span class=\"content \">MISSING</span>'.html_safe %>"
193
190
  else
194
191
  "<%= #{singular}.#{assoc}.try(:to_label) || '<span class=\"content \">MISSING</span>'.html_safe %>"
195
-
196
192
  end
197
193
 
198
194
  end
@@ -274,5 +270,33 @@ class AssociationField < Field
274
270
  end
275
271
  end
276
272
 
273
+ def prelookup_syntax
274
+ field = @alt_lookup[name.to_s]
275
+ if field[:with_create]
276
+
277
+ method_name = "find_or_create_by"
277
278
 
279
+ else
280
+ method_name = "find_by"
281
+ end
282
+ field_name = field[:assoc].downcase.gsub("_id","")
283
+ assoc_class = field[:assoc].classify
284
+
285
+ assoc = plural
286
+
287
+ ## TODO: add the hawk here
288
+ res = +""
289
+ if @hawk_keys[name.to_sym]
290
+ res << "#{field_name} = #{@hawk_keys[name.to_sym][:bind_to].first}.#{method_name}(#{field[:lookup_as]}: #{singular}_params[:__lookup_#{field[:assoc].downcase}_#{field[:lookup_as]}] )"
291
+ elsif @god
292
+ assoc_name = field[:assoc]
293
+ res << "#{field_name} = #{assoc_class}.#{method_name}(#{field[:lookup_as]}: #{singular}_params[:__lookup_#{field[:assoc].downcase}_#{field[:lookup_as]}] )"
294
+ else
295
+ raise "Field #{field_name} is an alt lookup in a non-Gd context which is a security vulnerability"
296
+ end
297
+
298
+ res << "\n modified_params.tap { |hs| hs.delete(:__lookup_#{field[:assoc].downcase}_#{field[:lookup_as]})}"
299
+ return res
300
+
301
+ end
278
302
  end
@@ -1,25 +1,6 @@
1
1
  class AttachmentField < Field
2
2
  attr_accessor :attachment_data
3
- def initialize(alt_lookup:,
4
- attachment_data:,
5
- plural:,
6
- auth:,
7
- class_name:,
8
- display_as:, singular:,
9
- default_boolean_display: ,
10
- form_placeholder_labels: ,
11
- form_labels_position:,
12
- hawk_keys:,
13
- layout_strategy: ,
14
- name:,
15
- namespace:,
16
- modify_as:,
17
- ownership_field:,
18
- pundit: ,
19
- sample_file_path: nil,
20
- self_auth:,
21
- update_show_only:
22
- )
3
+ def initialize(scaffold:, name:)
23
4
  super
24
5
 
25
6
  @attachment_data = attachment_data
@@ -23,9 +23,9 @@ class BooleanField < Field
23
23
 
24
24
  def radio_button_display
25
25
  " <%= f.radio_button(:#{name}, '0', checked: #{singular}.#{name} ? '' : 'checked', class: '#{@layout_strategy.form_checkbox_input_class}') %>\n" +
26
- " <%= f.label(:#{name}, value: '#{modify_binary? && modify_as[:binary][:falsy] || 'No'}', for: '#{singular}_#{name}_0') %>\n" +
26
+ " <%= f.label(:#{name}, value: '#{modify_binary? && modify_as[name.to_sym][:binary][:falsy] || 'No'}', for: '#{singular}_#{name}_0') %>\n" +
27
27
  " <br /> <%= f.radio_button(:#{name}, '1', checked: #{singular}.#{name} ? 'checked' : '' , class: '#{@layout_strategy.form_checkbox_input_class}') %>\n" +
28
- " <%= f.label(:#{name}, value: '#{modify_binary? && modify_as[:binary][:truthy] || 'Yes'}', for: '#{singular}_#{name}_1') %>\n"
28
+ " <%= f.label(:#{name}, value: '#{modify_binary? && modify_as[name.to_sym][:binary][:truthy] || 'Yes'}', for: '#{singular}_#{name}_1') %>\n"
29
29
  end
30
30
 
31
31
  def checkbox_display
@@ -38,16 +38,16 @@ class BooleanField < Field
38
38
 
39
39
  def form_field_display
40
40
  if display_boolean_as.nil?
41
-
42
41
  end
42
+
43
43
  "<span class='#{@layout_strategy.form_checkbox_wrapper_class} #{'form-switch' if display_boolean_as == 'switch'}'>\n" +
44
44
  (if display_boolean_as == 'radio'
45
- radio_button_display
46
- elsif display_boolean_as == 'checkbox'
47
- checkbox_display
48
- elsif display_boolean_as == 'switch'
49
- switch_display
50
- end) + "</span> \n"
45
+ radio_button_display
46
+ elsif display_boolean_as == 'checkbox'
47
+ checkbox_display
48
+ elsif display_boolean_as == 'switch'
49
+ switch_display
50
+ end) + "</span> \n"
51
51
  end
52
52
 
53
53
  def form_field_output
@@ -59,9 +59,9 @@ class BooleanField < Field
59
59
  "<% if #{singular}.#{name}.nil? %>
60
60
  <span class=''>MISSING</span>
61
61
  <% elsif #{singular}.#{name} %>
62
- #{modify_as[:binary][:truthy]}
62
+ #{modify_as[name.to_sym][:binary][:truthy]}
63
63
  <% else %>
64
- #{modify_as[:binary][:falsy]}
64
+ #{modify_as[name.to_sym][:binary][:falsy]}
65
65
  <% end %>"
66
66
  else
67
67
  "<% if #{singular}.#{name}.nil? %>
@@ -75,11 +75,11 @@ class BooleanField < Field
75
75
  end
76
76
 
77
77
  def truthy_value
78
- modify_as[:binary][:truthy] || 'Yes'
78
+ modify_as[name.to_sym][:binary][:truthy] || 'Yes'
79
79
  end
80
80
 
81
81
  def falsy_value
82
- modify_as[:binary][:falsy] || 'No'
82
+ modify_as[name.to_sym][:binary][:falsy] || 'No'
83
83
  end
84
84
 
85
85
  def label_class
@@ -10,8 +10,10 @@ class DateField < Field
10
10
 
11
11
 
12
12
  def form_field_output
13
- "<%= date_field_localized(f, :#{name}, #{singular}.#{name}, label: '#{ name.to_s.humanize }') %>"
14
- end
13
+ parts = name.to_s.split('_')
14
+ camelcase_name = parts.map(&:capitalize).join
15
+ "<%= date_field_localized(f, :#{name}, #{singular}.#{name}, label: '#{ name.to_s.humanize }'" + (stimmify ? ", html: {'data-#{@stimmify}-target': '#{camelcase_name}'}" : "") + ") %>"
16
+ end
15
17
 
16
18
  def line_field_output
17
19
  "<% unless #{singular}.#{name}.nil? %>
@@ -31,6 +31,11 @@ class EnumField < Field
31
31
  end
32
32
 
33
33
  def form_field_output
34
+ if @stimmify
35
+ col_target = HotGlue.to_camel_case(name.to_s.gsub("_", " "))
36
+ data_attr = ", data: {'#{@stimmify}-target': '#{col_target}'} "
37
+ end
38
+
34
39
  enum_type = eval("#{class_name}.columns.select{|x| x.name == '#{name}'}[0].sql_type")
35
40
 
36
41
  if eval("defined? #{class_name}.#{enum_type}_labels") == "method"
@@ -39,7 +44,7 @@ class EnumField < Field
39
44
  enum_definer = "#{class_name}.defined_enums['#{name}']"
40
45
  end
41
46
 
42
- res = "<%= f.collection_select(:#{name}, enum_to_collection_select(#{enum_definer}), :key, :value, {include_blank: true, selected: #{singular}.#{name} }, class: 'form-control') %>"
47
+ res = "<%= f.collection_select(:#{name}, enum_to_collection_select(#{enum_definer}), :key, :value, {include_blank: true, selected: #{singular}.#{name} }, class: 'form-control' #{data_attr} )%>"
43
48
 
44
49
 
45
50
  if modify_as && modify_as[:enum] == :partials
@@ -5,50 +5,38 @@ 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, :pundit, :plural
8
+ :update_show_only, :namespace, :pundit, :plural,
9
+ :stimmify, :hidden, :attachment_data, :god
10
+
9
11
 
10
12
  def initialize(
11
- auth: ,
12
- attachment_data: nil,
13
- class_name: ,
14
- alt_lookup: ,
15
- default_boolean_display: ,
16
- display_as: ,
17
- form_labels_position:,
18
- form_placeholder_labels: ,
19
- hawk_keys: nil,
20
- layout_strategy: ,
21
- modify_as: , #note non-standard naming as to avoid collision with Ruby reserved word modify
22
- name: ,
23
- ownership_field: ,
24
- sample_file_path: nil,
25
- singular: ,
26
- update_show_only:,
27
- self_auth:,
28
- namespace:,
29
- pundit: ,
30
- plural:
13
+ scaffold:, name:
14
+
31
15
  )
32
16
  @name = name
33
- @layout_strategy = layout_strategy
34
- @alt_lookup = alt_lookup
35
- @singular = singular
36
- @class_name = class_name
37
- @update_show_only = update_show_only
38
- @hawk_keys = hawk_keys
39
- @auth = auth
40
- @sample_file_path = sample_file_path
41
- @form_placeholder_labels = form_placeholder_labels
42
- @ownership_field = ownership_field
43
- @form_labels_position = form_labels_position
44
- @modify_as = modify_as
45
- @display_as = display_as
46
- @pundit = pundit
47
- @plural = plural
48
-
49
- @self_auth = self_auth
50
- @default_boolean_display = default_boolean_display
51
- @namespace = namespace
17
+ @layout_strategy = scaffold.layout_strategy
18
+ @alt_lookup = scaffold.alt_lookups
19
+ @singular = scaffold.singular
20
+ @class_name = scaffold.singular_class
21
+ @update_show_only = scaffold.update_show_only
22
+ @hawk_keys = scaffold.hawk_keys
23
+ @auth = scaffold.auth
24
+ @sample_file_path = scaffold.sample_file_path
25
+ @form_placeholder_labels = scaffold.form_placeholder_labels
26
+ @ownership_field = scaffold.ownership_field
27
+ @form_labels_position = scaffold.form_labels_position
28
+ @modify_as = scaffold.modify_as
29
+ @display_as = scaffold.display_as
30
+ @pundit = scaffold.pundit
31
+ @plural = scaffold.plural
32
+ @self_auth = scaffold.self_auth
33
+ @default_boolean_display = scaffold.default_boolean_display
34
+ @namespace = scaffold.namespace_value
35
+ @stimmify = scaffold.stimmify
36
+ @hidden = scaffold.hidden
37
+ @attachment_data = scaffold.attachments[name.to_sym]
38
+ @god = scaffold.god
39
+
52
40
 
53
41
  # TODO: remove knowledge of subclasses from Field
54
42
  unless self.class == AttachmentField || self.class == RelatedSetField
@@ -125,7 +113,7 @@ class Field
125
113
  end
126
114
 
127
115
  def viewable_output
128
- if modify_as
116
+ if modify_as[:modify]
129
117
  modified_display_output(show_only: true)
130
118
  else
131
119
  field_view_output
@@ -179,20 +167,34 @@ class Field
179
167
  if modify_as && modify_as[:timezone]
180
168
  "<%= f.time_zone_select :#{name}, ActiveSupport::TimeZone.all, {}, {class: 'form-control'} %>"
181
169
  else
182
- " <%= f.text_field :#{name}, value: #{singular}.#{name}, autocomplete: 'off', size: #{width}, class: 'form-control', type: '#{type}'" + (form_placeholder_labels ? ", placeholder: '#{name.to_s.humanize}'" : "") + " %>\n " + "\n"
170
+ parts = name.split('_')
171
+ camelcase_name = parts.first + parts[1..].map(&:capitalize).join
172
+ " <%= f.text_field :#{name}, value: #{singular}.#{name}, autocomplete: 'off', size: #{width}, class: 'form-control', type: '#{type}'" + (form_placeholder_labels ? ", placeholder: '#{name.to_s.humanize}'" : "") + (stimmify ? ", 'data-#{@stimmify}-target': '#{camelcase_name}' " : "") + " %>\n " + "\n"
183
173
  end
184
174
  end
185
175
 
176
+ def hidden_output
177
+ parts = name.split('_')
178
+ camelcase_name = parts.first + parts[1..].map(&:capitalize).join
179
+ "<%= f.hidden_field :#{name}, value: #{singular}.#{name} " +
180
+ (@stimmify ? ", 'data-#{@stimmify}-target': '#{camelcase_name}' " : "") +
181
+ " %>"
182
+ end
183
+
186
184
  def text_area_output(field_length, extra_classes: "")
187
185
  lines = field_length % 40
188
186
  if lines > 5
189
187
  lines = 5
190
188
  end
191
- "<%= f.text_area :#{name}, class: 'form-control#{extra_classes}', autocomplete: 'off', cols: 40, rows: '#{lines}'" + ( form_placeholder_labels ? ", placeholder: '#{name.to_s.humanize}'" : "") + " %>"
189
+
190
+ parts = name.split('_')
191
+ camelcase_name = parts.first + parts[1..].map(&:capitalize).join
192
+ "<%= f.text_area :#{name}, class: 'form-control#{extra_classes}', autocomplete: 'off', cols: 40, rows: '#{lines}'" + ( form_placeholder_labels ? ", placeholder: '#{name.to_s.humanize}'" : "") +
193
+ (@stimmify ? ", 'data-#{@stimmify}-target': '#{camelcase_name}' " : "") + " %>"
192
194
  end
193
195
 
194
- def modify_binary? # safe
195
- !!(modify_as && modify_as[:binary])
196
+ def modify_binary?
197
+ !!(modify_as && modify_as[name.to_sym] && modify_as[name.to_sym][:binary])
196
198
  end
197
199
 
198
200
  def display_boolean_as
@@ -201,8 +203,8 @@ class Field
201
203
  @default_boolean_display = "radio"
202
204
  end
203
205
 
204
- if display_as
205
- return display_as[:boolean] || "radio"
206
+ if display_as[name.to_sym]
207
+ return display_as[name.to_sym][:boolean] || "radio"
206
208
  else
207
209
  return @default_boolean_display
208
210
  end
@@ -224,4 +226,6 @@ class Field
224
226
  false
225
227
  end
226
228
 
229
+ def prelookup_syntax; nil; end
230
+
227
231
  end
@@ -29,6 +29,26 @@ class IntegerField < Field
29
29
  end
30
30
 
31
31
  def search_field_output
32
- raise "Integer search not implemented"
32
+ " <div>" +
33
+ "\n <%= f.select 'q[0][#{name}_match]', options_for_select([['', ''], ['=', '='], " +
34
+ "\n ['≥', '≥'], ['>', '>'], " +
35
+ "\n ['≤', '≤'], ['<', '<']], @q[\'0\']['#{name}_match'] ), {} ," +
36
+ "\n { class: 'form-control match' } %>"+
37
+ "\n <%= f.text_field 'q[0][#{name}_search]', {value: @q[\'0\'][:#{name}_search], autocomplete: 'off', size: 4, class: 'form-control', type: 'number'} %>" +
38
+ "\n </div>"
33
39
  end
40
+
41
+
42
+ def where_query_statement
43
+ ".where(\"#{name} \#{#{name}_query }\")"
44
+ end
45
+
46
+ def load_all_query_statement
47
+ "#{name}_query = integer_query_constructor(@q['0'][:#{name}_match], @q['0'][:#{name}_search])"
48
+ end
49
+
50
+ def code_to_reset_match_if_search_is_blank
51
+ " @q['0'][:#{name}_match] = '' if @q['0'][:#{name}_search] == ''"
52
+ end
53
+
34
54
  end
@@ -2,13 +2,7 @@ class RelatedSetField < Field
2
2
 
3
3
  attr_accessor :assoc_name, :assoc_class, :assoc
4
4
 
5
- def initialize( class_name: , default_boolean_display:, display_as: ,
6
- name: , singular: , plural:,
7
- alt_lookup: ,
8
- update_show_only: ,
9
- hawk_keys: , auth: , sample_file_path:, ownership_field: ,
10
- attachment_data: nil , layout_strategy: , form_placeholder_labels: nil,
11
- form_labels_position:, modify_as: , self_auth: , namespace:, pundit:)
5
+ def initialize( scaffold: , name: )
12
6
  super
13
7
 
14
8
  @related_set_model = eval("#{class_name}.reflect_on_association(:#{name})")
@@ -37,12 +31,13 @@ class RelatedSetField < Field
37
31
 
38
32
  def form_field_output
39
33
  disabled_syntax = +""
34
+
40
35
  if pundit
41
36
  disabled_syntax << ", {disabled: ! #{class_name}Policy.new(#{auth}, @#{singular}).role_ids_able?}"
42
37
  end
43
38
  " <%= f.collection_check_boxes :#{association_ids_method}, #{association_class_name}.all, :id, :label, {}#{disabled_syntax} do |m| %>
44
- <%= m.check_box %> <%= m.label %><br />
45
- <% end %>"
39
+ <%= m.check_box %> <%= m.label %><br />
40
+ <% end %>"
46
41
  end
47
42
 
48
43
  def association_ids_method
@@ -10,7 +10,8 @@ module HotGlue
10
10
  :form_placeholder_labels, :hawk_keys, :update_show_only,
11
11
  :attachments, :show_only, :columns_map, :pundit, :related_sets,
12
12
  :search, :search_fields, :search_query_fields, :search_position,
13
- :form_path, :layout_object, :search_clear_button, :search_autosearch
13
+ :form_path, :layout_object, :search_clear_button, :search_autosearch,
14
+ :stimmify, :stimmify_camel, :hidden
14
15
 
15
16
 
16
17
  def initialize(singular:, singular_class: ,
@@ -22,7 +23,7 @@ module HotGlue
22
23
  update_show_only:, attachments: , columns_map:, pundit:, related_sets:,
23
24
  search:, search_fields:, search_query_fields: , search_position:,
24
25
  search_clear_button:, search_autosearch:, layout_object:,
25
- form_path: )
26
+ form_path: , stimmify: , stimmify_camel:, hidden: )
26
27
 
27
28
 
28
29
  @form_path = form_path
@@ -31,6 +32,9 @@ module HotGlue
31
32
  @search_by_query = search_query_fields
32
33
  @search_position = search_position
33
34
  @layout_object = layout_object
35
+ @stimmify = stimmify
36
+ @stimmify_camel = stimmify_camel
37
+ @hidden = hidden
34
38
 
35
39
  @singular = singular
36
40
  @singular_class = singular_class
@@ -169,11 +173,21 @@ module HotGlue
169
173
 
170
174
  @tinymce_stimulus_controller = (columns_map[col].modify_as == {tinymce: 1} ? "data-controller='tiny-mce' " : "")
171
175
 
172
- add_spaces_each_line( "\n <span #{@tinymce_stimulus_controller}class='<%= \"alert alert-danger\" if #{singular}.errors.details.keys.include?(:#{field_error_name}) %>' #{'style="display: inherit;"'} >\n" +
173
- add_spaces_each_line( (form_labels_position == 'before' ? (the_label || "") + "<br />\n" : "") +
174
- + field_result +
175
- (form_labels_position == 'after' ? ( columns_map[col].newline_after_field? ? "<br />\n" : "") + (the_label || "") : "") , 4) +
176
- "\n </span>\n ", 2)
176
+ if @stimmify
177
+ col_target = HotGlue.to_camel_case(col.to_s.gsub("_", " "))
178
+ data_attr = " data-#{@stimmify}-target='#{col_target}Wrapper'"
179
+ end
180
+
181
+ unless hidden.include?(col.to_sym)
182
+ add_spaces_each_line( "\n <span #{@tinymce_stimulus_controller}class='<%= \"alert alert-danger\" if #{singular}.errors.details.keys.include?(:#{field_error_name}) %>' #{data_attr} >\n" +
183
+ add_spaces_each_line( (form_labels_position == 'before' ? (the_label || "") + "<br />\n" : "") +
184
+ + field_result +
185
+ (form_labels_position == 'after' ? ( columns_map[col].newline_after_field? ? "<br />\n" : "") + (the_label || "") : "") , 4) +
186
+ "\n </span>\n ", 2)
187
+ else
188
+ columns_map[col].hidden_output
189
+ end
190
+
177
191
 
178
192
  }.join("") + "\n </div>"
179
193
  }.join("\n")
@@ -214,6 +228,7 @@ module HotGlue
214
228
  if eval("#{singular_class}.columns_hash['#{col}']").nil? && !attachments.keys.include?(col) && !related_sets.include?(col)
215
229
  raise "Can't find column '#{col}' on #{singular_class}, are you sure that is the column name?"
216
230
  end
231
+
217
232
  field_output = columns_map[col].line_field_output
218
233
 
219
234
  label = "<label class='small form-text text-muted'>#{col.to_s.humanize}</label>"
@@ -20,7 +20,7 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
20
20
  :big_edit, :button_icons, :bootstrap_column_width,
21
21
  :columns,
22
22
  :default_boolean_display,
23
- :display_as, :downnest_children, :downnest_object, :hawk_keys, :layout_object,
23
+ :display_as, :downnest_children, :downnest_object, :god, :hawk_keys, :layout_object,
24
24
  :modify_as,
25
25
  :nest_with, :path, :plural, :sample_file_path, :show_only_data, :singular,
26
26
  :singular_class, :smart_layout, :stacked_downnesting,
@@ -28,7 +28,8 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
28
28
  :layout_strategy, :form_placeholder_labels,
29
29
  :form_labels_position, :no_nav_menu, :pundit,
30
30
  :self_auth, :namespace_value, :record_scope, :related_sets,
31
- :search_clear_button, :search_autosearch, :include_object_names
31
+ :search_clear_button, :search_autosearch, :include_object_names,
32
+ :stimmify, :stimmify_camel, :hidden
32
33
  # important: using an attr_accessor called :namespace indirectly causes a conflict with Rails class_name method
33
34
  # so we use namespace_value instead
34
35
 
@@ -56,6 +57,7 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
56
57
  class_option :big_edit, type: :boolean, default: false
57
58
  class_option :show_only, type: :string, default: ""
58
59
  class_option :update_show_only, type: :string, default: ""
60
+ class_option :hidden, type: :string, default: ""
59
61
  class_option :ujs_syntax, type: :boolean, default: nil
60
62
  class_option :downnest, type: :string, default: nil
61
63
  class_option :magic_buttons, type: :string, default: nil
@@ -82,7 +84,7 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
82
84
 
83
85
  # determines if labels appear within the rows of the VIEWABLE list (does NOT affect the list heading)
84
86
  class_option :inline_list_labels, default: nil # default is set below
85
- class_option :factory_creation, default: ''
87
+ class_option :factory_creation, default: nil
86
88
  class_option :alt_foreign_key_lookup, default: '' #
87
89
  class_option :attachments, default: ''
88
90
  class_option :stacked_downnesting, default: false
@@ -102,6 +104,7 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
102
104
  class_option :include_object_names, type: :boolean, default: false
103
105
  class_option :new_button_position, type: :string, default: 'above'
104
106
  class_option :downnest_shows_headings, type: :boolean, default: nil
107
+ class_option :stimmify, type: :string, default: nil
105
108
 
106
109
 
107
110
  # SEARCH OPTIONS
@@ -221,25 +224,18 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
221
224
  @include_fields += options['include'].split(":").collect { |x| x.split(",") }.flatten.collect(&:to_sym)
222
225
  end
223
226
 
224
- # @show_only_data = {}
225
- # if !options['show_only'].empty?
226
- # show_only_input = options['show_only'].split(",")
227
- # show_only_input.each do |setting|
228
- # if setting.include?("[")
229
- # setting =~ /(.*)\[(.*)\]/
230
- # key, lookup_as = $1, $2
231
- # @show_only_data[key.to_sym] = {cast: $2 }
232
- # else
233
- # @show_only_data[setting.to_sym] = {cast: nil}
234
- # end
235
- # end
236
- # end
227
+
237
228
 
238
229
  @show_only = options['show_only'].split(",").collect(&:to_sym)
239
230
  if @show_only.any?
240
231
  puts "show only field #{@show_only}}"
241
232
  end
242
233
 
234
+ @hidden = options['hidden'].split(",").collect(&:to_sym)
235
+ if @hidden.any?
236
+ puts "hidden fields #{@hidden}}"
237
+ end
238
+
243
239
  @modify_as = {}
244
240
  if !options['modify'].empty?
245
241
  modify_input = options['modify'].split(",")
@@ -483,7 +479,10 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
483
479
  end
484
480
  end
485
481
 
486
- @factory_creation = options['factory_creation'].gsub(";", "\n")
482
+ unless options['factory_creation'].nil?
483
+ @factory_creation = options['factory_creation'].gsub(";", "\n")
484
+ end
485
+
487
486
  identify_object_owner
488
487
  setup_fields
489
488
 
@@ -509,6 +508,9 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
509
508
  setting =~ /(.*){(.*)}/
510
509
  key, lookup_as = $1, $2
511
510
 
511
+ if !eval("#{class_name}.reflect_on_association(:#{key.to_s.gsub("_id","")})")
512
+ raise "couldn't find association for #{key} in the object #{class_name}"
513
+ end
512
514
  assoc = eval("#{class_name}.reflect_on_association(:#{key.to_s.gsub("_id","")}).class_name")
513
515
 
514
516
  data = {lookup_as: lookup_as.gsub("+",""),
@@ -517,14 +519,18 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
517
519
  @alt_lookups[key] = data
518
520
  end
519
521
 
520
- puts "------ ALT LOOKUPS for #{@alt_lookups}"
522
+
521
523
 
522
524
  # @update_alt_lookups = @alt_lookups.collect{|key, value|
523
525
  # @update_show_only.include?(key) ?
524
526
  # { key: value }
525
527
  # : nil}.compact
526
528
 
527
-
529
+ @stimmify = options['stimmify']
530
+ if @stimmify === "stimmify"
531
+ @stimmify = @singular.gsub("_", "-") + "-form"
532
+ @stimify_camel = @stimmify.camelize
533
+ end
528
534
 
529
535
  # build a new polymorphic object
530
536
  @associations = []
@@ -577,6 +583,18 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
577
583
  end
578
584
  end
579
585
 
586
+
587
+ puts "------ ALT LOOKUPS for #{@alt_lookups}"
588
+ @alt_lookups.each do |key, value|
589
+ if !@columns_map[key.to_sym].is_a?(AssociationField)
590
+ raise "You specified an alt-lookup for #{key} but that field is not an association field"
591
+ elsif !@columns_map[key.to_sym]
592
+ raise "You specified an alt-lookup for #{key} but that field does not exist in the list of columns"
593
+ elsif !@god && !@hawk_keys.include?(key.to_sym)
594
+ raise "You specified an alt-lookup for #{key} in non-Gd mode but this would leave the lookup unprotected. To fix, use with --hawk or with --factory-creation "
595
+ end
596
+ end
597
+
580
598
  # search
581
599
  @search = options['search']
582
600
  if @search == 'set'
@@ -649,7 +667,10 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
649
667
  search_position: @search_position,
650
668
  search_clear_button: @search_clear_button,
651
669
  search_autosearch: @search_autosearch,
652
- form_path: form_path_new_helper
670
+ form_path: form_path_new_helper,
671
+ stimmify: @stimmify,
672
+ stimmify_camel: @stimmify_camel,
673
+ hidden: @hidden
653
674
  )
654
675
  elsif @markup == "slim"
655
676
  raise(HotGlue::Error, "SLIM IS NOT IMPLEMENTED")
@@ -673,7 +694,6 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
673
694
  if options["hawk"]
674
695
  options['hawk'].split(",").each do |hawk_entry|
675
696
  # format is: abc_id[thing]
676
-
677
697
  if hawk_entry.include?("{")
678
698
  hawk_entry =~ /(.*){(.*)}/
679
699
  key, hawk_to = $1, $2
@@ -683,6 +703,7 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
683
703
  end
684
704
 
685
705
  hawk_scope = key.gsub("_id", "").pluralize
706
+
686
707
  if eval(singular_class + ".reflect_on_association(:#{key.gsub('_id', '')})").nil?
687
708
  raise "Could not find `#{key.gsub('_id', '')}` association; add this to the #{singular_class} class: \nbelongs_to :#{key.gsub('_id', '')} "
688
709
  end
@@ -832,6 +853,8 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
832
853
  :confirmation_token, :confirmed_at,
833
854
  :confirmation_sent_at, :unconfirmed_email
834
855
 
856
+
857
+ # TODO: this should exclude any nested parents
835
858
  @exclude_fields.push(@ownership_field.to_sym) if !@ownership_field.nil?
836
859
 
837
860
  @columns = @the_object.columns.map(&:name).map(&:to_sym).reject { |field| @exclude_fields.include?(field) }
@@ -840,7 +863,7 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
840
863
  @columns = @the_object.columns.map(&:name).map(&:to_sym).reject { |field| !@include_fields.include?(field) }
841
864
  end
842
865
 
843
- @columns = @columns -@nested_set.collect { |set| (set[:singular] + "_id").to_sym }
866
+ @columns = @columns - @nested_set.collect { |set| (set[:singular] + "_id").to_sym }
844
867
 
845
868
  if @attachments.any?
846
869
  puts "Adding attachments-as-columns: #{@attachments}"
@@ -884,8 +907,21 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
884
907
  end
885
908
 
886
909
  def creation_syntax
887
- if @factory_creation == ''
888
- "@#{singular } = #{ class_name }.new(modified_params)"
910
+ if @factory_creation.nil? && ! @alt_lookups.any?
911
+ ( @hawk_keys.any? ? "modified_params = hawk_params({#{ hawk_to_ruby }}, modified_params)\n " : "") + "@#{singular } = #{ class_name }.new(modified_params)"
912
+ elsif @factory_creation.nil? && @alt_lookups.any?
913
+
914
+ prelookup_syntax = @alt_lookups.collect{|lookup, data|
915
+ col = @columns_map[lookup.to_sym]
916
+ col.prelookup_syntax
917
+ }.join("\n")
918
+
919
+ prelookup_syntax + "\n @#{singular } = #{ class_name }.new(modified_params" +
920
+ (@alt_lookups.any? ? (".merge(" + @alt_lookups.collect{|lookup,field|
921
+ field_name = lookup.gsub("_id","")
922
+ "#{field_name}: #{field_name}"
923
+ }.join(",") + ")" ) : "") + ")"
924
+
889
925
  else
890
926
  res = +"begin
891
927
  #{@factory_creation}
@@ -950,10 +986,11 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
950
986
 
951
987
  template "system_spec.rb.erb", dest_file
952
988
  end
953
-
954
- if File.exist?("#{filepath_prefix}app/views#{namespace_with_dash}/_errors.#{@markup}")
955
- File.delete("#{filepath_prefix}app/views#{namespace_with_dash}/_errors.#{@markup}")
956
- end
989
+ # if !File.exist?("#{filepath_prefix}app/views#{namespace_with_dash}/_errors.#{@markup}")
990
+ # # File.delete("#{filepath_prefix}app/views#{namespace_with_dash}/_errors.#{@markup}")
991
+ #
992
+ # template "_errors.erb", File.join("#{filepath_prefix}app/views#{namespace_with_dash}", "_errors.#{@markup}")
993
+ # end
957
994
  end
958
995
 
959
996
  def spec_foreign_association_merge_hash
@@ -1085,6 +1122,17 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
1085
1122
  top_level: top_level)
1086
1123
  end
1087
1124
 
1125
+ def edit_parent_path_helper
1126
+ # the path to the edit route of the PARENT
1127
+ if @nested_set.any? && @nested
1128
+ "edit_#{@namespace + "_" if @namespace}#{(@nested_set.collect { |x| x[:singular] }.join("_") + "_" if @nested_set.any?)}path(" +
1129
+ "#{@nested_set.collect { |x| x[:singular] }.join(", ")}" + ")"
1130
+
1131
+ else
1132
+ "edit_#{@namespace + "_" if @namespace}path"
1133
+ end
1134
+ end
1135
+
1088
1136
  def datetime_fields_list
1089
1137
  @columns.select do |col|
1090
1138
  if @the_object.columns_hash[col.to_s]
@@ -27,10 +27,12 @@ class <%= controller_class_name %> < <%= controller_descends_from %>
27
27
  def <%= @nested_set[0][:singular] %><% if @god
28
28
  next_object = nil
29
29
  collect_objects = @nested_set.reverse.collect {|x|
30
- if eval("#{next_object || class_name}.reflect_on_association(:#{x[:singular]})").nil?
31
- raise "***** Unable to find the association `#{x[:singular]}` on the class #{next_object || class_name} ..... you probably want to add `belongs_to :#{x}` to the #{next_object || class_name} object?"
30
+ if eval("#{next_object || class_name}.reflect_on_association(:#{x[:singular]})").nil? #&& eval("! #{next_object || class_name}.instance_methods.include?(:#{x[:singular]})")
31
+ raise "***** Unable to find the association `#{x[:singular]}` on the class #{next_object || class_name} ..... you probably want to add `belongs_to :#{x[:singular]}` to the #{next_object || class_name} object?"
32
32
  end
33
+ # if eval("#{next_object || class_name}.reflect_on_association(:#{x[:singular]})")
33
34
  next_object = eval("#{next_object || class_name}.reflect_on_association(:#{x[:singular]})").class_name
35
+ # end
34
36
  }
35
37
  root_object = collect_objects.last
36
38
  else
@@ -110,11 +112,10 @@ class <%= controller_class_name %> < <%= controller_descends_from %>
110
112
  modified_params = modified_params.merge(<%= @object_owner_sym %>: <%= @object_owner_eval %>) <% elsif @object_owner_optional && any_nested? %>
111
113
  modified_params = modified_params.merge(<%= @object_owner_name %> ? {<%= @object_owner_sym %>: <%= @object_owner_eval %>} : {}) <% end %>
112
114
 
113
- <% if @hawk_keys.any? %>
114
- modified_params = hawk_params({<%= hawk_to_ruby %>}, modified_params)<% end %>
115
115
  <%= controller_attachment_orig_filename_pickup_syntax %>
116
116
  <%= creation_syntax %>
117
- <% if @pundit %><% @related_sets.each do |key, related_set| %>
117
+
118
+ <% if @pundit %><% @related_sets.each do |key, related_set| %>
118
119
  check_<%= related_set[:association_ids_method].to_s %>_permissions(modified_params, :create)<% end %><% end %>
119
120
  <% if @pundit && !@pundit_policy_override %>
120
121
  authorize @<%= singular %><% elsif @pundit && @pundit_policy_override %>
@@ -195,8 +196,20 @@ class <%= controller_class_name %> < <%= controller_descends_from %>
195
196
  modified_params = modified_params.merge(<%= @object_owner_name %> ? {<%= @object_owner_sym %>: <%= @object_owner_eval %>} : {}) <% end %>
196
197
  <% if @pundit %><% @related_sets.each do |key, related_set| %>
197
198
  check_<%= related_set[:association_ids_method].to_s %>_permissions(modified_params, :update)<% end %><% end %>
199
+ <% if (@alt_lookups.any?) %>
200
+ <%= @alt_lookups.collect{|lookup, data|
201
+ @columns_map[lookup.to_sym].prelookup_syntax unless @update_show_only.include?(lookup.to_sym)
202
+ }.join("\n") %>
203
+ <% elsif @factory_creation %>
198
204
 
199
- <% if @hawk_keys.any? %> modified_params = hawk_params({<%= hawk_to_ruby %>}, modified_params)<% end %>
205
+ <% end %>
206
+ <% if (@alt_lookups.keys.collect(&:to_sym) - @update_show_only).any? %>
207
+ modified_params.merge!(<%= @alt_lookups.collect{|lookup,field|
208
+ field_name = lookup.gsub("_id","")
209
+ "#{field_name}: #{field_name}" unless @update_show_only.include?(lookup.to_sym)
210
+ }.join(",") %>)
211
+ <% end %>
212
+ <% if @hawk_keys.any? %> modified_params = hawk_params({<%= hawk_to_ruby %>}, modified_params)<% end %>
200
213
  <%= controller_attachment_orig_filename_pickup_syntax %>
201
214
  <% if @pundit && !@pundit_policy_override %>
202
215
  authorize @<%= singular_name %>
@@ -221,6 +234,11 @@ class <%= controller_class_name %> < <%= controller_descends_from %>
221
234
  <% end %>
222
235
  else
223
236
  flash[:alert] = "<%= singular_name.titlecase %> could not be saved. #{@hawk_alarm}"
237
+ <%= @alt_lookups.collect{ |k,v|
238
+ assoc = k.gsub("_id","")
239
+ "@#{singular }.#{k} = #{class_name}.find(@#{singular }.id).person.id if @#{singular }.errors.include?(:#{assoc})"
240
+
241
+ }.join("\n") %>
224
242
  @action = 'edit'
225
243
  <% unless @big_edit %>render :update<% else %>render :edit<% end %>, status: :unprocessable_entity
226
244
  end<% if @pundit %>
@@ -261,12 +279,12 @@ class <%= controller_class_name %> < <%= controller_descends_from %>
261
279
  end<% end %><% end %>
262
280
 
263
281
  def <%=singular_name%>_params
264
- 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? %><%= ", " + @alt_lookups.collect{|k,v| ":__lookup_#{v[:lookup_as]}" }.join(", ") if @alt_lookups.any? %>)
282
+ 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? %><%= ", " + @alt_lookups.collect{|k,v| ":__lookup_#{v[:assoc].downcase}_#{v[:lookup_as]}" }.join(", ") if @alt_lookups.any? %>)
265
283
  end<% if @update_show_only %>
266
284
 
267
285
  <% unless @no_edit %>
268
286
  def update_<%=singular_name%>_params
269
- 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? %>)
287
+ 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? %><%= ", " + @alt_lookups.collect{|k,v| ":__lookup_#{v[:assoc].downcase}_#{v[:lookup_as]}" }.join(", ") if @alt_lookups.any? %>)
270
288
  end<% end %>
271
289
  <% end %>
272
290
 
@@ -4,7 +4,9 @@
4
4
  <\%= render(partial: "<%= namespace_with_trailing_dash %>errors", locals: {resource: <%= singular %> }) %>
5
5
  <\% end %>
6
6
  <h2>Editing <%= singular + " " if @include_object_names %><\%= <%= singular %>.<%= display_class %> %></h2>
7
- <\%= form_with model: <%= singular %>, url: <%= form_path_edit_helper %><%= ", html: {'data-turbo': false}" if @big_edit %> do |f| %>
7
+ <\%= form_with model: <%= singular %>,
8
+ <% if @stimmify %> data: {controller: '<%= @stimmify %>' },
9
+ <% end %>url: <%= form_path_edit_helper %><%= ", html: {'data-turbo': false}" if @big_edit %> do |f| %>
8
10
  <\%= 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
11
  <% 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 %>
10
12
  <\% end %>
@@ -2,7 +2,8 @@
2
2
  <%= form_fields_html %>
3
3
 
4
4
  <div class="<%= @layout_strategy.column_classes_for_button_column %>">
5
- <\%= link_to "Cancel", <%= path_helper_plural %>, {class: "btn btn-secondary"} %><% if @no_field_form %>
5
+ <\%= link_to "Cancel", <%=
6
+ @nested_set.none? ? path_helper_plural : edit_parent_path_helper %>, {class: "btn btn-secondary"} %><% if @no_field_form %>
6
7
  <\%= f.hidden_field "_________" %><% end %>
7
8
  <\%= f.submit "Save", class: "btn btn-primary pull-right" %>
8
9
  </div>
@@ -2,7 +2,9 @@
2
2
  <h3>
3
3
  <%= @new_form_heading %>
4
4
  </h3>
5
- <\%= form_with model: <%= singular %>, url: <%= form_path_new_helper %>, method: :post<%= @display_edit_after_create ? ", html: {'data-turbo': false}" : "" %> do |f| \%>
5
+ <\%= form_with model: <%= singular %>,
6
+ <% if @stimmify %>data: {controller: '<%= @stimmify %>' },
7
+ <% end %>url: <%= form_path_new_helper %>, method: :post<%= @display_edit_after_create ? ", html: {'data-turbo': false}" : "" %> do |f| \%>
6
8
  <\%= render partial: "<%= namespace_with_slash + @controller_build_folder %>/form",
7
9
  locals: { <%= singular %>: <%= singular %>, f: f}<%= @nested_set.collect{|arg| ".merge(defined?(#{arg[:singular]}) ? {#{arg[:singular]}: #{arg[:singular]}}: {})" }.join %> \%>
8
10
 
data/lib/hot-glue.rb CHANGED
@@ -10,6 +10,18 @@ module HotGlue
10
10
  class Error < StandardError
11
11
  end
12
12
 
13
+
14
+ def self.to_camel_case(str)
15
+ words = str.split(/[^a-zA-Z0-9]/) # split by non-alphanumeric characters
16
+ return '' if words.empty?
17
+
18
+ first_word = words.first.downcase
19
+ rest_words = words[1..-1].map { |w| w.capitalize }
20
+
21
+ first_word + rest_words.join
22
+ end
23
+
24
+
13
25
  def self.construct_downnest_object(input)
14
26
  res = input.split(",").map { |child|
15
27
  child_name = child.gsub("+","")
@@ -1,5 +1,5 @@
1
1
  module HotGlue
2
2
  class Version
3
- CURRENT = '0.6.16'
3
+ CURRENT = '0.6.18'
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.16
4
+ version: 0.6.18
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason Fleetwood-Boldt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-03-31 00:00:00.000000000 Z
11
+ date: 2025-05-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails