works_cited 0.1.11 → 0.1.16

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +8 -8
  3. data/Gemfile.lock +9 -10
  4. data/README.md +27 -2
  5. data/VERSION +1 -1
  6. data/app/assets/javascripts/works_cited/addFields.js +44 -0
  7. data/app/assets/javascripts/works_cited/application.js +6 -1
  8. data/app/assets/javascripts/works_cited/loadPreview.js +45 -0
  9. data/app/assets/javascripts/works_cited/removeFields.js +37 -0
  10. data/app/assets/javascripts/works_cited/shared.js +59 -0
  11. data/app/assets/javascripts/works_cited/showFields.js +9 -0
  12. data/app/assets/javascripts/works_cited/submitForm.js +33 -0
  13. data/app/controllers/concerns/works_cited/params.rb +24 -0
  14. data/app/controllers/works_cited/citations_controller.rb +26 -13
  15. data/app/helpers/works_cited/application_helper.rb +46 -1
  16. data/app/models/works_cited/citation.rb +31 -31
  17. data/app/models/works_cited/contributor.rb +11 -28
  18. data/app/views/works_cited/citation_types/fields/_book.html.haml +1 -1
  19. data/app/views/works_cited/citation_types/fields/_electronic.html.haml +1 -1
  20. data/app/views/works_cited/citation_types/fields/_email.html.haml +1 -1
  21. data/app/views/works_cited/citation_types/fields/_interview.html.haml +1 -1
  22. data/app/views/works_cited/citation_types/fields/_periodical.html.haml +1 -1
  23. data/app/views/works_cited/citation_types/fields/_tweet.html.haml +1 -1
  24. data/app/views/works_cited/citations/_citation_fields.html.haml +28 -0
  25. data/app/views/works_cited/citations/_fields.html.haml +14 -0
  26. data/app/views/works_cited/citations/_form.html.haml +16 -53
  27. data/app/views/works_cited/citations/preview.html.haml +1 -1
  28. data/app/views/works_cited/contributors/_contributor_fields.html.haml +11 -0
  29. data/config/routes.rb +8 -2
  30. data/db/migrate/20210915160902_add_index_to_works_cited_contributors.rb +9 -0
  31. data/lib/works_cited/mixins/has_works_cited.rb +14 -1
  32. data/spec/dummy/app/controllers/doodads_controller.rb +2 -1
  33. data/spec/dummy/app/controllers/things_controller.rb +1 -1
  34. data/spec/dummy/app/views/doodads/_form.html.haml +2 -0
  35. data/spec/dummy/db/development.sqlite3 +0 -0
  36. data/spec/dummy/db/migrate/{20210902202653_create_works_cited_citations.works_cited.rb → 20210915161011_create_works_cited_citations.works_cited.rb} +0 -0
  37. data/spec/dummy/db/migrate/{20210902202654_create_works_cited_contributors.works_cited.rb → 20210915161012_create_works_cited_contributors.works_cited.rb} +0 -0
  38. data/spec/dummy/db/migrate/20210915161013_add_index_to_works_cited_contributors.works_cited.rb +9 -0
  39. data/spec/dummy/db/schema.rb +2 -1
  40. data/spec/dummy/db/test.sqlite3 +0 -0
  41. data/spec/dummy/log/development.log +27312 -15
  42. data/spec/dummy/log/test.log +29188 -0
  43. data/spec/factories/things.rb +8 -0
  44. data/spec/models/doodad_spec.rb +13 -1
  45. data/spec/models/thing_spec.rb +13 -1
  46. data/spec/models/works_cited/citation_spec.rb +12 -0
  47. data/spec/requests/works_cited/citations_spec.rb +10 -8
  48. data/spec/routing/works_cited/citations_routing_spec.rb +5 -1
  49. data/works_cited.gemspec +28 -16
  50. metadata +51 -15
  51. data/app/views/works_cited/contributors/_fields.html.haml +0 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b6430ed9d0d1f18dc7bd27c9710815e90813925f19c88eebaf91e54509cef1f
