Class: Citation

Inherits:
ApplicationRecord show all
Includes:
Housekeeping, Shared::IsData, Shared::Notes, Shared::PolymorphicAnnotator, Shared::Tags, SoftValidation
Defined in:
app/models/citation.rb

Overview

A Citation is an assertion that the subject (i.e. citation object/record/data instance), or some attribute of it, was referenced or originated in a Source.

Constant Summary

Constants included from SoftValidation

SoftValidation::ANCESTORS_WITH_SOFT_VALIDATIONS

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from SoftValidation

#clear_soft_validations, #fix_for, #fix_soft_validations, #soft_fixed?, #soft_valid?, #soft_validate, #soft_validated?, #soft_validations, #soft_validators

Methods included from Shared::PolymorphicAnnotator

#annotated_object_is_persisted?

Methods included from Shared::IsData

#errors_excepting, #full_error_messages_excepting, #identical, #is_community?, #is_destroyable?, #is_editable?, #is_in_use?, #is_in_users_projects?, #metamorphosize, #similar

Methods included from Shared::Tags

#reject_tags, #tag_with, #tagged?, #tagged_with?

Methods included from Shared::Notes

#concatenated_notes_string, #reject_notes

Methods included from Housekeeping

#has_polymorphic_relationship?

Methods inherited from ApplicationRecord

transaction_with_retry

Instance Attribute Details

#citation_object_idInteger

Returns Rails STI, the id of the object being cited.

Returns:

  • (Integer)

    Rails STI, the id of the object being cited



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'app/models/citation.rb', line 29

class Citation < ApplicationRecord

  # Citations do not have Confidence or DataAttribute.

  include Housekeeping
  include Shared::Notes
  include Shared::Tags
  include Shared::IsData
  include Shared::PolymorphicAnnotator
  include SoftValidation

  attr_accessor :no_cached

  polymorphic_annotates('citation_object')

  # belongs_to :source, inverse_of: :origin_citations
  belongs_to :source, inverse_of: :citations

  has_many :citation_topics, inverse_of: :citation, dependent: :destroy
  has_many :topics, through: :citation_topics, inverse_of: :citations
  has_many :documents, through: :source

  validates_presence_of :source

  validates_uniqueness_of :source_id, scope: [:citation_object_id, :citation_object_type, :pages]
  validates_uniqueness_of :is_original, scope: [:citation_object_type, :citation_object_id], message: 'can only be assigned once per object.', allow_nil: true, if: -> { is_original? }

  accepts_nested_attributes_for :citation_topics, allow_destroy: true, reject_if: :reject_citation_topics
  accepts_nested_attributes_for :topics, allow_destroy: true, reject_if: :reject_topic

  before_destroy :prevent_if_required

  after_create :add_source_to_project

  before_save {@old_is_original = is_original_was}
  before_save {@old_citation_object_id = citation_object_id_was}
  before_save {@old_source_id = source_id_was}

  after_save :update_related_cached_values, if: :is_original?

  after_save :set_cached_names_for_taxon_names, unless: -> {self.no_cached}
  after_destroy :set_cached_names_for_taxon_names, unless: -> {self.no_cached}

  soft_validate(:sv_page_range, set: :page_range)

  def self.batch_create(params)
    ids = params[:citation_object_id]
    params.delete(:citation_object_id)

    citations = []
    Citation.transaction do
      begin
        ids.each do |id|
          citations.push Citation.create!(
            params.merge(
              citation_object_id: id
            )
          )
        end
      rescue ActiveRecord::RecordInvalid
        return false
      end
    end
    citations
  end

  # TODO: deprecate
  # @return [Scope of matching sources]
  def self.find_for_autocomplete(params)
    term = params['term']
    ending = term + '%'
    wrapped = '%' + term + '%'
    joins(:source).where('sources.cached ILIKE ? OR sources.cached ILIKE ? OR citation_object_type LIKE ?', ending, wrapped, ending).with_project_id(params[:project_id])
  end

  # @return [Boolean]
  #   true if is_original is checked, false if nil/false
  def is_original?
    is_original ? true : false
  end

  # @return [String, nil]
  #    the first integer in the string, as a string
  def first_page
    /(?<i>\d+)/ =~ pages
    i
  end

  # @return [Integer, nil]
  #    if a target document
  def target_document_page
    target_document.try(:pdf_page_for, first_page).try(:first)
  end

  # @return [Document, nil]
  def target_document
    documents.order('documentation.position').first
  end

  protected

  def add_source_to_project
    !!ProjectSource.find_or_create_by(project:, source:)
  end

  def reject_citation_topics(attributed)
    attributes['id'].blank? && attributed['topic_id'].blank? && attributed['topic'].blank? && attributed['topic_attributes'].blank?
  end

  def reject_topic(attributed)
    attributed['name'].blank? || attributed['definition'].blank?
  end

  def update_related_cached_values
    if is_original != @old_is_original || citation_object_id != @old_citation_object_id || source_id != @old_source_id
      if citation_object_type == 'TaxonName'
        citation_object.update_columns(
          cached_author_year: citation_object.get_author_and_year,
          cached_nomenclature_date: citation_object.nomenclature_date)  if citation_object.persisted?
      end
    end
    true
  end

  # TODO: modify for asserted distributions and other origin style relationships
  def prevent_if_required
    unless citation_object && citation_object.respond_to?(:ignore_citation_restriction) && citation_object.ignore_citation_restriction
      if !marked_for_destruction? && !new_record? && citation_object.requires_citation? && citation_object.citations.count == 1
        errors.add(:base, 'at least one citation is required')
        throw :abort
      end
    end
  end

  def set_cached_names_for_taxon_names
    if is_original != @old_is_original || citation_object_id != @old_citation_object_id || source_id != @old_source_id
      if citation_object_type == 'TaxonNameRelationship' && TAXON_NAME_RELATIONSHIP_NAMES_INVALID.include?(citation_object.try(:type_name))
        begin
          TaxonNameRelationship.transaction do
            t = citation_object.subject_taxon_name
            vn = t.get_valid_taxon_name

            t.update_columns(
              cached: t.get_full_name,
              cached_html: t.get_full_name_html,
              cached_valid_taxon_name_id: vn.id)

            # @proceps: This and below is not updating cached names.  Is this required because timing (new dates) may change synonymy?
            t.combination_list_self.each do |c|
              c.update_column(:cached_valid_taxon_name_id, vn.id)
            end

            vn.list_of_invalid_taxon_names.each do |s|
              s.update_column(:cached_valid_taxon_name_id, vn.id)
              s.combination_list_self.each do |c|
                c.update_column(:cached_valid_taxon_name_id, vn.id)
              end
            end
          end
        rescue ActiveRecord::RecordInvalid
          raise
        end
        false
      end
    end
  end

  def sv_page_range
    if pages.blank?
      soft_validations.add(:pages, 'Citation pages are not provided')
    elsif source.pages.present?
      matchdata1 = pages.match(/(\d+) ?[-–] ?(\d+)|(\d+)/)
      if matchdata1
        citMinP = matchdata1[1] ? matchdata1[1].to_i : matchdata1[3].to_i
        citMaxP = matchdata1[2] ? matchdata1[2].to_i : matchdata1[3].to_i
        matchdata = source.pages.match(/(\d+) ?[-–] ?(\d+)|(\d+)/)
        if citMinP && citMaxP && matchdata
          minP = matchdata[1] ? matchdata[1].to_i : matchdata[3].to_i
          maxP = matchdata[2] ? matchdata[2].to_i : matchdata[3].to_i
          minP = 1 if minP == maxP && %w{book booklet manual mastersthesis phdthesis techreport}.include?(source.bibtex_type)
          unless (maxP && minP && minP <= citMinP && maxP >= citMaxP)
            soft_validations.add(:pages, 'Citation is out of the source page range')
          end
        end
      end
    end
  end

