rocketjob_mission_control 5.0.1 → 6.0.0.beta

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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/app/assets/config/manifest.js +3 -0
  4. data/app/assets/javascripts/rocket_job_mission_control/application.js +1 -1
  5. data/app/assets/javascripts/rocket_job_mission_control/nested_fields.js +112 -0
  6. data/app/assets/stylesheets/rocket_job_mission_control/{application.scss → application.css} +9 -8
  7. data/app/assets/stylesheets/rocket_job_mission_control/base.css +420 -0
  8. data/app/assets/stylesheets/rocket_job_mission_control/{callout.scss → callout.css} +50 -52
  9. data/app/assets/stylesheets/rocket_job_mission_control/jobs.css +57 -0
  10. data/app/assets/stylesheets/rocket_job_mission_control/worker_processes.css +7 -0
  11. data/app/controllers/rocket_job_mission_control/dirmon_entries_controller.rb +20 -20
  12. data/app/controllers/rocket_job_mission_control/jobs_controller.rb +44 -4
  13. data/app/datatables/rocket_job_mission_control/jobs_datatable.rb +1 -1
  14. data/app/helpers/rocket_job_mission_control/application_helper.rb +43 -26
  15. data/app/helpers/rocket_job_mission_control/dirmon_entries_helper.rb +8 -0
  16. data/app/helpers/rocket_job_mission_control/jobs_helper.rb +76 -8
  17. data/app/models/rocket_job_mission_control/dirmon_sanitizer.rb +68 -0
  18. data/app/models/rocket_job_mission_control/job_sanitizer.rb +35 -0
  19. data/app/views/rocket_job_mission_control/dirmon_entries/_form.html.erb +78 -42
  20. data/app/views/rocket_job_mission_control/dirmon_entries/_input_categories.html.erb +59 -0
  21. data/app/views/rocket_job_mission_control/dirmon_entries/_input_category_fields.html.erb +56 -0
  22. data/app/views/rocket_job_mission_control/dirmon_entries/_output_categories.html.erb +44 -0
  23. data/app/views/rocket_job_mission_control/dirmon_entries/_output_category_fields.html.erb +36 -0
  24. data/app/views/rocket_job_mission_control/dirmon_entries/_status.html.erb +22 -16
  25. data/app/views/rocket_job_mission_control/dirmon_entries/copy.html.erb +1 -1
  26. data/app/views/rocket_job_mission_control/dirmon_entries/edit.html.erb +7 -3
  27. data/app/views/rocket_job_mission_control/dirmon_entries/show.html.erb +2 -3
  28. data/app/views/rocket_job_mission_control/jobs/_attributes.html.erb +89 -0
  29. data/app/views/rocket_job_mission_control/jobs/_dates.html.erb +39 -0
  30. data/app/views/rocket_job_mission_control/jobs/_details.html.erb +86 -0
  31. data/app/views/rocket_job_mission_control/jobs/_exception.html.erb +33 -0
  32. data/app/views/rocket_job_mission_control/jobs/_input_categories.html.erb +126 -0
  33. data/app/views/rocket_job_mission_control/jobs/_input_category_fields.html.erb +56 -0
  34. data/app/views/rocket_job_mission_control/jobs/_output_categories.html.erb +60 -0
  35. data/app/views/rocket_job_mission_control/jobs/_output_category_fields.html.erb +36 -0
  36. data/app/views/rocket_job_mission_control/jobs/_retryable.html.erb +27 -0
  37. data/app/views/rocket_job_mission_control/jobs/_status.html.erb +21 -49
  38. data/app/views/rocket_job_mission_control/jobs/edit.html.erb +28 -0
  39. data/app/views/rocket_job_mission_control/jobs/exception.html.erb +1 -1
  40. data/app/views/rocket_job_mission_control/jobs/index.html.erb +24 -18
  41. data/app/views/rocket_job_mission_control/jobs/show.html.erb +32 -58
  42. data/app/views/rocket_job_mission_control/jobs/view_slice.html.erb +4 -4
  43. data/config/initializers/assets.rb +3 -4
  44. data/lib/rocket_job_mission_control/engine.rb +0 -1
  45. data/lib/rocket_job_mission_control/version.rb +1 -1
  46. data/test/controllers/rocket_job_mission_control/jobs_controller_test.rb +2 -1
  47. data/test/models/rocket_job_mission_control/dirmon_sanitizer_test.rb +146 -0
  48. data/test/test_helper.rb +5 -11
  49. data/vendor/assets/fonts/glyphicons-halflings-regular.eot +0 -0
  50. data/vendor/assets/fonts/glyphicons-halflings-regular.svg +288 -0
  51. data/vendor/assets/fonts/glyphicons-halflings-regular.ttf +0 -0
  52. data/vendor/assets/fonts/glyphicons-halflings-regular.woff +0 -0
  53. data/vendor/assets/fonts/glyphicons-halflings-regular.woff2 +0 -0
  54. data/vendor/assets/stylesheets/bootstrap.min.css.erb +6 -0
  55. metadata +37 -29
  56. data/app/assets/stylesheets/rocket_job_mission_control/base.scss +0 -436
  57. data/app/assets/stylesheets/rocket_job_mission_control/bootstrap_and_overrides.scss +0 -488
  58. data/app/assets/stylesheets/rocket_job_mission_control/jobs.scss +0 -72
  59. data/app/assets/stylesheets/rocket_job_mission_control/worker_processes.scss +0 -9
  60. data/vendor/assets/stylesheets/bootstrap.min.css +0 -6