4
- data.tar.gz: 49fdafa4cb232c583ebe917ba1eb8bf33a007cf4cd66ef42734b91c0f03c0f34
3
+ metadata.gz: 033d5232ed69362edb7b0ab085c4c729375410926d86ede41585a409a82e83be
4
+ data.tar.gz: 54bb54fbfafa97b50e1485af06db35583ce20b8fa05bcc21738b73cd0a78cd36
5
5
  SHA512:
6
- metadata.gz: 9c9ec53fc50ad5051abddc755a06c20260cf72b8534039699bbcb68e1a586f5d348dd54b0126a3a4e717fd0a31f9f09edafa5cdc943035cd7f56611737220918
7
- data.tar.gz: 817041e6aff351dadc58f1ba2cc0b21a7b12e691e2b811268a5f17e1dee493b4fc6c555bb42e739a6274e27663ab079265f5cd3646defb1f552f3314ed804e1c
6
+ metadata.gz: d2d4d30e3e218374a33b2772117ecfa9b771eccb79f7e0e43a9d2b2e42b3fb2f5af47cd120de320852c9d757eb7bdd0f21c751520283f86c67e749c868d46ebe
7
+ data.tar.gz: 3cbab16a0fd3d123607e03a6b9dd5fd93938f1a5caa6bdbd34e77fd9b3f1a21411a393b7bb8581c4eedae271b4130596b9515e57893832bcc03781d38263307f
data/Gemfile CHANGED
@@ -5,7 +5,7 @@ source 'https://rubygems.org'
5
5
  gem 'rails', '>= 6', '< 7'
6
6
 
7
7
  gem 'haml-rails', '>= 2.0', '< 3'
8
- gem 'sass-rails', '>= 6', '< 7'
8
+ gem 'sass-rails', '>= 5', '< 7'
9
9
 
10
10
  gem 'cancancan', '>= 3.3', '< 4'
11
11
 
@@ -17,7 +17,7 @@ gem 'kaminari', '>= 1.2.1', '< 2'
17
17
  group :development, :test do
18
18
  gem 'database_cleaner', '>=1.8', '<2'
19
19
  gem 'devise', '>= 4.8', '< 5'
20
- gem 'faker', '>=0'
20
+ gem 'faker', '>= 0.2.19', '< 1'
21
21
  gem 'sqlite3', '>= 1.4.2', '< 2'
22
22
  end
23
23
 
@@ -25,16 +25,16 @@ group :development do
25
25
  gem 'bundler', '>= 2', '< 3'
26
26
  gem 'factory_bot_rails', '>=5.1', '<6'
27
27
  gem 'juwelier', '~> 2.1.0'
28
- gem 'rubocop', '>=0'
29
- gem 'rubocop-rails', '>=0'
30
- gem 'rubocop-rspec', '>=0'
28
+ gem 'rubocop', '>= 1.2.0', '<2'
29
+ gem 'rubocop-rails', '>=2.11.3', '<3'
30
+ gem 'rubocop-rspec', '>=2.4.0', '<3'
31
31
  end
32
32
 
33
33
  group :test do
34
34
  gem 'capybara', '>= 3.35', '< 4'
35
- gem 'rspec-html-matchers', '>= 0'
35
+ gem 'rspec-html-matchers', '>= 0.9.4', '< 1'
36
36
  gem 'rspec-rails', '~> 5.0.0'
37
- gem 'shoulda', '>= 0'
37
+ gem 'shoulda', '>= 4', '< 5'
38
38
  gem 'shoulda-matchers', '>=4.3', '<5'
39
- gem 'simplecov', '>= 0'
39
+ gem 'simplecov', '>= 0.21.2', '< 1'
40
40
  end