end

#citation_object_typeString

Returns Rails STI, the class of the object being cited.

Returns:

  • (String)

    Rails STI, the class of the object being cited



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'app/models/citation.rb', line 29

class Citation < ApplicationRecord

  # Citations do not have Confidence or DataAttribute.

  include Housekeeping
  include Shared::Notes
  include Shared::Tags
  include Shared::IsData
  include Shared::PolymorphicAnnotator
  include SoftValidation

  attr_accessor :no_cached

  polymorphic_annotates('citation_object')

  # belongs_to :source, inverse_of: :origin_citations
  belongs_to :source, inverse_of: :citations

  has_many :citation_topics, inverse_of: :citation, dependent: :destroy
  has_many :topics, through: :citation_topics, inverse_of: :citations
  has_many :documents, through: :source

  validates_presence_of :source

  validates_uniqueness_of :source_id, scope: [:citation_object_id, :citation_object_type, :pages]
  validates_uniqueness_of :is_original, scope: [:citation_object_type, :citation_object_id], message: 'can only be assigned once per object.', allow_nil: true, if: -> { is_original? }

  accepts_nested_attributes_for :citation_topics, allow_destroy: true, reject_if: :reject_citation_topics
  accepts_nested_attributes_for :topics, allow_destroy: true, reject_if: :reject_topic

  before_destroy :prevent_if_required

  after_create :add_source_to_project

  before_save {@old_is_original = is_original_was}
  before_save {@old_citation_object_id = citation_object_id_was}
  before_save {@old_source_id = source_id_was}

  after_save :update_related_cached_values, if: :is_original?

  after_save :set_cached_names_for_taxon_names, unless: -> {self.no_cached}
  after_destroy :set_cached_names_for_taxon_names, unless: -> {self.no_cached}

  soft_validate(:sv_page_range, set: :page_range)

  def self.batch_create(params)
    ids = params[:citation_object_id]
    params.delete(:citation_object_id)

    citations = []
    Citation.transaction do
      begin
        ids.each do |id|
          citations.push Citation.create!(
            params.merge(
              citation_object_id: id
            )
          )
        end
      rescue ActiveRecord::RecordInvalid
        return false
      end
    end
    citations
  end

  # TODO: deprecate
  # @return [Scope of matching sources]
  def self.find_for_autocomplete(params)
    term = params['term']
    ending = term + '%'
    wrapped = '%' + term + '%'
    joins(:source).where('sources.cached ILIKE ? OR sources.cached ILIKE ? OR citation_object_type LIKE ?', ending, wrapped, ending).with_project_id(params[:project_id])
  end

  # @return [Boolean]
  #   true if is_original is checked, false if nil/false
  def is_original?
    is_original ? true : false
  end

  # @return [String, nil]
  #    the first integer in the string, as a string
  def first_page
    /(?<i>\d+)/ =~ pages
    i
  end

  # @return [Integer, nil]
  #    if a target document
  def target_document_page
    target_document.try(:pdf_page_for, first_page).try(:first)
  end

  # @return [Document, nil]
  def target_document
    documents.order('documentation.position').first
  end

  protected

  def add_source_to_project
    !!ProjectSource.find_or_create_by(project:, source:)
  end

  def reject_citation_topics(attributed)
    attributes['id'].blank? && attributed['topic_id'].blank? && attributed['topic'].blank? && attributed['topic_attributes'].blank?
  end

  def reject_topic(attributed)
    attributed['name'].blank? || attributed['definition'].blank?
  end

  def update_related_cached_values
    if is_original != @old_is_original || citation_object_id != @old_citation_object_id || source_id != @old_source_id
      if citation_object_type == 'TaxonName'
        citation_object.update_columns(
          cached_author_year: citation_object.get_author_and_year,
          cached_nomenclature_date: citation_object.nomenclature_date)  if citation_object.persisted?
      end
    end
    true
  end

  # TODO: modify for asserted distributions and other origin style relationships
  def prevent_if_required
    unless citation_object && citation_object.respond_to?(:ignore_citation_restriction) && citation_object.ignore_citation_restriction
      if !marked_for_destruction? && !new_record? && citation_object.requires_citation? && citation_object.citations.count == 1
        errors.add(:base, 'at least one citation is required')
        throw :abort
      end
    end
  end

  def set_cached_names_for_taxon_names
    if is_original != @old_is_original || citation_object_id != @old_citation_object_id || source_id != @old_source_id
      if citation_object_type == 'TaxonNameRelationship' && TAXON_NAME_RELATIONSHIP_NAMES_INVALID.include?(citation_object.try(:type_name))
        begin
          TaxonNameRelationship.transaction do
            t = citation_object.subject_taxon_name
            vn = t.get_valid_taxon_name

            t.update_columns(
              cached: t.get_full_name,
              cached_html: t.get_full_name_html,
              cached_valid_taxon_name_id: vn.id)

            # @proceps: This and below is not updating cached names.  Is this required because timing (new dates) may change synonymy?
            t.combination_list_self.each do |c|
              c.update_column(:cached_valid_taxon_name_id, vn.id)
            end

            vn.list_of_invalid_taxon_names.each do |s|
              s.update_column(:cached_valid_taxon_name_id, vn.id)
              s.combination_list_self.each do |c|
                c.update_column(:cached_valid_taxon_name_id, vn.id)
              end
            end
          end
        rescue ActiveRecord::RecordInvalid
          raise
        end
        false
      end
    end
  end

  def sv_page_range
    if pages.blank?
      soft_validations.add(:pages, 'Citation pages are not provided')
    elsif source.pages.present?
      matchdata1 = pages.match(/(\d+) ?[-–] ?(\d+)|(\d+)/)
      if matchdata1
        citMinP = matchdata1[1] ? matchdata1[1].to_i : matchdata1[3].to_i
        citMaxP = matchdata1[2] ? matchdata1[2].to_i : matchdata1[3].to_i
        matchdata = source.pages.match(/(\d+) ?[-–] ?(\d+)|(\d+)/)
        if citMinP && citMaxP && matchdata
          minP = matchdata[1] ? matchdata[1].to_i : matchdata[3].to_i
          maxP = matchdata[2] ? matchdata[2].to_i : matchdata[3].to_i
          minP = 1 if minP == maxP && %w{book booklet manual mastersthesis phdthesis techreport}.include?(source.bibtex_type)
          unless (maxP && minP && minP <= citMinP && maxP >= citMaxP)
            soft_validations.add(:pages, 'Citation is out of the source page range')
          end
        end
      end
    end
  end