@@ -4,74 +4,72 @@
4
4
  border-left-width: 0.5em;
5
5
  }
6
6
  .callout-queued {
7
- border-left-color: #A29F00;
8
- a {
9
- color: #A29F00;
10
- }
7
+ border-left-color: #a29f00;
8
+ }
9
+ .callout-queued a {
10
+ color: #a29f00;
11
11
  }
12
12
  .callout-running {
13
13
  border-left-color: #337ab7;
14
- a {
15
- color: #337ab7;
16
- }
14
+ }
15
+ .callout-running a {
16
+ color: #337ab7;
17
17
  }
18
18
  .callout-sleeping {
19
19
  border-left-color: darkcyan;
20
- a {
21
- color: darkcyan;
22
- }
20
+ }
21
+ .callout-sleeping a {
22
+ color: darkcyan;
23
23
  }
24
24
  .callout-scheduled {
25
- border-left-color: #92001B;
26
- a {
27
- color: #92001B;
28
- }
25
+ border-left-color: purple;
26
+ }
27
+ .callout-scheduled a {
28
+ color: purple;
29
29
  }
30
30
  .callout-completed {
31
31
  border-left-color: green;
32
- a {
33
- color: green;
34
- }
32
+ }
33
+ .callout-completed a {
34
+ color: green;
35
35
  }
36
36
  .callout-paused {
37
37
  border-left-color: orange;
38
- a {
39
- color: orange;
40
- }
38
+ }
39
+ .callout-paused a {
40
+ color: orange;
41
41
  }
42
42
  .callout-failed {
43
43
  border-left-color: red;
44
- a {
45
- color: red;
46
- }
44
+ }
45
+ .callout-failed a {
46
+ color: red;
47
47
  }
48
48
  .callout-aborted {
49
49
  border-left-color: darkorange;
50
- a {
51
- color: darkorange;
52
- }
53
50
  }
54
-
51
+ .callout-aborted a {
52
+ color: darkorange;
53
+ }
55
54
  /* Used by Dirmon Entries */
56
55
  .callout-pending {
57
56
  border-color: orange;
58
- a {
59
- color: orange;
60
- }
57
+ }
58
+ .callout-pending a {
59
+ color: orange;
61
60
  }
62
61
  .callout-enabled {
63
62
  border-left-color: green;
64
- a {
65
- color: green;
66
- }
63
+ }
64
+ .callout-enabled a {
65
+ color: green;
67
66
  }
68
67
  .callout-disabled {
69
68
  border-left-color: grey;
70
- a {
71
- color: grey;
72
- }
73
69
  }
74
-
70
+ .callout-disabled a {
71
+ color: grey;
72
+ }
75
73
  /* Used by Servers */
76
74
  .callout-top {
77
75
  border: 1px solid #eee;
@@ -79,31 +77,31 @@
79
77
  }
80
78
  .callout-info {
81
79
  border-left-color: #1b809e;
82
- .title {
83
- color: #1b809e;
84
- }
80
+ }
81
+ .callout-info .title {
82
+ color: #1b809e;
85
83
  }
86
84
  .callout-success {
87
85
  border-left-color: #3c763d;
88
- .title {
89
- color: #3c763d;
90
- }
86
+ }
87
+ .callout-success .title {
88
+ color: #3c763d;
91
89
  }
92
90
  .callout-warning {
93
91
  border-left-color: #aa6708;
94
- .title {
95
- color: #aa6708;
96
- }
92
+ }
93
+ .callout-warning .title {
94
+ color: #aa6708;
97
95
  }
