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 +4 -4
- data/Gemfile.lock +2 -2
- data/README.md +235 -4
- data/app/helpers/hot_glue/controller_helper.rb +21 -0
- data/lib/generators/hot_glue/field_factory.rb +20 -20
- data/lib/generators/hot_glue/fields/association_field.rb +47 -23
- data/lib/generators/hot_glue/fields/attachment_field.rb +1 -20
- data/lib/generators/hot_glue/fields/boolean_field.rb +13 -13
- data/lib/generators/hot_glue/fields/date_field.rb +4 -2
- data/lib/generators/hot_glue/fields/enum_field.rb +6 -1
- data/lib/generators/hot_glue/fields/field.rb +51 -47
- data/lib/generators/hot_glue/fields/integer_field.rb +21 -1
- data/lib/generators/hot_glue/fields/related_set_field.rb +4 -9
- data/lib/generators/hot_glue/markup_templates/erb.rb +22 -7
- data/lib/generators/hot_glue/scaffold_generator.rb +76 -28
- data/lib/generators/hot_glue/templates/controller.rb.erb +26 -8
- data/lib/generators/hot_glue/templates/erb/_edit.erb +3 -1
- data/lib/generators/hot_glue/templates/erb/_form.erb +2 -1
- data/lib/generators/hot_glue/templates/erb/_new_form.erb +3 -1
- data/lib/hot-glue.rb +12 -0
- data/lib/hotglue/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ddd7d67de32f74ce4e79fabcaaa216e9e5cfbbd84c4bb6d9ab53b6ba2dd57f00
|
4
|
+
data.tar.gz: 5e4e51cce50f7c7e3579bb09c452c5c0b02b4331870a22c9bd249281dab8cd7a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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(
|
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}
|
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
|
-
|
107
|
-
|
108
|
-
|
109
|
-
#
|
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(
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
-
|
14
|
-
|
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},
|
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
|
-
|
12
|
-
|
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 =
|
35
|
-
@singular = singular
|
36
|
-
@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
|
-
@
|
50
|
-
@
|
51
|
-
@
|
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
|
-
|
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
|
-
|
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?
|
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
|
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
|
-
|
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(
|
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
|
-
|
45
|
-
|
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
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
955
|
-
|
956
|
-
|
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
|
-
|
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
|
-
|
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 %>,
|
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", <%=
|
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 %>,
|
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("+","")
|
data/lib/hotglue/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hot-glue
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.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-
|
11
|
+
date: 2025-05-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|