end

#is_originalBoolean

Returns is this the first citation in which the data were observed?.

Returns:

  • (Boolean)

    is this the first citation in which the data were observed?



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'app/models/citation.rb', line 29

class Citation < ApplicationRecord

  # Citations do not have Confidence or DataAttribute.

  include Housekeeping
  include Shared::Notes
  include Shared::Tags
  include Shared::IsData
  include Shared::PolymorphicAnnotator
  include SoftValidation

  attr_accessor :no_cached

  polymorphic_annotates('citation_object')

  # belongs_to :source, inverse_of: :origin_citations
  belongs_to :source, inverse_of: :citations

  has_many :citation_topics, inverse_of: :citation, dependent: :destroy
  has_many :topics, through: :citation_topics, inverse_of: :citations
  has_many :documents, through: :source

  validates_presence_of :source

  validates_uniqueness_of :source_id, scope: [:citation_object_id, :citation_object_type, :pages]
  validates_uniqueness_of :is_original, scope: [:citation_object_type, :citation_object_id], message: 'can only be assigned once per object.', allow_nil: true, if: -> { is_original? }

  accepts_nested_attributes_for :citation_topics, allow_destroy: true, reject_if: :reject_citation_topics
  accepts_nested_attributes_for :topics, allow_destroy: true, reject_if: :reject_topic

  before_destroy :prevent_if_required

  after_create :add_source_to_project

  before_save {@old_is_original = is_original_was}
  before_save {@old_citation_object_id = citation_object_id_was}
  before_save {@old_source_id = source_id_was}

  after_save :update_related_cached_values, if: :is_original?

  after_save :set_cached_names_for_taxon_names, unless: -> {self.no_cached}
  after_destroy :set_cached_names_for_taxon_names, unless: -> {self.no_cached}

  soft_validate(:sv_page_range, set: :page_range)

  def self.batch_create(params)
    ids = params[:citation_object_id]
    params.delete(:citation_object_id)

    citations = []
    Citation.transaction do
      begin
        ids.each do |id|
          citations.push Citation.create!(
            params.merge(
              citation_object_id: id
            )
          )
        end
      rescue ActiveRecord::RecordInvalid
        return false
      end
    end
    citations
  end

  # TODO: deprecate
  # @return [Scope of matching sources]
  def self.find_for_autocomplete(params)
    term = params['term']
    ending = term + '%'
    wrapped = '%' + term + '%'
    joins(:source).where('sources.cached ILIKE ? OR sources.cached ILIKE ? OR citation_object_type LIKE ?', ending, wrapped, ending).with_project_id(params[:project_id])
  end

  # @return [Boolean]
  #   true if is_original is checked, false if nil/false
  def is_original?
    is_original ? true : false
  end

  # @return [String, nil]
  #    the first integer in the string, as a string
  def first_page
    /(?<i>\d+)/ =~ pages
    i
  end

  # @return [Integer, nil]
  #    if a target document
  def target_document_page
    target_document.try(:pdf_page_for, first_page).try(:first)
  end

  # @return [Document, nil]
  def target_document
    documents.order('documentation.position').first
  end

  protected

  def add_source_to_project
    !!ProjectSource.find_or_create_by(project:, source:)
  end

  def reject_citation_topics(attributed)
    attributes['id'].blank? && attributed['topic_id'].blank? && attributed['topic'].blank? && attributed['topic_attributes'].blank?
  end

  def reject_topic(attributed)
    attributed['name'].blank? || attributed['definition'].blank?
  end

  def update_related_cached_values
    if is_original != @old_is_original || citation_object_id != @old_citation_object_id || source_id != @old_source_id
      if citation_object_type == 'TaxonName'
        citation_object.update_columns(
          cached_author_year: citation_object.get_author_and_year,
          cached_nomenclature_date: citation_object.nomenclature_date)  if citation_object.persisted?
      end
    end
    true
  end

  # TODO: modify for asserted distributions and other origin style relationships
  def prevent_if_required
    unless citation_object && citation_object.respond_to?(:ignore_citation_restriction) && citation_object.ignore_citation_restriction
      if !marked_for_destruction? && !new_record? && citation_object.requires_citation? && citation_object.citations.count == 1
        errors.add(:base, 'at least one citation is required')
        throw :abort
      end
    end
  end

  def set_cached_names_for_taxon_names
    if is_original != @old_is_original || citation_object_id != @old_citation_object_id || source_id != @old_source_id
      if citation_object_type == 'TaxonNameRelationship' && TAXON_NAME_RELATIONSHIP_NAMES_INVALID.include?(citation_object.try(:type_name))
        begin
          TaxonNameRelationship.transaction do
            t = citation_object.subject_taxon_name
            vn = t.get_valid_taxon_name

            t.update_columns(
              cached: t.get_full_name,
              cached_html: t.get_full_name_html,
              cached_valid_taxon_name_id: vn.id)

            # @proceps: This and below is not updating cached names.  Is this required because timing (new dates) may change synonymy?
            t.combination_list_self.each do |c|
              c.update_column(:cached_valid_taxon_name_id, vn.id)
            end

            vn.list_of_invalid_taxon_names.each do |s|
              s.update_column(:cached_valid_taxon_name_id, vn.id)
              s.combination_list_self.each do |c|
                c.update_column(:cached_valid_taxon_name_id, vn.id)
              end
            end
          end
        rescue ActiveRecord::RecordInvalid
          raise
        end
        false
      end
    end
  end

  def sv_page_range
    if pages.blank?
      soft_validations.add(:pages, 'Citation pages are not provided')
    elsif source.pages.present?
      matchdata1 = pages.match(/(\d+) ?[-–] ?(\d+)|(\d+)/)
      if matchdata1
        citMinP = matchdata1[1] ? matchdata1[1].to_i : matchdata1[3].to_i
        citMaxP = matchdata1[2] ? matchdata1[2].to_i : matchdata1[3].to_i
        matchdata = source.pages.match(/(\d+) ?[-–] ?(\d+)|(\d+)/)
        if citMinP && citMaxP && matchdata
          minP = matchdata[1] ? matchdata[1].to_i : matchdata[3].to_i
          maxP = matchdata[2] ? matchdata[2].to_i : matchdata[3].to_i
          minP = 1 if minP == maxP && %w{book booklet manual mastersthesis phdthesis techreport}.include?(source.bibtex_type)
          unless (maxP && minP && minP <= citMinP && maxP >= citMaxP)
            soft_validations.add(:pages, 'Citation is out of the source page range')
          end
        end
      end
    end
  end