98
96
  .callout-alert {
99
97
  border-left-color: #ce4844;
100
- .title {
101
- color: #ce4844;
102
- }
98
+ }
99
+ .callout-alert .title {
100
+ color: #ce4844;
103
101
  }
104
102
  .callout-zombie {
105
103
  border-left-color: darkred;
106
- .title {
107
- color: darkred;
108
- }
104
+ }
105
+ .callout-zombie .title {
106
+ color: darkred;
109
107
  }
@@ -0,0 +1,57 @@
1
+ .job-list {
2
+ /*padding: 1px 1px 1px 1px;
3
+ */
4
+ }
5
+ .job-list .breadcrumb {
6
+ margin-bottom: 0px;
7
+ }
8
+ .job-list .breadcrumb {
9
+ padding: 2px;
10
+ }
11
+ .job-list .breadcrumb > li {
12
+ margin: 5px 0 0 5px;
13
+ }
14
+ .job-list .list .progress {
15
+ width: 10em;
16
+ height: 1em;
17
+ margin: 5px 0px 0px 10px;
18
+ display: inline-block;
19
+ -webkit-border-radius: 0 !important;
20
+ -moz-border-radius: 0 !important;
21
+ border-radius: 0 !important;
22
+ }
23
+ .job-list .list .progress-bar {
24
+ min-width: 0em;
25
+ }
26
+ .error pre {
27
+ margin: 0px 0px 0px 0px;
28
+ border-bottom: 0;
29
+ border-radius: 0;
30
+ word-wrap: normal;
31
+ }
32
+ .error pre:last-child {
33
+ border: 1px solid #ccc;
34
+ }
35
+ .pagination {
36
+ margin-top: 1em;
37
+ }
38
+ .pagination .btn {
39
+ border-radius: 0;
40
+ }
41
+ .input_slices {
42
+ font-size: 14px;
43
+ line-height: 2;
44
+ resize: none;
45
+ width: 100%;
46
+ height: 20em;
47
+ padding-top: 5px;
48
+ padding-bottom: 5px;
49
+ margin-bottom: 4px;
50
+ }
51
+ .edit_button {
52
+ float: right;
53
+ margin-left: 3px;
54
+ }
55
+ .edit_input {
56
+ margin-left: 80px;
57
+ }
@@ -0,0 +1,7 @@
1
+ #servers .server-collection-actions {
2
+ margin: 1em 0;
3
+ }
4
+ #servers .actions > a {
5
+ display: inline;
6
+ margin: 5px;
7
+ }
@@ -1,18 +1,11 @@
1
1
  module RocketJobMissionControl
2
2
  class DirmonEntriesController < RocketJobMissionControl::ApplicationController
3
- if Rails.version.to_i < 5
4
- before_filter :find_entry_or_redirect, except: %i[index disabled enabled failed pending new create]
5
- before_filter :authorize_read, only: %i[index disabled enabled failed pending]
6
- before_filter :show_sidebar
7
- else
8
- before_action :find_entry_or_redirect, except: %i[index disabled enabled failed pending new create]
9
- before_action :authorize_read, only: %i[index disabled enabled failed pending]
10
- before_action :show_sidebar
11
- end
3
+ before_action :find_entry_or_redirect, except: %i[index disabled enabled failed pending new create]
4
+ before_action :authorize_read, only: %i[index disabled enabled failed pending]
5
+ before_action :show_sidebar
12
6
 
13
7
  rescue_from AccessGranted::AccessDenied do |exception|
14
8
  raise exception if Rails.env.development? || Rails.env.test?
15
-
16
9
  redirect_to :back, alert: "Access not authorized."
17
10
  end
18
11
 
@@ -56,6 +49,7 @@ module RocketJobMissionControl
56
49
  def create
57
50
  authorize! :create, RocketJob::DirmonEntry
58
51
  @dirmon_entry = RocketJob::DirmonEntry.new(dirmon_params)
52
+
59
53
  if properties = params[:rocket_job_dirmon_entry][:properties]
60
54
  @dirmon_entry.properties = JobSanitizer.sanitize(properties, @dirmon_entry.job_class, @dirmon_entry, false)
61
55
  end
@@ -76,19 +70,23 @@ module RocketJobMissionControl
76
70
  def edit
77
71
  authorize! :edit, @dirmon_entry
78
72
  end