data/Gemfile.lock CHANGED
@@ -94,8 +94,7 @@ GEM
94
94
  factory_bot_rails (5.2.0)
95
95
  factory_bot (~> 5.2.0)
96
96
  railties (>= 4.2.0)
97
- faker (2.19.0)
98
- i18n (>= 1.6, < 2)
97
+ faker (0.3.1)
99
98
  faraday (1.7.1)
100
99
  faraday-em_http (~> 1.0)
101
100
  faraday-em_synchrony (~> 1.0)
@@ -342,21 +341,21 @@ DEPENDENCIES
342
341
  database_cleaner (>= 1.8, < 2)
343
342
  devise (>= 4.8, < 5)
344
343
  factory_bot_rails (>= 5.1, < 6)
345
- faker
344
+ faker (>= 0.2.19, < 1)
346
345
  haml-rails (>= 2.0, < 3)
347
346
  juwelier (~> 2.1.0)
348
347
  kaminari (>= 1.2.1, < 2)
349
348
  rails (>= 6, < 7)
350
- rspec-html-matchers
349
+ rspec-html-matchers (>= 0.9.4, < 1)
351
350
  rspec-rails (~> 5.0.0)
352
- rubocop
353
- rubocop-rails
354
- rubocop-rspec
355
- sass-rails (>= 6, < 7)
356
- shoulda
351
+ rubocop (>= 1.2.0, < 2)
352
+ rubocop-rails (>= 2.11.3, < 3)
353
+ rubocop-rspec (>= 2.4.0, < 3)
354
+ sass-rails (>= 5, < 7)
355
+ shoulda (>= 4, < 5)
357
356
  shoulda-matchers (>= 4.3, < 5)
358
357
  simple_form (>= 5.1, < 6)
359
- simplecov
358
+ simplecov (>= 0.21.2, < 1)
360
359
  sqlite3 (>= 1.4.2, < 2)
361
360
  vanilla_nested (>= 1.3, < 2)
362
361
 
data/README.md CHANGED
@@ -3,8 +3,6 @@ Works Cited allows you to add a list of the works cited in ActiveRecord objects,
3
3
 
4
4
  Works Cited uses CanCanCan to authorize the editing of citations. This makes it easy for you to control access.
5
5
 
6
- Works Cited is compatible with, but does not require, Rails Admin.
7
-
8
6
  ## Installation
9
7
  Add this line to your application's Gemfile:
10
8
 
@@ -85,6 +83,33 @@ Add the helpers to the relevant views
85
83
  = works_cited_list @record