end

#no_cachedObject

Returns the value of attribute no_cached.



40
41
42
# File 'app/models/citation.rb', line 40

def no_cached
  @no_cached
end

#pagesString?

Returns a specific location/localization for the data in the Source, if you lead with an integer separated by space or punctation that integer will be returned as the “first” page and usable in direct linkouts to Documents if available.

Returns:

  • (String, nil)

    a specific location/localization for the data in the Source, if you lead with an integer separated by space or punctation that integer will be returned as the “first” page and usable in direct linkouts to Documents if available



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'app/models/citation.rb', line 29

class Citation < ApplicationRecord

  # Citations do not have Confidence or DataAttribute.

  include Housekeeping
  include Shared::Notes
  include Shared::Tags
  include Shared::IsData
  include Shared::PolymorphicAnnotator
  include SoftValidation

  attr_accessor :no_cached

  polymorphic_annotates('citation_object')

  # belongs_to :source, inverse_of: :origin_citations
  belongs_to :source, inverse_of: :citations

  has_many :citation_topics, inverse_of: :citation, dependent: :destroy
  has_many :topics, through: :citation_topics, inverse_of: :citations
  has_many :documents, through: :source

  validates_presence_of :source

  validates_uniqueness_of :source_id, scope: [:citation_object_id, :citation_object_type, :pages]
  validates_uniqueness_of :is_original, scope: [:citation_object_type, :citation_object_id], message: 'can only be assigned once per object.', allow_nil: true, if: -> { is_original? }

  accepts_nested_attributes_for :citation_topics, allow_destroy: true, reject_if: :reject_citation_topics
  accepts_nested_attributes_for :topics, allow_destroy: true, reject_if: :reject_topic

  before_destroy :prevent_if_required

  after_create :add_source_to_project

  before_save {@old_is_original = is_original_was}
  before_save {@old_citation_object_id = citation_object_id_was}
  before_save {@old_source_id = source_id_was}

  after_save :update_related_cached_values, if: :is_original?

  after_save :set_cached_names_for_taxon_names, unless: -> {self.no_cached}
  after_destroy :set_cached_names_for_taxon_names, unless: -> {self.no_cached}

  soft_validate(:sv_page_range, set: :page_range)

  def self.batch_create(params)
    ids = params[:citation_object_id]
    params.delete(:citation_object_id)

    citations = []
    Citation.transaction do
      begin
        ids.each do |id|
          citations.push Citation.create!(
            params.merge(
              citation_object_id: id
            )
          )
        end
      rescue ActiveRecord::RecordInvalid
        return false
      end
    end
    citations
  end

  # TODO: deprecate
  # @return [Scope of matching sources]
  def self.find_for_autocomplete(params)
    term = params['term']
    ending = term + '%'
    wrapped = '%' + term + '%'
    joins(:source).where('sources.cached ILIKE ? OR sources.cached ILIKE ? OR citation_object_type LIKE ?', ending, wrapped, ending).with_project_id(params[:project_id])
  end

  # @return [Boolean]
  #   true if is_original is checked, false if nil/false
  def is_original?
    is_original ? true : false
  end

  # @return [String, nil]
  #    the first integer in the string, as a string
  def first_page
    /(?<i>\d+)/ =~ pages
    i
  end

  # @return [Integer, nil]
  #    if a target document
  def target_document_page
    target_document.try(:pdf_page_for, first_page).try(:first)
  end

  # @return [Document, nil]
  def target_document
    documents.order('documentation.position').first
  end

  protected

  def add_source_to_project
    !!ProjectSource.find_or_create_by(project:, source:)
  end

  def reject_citation_topics(attributed)
    attributes['id'].blank? && attributed['topic_id'].blank? && attributed['topic'].blank? && attributed['topic_attributes'].blank?
  end

  def reject_topic(attributed)
    attributed['name'].blank? || attributed['definition'].blank?
  end

  def update_related_cached_values
    if is_original != @old_is_original || citation_object_id != @old_citation_object_id || source_id != @old_source_id
      if citation_object_type == 'TaxonName'
        citation_object.update_columns(
          cached_author_year: citation_object.get_author_and_year,
          cached_nomenclature_date: citation_object.nomenclature_date)  if citation_object.persisted?
      end
    end
    true
  end

  # TODO: modify for asserted distributions and other origin style relationships
  def prevent_if_required
    unless citation_object && citation_object.respond_to?(:ignore_citation_restriction) && citation_object.ignore_citation_restriction
      if !marked_for_destruction? && !new_record? && citation_object.requires_citation? && citation_object.citations.count == 1
        errors.add(:base, 'at least one citation is required')
        throw :abort
      end
    end
  end

  def set_cached_names_for_taxon_names
    if is_original != @old_is_original || citation_object_id != @old_citation_object_id || source_id != @old_source_id
      if citation_object_type == 'TaxonNameRelationship' && TAXON_NAME_RELATIONSHIP_NAMES_INVALID.include?(citation_object.try(:type_name))
        begin
          TaxonNameRelationship.transaction do
            t = citation_object.subject_taxon_name
            vn = t.get_valid_taxon_name

            t.update_columns(
              cached: t.get_full_name,
              cached_html: t.get_full_name_html,
              cached_valid_taxon_name_id: vn.id)

            # @proceps: This and below is not updating cached names.  Is this required because timing (new dates) may change synonymy?
            t.combination_list_self.each do |c|
              c.update_column(:cached_valid_taxon_name_id, vn.id)
            end

            vn.list_of_invalid_taxon_names.each do |s|
              s.update_column(:cached_valid_taxon_name_id, vn.id)
              s.combination_list_self.each do |c|
                c.update_column(:cached_valid_taxon_name_id, vn.id)
              end
            end
          end
        rescue ActiveRecord::RecordInvalid
          raise
        end
        false
      end
    end
  end

  def sv_page_range
    if pages.blank?
      soft_validations.add(:pages, 'Citation pages are not provided')
    elsif source.pages.present?
      matchdata1 = pages.match(/(\d+) ?[-–] ?(\d+)|(\d+)/)
      if matchdata1
        citMinP = matchdata1[1] ? matchdata1[1].to_i : matchdata1[3].to_i
        citMaxP = matchdata1[2] ? matchdata1[2].to_i : matchdata1[3].to_i
        matchdata = source.pages.match(/(\d+) ?[-–] ?(\d+)|(\d+)/)
        if citMinP && citMaxP && matchdata
          minP = matchdata[1] ? matchdata[1].to_i : matchdata[3].to_i
          maxP = matchdata[2] ? matchdata[2].to_i : matchdata[3].to_i
          minP = 1 if minP == maxP && %w{book booklet manual mastersthesis phdthesis techreport}.include?(source.bibtex_type)
          unless (maxP && minP && minP <= citMinP && maxP >= citMaxP)
            soft_validations.add(:pages, 'Citation is out of the source page range')
          end
        end
      end
    end
  end