79
- # When you click on the Copy button,
73
+
74
+ # When you click on the Copy button,
80
75
  # the copy method loads the Dirmon Entity attributes in Copy Dirmon Entry Page
81
76
  def copy
82
77
  authorize! :copy, @dirmon_entry
83
78
  end
79
+
84
80
  # When you click on the replicate button,
85
81
  # the replicate method clones the existing Dirmon Entity
86
82
  def replicate
87
83
  authorize! :replicate, @dirmon_entry
88
84
  dirmon_entry_replicate = RocketJob::DirmonEntry.new(@dirmon_entry.dup.attributes.except("id"))
85
+
89
86
  if properties = params[:rocket_job_dirmon_entry][:properties]
90
- dirmon_entry_replicate.properties = JobSanitizer.sanitize(properties, dirmon_entry_replicate.job_class, dirmon_entry_replicate, false)
87
+ dirmon_entry_replicate.properties = JobSanitizer.sanitize(properties, dirmon_entry_replicate.job_class, @dirmon_entry, false)
91
88
  end
89
+
92
90
  if dirmon_entry_replicate.errors.empty? && dirmon_entry_replicate.valid? && dirmon_entry_replicate.update_attributes(dirmon_params)
93
91
  redirect_to(rocket_job_mission_control.dirmon_entry_path(dirmon_entry_replicate))
94
92
  else
@@ -98,10 +96,12 @@ module RocketJobMissionControl
98
96
 
99
97
  def update
100
98
  authorize! :update, @dirmon_entry
101
- if properties = params[:rocket_job_dirmon_entry][:properties]
102
- @dirmon_entry.properties = JobSanitizer.sanitize(properties, @dirmon_entry.job_class, @dirmon_entry, false)
103
- end
104
- if @dirmon_entry.errors.empty? && @dirmon_entry.valid? && @dirmon_entry.update_attributes(dirmon_params)
99
+ sanitized_params = DirmonSanitizer.sanitize(params[:rocket_job_dirmon_entry], @dirmon_entry.job_class, @dirmon_entry)
100
+ properties = DirmonSanitizer.diff_properties(sanitized_params[:properties], @dirmon_entry)
101
+
102
+ sanitized_params[:properties] = properties
103
+
104
+ if @dirmon_entry.errors.empty? && @dirmon_entry.valid? && @dirmon_entry.update_attributes(sanitized_params)
105
105
  redirect_to(rocket_job_mission_control.dirmon_entry_path(@dirmon_entry))
106
106
  else
107
107
  render :edit
@@ -110,6 +110,7 @@ module RocketJobMissionControl
110
110
 
111
111
  def enable
112
112
  authorize! :enable, @dirmon_entry
113
+
113
114
  if @dirmon_entry.may_enable?
114
115
  @dirmon_entry.enable!
115
116
  redirect_to(rocket_job_mission_control.dirmon_entry_path(@dirmon_entry))
@@ -121,6 +122,7 @@ module RocketJobMissionControl
121
122
 
122
123
  def disable
123
124
  authorize! :disable, @dirmon_entry
125
+
124
126
  if @dirmon_entry.may_disable?
125
127
  @dirmon_entry.disable!
126
128
  redirect_to(rocket_job_mission_control.dirmon_entry_path(@dirmon_entry))
@@ -148,15 +150,12 @@ module RocketJobMissionControl
148
150
  def find_entry_or_redirect
149
151
  unless @dirmon_entry = RocketJob::DirmonEntry.where(id: params[:id]).first
150
152
  flash[:alert] = t(:failure, scope: %i[dirmon_entry find], id: params[:id])
151
-
152
153
  redirect_to(dirmon_entries_path)
153
154
  end
154
155
  end
155
156
 
156
157
  def dirmon_params
157
- params.
158
- fetch(:rocket_job_dirmon_entry, {}).
159
- permit(:name, :archive_directory, :pattern, :job_class_name)
158
+ params.fetch(:rocket_job_dirmon_entry, {}).permit(:name, :archive_directory, :pattern, :job_class_name, :properties)
160
159
  end
161
160
 
162
161
  def render_datatable(entries, description)
@@ -165,6 +164,7 @@ module RocketJobMissionControl
165
164
  @description = description
166
165
  render :index
167
166
  end
167
+
168
168
  format.json do
169
169
  query = RocketJobMissionControl::Query.new(entries, name: :asc)
170
170
  query.search_columns = %i[job_class_name name pattern]
@@ -74,8 +74,11 @@ module RocketJobMissionControl
74
74
 
75
75
  def update
76
76
  authorize! :update, @job