86
84
  ```
87
85
 
86
+ To add routes so that you can edit citations on their own pages:
87
+
88
+ ```ruby
89
+ mount WorksCited::Engine => '/works_cited'
90
+ ```
91
+
92
+ To add the fields, nested inside your forms
93
+
94
+ ```haml
95
+ = form_for(@doodad) do |f|
96
+ = works_cited_citations_fields f
97
+ ```
98
+
99
+ Don't forget to add the controller concern to enable nested attributes with strong parameters
100
+
101
+ ```ruby
102
+ class RecipesController < ApplicationController
103
+ include Cookbook::Params
104
+
105
+ #...
106
+
107
+ def recipe_params
108
+ params.require(:doodad).permit(:name, :description, works_cited_params)
109
+ end
110
+ end
111
+ ```
112
+
88
113
  ## Contributing
89
114
  * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
90
115
  * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.11
1
+ 0.1.16
@@ -0,0 +1,44 @@
1
+ class addFields {
2
+ // This executes when the function is instantiated.
3
+ constructor() {
4
+ this.links = document.querySelectorAll('.add_fields')
5
+ this.iterateLinks()
6
+ const event = new Event('works_cited:load')
7
+ window.dispatchEvent(event);
8
+ }
9
+
10
+ iterateLinks() {
11
+ // If there are no links on the page, stop the function from executing.
12
+ if (this.links.length === 0) return
13
+ // Loop over each link on the page. A page could have multiple nested forms.
14
+ this.links.forEach(link => {
15
+ link.addEventListener('click', e => {
16
+ this.handleClick(link, e)
17
+ })
18
+ })
19
+ }
20
+
21
+ handleClick(link, e) {
22
+ // Stop the function from executing if a link or event were not passed into the function.
23
+ if (!link || !e) return
24
+ // Prevent the browser from following the URL.
25
+ e.preventDefault()
26
+ // Save a unique timestamp to ensure the key of the associated array is unique.
27
+ let time = new Date().getTime()
28
+ // Save the data id attribute into a variable. This corresponds to `new_object.object_id`.
29
+ let linkId = link.dataset.id
30
+ // Create a new regular expression needed to find any instance of the `new_object.object_id` used in the fields data attribute if there's a value in `linkId`.
31
+ let regexp = linkId ? new RegExp(linkId, 'g') : null
32
+ // Replace all instances of the `new_object.object_id` with `time`, and save markup into a variable if there's a value in `regexp`.
33
+ let newFields = regexp ? link.dataset.fields.replace(regexp, time) : null
34
+ // Add the new markup to the form if there are fields to add.
35
+ newFields ? link.insertAdjacentHTML('beforebegin', newFields) : null
36
+ // Tell it new fields are there
37
+ const event = new Event('works_cited:add_fields')
38
+ window.dispatchEvent(event);
39
+ }
40
+ }
41
+
42
+ // Wait for turbolinks to load, otherwise `document.querySelectorAll()` won't work
43
+ window.addEventListener('load', () => new addFields())
44
+ window.addEventListener('works_cited:add_fields', () => new addFields())
@@ -1 +1,6 @@
1
- //= require vanilla_nested
1
+ //= require works_cited/shared.js
2
+ //= require works_cited/addFields.js
3
+ //= require works_cited/removeFields.js
4
+ //= require works_cited/showFields.js
5
+ //= require works_cited/loadPreview.js
6
+ //= require works_cited/submitForm.js
@@ -0,0 +1,45 @@
1
+ function startPreview(url, id, citationLocator) {
2
+ var element = document.getElementById(id)
3
+ var fields = element.querySelectorAll('input, select');
4
+ var index = 0;
5
+
6
+ for (index = 0; index < fields.length; ++index) {
7
+ var textField = fields[index];
8
+ textField.addEventListener('change', (e) => loadPreview(url, citationLocator))
9
+ }
10
+ loadPreview(url, citationLocator)
11
+ }
12
+
13
+ function loadPreview(url, locator) {
14
+ // Build formData object.
15
+ var formData = new FormData(document.querySelector('form'))
16
+ var data = {}
17
+ data.citation = getFieldByLocator(formData, locator)
18
+ var authenticity_token = document.head.querySelector('meta[name="csrf-token"]').content
19
+ var preview = document.getElementById('preview')
20
+ var container = document.getElementById('preview-html')
21
+
22
+ fetch(url + '.json',
23
+ {
24
+ method: 'post',
25
+ headers: {
26
+ 'X-Requested-With': 'XMLHttpRequest',
27
+ 'X-CSRF-Token': authenticity_token,
28
+ 'Content-type': 'application/json'
29
+ },
30
+ body: JSON.stringify(data),
31
+ credentials: 'same-origin'
32
+ })
33
+ .then(function(response) {
34
+ // When the page is loaded convert it to json
35
+ return response.json()
36
+ })
37
+ .then(function(json) {
38
+ // get the html value for it
39
+ return json.html
40
+ })
41
+ .then((html) => {
42
+ preview.style.display = 'block'
43
+ container.innerHTML = html
44
+ })
45
+ }
@@ -0,0 +1,37 @@
1
+ class removeFields {
2
+ // This executes when the function is instantiated.
3
+ constructor() {
4
+ this.iterateLinks()
5
+ }
6
+
7
+ iterateLinks() {
8
+ document.querySelectorAll('.remove_fields').forEach(link => {
9
+ link.addEventListener('click', e => {
10
+ this.handleClick(link, e)
11
+ })
12
+ })
13
+ }
14
+
15
+ handleClick(link, e) {
16
+ // Stop the function from executing if a link or event were not passed into the function.
17
+ if (!link || !e) return
18
+ // Prevent the browser from following the URL.
19
+ e.preventDefault()
20
+ // Find the parent wrapper for the set of nested fields.
21
+ let fieldParent = link.closest('.nested-fields')
22
+ // If there is a parent wrapper, find the hidden delete field.
23
+ let deleteField = fieldParent
24
+ ? fieldParent.querySelector('input[type="hidden"]')
25
+ : null
26
+ // If there is a delete field, update the value to `1` and hide the corresponding nested fields.
27
+ if (deleteField) {
28
+ deleteField.value = 1
29
+ fieldParent.style.display = 'none'
30
+ }
31
+ }
32
+ }
33
+
34
+ // Wait for turbolinks to load, otherwise `document.querySelectorAll()` won't work
35
+ window.addEventListener('load', () => new removeFields())
36
+ window.addEventListener('works_cited:load', () => new removeFields())
37
+ window.addEventListener('works_cited:add_fields', () => new removeFields())
@@ -0,0 +1,59 @@
1
+ function packInContainer(container, keyParts, value) {
2
+ var part = keyParts.shift()
3
+ var packed = value
4
+ if(keyParts.length > 0) {
5
+ var previous = container[part] || {}
6
+ packed = packInContainer(previous, keyParts, value)
7
+ }
8
+
9
+ return {...container, [part]: packed}
10
+ }
11
+
12
+ function getWritableField(key) {
13
+ var fields = document.getElementsByName(key)
14
+ var found
15
+
16
+ fields.forEach((field) => {
17
+ if(field.readonly === undefined || !field.readonly) {
18
+ if(field.value) {
19
+ found = field.value
20
+ return
21
+ }
22
+ }
23
+ })
24
+
25
+ return found || ''
26
+ }
27
+
28
+ function getFieldByLocator(data, locator) {
29
+ var regex = /^\[(.*)\]$/
30
+ var container = {}
31
+
32
+ // Display the key/value pairs
33
+ for(var pair of data.entries()) {
34
+ // We want to avoid using pair[1] even though it's there because some fields are readonly
35
+ var key = pair[0]
36
+ if(key.startsWith(locator)) {
37
+ var strippedKeyString = key.replace(locator, '').replace(regex, "$1")
38
+ var keyParts = strippedKeyString.split('][')
39
+ var value = getWritableField(key)
40
+ console.warn(strippedKeyString)
41
+ container = packInContainer(container, keyParts, value)
42
+ }
43
+ }
44
+
45
+ return container
46
+ }
47
+
48
+ function getFormDataPacked(data) {
49
+ var container = {}
50
+
51
+ // Display the key/value pairs
52
+ for(var pair of data.entries()) {
53
+ // We want to avoid using pair[1] even though it's there because some fields are readonly
54
+ var key = pair[0]
55
+ container[key] = getWritableField(key)
56
+ }
57
+
58
+ return container
59
+ }
@@ -0,0 +1,9 @@
1
+ function showFields(citation_id, citation_type) {
2
+ citation = document.getElementById(`citation-${citation_id}`)
3
+ for (let element of citation.getElementsByClassName('fields')){
4
+ element.style.display='none'
5
+ element.readonly=true
6
+ }
7
+ console.warn(citation.getElementsByClassName(`fields-${citation_type}`)[0])
8
+ citation.getElementsByClassName(`fields-${citation_type}`)[0].style.display = 'block'
9
+ }
@@ -0,0 +1,33 @@
1
+ function post(path, params, method = 'post') {
2
+
3
+ // The rest of this code assumes you are not using a library.
4
+ // It can be made less verbose if you use one.
5
+ const form = document.createElement('form')
6
+ form.method = method
7
+ form.action = path
8
+
9
+ for (const key in params) {
10
+ if (params.hasOwnProperty(key)) {
11
+ const hiddenField = document.createElement('input')
12
+ hiddenField.type = 'hidden'
13
+ hiddenField.name = key
14
+ hiddenField.value = params[key]
15
+
16
+ form.appendChild(hiddenField)
17
+ }
18
+ }
19
+
20
+ document.body.appendChild(form)
21
+ form.submit()
22
+ }
23
+
24
+ function addFilterToSubmit() {
25
+ var form = document.querySelector('form')
26
+
27
+ form.onsubmit = function (e) {
28
+ e.preventDefault()
29
+ var formData = new FormData(form)
30
+ var revisedData = getFormDataPacked(formData)
31
+ return post(form.action, revisedData)
32
+ }
33
+ }
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WorksCited
4
+ # Concern to allow strong parameters when using accepts_nested_attributes_for to edit Cookbook Uses
5
+ module Params
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ # rescue_from Pundit::NotAuthorizedError, with: :authorization_error
10
+ # include Pundit
11
+ end
12
+
13
+ def works_cited_params
14
+ {
15
+ works_cited_citations_attributes: [
16
+ :id, :citation_type, :title, :container_title, :publisher, :city, :edition, :volume,
17
+ :number, :series, :year, :record, :media, :url, :pages, :published_at, :online_database, :doi,
18
+ :accessed_at, :_destroy,
19
+ { works_cited_contributors_attributes: %i[id contributor_role first middle last suffix handle _destroy] }
20
+ ]
21
+ }
22
+ end
23
+ end
24
+ end
@@ -16,11 +16,10 @@ module WorksCited
16
16
 
17
17
  # GET /citations/preview
18
18
  def preview
19
- @citation = Citation.new(citation_params)
20
- authorize! :preview, @citation
21
-
22
- @contributors = contributors_from_params
23
- render :preview, layout: false
19
+ authorize! :preview, Citation
20
+ locals = { citation: citation_for_preview, contributors: contributors_for_preview }
21
+ html = render_to_string('preview', formats: [:html], layout: false, locals: locals)
22
+ render json: { html: html }
24
23
  end
25
24
 
26
25
  # GET /citations/new
@@ -55,9 +54,15 @@ module WorksCited
55
54
 
56
55
  private
57
56
 
58
- def contributors_from_params
57
+ def citation_for_preview
58
+ return Citation.new(preview_params) unless params[:id].present?
59
+
60
+ Citation.find(params[:id]).assign_attributes(preview_params)
61
+ end
62
+
63
+ def contributors_for_preview
59
64
  contributors_array = []
60
- raw_contributors = citation_params[:works_cited_contributors_attributes]
65
+ raw_contributors = preview_params[:works_cited_contributors_attributes]
61
66
  raw_contributors&.each do |_index, contributor|
62
67
  destroy = contributor.delete(:_destroy)
63
68
  next if destroy == '1'
@@ -92,12 +97,8 @@ module WorksCited
92
97
 
93
98
  def model_option_group(items, model)
94
99
  model_name = model.model_name
95
- OpenStruct.new(
96
- {
97
- record_type: model_name,
98
- records: items.map { |item| item_option(item, model_name) }
99
- }
100
- )
100
+ records = items.map { |item| item_option(item, model_name) }
101
+ OpenStruct.new({ record_type: model_name, records: records })
101
102
  end
102
103
 
103
104
  def set_records
@@ -109,6 +110,18 @@ module WorksCited
109
110
  end.compact
110
111
  end
111
112
 
113
+ # Only allow a list of trusted parameters through.
114
+ def preview_params
115
+ json = JSON.parse(request.raw_post)
116
+ json_params = ActionController::Parameters.new(json)
117
+ json_params.require(:citation).permit(
118
+ :id, :citation_type, :title, :container_title, :publisher, :city, :edition, :volume,
119
+ :number, :series, :year, :record, :media, :url, :pages, :published_at, :online_database, :doi,
120
+ :accessed_at,
121
+ works_cited_contributors_attributes: %i[id contributor_role first middle last suffix handle _destroy]
122
+ )
123
+ end
124
+
112
125
  # Only allow a list of trusted parameters through.
113
126
  def citation_params
114
127
  params.require(:citation).permit(
@@ -18,11 +18,15 @@ module WorksCited
18
18
  render path, citation: citation, contributors: contributors
19
19
  end
20
20
 
21
- def works_cited_citation_fields(form, citation_type)
21
+ def works_cited_type_fields(form, citation_type)
22
22
  path = partial_path_or_default('fields', citation_type)
23
23
  render path, f: form, citation_type: citation_type
24
24
  end
25
25
 
26
+ def works_cited_citations_fields(form)
27
+ render 'works_cited/citations/fields', form: form
28
+ end
29
+
26
30
  def list_names(names)
27
31
  first = first_contributor(names)
28
32
  first_contributor_name = first&.full_name(first.name_order)
@@ -36,6 +40,47 @@ module WorksCited
36
40
  end
37
41
  end
38
42
 
43
+ # This method creates a link with `data-id` `data-fields` attributes. These attributes are
44
+ # used to create new instances of the nested fields through Javascript.
45
+ def works_cited_link_to_add_fields(name, form, association, partial = nil)
46
+ # Takes an object (@person) and creates a new instance of its associated model (:addresses)
47
+ # To better understand, run the following in your terminal:
48
+ # rails c --sandbox
49
+ # @person = Person.new
50
+ # new_object = @person.send(:addresses).klass.new
51
+ new_object = form.object.send(association).klass.new
52
+
53
+ # Saves the unique ID of the object into a variable.
54
+ # This is needed to ensure the key of the associated array is unique. This is makes
55
+ # parsing the content in the `data-fields` attribute easier through Javascript.
56
+ # We could use another method to achive this.
57
+ id = new_object.object_id
58
+
59
+ # https://api.rubyonrails.org/ fields_for(record_name, record_object = nil, fields_options = {}, &block)
60
+ # record_name = :addresses
61
+ # record_object = new_object
62
+ # fields_options = { child_index: id }
63
+ # child_index` is used to ensure the key of the associated array is unique, and that it
64
+ # matched the value in the `data-id` attribute.
65
+ # `person[addresses_attributes][child_index_value][_destroy]`
66
+ fields = form.simple_fields_for(association, new_object, child_index: id) do |builder|
67
+ # `association.to_s.singularize + "_fields"` ends up evaluating to `address_fields`
68
+ # The render function will then look for `views/people/_address_fields.html.erb`
69
+ # The render function also needs to be passed the value of 'builder', because
70
+ # `views/people/_address_fields.html.erb` needs this to render the form tags.
71
+ partial ||= "#{association.to_s.singularize}_fields"
72
+ render(partial, record: new_object, f: builder)
73
+ end
74
+
75
+ # This renders a simple link, but passes information into `data` attributes.
76
+ # This info can be named anything we want, but in this case we chose `data-id:` and `data-fields:`.
77
+ # The `id:` is from `new_object.object_id`.
78
+ # The `fields:` are rendered from the `fields` blocks.
79
+ # We use `gsub("\n", "")` to remove anywhite space from the rendered partial.
80
+ # The `id:` value needs to match the value used in `child_index: id`.
81
+ link_to(name, '#', class: 'add_fields button button-action', data: { id: id, fields: fields.gsub("\n", '') })
82
+ end
83
+
39
84
  private