end

#project_idInteger

the project ID

Returns:

  • (Integer)


29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'app/models/citation.rb', line 29

class Citation < ApplicationRecord

  # Citations do not have Confidence or DataAttribute.

  include Housekeeping
  include Shared::Notes
  include Shared::Tags
  include Shared::IsData
  include Shared::PolymorphicAnnotator
  include SoftValidation

  attr_accessor :no_cached

  polymorphic_annotates('citation_object')

  # belongs_to :source, inverse_of: :origin_citations
  belongs_to :source, inverse_of: :citations

  has_many :citation_topics, inverse_of: :citation, dependent: :destroy
  has_many :topics, through: :citation_topics, inverse_of: :citations
  has_many :documents, through: :source

  validates_presence_of :source

  validates_uniqueness_of :source_id, scope: [:citation_object_id, :citation_object_type, :pages]
  validates_uniqueness_of :is_original, scope: [:citation_object_type, :citation_object_id], message: 'can only be assigned once per object.', allow_nil: true, if: -> { is_original? }

  accepts_nested_attributes_for :citation_topics, allow_destroy: true, reject_if: :reject_citation_topics
  accepts_nested_attributes_for :topics, allow_destroy: true, reject_if: :reject_topic

  before_destroy :prevent_if_required

  after_create :add_source_to_project

  before_save {@old_is_original = is_original_was}
  before_save {@old_citation_object_id = citation_object_id_was}
  before_save {@old_source_id = source_id_was}

  after_save :update_related_cached_values, if: :is_original?

  after_save :set_cached_names_for_taxon_names, unless: -> {self.no_cached}
  after_destroy :set_cached_names_for_taxon_names, unless: -> {self.no_cached}

  soft_validate(:sv_page_range, set: :page_range)

  def self.batch_create(params)
    ids = params[:citation_object_id]
    params.delete(:citation_object_id)

    citations = []
    Citation.transaction do
      begin
        ids.each do |id|
          citations.push Citation.create!(
            params.merge(
              citation_object_id: id
            )
          )
        end
      rescue ActiveRecord::RecordInvalid
        return false
      end
    end
    citations
  end

  # TODO: deprecate
  # @return [Scope of matching sources]
  def self.find_for_autocomplete(params)
    term = params['term']
    ending = term + '%'
    wrapped = '%' + term + '%'
    joins(:source).where('sources.cached ILIKE ? OR sources.cached ILIKE ? OR citation_object_type LIKE ?', ending, wrapped, ending).with_project_id(params[:project_id])
  end

  # @return [Boolean]
  #   true if is_original is checked, false if nil/false
  def is_original?
    is_original ? true : false
  end

  # @return [String, nil]
  #    the first integer in the string, as a string
  def first_page
    /(?<i>\d+)/ =~ pages
    i
  end

  # @return [Integer, nil]
  #    if a target document
  def target_document_page
    target_document.try(:pdf_page_for, first_page).try(:first)
  end

  # @return [Document, nil]
  def target_document
    documents.order('documentation.position').first
  end

  protected

  def add_source_to_project
    !!ProjectSource.find_or_create_by(project:, source:)
  end

  def reject_citation_topics(attributed)
    attributes['id'].blank? && attributed['topic_id'].blank? && attributed['topic'].blank? && attributed['topic_attributes'].blank?
  end

  def reject_topic(attributed)
    attributed['name'].blank? || attributed['definition'].blank?
  end

  def update_related_cached_values
    if is_original != @old_is_original || citation_object_id != @old_citation_object_id || source_id != @old_source_id
      if citation_object_type == 'TaxonName'
        citation_object.update_columns(
          cached_author_year: citation_object.get_author_and_year,
          cached_nomenclature_date: citation_object.nomenclature_date)  if citation_object.persisted?
      end
    end
    true
  end

  # TODO: modify for asserted distributions and other origin style relationships
  def prevent_if_required
    unless citation_object && citation_object.respond_to?(:ignore_citation_restriction) && citation_object.ignore_citation_restriction
      if !marked_for_destruction? && !new_record? && citation_object.requires_citation? && citation_object.citations.count == 1
        errors.add(:base, 'at least one citation is required')
        throw :abort
      end
    end
  end

  def set_cached_names_for_taxon_names
    if is_original != @old_is_original || citation_object_id != @old_citation_object_id || source_id != @old_source_id
      if citation_object_type == 'TaxonNameRelationship' && TAXON_NAME_RELATIONSHIP_NAMES_INVALID.include?(citation_object.try(:type_name))
        begin
          TaxonNameRelationship.transaction do
            t = citation_object.subject_taxon_name
            vn = t.get_valid_taxon_name

            t.update_columns(
              cached: t.get_full_name,
              cached_html: t.get_full_name_html,
              cached_valid_taxon_name_id: vn.id)

            # @proceps: This and below is not updating cached names.  Is this required because timing (new dates) may change synonymy?
            t.combination_list_self.each do |c|
              c.update_column(:cached_valid_taxon_name_id, vn.id)
            end

            vn.list_of_invalid_taxon_names.each do |s|
              s.update_column(:cached_valid_taxon_name_id, vn.id)
              s.combination_list_self.each do |c|
                c.update_column(:cached_valid_taxon_name_id, vn.id)
              end
            end
          end
        rescue ActiveRecord::RecordInvalid
          raise
        end
        false
      end
    end
  end

  def sv_page_range
    if pages.blank?
      soft_validations.add(:pages, 'Citation pages are not provided')
    elsif source.pages.present?
      matchdata1 = pages.match(/(\d+) ?[-–] ?(\d+)|(\d+)/)
      if matchdata1
        citMinP = matchdata1[1] ? matchdata1[1].to_i : matchdata1[3].to_i
        citMaxP = matchdata1[2] ? matchdata1[2].to_i : matchdata1[3].to_i
        matchdata = source.pages.match(/(\d+) ?[-–] ?(\d+)|(\d+)/)
        if citMinP && citMaxP && matchdata
          minP = matchdata[1] ? matchdata[1].to_i : matchdata[3].to_i
          maxP = matchdata[2] ? matchdata[2].to_i : matchdata[3].to_i
          minP = 1 if minP == maxP && %w{book booklet manual mastersthesis phdthesis techreport}.include?(source.bibtex_type)
          unless (maxP && minP && minP <= citMinP && maxP >= citMaxP)
            soft_validations.add(:pages, 'Citation is out of the source page range')
          end
        end
      end
    end
  end