77
- permitted_params = JobSanitizer.sanitize(params[:job], @job.class, @job)
78
- if @job.errors.empty? && @job.valid? && @job.update_attributes(permitted_params)
77
+ permitted_params = JobSanitizer.sanitize(job_params, @job.class, @job)
78
+
79
+ assign_job_values(@job, permitted_params)
80
+
81
+ if @job.errors.empty? && @job.valid? && @job.save
79
82
  redirect_to job_path(@job)
80
83
  else
81
84
  render :edit
@@ -247,6 +250,24 @@ module RocketJobMissionControl
247
250
 
248
251
  private
249
252
 
253
+ def assign_job_values(target, hash)
254
+ hash.each_pair do |attribute, value|
255
+ if attribute == :input_categories
256
+ value.each do |category_hash|
257
+ h = category_hash.dup
258
+ assign_job_values(target.input_category(h.delete(:name)), h)
259
+ end
260
+ elsif attribute == :output_categories
261
+ value.each do |category_hash|
262
+ h = category_hash.dup
263
+ assign_job_values(target.output_category(h.delete(:name)), h)
264
+ end
265
+ elsif attribute != :id
266
+ target.send("#{attribute}=", value)
267
+ end
268
+ end
269
+ end
270
+
250
271
  def authorize_read
251
272
  authorize! :read, RocketJob::Job
252
273
  end
@@ -264,7 +285,26 @@ module RocketJobMissionControl
264
285
  end
265
286
 
266
287
  def job_params
267
- params.require(:job).permit(@job.class.user_editable_fields)
288
+ params.require(:job).permit(
289
+ @job.class.user_editable_fields,
290
+ input_categories_attributes: [
291
+ :id,
292
+ :name,
293
+ :format,
294
+ :format_options,
295
+ :mode,
296
+ :skip_unknown,
297
+ :slice_size,
298
+ columns: []
299
+ ],
300
+ output_categories_attributes: [
301
+ :id,
302
+ :name,
303
+ :format,
304
+ :format_options,
305
+ columns: []
306
+ ]
307
+ )
268
308
  end
269
309
 
270
310
  def error_occurred(exception)
@@ -308,7 +348,7 @@ module RocketJobMissionControl
308
348
  h = {data: index.to_s}
309
349
  h[:width] = column[:width] if column.key?(:width)
310
350
  h[:orderable] = column[:orderable] if column.key?(:orderable)
311
- index += 1
351
+ index += 1
312
352
  h
313
353
  end
314
354
  end
@@ -47,7 +47,7 @@ module RocketJobMissionControl
47
47
  {display: "Started", value: :started, field: "started_at"},
48
48
  {display: "Actions", value: :action_buttons, orderable: false}
49
49
  ].freeze
50
- RUNNING_FIELDS = (COMMON_FIELDS + %i[record_count collect_output input_categories output_categories encrypt compress slice_size priority sub_state percent_complete]).freeze
50
+ RUNNING_FIELDS = (COMMON_FIELDS + %i[record_count input_categories output_categories priority sub_state percent_complete]).freeze
51
51
 
