works_cited 0.1.14 → 0.1.15

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +27 -2
  3. data/VERSION +1 -1
  4. data/app/assets/javascripts/works_cited/addFields.js +44 -0
  5. data/app/assets/javascripts/works_cited/application.js +6 -1
  6. data/app/assets/javascripts/works_cited/loadPreview.js +45 -0
  7. data/app/assets/javascripts/works_cited/removeFields.js +37 -0
  8. data/app/assets/javascripts/works_cited/shared.js +59 -0
  9. data/app/assets/javascripts/works_cited/showFields.js +9 -0
  10. data/app/assets/javascripts/works_cited/submitForm.js +33 -0
  11. data/app/controllers/concerns/works_cited/params.rb +24 -0
  12. data/app/controllers/works_cited/citations_controller.rb +26 -13
  13. data/app/helpers/works_cited/application_helper.rb +46 -1
  14. data/app/models/works_cited/citation.rb +30 -34
  15. data/app/models/works_cited/contributor.rb +11 -28
  16. data/app/views/works_cited/citation_types/fields/_book.html.haml +1 -1
  17. data/app/views/works_cited/citation_types/fields/_electronic.html.haml +1 -1
  18. data/app/views/works_cited/citation_types/fields/_email.html.haml +1 -1
  19. data/app/views/works_cited/citation_types/fields/_interview.html.haml +1 -1
  20. data/app/views/works_cited/citation_types/fields/_periodical.html.haml +1 -1
  21. data/app/views/works_cited/citation_types/fields/_tweet.html.haml +1 -1
  22. data/app/views/works_cited/citations/_citation_fields.html.haml +28 -0
  23. data/app/views/works_cited/citations/_fields.html.haml +14 -0
  24. data/app/views/works_cited/citations/_form.html.haml +16 -53
  25. data/app/views/works_cited/citations/preview.html.haml +1 -1
  26. data/app/views/works_cited/contributors/_contributor_fields.html.haml +11 -0
  27. data/config/routes.rb +8 -2
  28. data/db/migrate/20210915160902_add_index_to_works_cited_contributors.rb +5 -1
  29. data/lib/works_cited/mixins/has_works_cited.rb +14 -1
  30. data/spec/dummy/app/controllers/doodads_controller.rb +2 -1
  31. data/spec/dummy/app/controllers/things_controller.rb +1 -1
  32. data/spec/dummy/app/views/doodads/_form.html.haml +2 -0
  33. data/spec/dummy/db/development.sqlite3 +0 -0
  34. data/spec/dummy/db/migrate/20210915161011_create_works_cited_citations.works_cited.rb +1 -0
  35. data/spec/dummy/db/migrate/20210915161012_create_works_cited_contributors.works_cited.rb +1 -0
  36. data/spec/dummy/db/migrate/20210915161013_add_index_to_works_cited_contributors.works_cited.rb +4 -1
  37. data/spec/dummy/db/schema.rb +60 -59
  38. data/spec/dummy/db/test.sqlite3 +0 -0
  39. data/spec/dummy/log/development.log +26893 -15
  40. data/spec/dummy/log/test.log +24838 -0
  41. data/spec/factories/things.rb +8 -0
  42. data/spec/models/doodad_spec.rb +13 -1
  43. data/spec/models/thing_spec.rb +13 -1
  44. data/spec/models/works_cited/citation_spec.rb +12 -0
  45. data/spec/requests/works_cited/citations_spec.rb +10 -8
  46. data/spec/routing/works_cited/citations_routing_spec.rb +5 -1
  47. data/works_cited.gemspec +14 -4
  48. metadata +13 -3
  49. 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: aa5b9e63b43e8210682d2664ea515311f6e752fe7d2149b46dfeea134db70427
4
- data.tar.gz: 81b60e1bcb0fbddb8fd370dc1878998cedb7d2d8f22e3f1bd8a7c73cc681a940
3
+ metadata.gz: 5db67f2a964c0433ad04e7e3fba13605d8bd38cf6d743a56f3eebf71e08a016d
4
+ data.tar.gz: f61b70f80049f28cbb897e2e4c9b1d107237838460266a71ae0c54b318d80cc5
5
5
  SHA512:
6
- metadata.gz: '09189d70a8ab33d4d956ce4372495df050768fc96f844a9cdfd7ba0422bba2b710e96d08366fc899aef1f010cc0154209951eb3179835c3ceb62d44293e352d3'
7
- data.tar.gz: 39f009806bde960422e5e192e3a36f6b40d34a616eea0f919f3cdc174fc4fa9ec26d5adc5731d5561fdeb2c3010372effcbaced13af46fb2f7da5f5618355fe9
6
+ metadata.gz: bab946554a8189670c7a8b5de7b15cd208ddc0df3c0d7539ad8200324cb2091e5b44452a3b3380b692e7a720f142cd4cec4b3c1a41b6118bb7630469f3be4d4f
7
+ data.tar.gz: 6989928293676bce8baf3332ad34c837394cb51e0b096e2997b980d8a54e51801536e259b355548f1a8a22fdc94338bdc9b22c7ee19212a47bca5f91127edd71
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.14
1
+ 0.1.15
@@ -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,20 +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
33
  .order('MIN(works_cited_contributors.last) ASC, '\
30
- 'MIN(works_cited_contributors.first) ASC, '\
31
- 'MIN(works_cited_contributors.middle) ASC, '\
32
- 'MIN(works_cited_contributors.suffix) ASC, '\
33
- 'MIN(works_cited_contributors.handle) 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')
34
38
  .group(:id)