end

#source_idInteger

the source ID

Returns:

  • (Integer)


29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'app/models/citation.rb', line 29

class Citation < ApplicationRecord

  # Citations do not have Confidence or DataAttribute.

  include Housekeeping
  include Shared::Notes
  include Shared::Tags
  include Shared::IsData
  include Shared::PolymorphicAnnotator
  include SoftValidation

  attr_accessor :no_cached

  polymorphic_annotates('citation_object')

  # belongs_to :source, inverse_of: :origin_citations
  belongs_to :source, inverse_of: :citations

  has_many :citation_topics, inverse_of: :citation, dependent: :destroy
  has_many :topics, through: :citation_topics, inverse_of: :citations
  has_many :documents, through: :source

  validates_presence_of :source

  validates_uniqueness_of :source_id, scope: [:citation_object_id, :citation_object_type, :pages]
  validates_uniqueness_of :is_original, scope: [:citation_object_type, :citation_object_id], message: 'can only be assigned once per object.', allow_nil: true, if: -> { is_original? }

  accepts_nested_attributes_for :citation_topics, allow_destroy: true, reject_if: :reject_citation_topics
  accepts_nested_attributes_for :topics, allow_destroy: true, reject_if: :reject_topic

  before_destroy :prevent_if_required

  after_create :add_source_to_project

  before_save {@old_is_original = is_original_was}
  before_save {@old_citation_object_id = citation_object_id_was}
  before_save {@old_source_id = source_id_was}

  after_save :update_related_cached_values, if: :is_original?

  after_save :set_cached_names_for_taxon_names, unless: -> {self.no_cached}
  after_destroy :set_cached_names_for_taxon_names, unless: -> {self.no_cached}

  soft_validate(:sv_page_range, set: :page_range)

  def self.batch_create(params)
    ids = params[:citation_object_id]
    params.delete(:citation_object_id)

    citations = []
    Citation.transaction do
      begin
        ids.each do |id|
          citations.push Citation.create!(
            params.merge(
              citation_object_id: id
            )
          )
        end
      rescue ActiveRecord::RecordInvalid
        return false
      end
    end
    citations
  end

  # TODO: deprecate
  # @return [Scope of matching sources]
  def self.find_for_autocomplete(params)
    term = params['term']
    ending = term + '%'
    wrapped = '%' + term + '%'
    joins(:source).where('sources.cached ILIKE ? OR sources.cached ILIKE ? OR citation_object_type LIKE ?', ending, wrapped, ending).with_project_id(params[:project_id])
  end

  # @return [Boolean]
  #   true if is_original is checked, false if nil/false
  def is_original?
    is_original ? true : false
  end

  # @return [String, nil]
  #    the first integer in the string, as a string
  def first_page
    /(?<i>\d+)/ =~ pages
    i
  end

  # @return [Integer, nil]
  #    if a target document
  def target_document_page
    target_document.try(:pdf_page_for, first_page).try(:first)
  end

  # @return [Document, nil]
  def target_document
    documents.order('documentation.position').first
  end

  protected

  def add_source_to_project
    !!ProjectSource.find_or_create_by(project:, source:)
  end

  def reject_citation_topics(attributed)
    attributes['id'].blank? && attributed['topic_id'].blank? && attributed['topic'].blank? && attributed['topic_attributes'].blank?
  end

  def reject_topic(attributed)
    attributed['name'].blank? || attributed['definition'].blank?
  end

  def update_related_cached_values
    if is_original != @old_is_original || citation_object_id != @old_citation_object_id || source_id != @old_source_id
      if citation_object_type == 'TaxonName'
        citation_object.update_columns(
          cached_author_year: citation_object.get_author_and_year,
          cached_nomenclature_date: citation_object.nomenclature_date)  if citation_object.persisted?
      end
    end
    true
  end

  # TODO: modify for asserted distributions and other origin style relationships
  def prevent_if_required
    unless citation_object && citation_object.respond_to?(:ignore_citation_restriction) && citation_object.ignore_citation_restriction
      if !marked_for_destruction? && !new_record? && citation_object.requires_citation? && citation_object.citations.count == 1
        errors.add(:base, 'at least one citation is required')
        throw :abort
      end
    end
  end

  def set_cached_names_for_taxon_names
    if is_original != @old_is_original || citation_object_id != @old_citation_object_id || source_id != @old_source_id
      if citation_object_type == 'TaxonNameRelationship' && TAXON_NAME_RELATIONSHIP_NAMES_INVALID.include?(citation_object.try(:type_name))
        begin
          TaxonNameRelationship.transaction do
            t = citation_object.subject_taxon_name
            vn = t.get_valid_taxon_name

            t.update_columns(
              cached: t.get_full_name,
              cached_html: t.get_full_name_html,
              cached_valid_taxon_name_id: vn.id)

            # @proceps: This and below is not updating cached names.  Is this required because timing (new dates) may change synonymy?
            t.combination_list_self.each do |c|
              c.update_column(:cached_valid_taxon_name_id, vn.id)
            end

            vn.list_of_invalid_taxon_names.each do |s|
              s.update_column(:cached_valid_taxon_name_id, vn.id)
              s.combination_list_self.each do |c|
                c.update_column(:cached_valid_taxon_name_id, vn.id)
              end
            end
          end
        rescue ActiveRecord::RecordInvalid
          raise
        end
        false
      end
    end
  end

  def sv_page_range
    if pages.blank?
      soft_validations.add(:pages, 'Citation pages are not provided')
    elsif source.pages.present?
      matchdata1 = pages.match(/(\d+) ?[-–] ?(\d+)|(\d+)/)
      if matchdata1
        citMinP = matchdata1[1] ? matchdata1[1].to_i : matchdata1[3].to_i
        citMaxP = matchdata1[2] ? matchdata1[2].to_i : matchdata1[3].to_i
        matchdata = source.pages.match(/(\d+) ?[-–] ?(\d+)|(\d+)/)
        if citMinP && citMaxP && matchdata
          minP = matchdata[1] ? matchdata[1].to_i : matchdata[3].to_i
          maxP = matchdata[2] ? matchdata[2].to_i : matchdata[3].to_i
          minP = 1 if minP == maxP && %w{book booklet manual mastersthesis phdthesis techreport}.include?(source.bibtex_type)
          unless (maxP && minP && minP <= citMinP && maxP >= citMaxP)
            soft_validations.add(:pages, 'Citation is out of the source page range')
          end
        end
      end
    end
  end