40
85
 
41
86
  def partial_path_or_default(purpose, citation_type)
@@ -17,16 +17,24 @@ module WorksCited
17
17
 
18
18
  # Relationships
19
19
  belongs_to :record, polymorphic: true
20
- has_many :works_cited_contributors, -> { order(:last, :first, :middle, :suffix, :handle) }, inverse_of: :works_cited_citation, class_name: 'WorksCited::Contributor',
21
- foreign_key: :works_cited_citation_id
22
- has_many :works_cited_authors, -> { authors.order(:last, :first, :middle, :suffix, :handle) }, inverse_of: :works_cited_citation, class_name: 'WorksCited::Contributor',
23
- foreign_key: :works_cited_citation_id
24
- accepts_nested_attributes_for :works_cited_contributors, allow_destroy: true
20
+ has_many :works_cited_contributors, lambda {
21
+ order(:last, :first, :middle, :suffix, :handle)
22
+ }, inverse_of: :works_cited_citation, class_name: 'WorksCited::Contributor',
23
+ foreign_key: :works_cited_citation_id, dependent: :destroy
24
+ has_many :works_cited_authors, lambda {
25
+ authors.order(:last, :first, :middle, :suffix, :handle)
26
+ }, inverse_of: :works_cited_citation, class_name: 'WorksCited::Contributor',
27
+ foreign_key: :works_cited_citation_id
28
+ accepts_nested_attributes_for :works_cited_contributors, reject_if: :all_blank, allow_destroy: true
25
29
 