35
39
  end)
36
40
 
@@ -44,17 +48,31 @@ module WorksCited
44
48
  end
45
49
 
46
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
+
47
65
  def record=(value)
48
66
  unless value.is_a? String
49
67
  super(value)
50
68
  return
51
69
  end
52
70
 
53
- model_name, id = value.split(':')
54
- return unless model_name.present? && id.present?
71
+ model_name, my_id = value.split(':')
72
+ return unless model_name.present? && my_id.present?
55
73
 
56
74
  model = model_name.constantize
57
- super model.find(id)
75
+ super model.find(my_id)
58
76
  end
59
77
 
60
78
  def periodical?
@@ -76,27 +94,5 @@ module WorksCited
76
94
  def email?
77
95
  citation_type == 'email'
78
96
  end
79
-
80
- if defined?(RailsAdmin)
81
- rails_admin do
82
- visible false
83
- edit do
84
- field :citation_type, :enum do
85
- enum do
86
- WorksCited.configuration.valid_citation_types
87
- end
88
- end
89
- field :works_cited_contributors do
90
- label 'Contributors'
91
- end
92
- include_all_fields
93
- field :media
94
- field :record do
95
- # Can't remove this using :inverse_of because it's polymorphic
96
- visible false
97
- end
98
- end
99
- end
100
- end
101
97
  end
102
98
  end
@@ -57,28 +57,25 @@ module WorksCited
57
57
 
58
58
  def name_parts
59
59
  parts = []
60
- parts << first_name_or_initial
61
- parts << middle_initial
62
- parts << if suffix.present? && last.present?
63
- "#{last}, #{suffix}"
64
- else
65
- suffix.presence || last.presence
66
- end
60
+ parts << first_name_or_initial.presence
61
+ parts << middle_initial.presence
62
+ parts << [last.presence, suffix.presence].compact.join(', ').presence
67
63
  parts
68
64
  end
69
65
 
70
66
  def name_parts_reversed
71
67
  parts = []
72
- parts << "#{last}," if last.present?
73
- parts << first_name_or_initial
74
- parts << if suffix.present? && middle_initial.present?
75
- "#{middle_initial}, #{suffix}"
76
- else
77
- suffix.presence || middle_initial
78
- end
68
+ parts << [last.presence, name_other_parts.presence].compact.join(', ').presence
79
69
  parts
80
70
  end
81
71
 
72
+ def name_other_parts
73
+ other_parts = []
74
+ other_parts << first_name_or_initial.presence
75
+ other_parts << [middle_initial.presence, suffix.presence].compact.join(', ').presence
76
+ other_parts.compact.join(' ').presence
77
+ end
78
+
82
79
  def first_name_or_initial
83
80
  return nil unless first.present?
84
81
 
@@ -90,19 +87,5 @@ module WorksCited
90
87
 
91
88
  middle[0, 1]&.upcase
92
89
  end
93
-
94
- if defined?(RailsAdmin)
95
- rails_admin do
96
- visible false
97
- edit do
98
- field :contributor_role, :enum do
99
- enum do
100
- WorksCited.configuration.valid_contributor_roles
101
- end
102
- end
103
- include_all_fields
104
- end
105
- end
106
- end
107
90
  end
108
91
  end
@@ -1,4 +1,4 @@
1
- = f.input :url
1
+ = f.input :url, as: :string
2
2
  = f.input :title
3
3
  = f.input :edition, hint: 'like "1st ed."'
4
4
  = f.input :volume, hint: 'like "vol. 1"'
@@ -10,5 +10,5 @@
10
10
  = f.input :pages, hint: 'like "pp. 150-53"'
11
11
  = f.input :online_database
12
12
  = f.input :doi, label: 'Digital Object Identifier'
13
- = f.input :url
13
+ = f.input :url, as: :string
14
14
  = f.input :accessed_at, as: :string, hint: 'Will be parsed and formatted', input_html: { value: f.object.accessed_at&.mla_date }
@@ -10,4 +10,4 @@
10
10
  = f.input :pages, hint: 'like "pp. 150-53"'
11
11
  = f.input :online_database
12
12
  = f.input :doi, label: 'Digital Object Identifier'
13
- = f.input :url
13
+ = f.input :url, as: :string
@@ -9,4 +9,4 @@
9
9
  = f.input :series
10
10
  = f.input :published_at, as: :string, hint: 'Will be parsed and formatted', input_html: { value: f.object.published_at&.mla_date }
11
11
  = f.input :pages, hint: 'like "pp. 150-53"'
12
- = f.input :url
12
+ = f.input :url, as: :string
@@ -7,4 +7,4 @@
7
7
  = f.input :year
8
8
  = f.input :published_at, as: :string, hint: 'Will be parsed and formatted', input_html: { value: f.object.published_at&.mla_date }
9
9
  = f.input :pages, hint: 'like "pp. 150-53"'
10
- = f.input :url
10
+ = f.input :url, as: :string
@@ -1,4 +1,4 @@
1
1
  = f.input :title, label: 'Tweet text'
2
2
  = f.input :publisher, default: 'Twitter'
3
3
  = f.input :published_at, label: 'Time posted', as: :string, hint: 'Will be parsed and formatted', input_html: { value: f.object.published_at&.mla_datetime }
4
- = f.input :url
4
+ = f.input :url, as: :string