rocketjob_mission_control 5.0.1 → 6.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
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