end

Class Method Details

.batch_create(params) ⇒ Object



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'app/models/citation.rb', line 74

def self.batch_create(params)
  ids = params[:citation_object_id]
  params.delete(:citation_object_id)

  citations = []
  Citation.transaction do
    begin
      ids.each do |id|
        citations.push Citation.create!(
          params.merge(
            citation_object_id: id
          )
        )
      end
    rescue ActiveRecord::RecordInvalid
      return false
    end
  end
  citations
end

.find_for_autocomplete(params) ⇒ Scope of matching sources

TODO: deprecate

Returns:

  • (Scope of matching sources)


97
98
99
100
101
102
# File 'app/models/citation.rb', line 97

def self.find_for_autocomplete(params)
  term = params['term']
  ending = term + '%'
  wrapped = '%' + term + '%'
  joins(:source).where('sources.cached ILIKE ? OR sources.cached ILIKE ? OR citation_object_type LIKE ?', ending, wrapped, ending).with_project_id(params[:project_id])
end

Instance Method Details

#add_source_to_projectObject (protected)



130
131
132
# File 'app/models/citation.rb', line 130

def add_source_to_project
  !!ProjectSource.find_or_create_by(project:, source:)
end

#first_pageString?

Returns the first integer in the string, as a string.

Returns:

  • (String, nil)

    the first integer in the string, as a string