52
52
  SCHEDULED_COLUMNS = [
53
53
  {display: "Class", value: :class_with_link, field: "_type"},
@@ -57,7 +57,7 @@ module RocketJobMissionControl
57
57
  end
58
58
 
59
59
  # Returns the editable field as html for use in editing dynamic fields from a Job class.
60
- def editable_field_html(klass, field_name, value, f, include_nil_selectors = false)
60
+ def editable_field_html(klass, field_name, value, f)
61
61
  # When editing a job the values are of the correct type.
62
62
  # When editing a dirmon entry values are strings.
63
63
  field = klass.fields[field_name.to_s]
@@ -67,44 +67,61 @@ module RocketJobMissionControl
67
67
  placeholder = nil if placeholder.is_a?(Proc)
68
68
 
69
69
  case field.type.name
70
- when "Symbol", "String", "Integer"
70
+ when "Integer"
71
+ options = extract_inclusion_values(klass, field_name)
72
+ f.number_field(field_name, in: options, include_blank: false, value: value, class: "form-control", placeholder: placeholder)
73
+ when "String", "Symbol", "Mongoid::StringifiedSymbol"
71
74
  options = extract_inclusion_values(klass, field_name)
72
- str = "[#{field.type.name}]\n".html_safe
73
75
  if options
74
- str + f.select(field_name, options, {include_blank: options.include?(nil) || include_nil_selectors, selected: value}, {class: "selectize form-control"})
76
+ f.select(field_name, options, {include_blank: options.include?(nil), selected: value}, {class: "selectize form-control"})
75
77
  else
76
- if field.type.name == "Integer"
77
- str + f.number_field(field_name, value: value, class: "form-control", placeholder: placeholder)
78
- else
79
- str + f.text_field(field_name, value: value, class: "form-control", placeholder: placeholder)
80
- end
78
+ f.text_field(field_name, value: value, class: "form-control", placeholder: placeholder)
81
79
  end
80
+ when "Boolean", "Mongoid::Boolean"
81
+ options = extract_inclusion_values(klass, field_name) || [nil, "true", "false"]
82
+ f.select(field_name, options, {include_blank: options.include?(nil), selected: value}, {class: "selectize form-control"})
82
83
  when "Hash"
83
84
  "[JSON Hash]\n".html_safe +
84
85
  f.text_field(field_name, value: value ? value.to_json : "", class: "form-control", placeholder: '{"key1":"value1", "key2":"value2", "key3":"value3"}')
85
86
  when "Array"
86
87
  options = Array(value)
87
- "[Array]\n".html_safe +
88
- f.select(field_name, options_for_select(options, options), {include_hidden: false}, {class: "selectize form-control", multiple: true})
89
- when "Mongoid::Boolean"
90
- name = "#{field_name}_true".to_sym
91
- value = value.to_s
92
- str = '<div class="radio-buttons">'.html_safe
93
- str << f.radio_button(field_name, "true", checked: value == "true")
94
- str << " ".html_safe + f.label(name, "true")
95
- str << " ".html_safe + f.radio_button(field_name, "false", checked: value == "false")
96
- str << " ".html_safe + f.label(name, "false")
97
- # Allow this field to be unset (nil).
98
- if include_nil_selectors
99
- str << " ".html_safe + f.radio_button(field_name, "", checked: value == "")
100
- str << " ".html_safe + f.label(name, "nil")
101
- end
102
-
103
- str << "</div>".html_safe
88
+ f.select(field_name, options_for_select(options, options), {include_hidden: false}, {class: "selectize form-control", multiple: true})
104
89
  else
105
90
  "[#{field.type.name}]".html_safe +
106
91
  f.text_field(field_name, value: value, class: "form-control", placeholder: placeholder)
107
92
  end
108
93
  end
94
+
95
+ # This method creates a link with `data-id` `data-fields` attributes. These attributes are used to create new instances of the nested fields through Javascript.
96
+ def link_to_add_fields(name, f, association, option)
97
+ # Takes an object (@job) and creates a new instance of its associated model (:properties)
98
+ new_object = f.object.send(association).klass.new
99
+
100
+ # Saves the unique ID of the object into a variable.
101
+ # This is needed to ensure the key of the associated array is unique. This is makes parsing the content in the `data-fields` attribute easier through Javascript.
102
+ # We could use another method to achive this.
103
+ id = new_object.object_id
104
+
105
+ # https://api.rubyonrails.org/ fields_for(record_name, record_object = nil, fields_options = {}, &block)
106
+ # record_name = :addresses
107
+ # record_object = new_object
108
+ # fields_options = { child_index: id }
109
+ # child_index` is used to ensure the key of the associated array is unique, and that it matched the value in the `data-id` attribute.
110
+ # `person[addresses_attributes][child_index_value][_destroy]`
111
+ fields = f.fields_for(association, new_object, child_index: id) do |builder|
112
+ # `association.to_s.singularize + "_fields"` ends up evaluating to `address_fields`
113
+ # The render function will then look for `views/people/_address_fields.html.erb`
114
+ # The render function also needs to be passed the value of 'builder', because `views/dirmon_entries/_input_categories.html.erb` needs this to render the form tags.
115
+ render(association.to_s.singularize + "_fields", f: builder)
116
+ end
117
+
118
+ # This renders a simple link, but passes information into `data` attributes.
119
+ # This info can be named anything we want, but in this case we chose `data-id:` and `data-fields:`.
120
+ # The `id:` is from `new_object.object_id`.
121
+ # The `fields:` are rendered from the `fields` blocks.
122
+ # We use `gsub("\n", "")` to remove anywhite space from the rendered partial.
123
+ # The `id:` value needs to match the value used in `child_index: id`.
124
+ link_to(name, '#', class: "add_fields btn btn-#{option}", data: { id: id, fields: fields.gsub("\n", "") })
125
+ end
109
126
  end
110
127
  end