26
30
  # Scopes
27
31
  scope :ordered_by_author, (lambda do
28
32
  joins(:works_cited_authors)
29
- .order('MIN(works_cited_contributors.last) ASC')
33
+ .order('MIN(works_cited_contributors.last) ASC, '\
34
+ 'MIN(works_cited_contributors.first) ASC, '\
35
+ 'MIN(works_cited_contributors.middle) ASC, '\
36
+ 'MIN(works_cited_contributors.suffix) ASC, '\
37
+ 'MIN(works_cited_contributors.handle) ASC')
30
38
  .group(:id)
31
39
  end)
32
40
 
@@ -40,17 +48,31 @@ module WorksCited
40
48
  end
41
49
 
42
50
  # Instance Methods
51
+ def works_cited_contributors_attributes=(raw_contributors)
52
+ array = []
53
+ raw_contributors&.each do |_index, contributor|
54
+ destroy = contributor.delete(:_destroy)
55
+ if destroy == '1'
56
+ Contributor.find(contributor[:id]).destroy if contributor[:id]
57
+ next
58
+ end
59
+
60
+ array << contributor
61
+ end
62
+ super array
63
+ end
64
+
43
65
  def record=(value)
44
66
  unless value.is_a? String
45
67
  super(value)
46
68
  return
47
69
  end
48
70
 
49
- model_name, id = value.split(':')
50
- return unless model_name.present? && id.present?
71
+ model_name, my_id = value.split(':')
72
+ return unless model_name.present? && my_id.present?
51
73
 
52
74
  model = model_name.constantize
53
- super model.find(id)
75
+ super model.find(my_id)
54
76
  end
55
77
 
56
78
  def periodical?
@@ -72,27 +94,5 @@ module WorksCited
72
94
  def email?
73
95
  citation_type == 'email'
74
96
  end
75
-
76
- if defined?(RailsAdmin)
77
- rails_admin do
78
- visible false
79
- edit do
80
- field :citation_type, :enum do
81
- enum do
82
- WorksCited.configuration.valid_citation_types
83
- end
84
- end
85
- field :works_cited_contributors do
86
- label 'Contributors'
87
- end
88
- include_all_fields
89
- field :media
90
- field :record do
91
- # Can't remove this using :inverse_of because it's polymorphic
92
- visible false
93
- end
94
- end
95
- end
96
- end
97
97
  end
98
98
  end