112
113
114
115
# File 'app/models/citation.rb', line 112

def first_page
  /(?<i>\d+)/ =~ pages
  i
end

#is_original?Boolean

Returns true if is_original is checked, false if nil/false.

Returns:

  • (Boolean)

    true if is_original is checked, false if nil/false



106
107
108
# File 'app/models/citation.rb', line 106

def is_original?
  is_original ? true : false
end

#prevent_if_requiredObject (protected)

TODO: modify for asserted distributions and other origin style relationships



154
155
156
157
158
159
160
161
# File 'app/models/citation.rb', line 154

def prevent_if_required
  unless citation_object && citation_object.respond_to?(:ignore_citation_restriction) && citation_object.ignore_citation_restriction
    if !marked_for_destruction? && !new_record? && citation_object.requires_citation? && citation_object.citations.count == 1
      errors.add(:base, 'at least one citation is required')
      throw :abort
    end
  end
end

#reject_citation_topics(attributed) ⇒ Object (protected)



134
135
136
# File 'app/models/citation.rb', line 134

def reject_citation_topics(attributed)
  attributes['id'].blank? && attributed['topic_id'].blank? && attributed['topic'].blank? && attributed['topic_attributes'].blank?
end

#reject_topic(attributed) ⇒ Object (protected)



138
139
140
# File 'app/models/citation.rb', line 138

def reject_topic(attributed)
  attributed['name'].blank? || attributed['definition'].blank?
end

#set_cached_names_for_taxon_namesObject (protected)



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'app/models/citation.rb', line 163

def set_cached_names_for_taxon_names
  if is_original != @old_is_original || citation_object_id != @old_citation_object_id || source_id != @old_source_id
    if citation_object_type == 'TaxonNameRelationship' && TAXON_NAME_RELATIONSHIP_NAMES_INVALID.include?(citation_object.try(:type_name))
      begin
        TaxonNameRelationship.transaction do
          t = citation_object.subject_taxon_name
          vn = t.get_valid_taxon_name

          t.update_columns(
            cached: t.get_full_name,
            cached_html: t.get_full_name_html,
            cached_valid_taxon_name_id: vn.id)

          # @proceps: This and below is not updating cached names.  Is this required because timing (new dates) may change synonymy?
          t.combination_list_self.each do |c|
            c.update_column(:cached_valid_taxon_name_id, vn.id)
          end

          vn.list_of_invalid_taxon_names.each do |s|
            s.update_column(:cached_valid_taxon_name_id, vn.id)
            s.combination_list_self.each do |c|
              c.update_column(:cached_valid_taxon_name_id, vn.id)
            end
          end
        end
      rescue ActiveRecord::RecordInvalid
        raise
      end
      false
    end
  end
end

#sv_page_rangeObject (protected)



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'app/models/citation.rb', line 196

def sv_page_range
  if pages.blank?
    soft_validations.add(:pages, 'Citation pages are not provided')
  elsif source.pages.present?
    matchdata1 = pages.match(/(\d+) ?[-–] ?(\d+)|(\d+)/)
    if matchdata1
      citMinP = matchdata1[1] ? matchdata1[1].to_i : matchdata1[3].to_i
      citMaxP = matchdata1[2] ? matchdata1[2].to_i : matchdata1[3].to_i
      matchdata = source.pages.match(/(\d+) ?[-–] ?(\d+)|(\d+)/)
      if citMinP && citMaxP && matchdata
        minP = matchdata[1] ? matchdata[1].to_i : matchdata[3].to_i
        maxP = matchdata[2] ? matchdata[2].to_i : matchdata[3].to_i
        minP = 1 if minP == maxP && %w{book booklet manual mastersthesis phdthesis techreport}.include?(source.bibtex_type)
        unless (maxP && minP && minP <= citMinP && maxP >= citMaxP)
          soft_validations.add(:pages, 'Citation is out of the source page range')
        end
      end
    end
  end
end

#target_documentDocument?

Returns:



124
125
126
# File 'app/models/citation.rb', line 124

def target_document
  documents.order('documentation.position').first
end

#target_document_pageInteger?

Returns if a target document.

Returns:

  • (Integer, nil)

    if a target document



119
120
121
# File 'app/models/citation.rb', line 119

def target_document_page
  target_document.try(:pdf_page_for, first_page).try(:first)
end


142
143
144
145
146
147
148
149
150
151
# File 'app/models/citation.rb', line 142

def update_related_cached_values
  if is_original != @old_is_original || citation_object_id != @old_citation_object_id || source_id != @old_source_id
    if citation_object_type == 'TaxonName'
      citation_object.update_columns(
        cached_author_year: citation_object.get_author_and_year,
        cached_nomenclature_date: citation_object.nomenclature_date)  if citation_object.persisted?
    end
  end
  true
end