Class: Source

Inherits:
ApplicationRecord show all
Includes:
Housekeeping::Timestamps, Housekeeping::Users, Shared::AlternateValues, Shared::DataAttributes, Shared::Documentation, Shared::HasPapertrail, Shared::HasRoles, Shared::Identifiers, Shared::IsData, Shared::Notes, Shared::SharedAcrossProjects, Shared::Tags, SoftValidation
Defined in:
app/models/source.rb

Overview

A Source is the metadata that identifies the origin of some information/data.

The primary purpose of Source metadata is to allow the user to find the source, that's all.

See en.wikipedia.org/wiki/BibTeX for a definition of attributes, in nearly all cases they are 1:1 with the TW model. We use this github.com/inukshuk/bibtex-ruby awesomeness. See github.com/inukshuk/bibtex-ruby/tree/master/lib/bibtex/entry, in particular rdf_converter.rb for the types of field managed.

Direct Known Subclasses

Bibtex, Human, Verbatim

Defined Under Namespace

Classes: Bibtex, Human, Verbatim

Constant Summary collapse

ALTERNATE_VALUES_FOR =
[:address, :annote, :booktitle, :edition, :editor, :institution, :journal, :note, :organization,
:publisher, :school, :title, :doi, :abstract, :language, :translator, :author, :url].freeze

Constants included from SoftValidation

SoftValidation::ANCESTORS_WITH_SOFT_VALIDATIONS

Instance Attribute Summary collapse

Attributes included from Housekeeping::Users

#by

Class Method Summary collapse

Instance Method Summary collapse

Methods included from SoftValidation

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

Methods included from Housekeeping::Timestamps

#data_breakdown_for_chartkick_recent

Methods included from Housekeeping::Users

#set_created_by_id, #set_updated_by_id

Methods inherited from ApplicationRecord

transaction_with_retry

Instance Attribute Details

#abstractString

Returns:


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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'app/models/source.rb', line 188

class Source < ApplicationRecord
  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::AlternateValues
  include Shared::DataAttributes
  include Shared::Identifiers
  include Shared::Notes
  include Shared::SharedAcrossProjects
  include Shared::Tags
  include Shared::Documentation
  include Shared::HasRoles
  include Shared::IsData
  include Shared::HasPapertrail
  include SoftValidation

  ignore_whitespace_on(:verbatim_contents)

  ALTERNATE_VALUES_FOR = [:address, :annote, :booktitle, :edition, :editor, :institution, :journal, :note, :organization,
                          :publisher, :school, :title, :doi, :abstract, :language, :translator, :author, :url].freeze

  # @return [Boolean]
  #  When true, cached values are not built
  attr_accessor :no_year_suffix_validation

  # Keep this order for citations/topics
  has_many :citations, inverse_of: :source, dependent: :restrict_with_error
  has_many :citation_topics, through: :citations, inverse_of: :source
  has_many :topics, through: :citation_topics, inverse_of: :sources

  # !! must be below has_many :citations
  has_many :asserted_distributions, through: :citations, source: :citation_object, source_type: 'AssertedDistribution'

  has_many :project_sources, dependent: :destroy
  has_many :projects, through: :project_sources

  after_save :set_cached

  validates_presence_of :type
  validates :type, inclusion: {in: ['Source::Bibtex', 'Source::Human', 'Source::Verbatim']} # TODO: not needed
  validate :validate_year_suffix, unless: -> { self.no_year_suffix_validation || (self.type != 'Source::Bibtex') }

  accepts_nested_attributes_for :project_sources, reject_if: :reject_project_sources

  # Redirect type here
  # @param [String] file
  # @return [[Array, message]]
  #   TODO: return a more informative response?
  def self.batch_preview(file)
    begin
      bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
      sources = []
      bibliography.each do |record|
        a = Source::Bibtex.new_from_bibtex(record)#        a.soft_validate() # why?

        sources.push(a)
      end
      return sources, nil
    rescue BibTeX::ParseError => e
      return [], e.message
    rescue
      raise
    end
  end

  # @param [String] file
  # @return [Array, Boolean]
  def self.batch_create(file)
    sources = []
    valid = 0
    begin
      # error_msg = []
      Source.transaction do
        bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
        bibliography.each do |record|
          a = Source::Bibtex.new_from_bibtex(record)
          if a.valid?
            if a.save
              valid += 1
            end#            a.soft_validate()

          else
            # error_msg = a.errors.messages.to_s
          end
          sources.push(a)
        end
      end
    rescue
      return false
    end
    return {records: sources, count: valid}
  end

  # @param used_on [String] a model name 
  # @return [Scope]
  #    the max 10 most recently used (1 week, could parameterize) TaxonName, as used 
  def self.used_recently(used_on = 'TaxonName')
    t = Citation.arel_table
    p = Source.arel_table

    # i is a select manager
    i = t.project(t['source_id'], t['created_at']).from(t)
      .where(t['created_at'].gt(1.weeks.ago))
      .where(t['citation_object_type'].eq(used_on))
      .order(t['created_at'])
      .take(10)
      .distinct

    # z is a table alias
    z = i.as('recent_t')

    Source.joins(
      Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(z['source_id'].eq(p['id'])))
    )
  end

  # @params target [String] a citable model name
  # @return [Hash] sources optimized for user selection
  def self.select_optimized(user_id, project_id, target = 'TaxonName')
    h = {
      quick: [],
      pinboard: Source.pinned_by(user_id).where(pinboard_items: {project_id: project_id}).to_a
    }

    h[:recent] = (
      Source.joins(:citations)
      .where( citations: { project_id: project_id, updated_by_id: user_id } )
      .used_recently(target)
      .limit(5).distinct.to_a +
    Source.where(created_by_id: user_id, updated_at: 2.hours.ago..Time.now )
      .order('created_at DESC')
      .limit(5).to_a
    ).uniq

    h[:recent] ||= []

    h[:quick] = ( Source.pinned_by(user_id).pinboard_inserted.where(pinboard_items: {project_id: project_id}).to_a + h[:recent][0..3]).uniq
    h
  end

  # @return [Array]
  #    objects this source is linked to through citations
  def cited_objects
    self.citations.collect { |t| t.citation_object }
  end

  # @return [Boolean]
  def is_bibtex?
    type == 'Source::Bibtex'
  end

  # @return [Boolean]
  def is_in_project?(project_id)
    projects.where(id: project_id).any?
  end

  # @return [Source, false]
  def clone
    s = dup
    m = "[CLONE of #{id}] "
    begin
      Source.transaction do |t|
        roles.each do |r|
          s.roles << Role.new(person: r.person, type: r.type, position: r.position )
        end

        case type
        when 'Source::Verbatim'
          s.verbatim = m + verbatim
        when 'Source::Bibtex'
          s.title = m + title
        end

        s.save!
      end
    rescue ActiveRecord::RecordInvalid
    end
    s
  end

  protected

  # Defined in subclasses
  # @return [Nil]
  def set_cached
  end

  # @param [Hash] attributed
  # @return [Boolean]
  def reject_project_sources(attributed)
    return true if attributed['project_id'].blank?
    return true if ProjectSource.where(project_id: attributed['project_id'], source_id: id).any?
  end

  def validate_year_suffix
      a = get_author 
    unless year_suffix.blank? || year.blank? || a.blank?
      if new_record?
        s = Source.where(author: a, year: year, year_suffix: year_suffix).first
      else
        s = Source.where(author: a, year: year, year_suffix: year_suffix).not_self(self).first
      end
      errors.add(:year_suffix, " '#{year_suffix}' is already used for #{a} #{year}") unless s.nil?
    end
  end

end

#addressString

Returns:


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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'app/models/source.rb', line 188

class Source < ApplicationRecord
  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::AlternateValues
  include Shared::DataAttributes
  include Shared::Identifiers
  include Shared::Notes
  include Shared::SharedAcrossProjects
  include Shared::Tags
  include Shared::Documentation
  include Shared::HasRoles
  include Shared::IsData
  include Shared::HasPapertrail
  include SoftValidation

  ignore_whitespace_on(:verbatim_contents)

  ALTERNATE_VALUES_FOR = [:address, :annote, :booktitle, :edition, :editor, :institution, :journal, :note, :organization,
                          :publisher, :school, :title, :doi, :abstract, :language, :translator, :author, :url].freeze

  # @return [Boolean]
  #  When true, cached values are not built
  attr_accessor :no_year_suffix_validation

  # Keep this order for citations/topics
  has_many :citations, inverse_of: :source, dependent: :restrict_with_error
  has_many :citation_topics, through: :citations, inverse_of: :source
  has_many :topics, through: :citation_topics, inverse_of: :sources

  # !! must be below has_many :citations
  has_many :asserted_distributions, through: :citations, source: :citation_object, source_type: 'AssertedDistribution'

  has_many :project_sources, dependent: :destroy
  has_many :projects, through: :project_sources

  after_save :set_cached

  validates_presence_of :type
  validates :type, inclusion: {in: ['Source::Bibtex', 'Source::Human', 'Source::Verbatim']} # TODO: not needed
  validate :validate_year_suffix, unless: -> { self.no_year_suffix_validation || (self.type != 'Source::Bibtex') }

  accepts_nested_attributes_for :project_sources, reject_if: :reject_project_sources

  # Redirect type here
  # @param [String] file
  # @return [[Array, message]]
  #   TODO: return a more informative response?
  def self.batch_preview(file)
    begin
      bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
      sources = []
      bibliography.each do |record|
        a = Source::Bibtex.new_from_bibtex(record)#        a.soft_validate() # why?

        sources.push(a)
      end
      return sources, nil
    rescue BibTeX::ParseError => e
      return [], e.message
    rescue
      raise
    end
  end

  # @param [String] file
  # @return [Array, Boolean]
  def self.batch_create(file)
    sources = []
    valid = 0
    begin
      # error_msg = []
      Source.transaction do
        bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
        bibliography.each do |record|
          a = Source::Bibtex.new_from_bibtex(record)
          if a.valid?
            if a.save
              valid += 1
            end#            a.soft_validate()

          else
            # error_msg = a.errors.messages.to_s
          end
          sources.push(a)
        end
      end
    rescue
      return false
    end
    return {records: sources, count: valid}
  end

  # @param used_on [String] a model name 
  # @return [Scope]
  #    the max 10 most recently used (1 week, could parameterize) TaxonName, as used 
  def self.used_recently(used_on = 'TaxonName')
    t = Citation.arel_table
    p = Source.arel_table

    # i is a select manager
    i = t.project(t['source_id'], t['created_at']).from(t)
      .where(t['created_at'].gt(1.weeks.ago))
      .where(t['citation_object_type'].eq(used_on))
      .order(t['created_at'])
      .take(10)
      .distinct

    # z is a table alias
    z = i.as('recent_t')

    Source.joins(
      Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(z['source_id'].eq(p['id'])))
    )
  end

  # @params target [String] a citable model name
  # @return [Hash] sources optimized for user selection
  def self.select_optimized(user_id, project_id, target = 'TaxonName')
    h = {
      quick: [],
      pinboard: Source.pinned_by(user_id).where(pinboard_items: {project_id: project_id}).to_a
    }

    h[:recent] = (
      Source.joins(:citations)
      .where( citations: { project_id: project_id, updated_by_id: user_id } )
      .used_recently(target)
      .limit(5).distinct.to_a +
    Source.where(created_by_id: user_id, updated_at: 2.hours.ago..Time.now )
      .order('created_at DESC')
      .limit(5).to_a
    ).uniq

    h[:recent] ||= []

    h[:quick] = ( Source.pinned_by(user_id).pinboard_inserted.where(pinboard_items: {project_id: project_id}).to_a + h[:recent][0..3]).uniq
    h
  end

  # @return [Array]
  #    objects this source is linked to through citations
  def cited_objects
    self.citations.collect { |t| t.citation_object }
  end

  # @return [Boolean]
  def is_bibtex?
    type == 'Source::Bibtex'
  end

  # @return [Boolean]
  def is_in_project?(project_id)
    projects.where(id: project_id).any?
  end

  # @return [Source, false]
  def clone
    s = dup
    m = "[CLONE of #{id}] "
    begin
      Source.transaction do |t|
        roles.each do |r|
          s.roles << Role.new(person: r.person, type: r.type, position: r.position )
        end

        case type
        when 'Source::Verbatim'
          s.verbatim = m + verbatim
        when 'Source::Bibtex'
          s.title = m + title
        end

        s.save!
      end
    rescue ActiveRecord::RecordInvalid
    end
    s
  end

  protected

  # Defined in subclasses
  # @return [Nil]
  def set_cached
  end

  # @param [Hash] attributed
  # @return [Boolean]
  def reject_project_sources(attributed)
    return true if attributed['project_id'].blank?
    return true if ProjectSource.where(project_id: attributed['project_id'], source_id: id).any?
  end

  def validate_year_suffix
      a = get_author 
    unless year_suffix.blank? || year.blank? || a.blank?
      if new_record?
        s = Source.where(author: a, year: year, year_suffix: year_suffix).first
      else
        s = Source.where(author: a, year: year, year_suffix: year_suffix).not_self(self).first
      end
      errors.add(:year_suffix, " '#{year_suffix}' is already used for #{a} #{year}") unless s.nil?
    end
  end

end

#annoteString

Returns:


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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'app/models/source.rb', line 188

class Source < ApplicationRecord
  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::AlternateValues
  include Shared::DataAttributes
  include Shared::Identifiers
  include Shared::Notes
  include Shared::SharedAcrossProjects
  include Shared::Tags
  include Shared::Documentation
  include Shared::HasRoles
  include Shared::IsData
  include Shared::HasPapertrail
  include SoftValidation

  ignore_whitespace_on(:verbatim_contents)

  ALTERNATE_VALUES_FOR = [:address, :annote, :booktitle, :edition, :editor, :institution, :journal, :note, :organization,
                          :publisher, :school, :title, :doi, :abstract, :language, :translator, :author, :url].freeze

  # @return [Boolean]
  #  When true, cached values are not built
  attr_accessor :no_year_suffix_validation

  # Keep this order for citations/topics
  has_many :citations, inverse_of: :source, dependent: :restrict_with_error
  has_many :citation_topics, through: :citations, inverse_of: :source
  has_many :topics, through: :citation_topics, inverse_of: :sources

  # !! must be below has_many :citations
  has_many :asserted_distributions, through: :citations, source: :citation_object, source_type: 'AssertedDistribution'

  has_many :project_sources, dependent: :destroy
  has_many :projects, through: :project_sources

  after_save :set_cached

  validates_presence_of :type
  validates :type, inclusion: {in: ['Source::Bibtex', 'Source::Human', 'Source::Verbatim']} # TODO: not needed
  validate :validate_year_suffix, unless: -> { self.no_year_suffix_validation || (self.type != 'Source::Bibtex') }

  accepts_nested_attributes_for :project_sources, reject_if: :reject_project_sources

  # Redirect type here
  # @param [String] file
  # @return [[Array, message]]
  #   TODO: return a more informative response?
  def self.batch_preview(file)
    begin
      bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
      sources = []
      bibliography.each do |record|
        a = Source::Bibtex.new_from_bibtex(record)#        a.soft_validate() # why?

        sources.push(a)
      end
      return sources, nil
    rescue BibTeX::ParseError => e
      return [], e.message
    rescue
      raise
    end
  end

  # @param [String] file
  # @return [Array, Boolean]
  def self.batch_create(file)
    sources = []
    valid = 0
    begin
      # error_msg = []
      Source.transaction do
        bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
        bibliography.each do |record|
          a = Source::Bibtex.new_from_bibtex(record)
          if a.valid?
            if a.save
              valid += 1
            end#            a.soft_validate()

          else
            # error_msg = a.errors.messages.to_s
          end
          sources.push(a)
        end
      end
    rescue
      return false
    end
    return {records: sources, count: valid}
  end

  # @param used_on [String] a model name 
  # @return [Scope]
  #    the max 10 most recently used (1 week, could parameterize) TaxonName, as used 
  def self.used_recently(used_on = 'TaxonName')
    t = Citation.arel_table
    p = Source.arel_table

    # i is a select manager
    i = t.project(t['source_id'], t['created_at']).from(t)
      .where(t['created_at'].gt(1.weeks.ago))
      .where(t['citation_object_type'].eq(used_on))
      .order(t['created_at'])
      .take(10)
      .distinct

    # z is a table alias
    z = i.as('recent_t')

    Source.joins(
      Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(z['source_id'].eq(p['id'])))
    )
  end

  # @params target [String] a citable model name
  # @return [Hash] sources optimized for user selection
  def self.select_optimized(user_id, project_id, target = 'TaxonName')
    h = {
      quick: [],
      pinboard: Source.pinned_by(user_id).where(pinboard_items: {project_id: project_id}).to_a
    }

    h[:recent] = (
      Source.joins(:citations)
      .where( citations: { project_id: project_id, updated_by_id: user_id } )
      .used_recently(target)
      .limit(5).distinct.to_a +
    Source.where(created_by_id: user_id, updated_at: 2.hours.ago..Time.now )
      .order('created_at DESC')
      .limit(5).to_a
    ).uniq

    h[:recent] ||= []

    h[:quick] = ( Source.pinned_by(user_id).pinboard_inserted.where(pinboard_items: {project_id: project_id}).to_a + h[:recent][0..3]).uniq
    h
  end

  # @return [Array]
  #    objects this source is linked to through citations
  def cited_objects
    self.citations.collect { |t| t.citation_object }
  end

  # @return [Boolean]
  def is_bibtex?
    type == 'Source::Bibtex'
  end

  # @return [Boolean]
  def is_in_project?(project_id)
    projects.where(id: project_id).any?
  end

  # @return [Source, false]
  def clone
    s = dup
    m = "[CLONE of #{id}] "
    begin
      Source.transaction do |t|
        roles.each do |r|
          s.roles << Role.new(person: r.person, type: r.type, position: r.position )
        end

        case type
        when 'Source::Verbatim'
          s.verbatim = m + verbatim
        when 'Source::Bibtex'
          s.title = m + title
        end

        s.save!
      end
    rescue ActiveRecord::RecordInvalid
    end
    s
  end

  protected

  # Defined in subclasses
  # @return [Nil]
  def set_cached
  end

  # @param [Hash] attributed
  # @return [Boolean]
  def reject_project_sources(attributed)
    return true if attributed['project_id'].blank?
    return true if ProjectSource.where(project_id: attributed['project_id'], source_id: id).any?
  end

  def validate_year_suffix
      a = get_author 
    unless year_suffix.blank? || year.blank? || a.blank?
      if new_record?
        s = Source.where(author: a, year: year, year_suffix: year_suffix).first
      else
        s = Source.where(author: a, year: year, year_suffix: year_suffix).not_self(self).first
      end
      errors.add(:year_suffix, " '#{year_suffix}' is already used for #{a} #{year}") unless s.nil?
    end
  end

end

#authorString

Returns:


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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'app/models/source.rb', line 188

class Source < ApplicationRecord
  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::AlternateValues
  include Shared::DataAttributes
  include Shared::Identifiers
  include Shared::Notes
  include Shared::SharedAcrossProjects
  include Shared::Tags
  include Shared::Documentation
  include Shared::HasRoles
  include Shared::IsData
  include Shared::HasPapertrail
  include SoftValidation

  ignore_whitespace_on(:verbatim_contents)

  ALTERNATE_VALUES_FOR = [:address, :annote, :booktitle, :edition, :editor, :institution, :journal, :note, :organization,
                          :publisher, :school, :title, :doi, :abstract, :language, :translator, :author, :url].freeze

  # @return [Boolean]
  #  When true, cached values are not built
  attr_accessor :no_year_suffix_validation

  # Keep this order for citations/topics
  has_many :citations, inverse_of: :source, dependent: :restrict_with_error
  has_many :citation_topics, through: :citations, inverse_of: :source
  has_many :topics, through: :citation_topics, inverse_of: :sources

  # !! must be below has_many :citations
  has_many :asserted_distributions, through: :citations, source: :citation_object, source_type: 'AssertedDistribution'

  has_many :project_sources, dependent: :destroy
  has_many :projects, through: :project_sources

  after_save :set_cached

  validates_presence_of :type
  validates :type, inclusion: {in: ['Source::Bibtex', 'Source::Human', 'Source::Verbatim']} # TODO: not needed
  validate :validate_year_suffix, unless: -> { self.no_year_suffix_validation || (self.type != 'Source::Bibtex') }

  accepts_nested_attributes_for :project_sources, reject_if: :reject_project_sources

  # Redirect type here
  # @param [String] file
  # @return [[Array, message]]
  #   TODO: return a more informative response?
  def self.batch_preview(file)
    begin
      bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
      sources = []
      bibliography.each do |record|
        a = Source::Bibtex.new_from_bibtex(record)#        a.soft_validate() # why?

        sources.push(a)
      end
      return sources, nil
    rescue BibTeX::ParseError => e
      return [], e.message
    rescue
      raise
    end
  end

  # @param [String] file
  # @return [Array, Boolean]
  def self.batch_create(file)
    sources = []
    valid = 0
    begin
      # error_msg = []
      Source.transaction do
        bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
        bibliography.each do |record|
          a = Source::Bibtex.new_from_bibtex(record)
          if a.valid?
            if a.save
              valid += 1
            end#            a.soft_validate()

          else
            # error_msg = a.errors.messages.to_s
          end
          sources.push(a)
        end
      end
    rescue
      return false
    end
    return {records: sources, count: valid}
  end

  # @param used_on [String] a model name 
  # @return [Scope]
  #    the max 10 most recently used (1 week, could parameterize) TaxonName, as used 
  def self.used_recently(used_on = 'TaxonName')
    t = Citation.arel_table
    p = Source.arel_table

    # i is a select manager
    i = t.project(t['source_id'], t['created_at']).from(t)
      .where(t['created_at'].gt(1.weeks.ago))
      .where(t['citation_object_type'].eq(used_on))
      .order(t['created_at'])
      .take(10)
      .distinct

    # z is a table alias
    z = i.as('recent_t')

    Source.joins(
      Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(z['source_id'].eq(p['id'])))
    )
  end

  # @params target [String] a citable model name
  # @return [Hash] sources optimized for user selection
  def self.select_optimized(user_id, project_id, target = 'TaxonName')
    h = {
      quick: [],
      pinboard: Source.pinned_by(user_id).where(pinboard_items: {project_id: project_id}).to_a
    }

    h[:recent] = (
      Source.joins(:citations)
      .where( citations: { project_id: project_id, updated_by_id: user_id } )
      .used_recently(target)
      .limit(5).distinct.to_a +
    Source.where(created_by_id: user_id, updated_at: 2.hours.ago..Time.now )
      .order('created_at DESC')
      .limit(5).to_a
    ).uniq

    h[:recent] ||= []

    h[:quick] = ( Source.pinned_by(user_id).pinboard_inserted.where(pinboard_items: {project_id: project_id}).to_a + h[:recent][0..3]).uniq
    h
  end

  # @return [Array]
  #    objects this source is linked to through citations
  def cited_objects
    self.citations.collect { |t| t.citation_object }
  end

  # @return [Boolean]
  def is_bibtex?
    type == 'Source::Bibtex'
  end

  # @return [Boolean]
  def is_in_project?(project_id)
    projects.where(id: project_id).any?
  end

  # @return [Source, false]
  def clone
    s = dup
    m = "[CLONE of #{id}] "
    begin
      Source.transaction do |t|
        roles.each do |r|
          s.roles << Role.new(person: r.person, type: r.type, position: r.position )
        end

        case type
        when 'Source::Verbatim'
          s.verbatim = m + verbatim
        when 'Source::Bibtex'
          s.title = m + title
        end

        s.save!
      end
    rescue ActiveRecord::RecordInvalid
    end
    s
  end

  protected

  # Defined in subclasses
  # @return [Nil]
  def set_cached
  end

  # @param [Hash] attributed
  # @return [Boolean]
  def reject_project_sources(attributed)
    return true if attributed['project_id'].blank?
    return true if ProjectSource.where(project_id: attributed['project_id'], source_id: id).any?
  end

  def validate_year_suffix
      a = get_author 
    unless year_suffix.blank? || year.blank? || a.blank?
      if new_record?
        s = Source.where(author: a, year: year, year_suffix: year_suffix).first
      else
        s = Source.where(author: a, year: year, year_suffix: year_suffix).not_self(self).first
      end
      errors.add(:year_suffix, " '#{year_suffix}' is already used for #{a} #{year}") unless s.nil?
    end
  end

end

#bibtex_typeString

Returns alias for “type” in the bibtex framework see en.wikipedia.org/wiki/BibTeX#Field_types.

Returns:


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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'app/models/source.rb', line 188

class Source < ApplicationRecord
  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::AlternateValues
  include Shared::DataAttributes
  include Shared::Identifiers
  include Shared::Notes
  include Shared::SharedAcrossProjects
  include Shared::Tags
  include Shared::Documentation
  include Shared::HasRoles
  include Shared::IsData
  include Shared::HasPapertrail
  include SoftValidation

  ignore_whitespace_on(:verbatim_contents)

  ALTERNATE_VALUES_FOR = [:address, :annote, :booktitle, :edition, :editor, :institution, :journal, :note, :organization,
                          :publisher, :school, :title, :doi, :abstract, :language, :translator, :author, :url].freeze

  # @return [Boolean]
  #  When true, cached values are not built
  attr_accessor :no_year_suffix_validation

  # Keep this order for citations/topics
  has_many :citations, inverse_of: :source, dependent: :restrict_with_error
  has_many :citation_topics, through: :citations, inverse_of: :source
  has_many :topics, through: :citation_topics, inverse_of: :sources

  # !! must be below has_many :citations
  has_many :asserted_distributions, through: :citations, source: :citation_object, source_type: 'AssertedDistribution'

  has_many :project_sources, dependent: :destroy
  has_many :projects, through: :project_sources

  after_save :set_cached

  validates_presence_of :type
  validates :type, inclusion: {in: ['Source::Bibtex', 'Source::Human', 'Source::Verbatim']} # TODO: not needed
  validate :validate_year_suffix, unless: -> { self.no_year_suffix_validation || (self.type != 'Source::Bibtex') }

  accepts_nested_attributes_for :project_sources, reject_if: :reject_project_sources

  # Redirect type here
  # @param [String] file
  # @return [[Array, message]]
  #   TODO: return a more informative response?
  def self.batch_preview(file)
    begin
      bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
      sources = []
      bibliography.each do |record|
        a = Source::Bibtex.new_from_bibtex(record)#        a.soft_validate() # why?

        sources.push(a)
      end
      return sources, nil
    rescue BibTeX::ParseError => e
      return [], e.message
    rescue
      raise
    end
  end

  # @param [String] file
  # @return [Array, Boolean]
  def self.batch_create(file)
    sources = []
    valid = 0
    begin
      # error_msg = []
      Source.transaction do
        bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
        bibliography.each do |record|
          a = Source::Bibtex.new_from_bibtex(record)
          if a.valid?
            if a.save
              valid += 1
            end#            a.soft_validate()

          else
            # error_msg = a.errors.messages.to_s
          end
          sources.push(a)
        end
      end
    rescue
      return false
    end
    return {records: sources, count: valid}
  end

  # @param used_on [String] a model name 
  # @return [Scope]
  #    the max 10 most recently used (1 week, could parameterize) TaxonName, as used 
  def self.used_recently(used_on = 'TaxonName')
    t = Citation.arel_table
    p = Source.arel_table

    # i is a select manager
    i = t.project(t['source_id'], t['created_at']).from(t)
      .where(t['created_at'].gt(1.weeks.ago))
      .where(t['citation_object_type'].eq(used_on))
      .order(t['created_at'])
      .take(10)
      .distinct

    # z is a table alias
    z = i.as('recent_t')

    Source.joins(
      Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(z['source_id'].eq(p['id'])))
    )
  end

  # @params target [String] a citable model name
  # @return [Hash] sources optimized for user selection
  def self.select_optimized(user_id, project_id, target = 'TaxonName')
    h = {
      quick: [],
      pinboard: Source.pinned_by(user_id).where(pinboard_items: {project_id: project_id}).to_a
    }

    h[:recent] = (
      Source.joins(:citations)
      .where( citations: { project_id: project_id, updated_by_id: user_id } )
      .used_recently(target)
      .limit(5).distinct.to_a +
    Source.where(created_by_id: user_id, updated_at: 2.hours.ago..Time.now )
      .order('created_at DESC')
      .limit(5).to_a
    ).uniq

    h[:recent] ||= []

    h[:quick] = ( Source.pinned_by(user_id).pinboard_inserted.where(pinboard_items: {project_id: project_id}).to_a + h[:recent][0..3]).uniq
    h
  end

  # @return [Array]
  #    objects this source is linked to through citations
  def cited_objects
    self.citations.collect { |t| t.citation_object }
  end

  # @return [Boolean]
  def is_bibtex?
    type == 'Source::Bibtex'
  end

  # @return [Boolean]
  def is_in_project?(project_id)
    projects.where(id: project_id).any?
  end

  # @return [Source, false]
  def clone
    s = dup
    m = "[CLONE of #{id}] "
    begin
      Source.transaction do |t|
        roles.each do |r|
          s.roles << Role.new(person: r.person, type: r.type, position: r.position )
        end

        case type
        when 'Source::Verbatim'
          s.verbatim = m + verbatim
        when 'Source::Bibtex'
          s.title = m + title
        end

        s.save!
      end
    rescue ActiveRecord::RecordInvalid
    end
    s
  end

  protected

  # Defined in subclasses
  # @return [Nil]
  def set_cached
  end

  # @param [Hash] attributed
  # @return [Boolean]
  def reject_project_sources(attributed)
    return true if attributed['project_id'].blank?
    return true if ProjectSource.where(project_id: attributed['project_id'], source_id: id).any?
  end

  def validate_year_suffix
      a = get_author 
    unless year_suffix.blank? || year.blank? || a.blank?
      if new_record?
        s = Source.where(author: a, year: year, year_suffix: year_suffix).first
      else
        s = Source.where(author: a, year: year, year_suffix: year_suffix).not_self(self).first
      end
      errors.add(:year_suffix, " '#{year_suffix}' is already used for #{a} #{year}") unless s.nil?
    end
  end

end

#booktitleString

Returns:


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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'app/models/source.rb', line 188

class Source < ApplicationRecord
  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::AlternateValues
  include Shared::DataAttributes
  include Shared::Identifiers
  include Shared::Notes
  include Shared::SharedAcrossProjects
  include Shared::Tags
  include Shared::Documentation
  include Shared::HasRoles
  include Shared::IsData
  include Shared::HasPapertrail
  include SoftValidation

  ignore_whitespace_on(:verbatim_contents)

  ALTERNATE_VALUES_FOR = [:address, :annote, :booktitle, :edition, :editor, :institution, :journal, :note, :organization,
                          :publisher, :school, :title, :doi, :abstract, :language, :translator, :author, :url].freeze

  # @return [Boolean]
  #  When true, cached values are not built
  attr_accessor :no_year_suffix_validation

  # Keep this order for citations/topics
  has_many :citations, inverse_of: :source, dependent: :restrict_with_error
  has_many :citation_topics, through: :citations, inverse_of: :source
  has_many :topics, through: :citation_topics, inverse_of: :sources

  # !! must be below has_many :citations
  has_many :asserted_distributions, through: :citations, source: :citation_object, source_type: 'AssertedDistribution'

  has_many :project_sources, dependent: :destroy
  has_many :projects, through: :project_sources

  after_save :set_cached

  validates_presence_of :type
  validates :type, inclusion: {in: ['Source::Bibtex', 'Source::Human', 'Source::Verbatim']} # TODO: not needed
  validate :validate_year_suffix, unless: -> { self.no_year_suffix_validation || (self.type != 'Source::Bibtex') }

  accepts_nested_attributes_for :project_sources, reject_if: :reject_project_sources

  # Redirect type here
  # @param [String] file
  # @return [[Array, message]]
  #   TODO: return a more informative response?
  def self.batch_preview(file)
    begin
      bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
      sources = []
      bibliography.each do |record|
        a = Source::Bibtex.new_from_bibtex(record)#        a.soft_validate() # why?

        sources.push(a)
      end
      return sources, nil
    rescue BibTeX::ParseError => e
      return [], e.message
    rescue
      raise
    end
  end

  # @param [String] file
  # @return [Array, Boolean]
  def self.batch_create(file)
    sources = []
    valid = 0
    begin
      # error_msg = []
      Source.transaction do
        bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
        bibliography.each do |record|
          a = Source::Bibtex.new_from_bibtex(record)
          if a.valid?
            if a.save
              valid += 1
            end#            a.soft_validate()

          else
            # error_msg = a.errors.messages.to_s
          end
          sources.push(a)
        end
      end
    rescue
      return false
    end
    return {records: sources, count: valid}
  end

  # @param used_on [String] a model name 
  # @return [Scope]
  #    the max 10 most recently used (1 week, could parameterize) TaxonName, as used 
  def self.used_recently(used_on = 'TaxonName')
    t = Citation.arel_table
    p = Source.arel_table

    # i is a select manager
    i = t.project(t['source_id'], t['created_at']).from(t)
      .where(t['created_at'].gt(1.weeks.ago))
      .where(t['citation_object_type'].eq(used_on))
      .order(t['created_at'])
      .take(10)
      .distinct

    # z is a table alias
    z = i.as('recent_t')

    Source.joins(
      Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(z['source_id'].eq(p['id'])))
    )
  end

  # @params target [String] a citable model name
  # @return [Hash] sources optimized for user selection
  def self.select_optimized(user_id, project_id, target = 'TaxonName')
    h = {
      quick: [],
      pinboard: Source.pinned_by(user_id).where(pinboard_items: {project_id: project_id}).to_a
    }

    h[:recent] = (
      Source.joins(:citations)
      .where( citations: { project_id: project_id, updated_by_id: user_id } )
      .used_recently(target)
      .limit(5).distinct.to_a +
    Source.where(created_by_id: user_id, updated_at: 2.hours.ago..Time.now )
      .order('created_at DESC')
      .limit(5).to_a
    ).uniq

    h[:recent] ||= []

    h[:quick] = ( Source.pinned_by(user_id).pinboard_inserted.where(pinboard_items: {project_id: project_id}).to_a + h[:recent][0..3]).uniq
    h
  end

  # @return [Array]
  #    objects this source is linked to through citations
  def cited_objects
    self.citations.collect { |t| t.citation_object }
  end

  # @return [Boolean]
  def is_bibtex?
    type == 'Source::Bibtex'
  end

  # @return [Boolean]
  def is_in_project?(project_id)
    projects.where(id: project_id).any?
  end

  # @return [Source, false]
  def clone
    s = dup
    m = "[CLONE of #{id}] "
    begin
      Source.transaction do |t|
        roles.each do |r|
          s.roles << Role.new(person: r.person, type: r.type, position: r.position )
        end

        case type
        when 'Source::Verbatim'
          s.verbatim = m + verbatim
        when 'Source::Bibtex'
          s.title = m + title
        end

        s.save!
      end
    rescue ActiveRecord::RecordInvalid
    end
    s
  end

  protected

  # Defined in subclasses
  # @return [Nil]
  def set_cached
  end

  # @param [Hash] attributed
  # @return [Boolean]
  def reject_project_sources(attributed)
    return true if attributed['project_id'].blank?
    return true if ProjectSource.where(project_id: attributed['project_id'], source_id: id).any?
  end

  def validate_year_suffix
      a = get_author 
    unless year_suffix.blank? || year.blank? || a.blank?
      if new_record?
        s = Source.where(author: a, year: year, year_suffix: year_suffix).first
      else
        s = Source.where(author: a, year: year, year_suffix: year_suffix).not_self(self).first
      end
      errors.add(:year_suffix, " '#{year_suffix}' is already used for #{a} #{year}") unless s.nil?
    end
  end

end

#cachedString

Returns calculated full citation, searched again in “full text”.

Returns:

  • (String)

    calculated full citation, searched again in “full text”


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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'app/models/source.rb', line 188

class Source < ApplicationRecord
  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::AlternateValues
  include Shared::DataAttributes
  include Shared::Identifiers
  include Shared::Notes
  include Shared::SharedAcrossProjects
  include Shared::Tags
  include Shared::Documentation
  include Shared::HasRoles
  include Shared::IsData
  include Shared::HasPapertrail
  include SoftValidation

  ignore_whitespace_on(:verbatim_contents)

  ALTERNATE_VALUES_FOR = [:address, :annote, :booktitle, :edition, :editor, :institution, :journal, :note, :organization,
                          :publisher, :school, :title, :doi, :abstract, :language, :translator, :author, :url].freeze

  # @return [Boolean]
  #  When true, cached values are not built
  attr_accessor :no_year_suffix_validation

  # Keep this order for citations/topics
  has_many :citations, inverse_of: :source, dependent: :restrict_with_error
  has_many :citation_topics, through: :citations, inverse_of: :source
  has_many :topics, through: :citation_topics, inverse_of: :sources

  # !! must be below has_many :citations
  has_many :asserted_distributions, through: :citations, source: :citation_object, source_type: 'AssertedDistribution'

  has_many :project_sources, dependent: :destroy
  has_many :projects, through: :project_sources

  after_save :set_cached

  validates_presence_of :type
  validates :type, inclusion: {in: ['Source::Bibtex', 'Source::Human', 'Source::Verbatim']} # TODO: not needed
  validate :validate_year_suffix, unless: -> { self.no_year_suffix_validation || (self.type != 'Source::Bibtex') }

  accepts_nested_attributes_for :project_sources, reject_if: :reject_project_sources

  # Redirect type here
  # @param [String] file
  # @return [[Array, message]]
  #   TODO: return a more informative response?
  def self.batch_preview(file)
    begin
      bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
      sources = []
      bibliography.each do |record|
        a = Source::Bibtex.new_from_bibtex(record)#        a.soft_validate() # why?

        sources.push(a)
      end
      return sources, nil
    rescue BibTeX::ParseError => e
      return [], e.message
    rescue
      raise
    end
  end

  # @param [String] file
  # @return [Array, Boolean]
  def self.batch_create(file)
    sources = []
    valid = 0
    begin
      # error_msg = []
      Source.transaction do
        bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
        bibliography.each do |record|
          a = Source::Bibtex.new_from_bibtex(record)
          if a.valid?
            if a.save
              valid += 1
            end#            a.soft_validate()

          else
            # error_msg = a.errors.messages.to_s
          end
          sources.push(a)
        end
      end
    rescue
      return false
    end
    return {records: sources, count: valid}
  end

  # @param used_on [String] a model name 
  # @return [Scope]
  #    the max 10 most recently used (1 week, could parameterize) TaxonName, as used 
  def self.used_recently(used_on = 'TaxonName')
    t = Citation.arel_table
    p = Source.arel_table

    # i is a select manager
    i = t.project(t['source_id'], t['created_at']).from(t)
      .where(t['created_at'].gt(1.weeks.ago))
      .where(t['citation_object_type'].eq(used_on))
      .order(t['created_at'])
      .take(10)
      .distinct

    # z is a table alias
    z = i.as('recent_t')

    Source.joins(
      Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(z['source_id'].eq(p['id'])))
    )
  end

  # @params target [String] a citable model name
  # @return [Hash] sources optimized for user selection
  def self.select_optimized(user_id, project_id, target = 'TaxonName')
    h = {
      quick: [],
      pinboard: Source.pinned_by(user_id).where(pinboard_items: {project_id: project_id}).to_a
    }

    h[:recent] = (
      Source.joins(:citations)
      .where( citations: { project_id: project_id, updated_by_id: user_id } )
      .used_recently(target)
      .limit(5).distinct.to_a +
    Source.where(created_by_id: user_id, updated_at: 2.hours.ago..Time.now )
      .order('created_at DESC')
      .limit(5).to_a
    ).uniq

    h[:recent] ||= []

    h[:quick] = ( Source.pinned_by(user_id).pinboard_inserted.where(pinboard_items: {project_id: project_id}).to_a + h[:recent][0..3]).uniq
    h
  end

  # @return [Array]
  #    objects this source is linked to through citations
  def cited_objects
    self.citations.collect { |t| t.citation_object }
  end

  # @return [Boolean]
  def is_bibtex?
    type == 'Source::Bibtex'
  end

  # @return [Boolean]
  def is_in_project?(project_id)
    projects.where(id: project_id).any?
  end

  # @return [Source, false]
  def clone
    s = dup
    m = "[CLONE of #{id}] "
    begin
      Source.transaction do |t|
        roles.each do |r|
          s.roles << Role.new(person: r.person, type: r.type, position: r.position )
        end

        case type
        when 'Source::Verbatim'
          s.verbatim = m + verbatim
        when 'Source::Bibtex'
          s.title = m + title
        end

        s.save!
      end
    rescue ActiveRecord::RecordInvalid
    end
    s
  end

  protected

  # Defined in subclasses
  # @return [Nil]
  def set_cached
  end

  # @param [Hash] attributed
  # @return [Boolean]
  def reject_project_sources(attributed)
    return true if attributed['project_id'].blank?
    return true if ProjectSource.where(project_id: attributed['project_id'], source_id: id).any?
  end

  def validate_year_suffix
      a = get_author 
    unless year_suffix.blank? || year.blank? || a.blank?
      if new_record?
        s = Source.where(author: a, year: year, year_suffix: year_suffix).first
      else
        s = Source.where(author: a, year: year, year_suffix: year_suffix).not_self(self).first
      end
      errors.add(:year_suffix, " '#{year_suffix}' is already used for #{a} #{year}") unless s.nil?
    end
  end

end

#cached_author_stringString

Returns calculated author string.

Returns:

  • (String)

    calculated author string


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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'app/models/source.rb', line 188

class Source < ApplicationRecord
  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::AlternateValues
  include Shared::DataAttributes
  include Shared::Identifiers
  include Shared::Notes
  include Shared::SharedAcrossProjects
  include Shared::Tags
  include Shared::Documentation
  include Shared::HasRoles
  include Shared::IsData
  include Shared::HasPapertrail
  include SoftValidation

  ignore_whitespace_on(:verbatim_contents)

  ALTERNATE_VALUES_FOR = [:address, :annote, :booktitle, :edition, :editor, :institution, :journal, :note, :organization,
                          :publisher, :school, :title, :doi, :abstract, :language, :translator, :author, :url].freeze

  # @return [Boolean]
  #  When true, cached values are not built
  attr_accessor :no_year_suffix_validation

  # Keep this order for citations/topics
  has_many :citations, inverse_of: :source, dependent: :restrict_with_error
  has_many :citation_topics, through: :citations, inverse_of: :source
  has_many :topics, through: :citation_topics, inverse_of: :sources

  # !! must be below has_many :citations
  has_many :asserted_distributions, through: :citations, source: :citation_object, source_type: 'AssertedDistribution'

  has_many :project_sources, dependent: :destroy
  has_many :projects, through: :project_sources

  after_save :set_cached

  validates_presence_of :type
  validates :type, inclusion: {in: ['Source::Bibtex', 'Source::Human', 'Source::Verbatim']} # TODO: not needed
  validate :validate_year_suffix, unless: -> { self.no_year_suffix_validation || (self.type != 'Source::Bibtex') }

  accepts_nested_attributes_for :project_sources, reject_if: :reject_project_sources

  # Redirect type here
  # @param [String] file
  # @return [[Array, message]]
  #   TODO: return a more informative response?
  def self.batch_preview(file)
    begin
      bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
      sources = []
      bibliography.each do |record|
        a = Source::Bibtex.new_from_bibtex(record)#        a.soft_validate() # why?

        sources.push(a)
      end
      return sources, nil
    rescue BibTeX::ParseError => e
      return [], e.message
    rescue
      raise
    end
  end

  # @param [String] file
  # @return [Array, Boolean]
  def self.batch_create(file)
    sources = []
    valid = 0
    begin
      # error_msg = []
      Source.transaction do
        bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
        bibliography.each do |record|
          a = Source::Bibtex.new_from_bibtex(record)
          if a.valid?
            if a.save
              valid += 1
            end#            a.soft_validate()

          else
            # error_msg = a.errors.messages.to_s
          end
          sources.push(a)
        end
      end
    rescue
      return false
    end
    return {records: sources, count: valid}
  end

  # @param used_on [String] a model name 
  # @return [Scope]
  #    the max 10 most recently used (1 week, could parameterize) TaxonName, as used 
  def self.used_recently(used_on = 'TaxonName')
    t = Citation.arel_table
    p = Source.arel_table

    # i is a select manager
    i = t.project(t['source_id'], t['created_at']).from(t)
      .where(t['created_at'].gt(1.weeks.ago))
      .where(t['citation_object_type'].eq(used_on))
      .order(t['created_at'])
      .take(10)
      .distinct

    # z is a table alias
    z = i.as('recent_t')

    Source.joins(
      Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(z['source_id'].eq(p['id'])))
    )
  end

  # @params target [String] a citable model name
  # @return [Hash] sources optimized for user selection
  def self.select_optimized(user_id, project_id, target = 'TaxonName')
    h = {
      quick: [],
      pinboard: Source.pinned_by(user_id).where(pinboard_items: {project_id: project_id}).to_a
    }

    h[:recent] = (
      Source.joins(:citations)
      .where( citations: { project_id: project_id, updated_by_id: user_id } )
      .used_recently(target)
      .limit(5).distinct.to_a +
    Source.where(created_by_id: user_id, updated_at: 2.hours.ago..Time.now )
      .order('created_at DESC')
      .limit(5).to_a
    ).uniq

    h[:recent] ||= []

    h[:quick] = ( Source.pinned_by(user_id).pinboard_inserted.where(pinboard_items: {project_id: project_id}).to_a + h[:recent][0..3]).uniq
    h
  end

  # @return [Array]
  #    objects this source is linked to through citations
  def cited_objects
    self.citations.collect { |t| t.citation_object }
  end

  # @return [Boolean]
  def is_bibtex?
    type == 'Source::Bibtex'
  end

  # @return [Boolean]
  def is_in_project?(project_id)
    projects.where(id: project_id).any?
  end

  # @return [Source, false]
  def clone
    s = dup
    m = "[CLONE of #{id}] "
    begin
      Source.transaction do |t|
        roles.each do |r|
          s.roles << Role.new(person: r.person, type: r.type, position: r.position )
        end

        case type
        when 'Source::Verbatim'
          s.verbatim = m + verbatim
        when 'Source::Bibtex'
          s.title = m + title
        end

        s.save!
      end
    rescue ActiveRecord::RecordInvalid
    end
    s
  end

  protected

  # Defined in subclasses
  # @return [Nil]
  def set_cached
  end

  # @param [Hash] attributed
  # @return [Boolean]
  def reject_project_sources(attributed)
    return true if attributed['project_id'].blank?
    return true if ProjectSource.where(project_id: attributed['project_id'], source_id: id).any?
  end

  def validate_year_suffix
      a = get_author 
    unless year_suffix.blank? || year.blank? || a.blank?
      if new_record?
        s = Source.where(author: a, year: year, year_suffix: year_suffix).first
      else
        s = Source.where(author: a, year: year, year_suffix: year_suffix).not_self(self).first
      end
      errors.add(:year_suffix, " '#{year_suffix}' is already used for #{a} #{year}") unless s.nil?
    end
  end

end

#cached_nomenclature_dateDateTime

Returns Date sensu nomenclature algorithm in TaxonWorks (see Utilities::Dates).

Returns:

  • (DateTime)

    Date sensu nomenclature algorithm in TaxonWorks (see Utilities::Dates)


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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'app/models/source.rb', line 188

class Source < ApplicationRecord
  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::AlternateValues
  include Shared::DataAttributes
  include Shared::Identifiers
  include Shared::Notes
  include Shared::SharedAcrossProjects
  include Shared::Tags
  include Shared::Documentation
  include Shared::HasRoles
  include Shared::IsData
  include Shared::HasPapertrail
  include SoftValidation

  ignore_whitespace_on(:verbatim_contents)

  ALTERNATE_VALUES_FOR = [:address, :annote, :booktitle, :edition, :editor, :institution, :journal, :note, :organization,
                          :publisher, :school, :title, :doi, :abstract, :language, :translator, :author, :url].freeze

  # @return [Boolean]
  #  When true, cached values are not built
  attr_accessor :no_year_suffix_validation

  # Keep this order for citations/topics
  has_many :citations, inverse_of: :source, dependent: :restrict_with_error
  has_many :citation_topics, through: :citations, inverse_of: :source
  has_many :topics, through: :citation_topics, inverse_of: :sources

  # !! must be below has_many :citations
  has_many :asserted_distributions, through: :citations, source: :citation_object, source_type: 'AssertedDistribution'

  has_many :project_sources, dependent: :destroy
  has_many :projects, through: :project_sources

  after_save :set_cached

  validates_presence_of :type
  validates :type, inclusion: {in: ['Source::Bibtex', 'Source::Human', 'Source::Verbatim']} # TODO: not needed
  validate :validate_year_suffix, unless: -> { self.no_year_suffix_validation || (self.type != 'Source::Bibtex') }

  accepts_nested_attributes_for :project_sources, reject_if: :reject_project_sources

  # Redirect type here
  # @param [String] file
  # @return [[Array, message]]
  #   TODO: return a more informative response?
  def self.batch_preview(file)
    begin
      bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
      sources = []
      bibliography.each do |record|
        a = Source::Bibtex.new_from_bibtex(record)#        a.soft_validate() # why?

        sources.push(a)
      end
      return sources, nil
    rescue BibTeX::ParseError => e
      return [], e.message
    rescue
      raise
    end
  end

  # @param [String] file
  # @return [Array, Boolean]
  def self.batch_create(file)
    sources = []
    valid = 0
    begin
      # error_msg = []
      Source.transaction do
        bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
        bibliography.each do |record|
          a = Source::Bibtex.new_from_bibtex(record)
          if a.valid?
            if a.save
              valid += 1
            end#            a.soft_validate()

          else
            # error_msg = a.errors.messages.to_s
          end
          sources.push(a)
        end
      end
    rescue
      return false
    end
    return {records: sources, count: valid}
  end

  # @param used_on [String] a model name 
  # @return [Scope]
  #    the max 10 most recently used (1 week, could parameterize) TaxonName, as used 
  def self.used_recently(used_on = 'TaxonName')
    t = Citation.arel_table
    p = Source.arel_table

    # i is a select manager
    i = t.project(t['source_id'], t['created_at']).from(t)
      .where(t['created_at'].gt(1.weeks.ago))
      .where(t['citation_object_type'].eq(used_on))
      .order(t['created_at'])
      .take(10)
      .distinct

    # z is a table alias
    z = i.as('recent_t')

    Source.joins(
      Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(z['source_id'].eq(p['id'])))
    )
  end

  # @params target [String] a citable model name
  # @return [Hash] sources optimized for user selection
  def self.select_optimized(user_id, project_id, target = 'TaxonName')
    h = {
      quick: [],
      pinboard: Source.pinned_by(user_id).where(pinboard_items: {project_id: project_id}).to_a
    }

    h[:recent] = (
      Source.joins(:citations)
      .where( citations: { project_id: project_id, updated_by_id: user_id } )
      .used_recently(target)
      .limit(5).distinct.to_a +
    Source.where(created_by_id: user_id, updated_at: 2.hours.ago..Time.now )
      .order('created_at DESC')
      .limit(5).to_a
    ).uniq

    h[:recent] ||= []

    h[:quick] = ( Source.pinned_by(user_id).pinboard_inserted.where(pinboard_items: {project_id: project_id}).to_a + h[:recent][0..3]).uniq
    h
  end

  # @return [Array]
  #    objects this source is linked to through citations
  def cited_objects
    self.citations.collect { |t| t.citation_object }
  end

  # @return [Boolean]
  def is_bibtex?
    type == 'Source::Bibtex'
  end

  # @return [Boolean]
  def is_in_project?(project_id)
    projects.where(id: project_id).any?
  end

  # @return [Source, false]
  def clone
    s = dup
    m = "[CLONE of #{id}] "
    begin
      Source.transaction do |t|
        roles.each do |r|
          s.roles << Role.new(person: r.person, type: r.type, position: r.position )
        end

        case type
        when 'Source::Verbatim'
          s.verbatim = m + verbatim
        when 'Source::Bibtex'
          s.title = m + title
        end

        s.save!
      end
    rescue ActiveRecord::RecordInvalid
    end
    s
  end

  protected

  # Defined in subclasses
  # @return [Nil]
  def set_cached
  end

  # @param [Hash] attributed
  # @return [Boolean]
  def reject_project_sources(attributed)
    return true if attributed['project_id'].blank?
    return true if ProjectSource.where(project_id: attributed['project_id'], source_id: id).any?
  end

  def validate_year_suffix
      a = get_author 
    unless year_suffix.blank? || year.blank? || a.blank?
      if new_record?
        s = Source.where(author: a, year: year, year_suffix: year_suffix).first
      else
        s = Source.where(author: a, year: year, year_suffix: year_suffix).not_self(self).first
      end
      errors.add(:year_suffix, " '#{year_suffix}' is already used for #{a} #{year}") unless s.nil?
    end
  end

end

#chapterString

Returns:


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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'app/models/source.rb', line 188

class Source < ApplicationRecord
  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::AlternateValues
  include Shared::DataAttributes
  include Shared::Identifiers
  include Shared::Notes
  include Shared::SharedAcrossProjects
  include Shared::Tags
  include Shared::Documentation
  include Shared::HasRoles
  include Shared::IsData
  include Shared::HasPapertrail
  include SoftValidation

  ignore_whitespace_on(:verbatim_contents)

  ALTERNATE_VALUES_FOR = [:address, :annote, :booktitle, :edition, :editor, :institution, :journal, :note, :organization,
                          :publisher, :school, :title, :doi, :abstract, :language, :translator, :author, :url].freeze

  # @return [Boolean]
  #  When true, cached values are not built
  attr_accessor :no_year_suffix_validation

  # Keep this order for citations/topics
  has_many :citations, inverse_of: :source, dependent: :restrict_with_error
  has_many :citation_topics, through: :citations, inverse_of: :source
  has_many :topics, through: :citation_topics, inverse_of: :sources

  # !! must be below has_many :citations
  has_many :asserted_distributions, through: :citations, source: :citation_object, source_type: 'AssertedDistribution'

  has_many :project_sources, dependent: :destroy
  has_many :projects, through: :project_sources

  after_save :set_cached

  validates_presence_of :type
  validates :type, inclusion: {in: ['Source::Bibtex', 'Source::Human', 'Source::Verbatim']} # TODO: not needed
  validate :validate_year_suffix, unless: -> { self.no_year_suffix_validation || (self.type != 'Source::Bibtex') }

  accepts_nested_attributes_for :project_sources, reject_if: :reject_project_sources

  # Redirect type here
  # @param [String] file
  # @return [[Array, message]]
  #   TODO: return a more informative response?
  def self.batch_preview(file)
    begin
      bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
      sources = []
      bibliography.each do |record|
        a = Source::Bibtex.new_from_bibtex(record)#        a.soft_validate() # why?

        sources.push(a)
      end
      return sources, nil
    rescue BibTeX::ParseError => e
      return [], e.message
    rescue
      raise
    end
  end

  # @param [String] file
  # @return [Array, Boolean]
  def self.batch_create(file)
    sources = []
    valid = 0
    begin
      # error_msg = []
      Source.transaction do
        bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
        bibliography.each do |record|
          a = Source::Bibtex.new_from_bibtex(record)
          if a.valid?
            if a.save
              valid += 1
            end#            a.soft_validate()

          else
            # error_msg = a.errors.messages.to_s
          end
          sources.push(a)
        end
      end
    rescue
      return false
    end
    return {records: sources, count: valid}
  end

  # @param used_on [String] a model name 
  # @return [Scope]
  #    the max 10 most recently used (1 week, could parameterize) TaxonName, as used 
  def self.used_recently(used_on = 'TaxonName')
    t = Citation.arel_table
    p = Source.arel_table

    # i is a select manager
    i = t.project(t['source_id'], t['created_at']).from(t)
      .where(t['created_at'].gt(1.weeks.ago))
      .where(t['citation_object_type'].eq(used_on))
      .order(t['created_at'])
      .take(10)
      .distinct

    # z is a table alias
    z = i.as('recent_t')

    Source.joins(
      Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(z['source_id'].eq(p['id'])))
    )
  end

  # @params target [String] a citable model name
  # @return [Hash] sources optimized for user selection
  def self.select_optimized(user_id, project_id, target = 'TaxonName')
    h = {
      quick: [],
      pinboard: Source.pinned_by(user_id).where(pinboard_items: {project_id: project_id}).to_a
    }

    h[:recent] = (
      Source.joins(:citations)
      .where( citations: { project_id: project_id, updated_by_id: user_id } )
      .used_recently(target)
      .limit(5).distinct.to_a +
    Source.where(created_by_id: user_id, updated_at: 2.hours.ago..Time.now )
      .order('created_at DESC')
      .limit(5).to_a
    ).uniq

    h[:recent] ||= []

    h[:quick] = ( Source.pinned_by(user_id).pinboard_inserted.where(pinboard_items: {project_id: project_id}).to_a + h[:recent][0..3]).uniq
    h
  end

  # @return [Array]
  #    objects this source is linked to through citations
  def cited_objects
    self.citations.collect { |t| t.citation_object }
  end

  # @return [Boolean]
  def is_bibtex?
    type == 'Source::Bibtex'
  end

  # @return [Boolean]
  def is_in_project?(project_id)
    projects.where(id: project_id).any?
  end

  # @return [Source, false]
  def clone
    s = dup
    m = "[CLONE of #{id}] "
    begin
      Source.transaction do |t|
        roles.each do |r|
          s.roles << Role.new(person: r.person, type: r.type, position: r.position )
        end

        case type
        when 'Source::Verbatim'
          s.verbatim = m + verbatim
        when 'Source::Bibtex'
          s.title = m + title
        end

        s.save!
      end
    rescue ActiveRecord::RecordInvalid
    end
    s
  end

  protected

  # Defined in subclasses
  # @return [Nil]
  def set_cached
  end

  # @param [Hash] attributed
  # @return [Boolean]
  def reject_project_sources(attributed)
    return true if attributed['project_id'].blank?
    return true if ProjectSource.where(project_id: attributed['project_id'], source_id: id).any?
  end

  def validate_year_suffix
      a = get_author 
    unless year_suffix.blank? || year.blank? || a.blank?
      if new_record?
        s = Source.where(author: a, year: year, year_suffix: year_suffix).first
      else
        s = Source.where(author: a, year: year, year_suffix: year_suffix).not_self(self).first
      end
      errors.add(:year_suffix, " '#{year_suffix}' is already used for #{a} #{year}") unless s.nil?
    end
  end

end

Returns:


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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'app/models/source.rb', line 188

class Source < ApplicationRecord
  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::AlternateValues
  include Shared::DataAttributes
  include Shared::Identifiers
  include Shared::Notes
  include Shared::SharedAcrossProjects
  include Shared::Tags
  include Shared::Documentation
  include Shared::HasRoles
  include Shared::IsData
  include Shared::HasPapertrail
  include SoftValidation

  ignore_whitespace_on(:verbatim_contents)

  ALTERNATE_VALUES_FOR = [:address, :annote, :booktitle, :edition, :editor, :institution, :journal, :note, :organization,
                          :publisher, :school, :title, :doi, :abstract, :language, :translator, :author, :url].freeze

  # @return [Boolean]
  #  When true, cached values are not built
  attr_accessor :no_year_suffix_validation

  # Keep this order for citations/topics
  has_many :citations, inverse_of: :source, dependent: :restrict_with_error
  has_many :citation_topics, through: :citations, inverse_of: :source
  has_many :topics, through: :citation_topics, inverse_of: :sources

  # !! must be below has_many :citations
  has_many :asserted_distributions, through: :citations, source: :citation_object, source_type: 'AssertedDistribution'

  has_many :project_sources, dependent: :destroy
  has_many :projects, through: :project_sources

  after_save :set_cached

  validates_presence_of :type
  validates :type, inclusion: {in: ['Source::Bibtex', 'Source::Human', 'Source::Verbatim']} # TODO: not needed
  validate :validate_year_suffix, unless: -> { self.no_year_suffix_validation || (self.type != 'Source::Bibtex') }

  accepts_nested_attributes_for :project_sources, reject_if: :reject_project_sources

  # Redirect type here
  # @param [String] file
  # @return [[Array, message]]
  #   TODO: return a more informative response?
  def self.batch_preview(file)
    begin
      bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
      sources = []
      bibliography.each do |record|
        a = Source::Bibtex.new_from_bibtex(record)#        a.soft_validate() # why?

        sources.push(a)
      end
      return sources, nil
    rescue BibTeX::ParseError => e
      return [], e.message
    rescue
      raise
    end
  end

  # @param [String] file
  # @return [Array, Boolean]
  def self.batch_create(file)
    sources = []
    valid = 0
    begin
      # error_msg = []
      Source.transaction do
        bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
        bibliography.each do |record|
          a = Source::Bibtex.new_from_bibtex(record)
          if a.valid?
            if a.save
              valid += 1
            end#            a.soft_validate()

          else
            # error_msg = a.errors.messages.to_s
          end
          sources.push(a)
        end
      end
    rescue
      return false
    end
    return {records: sources, count: valid}
  end

  # @param used_on [String] a model name 
  # @return [Scope]
  #    the max 10 most recently used (1 week, could parameterize) TaxonName, as used 
  def self.used_recently(used_on = 'TaxonName')
    t = Citation.arel_table
    p = Source.arel_table

    # i is a select manager
    i = t.project(t['source_id'], t['created_at']).from(t)
      .where(t['created_at'].gt(1.weeks.ago))
      .where(t['citation_object_type'].eq(used_on))
      .order(t['created_at'])
      .take(10)
      .distinct

    # z is a table alias
    z = i.as('recent_t')

    Source.joins(
      Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(z['source_id'].eq(p['id'])))
    )
  end

  # @params target [String] a citable model name
  # @return [Hash] sources optimized for user selection
  def self.select_optimized(user_id, project_id, target = 'TaxonName')
    h = {
      quick: [],
      pinboard: Source.pinned_by(user_id).where(pinboard_items: {project_id: project_id}).to_a
    }

    h[:recent] = (
      Source.joins(:citations)
      .where( citations: { project_id: project_id, updated_by_id: user_id } )
      .used_recently(target)
      .limit(5).distinct.to_a +
    Source.where(created_by_id: user_id, updated_at: 2.hours.ago..Time.now )
      .order('created_at DESC')
      .limit(5).to_a
    ).uniq

    h[:recent] ||= []

    h[:quick] = ( Source.pinned_by(user_id).pinboard_inserted.where(pinboard_items: {project_id: project_id}).to_a + h[:recent][0..3]).uniq
    h
  end

  # @return [Array]
  #    objects this source is linked to through citations
  def cited_objects
    self.citations.collect { |t| t.citation_object }
  end

  # @return [Boolean]
  def is_bibtex?
    type == 'Source::Bibtex'
  end

  # @return [Boolean]
  def is_in_project?(project_id)
    projects.where(id: project_id).any?
  end

  # @return [Source, false]
  def clone
    s = dup
    m = "[CLONE of #{id}] "
    begin
      Source.transaction do |t|
        roles.each do |r|
          s.roles << Role.new(person: r.person, type: r.type, position: r.position )
        end

        case type
        when 'Source::Verbatim'
          s.verbatim = m + verbatim
        when 'Source::Bibtex'
          s.title = m + title
        end

        s.save!
      end
    rescue ActiveRecord::RecordInvalid
    end
    s
  end

  protected

  # Defined in subclasses
  # @return [Nil]
  def set_cached
  end

  # @param [Hash] attributed
  # @return [Boolean]
  def reject_project_sources(attributed)
    return true if attributed['project_id'].blank?
    return true if ProjectSource.where(project_id: attributed['project_id'], source_id: id).any?
  end

  def validate_year_suffix
      a = get_author 
    unless year_suffix.blank? || year.blank? || a.blank?
      if new_record?
        s = Source.where(author: a, year: year, year_suffix: year_suffix).first
      else
        s = Source.where(author: a, year: year, year_suffix: year_suffix).not_self(self).first
      end
      errors.add(:year_suffix, " '#{year_suffix}' is already used for #{a} #{year}") unless s.nil?
    end
  end

end

#crossrefString

Returns:


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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'app/models/source.rb', line 188

class Source < ApplicationRecord
  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::AlternateValues
  include Shared::DataAttributes
  include Shared::Identifiers
  include Shared::Notes
  include Shared::SharedAcrossProjects
  include Shared::Tags
  include Shared::Documentation
  include Shared::HasRoles
  include Shared::IsData
  include Shared::HasPapertrail
  include SoftValidation

  ignore_whitespace_on(:verbatim_contents)

  ALTERNATE_VALUES_FOR = [:address, :annote, :booktitle, :edition, :editor, :institution, :journal, :note, :organization,
                          :publisher, :school, :title, :doi, :abstract, :language, :translator, :author, :url].freeze

  # @return [Boolean]
  #  When true, cached values are not built
  attr_accessor :no_year_suffix_validation

  # Keep this order for citations/topics
  has_many :citations, inverse_of: :source, dependent: :restrict_with_error
  has_many :citation_topics, through: :citations, inverse_of: :source
  has_many :topics, through: :citation_topics, inverse_of: :sources

  # !! must be below has_many :citations
  has_many :asserted_distributions, through: :citations, source: :citation_object, source_type: 'AssertedDistribution'

  has_many :project_sources, dependent: :destroy
  has_many :projects, through: :project_sources

  after_save :set_cached

  validates_presence_of :type
  validates :type, inclusion: {in: ['Source::Bibtex', 'Source::Human', 'Source::Verbatim']} # TODO: not needed
  validate :validate_year_suffix, unless: -> { self.no_year_suffix_validation || (self.type != 'Source::Bibtex') }

  accepts_nested_attributes_for :project_sources, reject_if: :reject_project_sources

  # Redirect type here
  # @param [String] file
  # @return [[Array, message]]
  #   TODO: return a more informative response?
  def self.batch_preview(file)
    begin
      bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
      sources = []
      bibliography.each do |record|
        a = Source::Bibtex.new_from_bibtex(record)#        a.soft_validate() # why?

        sources.push(a)
      end
      return sources, nil
    rescue BibTeX::ParseError => e
      return [], e.message
    rescue
      raise
    end
  end

  # @param [String] file
  # @return [Array, Boolean]
  def self.batch_create(file)
    sources = []
    valid = 0
    begin
      # error_msg = []
      Source.transaction do
        bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
        bibliography.each do |record|
          a = Source::Bibtex.new_from_bibtex(record)
          if a.valid?
            if a.save
              valid += 1
            end#            a.soft_validate()

          else
            # error_msg = a.errors.messages.to_s
          end
          sources.push(a)
        end
      end
    rescue
      return false
    end
    return {records: sources, count: valid}
  end

  # @param used_on [String] a model name 
  # @return [Scope]
  #    the max 10 most recently used (1 week, could parameterize) TaxonName, as used 
  def self.used_recently(used_on = 'TaxonName')
    t = Citation.arel_table
    p = Source.arel_table

    # i is a select manager
    i = t.project(t['source_id'], t['created_at']).from(t)
      .where(t['created_at'].gt(1.weeks.ago))
      .where(t['citation_object_type'].eq(used_on))
      .order(t['created_at'])
      .take(10)
      .distinct

    # z is a table alias
    z = i.as('recent_t')

    Source.joins(
      Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(z['source_id'].eq(p['id'])))
    )
  end

  # @params target [String] a citable model name
  # @return [Hash] sources optimized for user selection
  def self.select_optimized(user_id, project_id, target = 'TaxonName')
    h = {
      quick: [],
      pinboard: Source.pinned_by(user_id).where(pinboard_items: {project_id: project_id}).to_a
    }

    h[:recent] = (
      Source.joins(:citations)
      .where( citations: { project_id: project_id, updated_by_id: user_id } )
      .used_recently(target)
      .limit(5).distinct.to_a +
    Source.where(created_by_id: user_id, updated_at: 2.hours.ago..Time.now )
      .order('created_at DESC')
      .limit(5).to_a
    ).uniq

    h[:recent] ||= []

    h[:quick] = ( Source.pinned_by(user_id).pinboard_inserted.where(pinboard_items: {project_id: project_id}).to_a + h[:recent][0..3]).uniq
    h
  end

  # @return [Array]
  #    objects this source is linked to through citations
  def cited_objects
    self.citations.collect { |t| t.citation_object }
  end

  # @return [Boolean]
  def is_bibtex?
    type == 'Source::Bibtex'
  end

  # @return [Boolean]
  def is_in_project?(project_id)
    projects.where(id: project_id).any?
  end

  # @return [Source, false]
  def clone
    s = dup
    m = "[CLONE of #{id}] "
    begin
      Source.transaction do |t|
        roles.each do |r|
          s.roles << Role.new(person: r.person, type: r.type, position: r.position )
        end

        case type
        when 'Source::Verbatim'
          s.verbatim = m + verbatim
        when 'Source::Bibtex'
          s.title = m + title
        end

        s.save!
      end
    rescue ActiveRecord::RecordInvalid
    end
    s
  end

  protected

  # Defined in subclasses
  # @return [Nil]
  def set_cached
  end

  # @param [Hash] attributed
  # @return [Boolean]
  def reject_project_sources(attributed)
    return true if attributed['project_id'].blank?
    return true if ProjectSource.where(project_id: attributed['project_id'], source_id: id).any?
  end

  def validate_year_suffix
      a = get_author 
    unless year_suffix.blank? || year.blank? || a.blank?
      if new_record?
        s = Source.where(author: a, year: year, year_suffix: year_suffix).first
      else
        s = Source.where(author: a, year: year, year_suffix: year_suffix).not_self(self).first
      end
      errors.add(:year_suffix, " '#{year_suffix}' is already used for #{a} #{year}") unless s.nil?
    end
  end

end

#dayInteger

Returns the calendar day (1-31).

Returns:

  • (Integer)

    the calendar day (1-31)


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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'app/models/source.rb', line 188

class Source < ApplicationRecord
  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::AlternateValues
  include Shared::DataAttributes
  include Shared::Identifiers
  include Shared::Notes
  include Shared::SharedAcrossProjects
  include Shared::Tags
  include Shared::Documentation
  include Shared::HasRoles
  include Shared::IsData
  include Shared::HasPapertrail
  include SoftValidation

  ignore_whitespace_on(:verbatim_contents)

  ALTERNATE_VALUES_FOR = [:address, :annote, :booktitle, :edition, :editor, :institution, :journal, :note, :organization,
                          :publisher, :school, :title, :doi, :abstract, :language, :translator, :author, :url].freeze

  # @return [Boolean]
  #  When true, cached values are not built
  attr_accessor :no_year_suffix_validation

  # Keep this order for citations/topics
  has_many :citations, inverse_of: :source, dependent: :restrict_with_error
  has_many :citation_topics, through: :citations, inverse_of: :source
  has_many :topics, through: :citation_topics, inverse_of: :sources

  # !! must be below has_many :citations
  has_many :asserted_distributions, through: :citations, source: :citation_object, source_type: 'AssertedDistribution'

  has_many :project_sources, dependent: :destroy
  has_many :projects, through: :project_sources

  after_save :set_cached

  validates_presence_of :type
  validates :type, inclusion: {in: ['Source::Bibtex', 'Source::Human', 'Source::Verbatim']} # TODO: not needed
  validate :validate_year_suffix, unless: -> { self.no_year_suffix_validation || (self.type != 'Source::Bibtex') }

  accepts_nested_attributes_for :project_sources, reject_if: :reject_project_sources

  # Redirect type here
  # @param [String] file
  # @return [[Array, message]]
  #   TODO: return a more informative response?
  def self.batch_preview(file)
    begin
      bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
      sources = []
      bibliography.each do |record|
        a = Source::Bibtex.new_from_bibtex(record)#        a.soft_validate() # why?

        sources.push(a)
      end
      return sources, nil
    rescue BibTeX::ParseError => e
      return [], e.message
    rescue
      raise
    end
  end

  # @param [String] file
  # @return [Array, Boolean]
  def self.batch_create(file)
    sources = []
    valid = 0
    begin
      # error_msg = []
      Source.transaction do
        bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
        bibliography.each do |record|
          a = Source::Bibtex.new_from_bibtex(record)
          if a.valid?
            if a.save
              valid += 1
            end#            a.soft_validate()

          else
            # error_msg = a.errors.messages.to_s
          end
          sources.push(a)
        end
      end
    rescue
      return false
    end
    return {records: sources, count: valid}
  end

  # @param used_on [String] a model name 
  # @return [Scope]
  #    the max 10 most recently used (1 week, could parameterize) TaxonName, as used 
  def self.used_recently(used_on = 'TaxonName')
    t = Citation.arel_table
    p = Source.arel_table

    # i is a select manager
    i = t.project(t['source_id'], t['created_at']).from(t)
      .where(t['created_at'].gt(1.weeks.ago))
      .where(t['citation_object_type'].eq(used_on))
      .order(t['created_at'])
      .take(10)
      .distinct

    # z is a table alias
    z = i.as('recent_t')

    Source.joins(
      Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(z['source_id'].eq(p['id'])))
    )
  end

  # @params target [String] a citable model name
  # @return [Hash] sources optimized for user selection
  def self.select_optimized(user_id, project_id, target = 'TaxonName')
    h = {
      quick: [],
      pinboard: Source.pinned_by(user_id).where(pinboard_items: {project_id: project_id}).to_a
    }

    h[:recent] = (
      Source.joins(:citations)
      .where( citations: { project_id: project_id, updated_by_id: user_id } )
      .used_recently(target)
      .limit(5).distinct.to_a +
    Source.where(created_by_id: user_id, updated_at: 2.hours.ago..Time.now )
      .order('created_at DESC')
      .limit(5).to_a
    ).uniq

    h[:recent] ||= []

    h[:quick] = ( Source.pinned_by(user_id).pinboard_inserted.where(pinboard_items: {project_id: project_id}).to_a + h[:recent][0..3]).uniq
    h
  end

  # @return [Array]
  #    objects this source is linked to through citations
  def cited_objects
    self.citations.collect { |t| t.citation_object }
  end

  # @return [Boolean]
  def is_bibtex?
    type == 'Source::Bibtex'
  end

  # @return [Boolean]
  def is_in_project?(project_id)
    projects.where(id: project_id).any?
  end

  # @return [Source, false]
  def clone
    s = dup
    m = "[CLONE of #{id}] "
    begin
      Source.transaction do |t|
        roles.each do |r|
          s.roles << Role.new(person: r.person, type: r.type, position: r.position )
        end

        case type
        when 'Source::Verbatim'
          s.verbatim = m + verbatim
        when 'Source::Bibtex'
          s.title = m + title
        end

        s.save!
      end
    rescue ActiveRecord::RecordInvalid
    end
    s
  end

  protected

  # Defined in subclasses
  # @return [Nil]
  def set_cached
  end

  # @param [Hash] attributed
  # @return [Boolean]
  def reject_project_sources(attributed)
    return true if attributed['project_id'].blank?
    return true if ProjectSource.where(project_id: attributed['project_id'], source_id: id).any?
  end

  def validate_year_suffix
      a = get_author 
    unless year_suffix.blank? || year.blank? || a.blank?
      if new_record?
        s = Source.where(author: a, year: year, year_suffix: year_suffix).first
      else
        s = Source.where(author: a, year: year, year_suffix: year_suffix).not_self(self).first
      end
      errors.add(:year_suffix, " '#{year_suffix}' is already used for #{a} #{year}") unless s.nil?
    end
  end

end

#doiString

Returns When provided also cloned to an Identifier::Global. See en.wikipedia.org/wiki/BibTeX#Field_types.

Returns:


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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'app/models/source.rb', line 188

class Source < ApplicationRecord
  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::AlternateValues
  include Shared::DataAttributes
  include Shared::Identifiers
  include Shared::Notes
  include Shared::SharedAcrossProjects
  include Shared::Tags
  include Shared::Documentation
  include Shared::HasRoles
  include Shared::IsData
  include Shared::HasPapertrail
  include SoftValidation

  ignore_whitespace_on(:verbatim_contents)

  ALTERNATE_VALUES_FOR = [:address, :annote, :booktitle, :edition, :editor, :institution, :journal, :note, :organization,
                          :publisher, :school, :title, :doi, :abstract, :language, :translator, :author, :url].freeze

  # @return [Boolean]
  #  When true, cached values are not built
  attr_accessor :no_year_suffix_validation

  # Keep this order for citations/topics
  has_many :citations, inverse_of: :source, dependent: :restrict_with_error
  has_many :citation_topics, through: :citations, inverse_of: :source
  has_many :topics, through: :citation_topics, inverse_of: :sources

  # !! must be below has_many :citations
  has_many :asserted_distributions, through: :citations, source: :citation_object, source_type: 'AssertedDistribution'

  has_many :project_sources, dependent: :destroy
  has_many :projects, through: :project_sources

  after_save :set_cached

  validates_presence_of :type
  validates :type, inclusion: {in: ['Source::Bibtex', 'Source::Human', 'Source::Verbatim']} # TODO: not needed
  validate :validate_year_suffix, unless: -> { self.no_year_suffix_validation || (self.type != 'Source::Bibtex') }

  accepts_nested_attributes_for :project_sources, reject_if: :reject_project_sources

  # Redirect type here
  # @param [String] file
  # @return [[Array, message]]
  #   TODO: return a more informative response?
  def self.batch_preview(file)
    begin
      bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
      sources = []
      bibliography.each do |record|
        a = Source::Bibtex.new_from_bibtex(record)#        a.soft_validate() # why?

        sources.push(a)
      end
      return sources, nil
    rescue BibTeX::ParseError => e
      return [], e.message
    rescue
      raise
    end
  end

  # @param [String] file
  # @return [Array, Boolean]
  def self.batch_create(file)
    sources = []
    valid = 0
    begin
      # error_msg = []
      Source.transaction do
        bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
        bibliography.each do |record|
          a = Source::Bibtex.new_from_bibtex(record)
          if a.valid?
            if a.save
              valid += 1
            end#            a.soft_validate()

          else
            # error_msg = a.errors.messages.to_s
          end
          sources.push(a)
        end
      end
    rescue
      return false
    end
    return {records: sources, count: valid}
  end

  # @param used_on [String] a model name 
  # @return [Scope]
  #    the max 10 most recently used (1 week, could parameterize) TaxonName, as used 
  def self.used_recently(used_on = 'TaxonName')
    t = Citation.arel_table
    p = Source.arel_table

    # i is a select manager
    i = t.project(t['source_id'], t['created_at']).from(t)
      .where(t['created_at'].gt(1.weeks.ago))
      .where(t['citation_object_type'].eq(used_on))
      .order(t['created_at'])
      .take(10)
      .distinct

    # z is a table alias
    z = i.as('recent_t')

    Source.joins(
      Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(z['source_id'].eq(p['id'])))
    )
  end

  # @params target [String] a citable model name
  # @return [Hash] sources optimized for user selection
  def self.select_optimized(user_id, project_id, target = 'TaxonName')
    h = {
      quick: [],
      pinboard: Source.pinned_by(user_id).where(pinboard_items: {project_id: project_id}).to_a
    }

    h[:recent] = (
      Source.joins(:citations)
      .where( citations: { project_id: project_id, updated_by_id: user_id } )
      .used_recently(target)
      .limit(5).distinct.to_a +
    Source.where(created_by_id: user_id, updated_at: 2.hours.ago..Time.now )
      .order('created_at DESC')
      .limit(5).to_a
    ).uniq

    h[:recent] ||= []

    h[:quick] = ( Source.pinned_by(user_id).pinboard_inserted.where(pinboard_items: {project_id: project_id}).to_a + h[:recent][0..3]).uniq
    h
  end

  # @return [Array]
  #    objects this source is linked to through citations
  def cited_objects
    self.citations.collect { |t| t.citation_object }
  end

  # @return [Boolean]
  def is_bibtex?
    type == 'Source::Bibtex'
  end

  # @return [Boolean]
  def is_in_project?(project_id)
    projects.where(id: project_id).any?
  end

  # @return [Source, false]
  def clone
    s = dup
    m = "[CLONE of #{id}] "
    begin
      Source.transaction do |t|
        roles.each do |r|
          s.roles << Role.new(person: r.person, type: r.type, position: r.position )
        end

        case type
        when 'Source::Verbatim'
          s.verbatim = m + verbatim
        when 'Source::Bibtex'
          s.title = m + title
        end

        s.save!
      end
    rescue ActiveRecord::RecordInvalid
    end
    s
  end

  protected

  # Defined in subclasses
  # @return [Nil]
  def set_cached
  end

  # @param [Hash] attributed
  # @return [Boolean]
  def reject_project_sources(attributed)
    return true if attributed['project_id'].blank?
    return true if ProjectSource.where(project_id: attributed['project_id'], source_id: id).any?
  end

  def validate_year_suffix
      a = get_author 
    unless year_suffix.blank? || year.blank? || a.blank?
      if new_record?
        s = Source.where(author: a, year: year, year_suffix: year_suffix).first
      else
        s = Source.where(author: a, year: year, year_suffix: year_suffix).not_self(self).first
      end
      errors.add(:year_suffix, " '#{year_suffix}' is already used for #{a} #{year}") unless s.nil?
    end
  end

end

#editionString

Returns:


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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'app/models/source.rb', line 188

class Source < ApplicationRecord
  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::AlternateValues
  include Shared::DataAttributes
  include Shared::Identifiers
  include Shared::Notes
  include Shared::SharedAcrossProjects
  include Shared::Tags
  include Shared::Documentation
  include Shared::HasRoles
  include Shared::IsData
  include Shared::HasPapertrail
  include SoftValidation

  ignore_whitespace_on(:verbatim_contents)

  ALTERNATE_VALUES_FOR = [:address, :annote, :booktitle, :edition, :editor, :institution, :journal, :note, :organization,
                          :publisher, :school, :title, :doi, :abstract, :language, :translator, :author, :url].freeze

  # @return [Boolean]
  #  When true, cached values are not built
  attr_accessor :no_year_suffix_validation

  # Keep this order for citations/topics
  has_many :citations, inverse_of: :source, dependent: :restrict_with_error
  has_many :citation_topics, through: :citations, inverse_of: :source
  has_many :topics, through: :citation_topics, inverse_of: :sources

  # !! must be below has_many :citations
  has_many :asserted_distributions, through: :citations, source: :citation_object, source_type: 'AssertedDistribution'

  has_many :project_sources, dependent: :destroy
  has_many :projects, through: :project_sources

  after_save :set_cached

  validates_presence_of :type
  validates :type, inclusion: {in: ['Source::Bibtex', 'Source::Human', 'Source::Verbatim']} # TODO: not needed
  validate :validate_year_suffix, unless: -> { self.no_year_suffix_validation || (self.type != 'Source::Bibtex') }

  accepts_nested_attributes_for :project_sources, reject_if: :reject_project_sources

  # Redirect type here
  # @param [String] file
  # @return [[Array, message]]
  #   TODO: return a more informative response?
  def self.batch_preview(file)
    begin
      bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
      sources = []
      bibliography.each do |record|
        a = Source::Bibtex.new_from_bibtex(record)#        a.soft_validate() # why?

        sources.push(a)
      end
      return sources, nil
    rescue BibTeX::ParseError => e
      return [], e.message
    rescue
      raise
    end
  end

  # @param [String] file
  # @return [Array, Boolean]
  def self.batch_create(file)
    sources = []
    valid = 0
    begin
      # error_msg = []
      Source.transaction do
        bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
        bibliography.each do |record|
          a = Source::Bibtex.new_from_bibtex(record)
          if a.valid?
            if a.save
              valid += 1
            end#            a.soft_validate()

          else
            # error_msg = a.errors.messages.to_s
          end
          sources.push(a)
        end
      end
    rescue
      return false
    end
    return {records: sources, count: valid}
  end

  # @param used_on [String] a model name 
  # @return [Scope]
  #    the max 10 most recently used (1 week, could parameterize) TaxonName, as used 
  def self.used_recently(used_on = 'TaxonName')
    t = Citation.arel_table
    p = Source.arel_table

    # i is a select manager
    i = t.project(t['source_id'], t['created_at']).from(t)
      .where(t['created_at'].gt(1.weeks.ago))
      .where(t['citation_object_type'].eq(used_on))
      .order(t['created_at'])
      .take(10)
      .distinct

    # z is a table alias
    z = i.as('recent_t')

    Source.joins(
      Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(z['source_id'].eq(p['id'])))
    )
  end

  # @params target [String] a citable model name
  # @return [Hash] sources optimized for user selection
  def self.select_optimized(user_id, project_id, target = 'TaxonName')
    h = {
      quick: [],
      pinboard: Source.pinned_by(user_id).where(pinboard_items: {project_id: project_id}).to_a
    }

    h[:recent] = (
      Source.joins(:citations)
      .where( citations: { project_id: project_id, updated_by_id: user_id } )
      .used_recently(target)
      .limit(5).distinct.to_a +
    Source.where(created_by_id: user_id, updated_at: 2.hours.ago..Time.now )
      .order('created_at DESC')
      .limit(5).to_a
    ).uniq

    h[:recent] ||= []

    h[:quick] = ( Source.pinned_by(user_id).pinboard_inserted.where(pinboard_items: {project_id: project_id}).to_a + h[:recent][0..3]).uniq
    h
  end

  # @return [Array]
  #    objects this source is linked to through citations
  def cited_objects
    self.citations.collect { |t| t.citation_object }
  end

  # @return [Boolean]
  def is_bibtex?
    type == 'Source::Bibtex'
  end

  # @return [Boolean]
  def is_in_project?(project_id)
    projects.where(id: project_id).any?
  end

  # @return [Source, false]
  def clone
    s = dup
    m = "[CLONE of #{id}] "
    begin
      Source.transaction do |t|
        roles.each do |r|
          s.roles << Role.new(person: r.person, type: r.type, position: r.position )
        end

        case type
        when 'Source::Verbatim'
          s.verbatim = m + verbatim
        when 'Source::Bibtex'
          s.title = m + title
        end

        s.save!
      end
    rescue ActiveRecord::RecordInvalid
    end
    s
  end

  protected

  # Defined in subclasses
  # @return [Nil]
  def set_cached
  end

  # @param [Hash] attributed
  # @return [Boolean]
  def reject_project_sources(attributed)
    return true if attributed['project_id'].blank?
    return true if ProjectSource.where(project_id: attributed['project_id'], source_id: id).any?
  end

  def validate_year_suffix
      a = get_author 
    unless year_suffix.blank? || year.blank? || a.blank?
      if new_record?
        s = Source.where(author: a, year: year, year_suffix: year_suffix).first
      else
        s = Source.where(author: a, year: year, year_suffix: year_suffix).not_self(self).first
      end
      errors.add(:year_suffix, " '#{year_suffix}' is already used for #{a} #{year}") unless s.nil?
    end
  end

end

#editorString

Returns:


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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'app/models/source.rb', line 188

class Source < ApplicationRecord
  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::AlternateValues
  include Shared::DataAttributes
  include Shared::Identifiers
  include Shared::Notes
  include Shared::SharedAcrossProjects
  include Shared::Tags
  include Shared::Documentation
  include Shared::HasRoles
  include Shared::IsData
  include Shared::HasPapertrail
  include SoftValidation

  ignore_whitespace_on(:verbatim_contents)

  ALTERNATE_VALUES_FOR = [:address, :annote, :booktitle, :edition, :editor, :institution, :journal, :note, :organization,
                          :publisher, :school, :title, :doi, :abstract, :language, :translator, :author, :url].freeze

  # @return [Boolean]
  #  When true, cached values are not built
  attr_accessor :no_year_suffix_validation

  # Keep this order for citations/topics
  has_many :citations, inverse_of: :source, dependent: :restrict_with_error
  has_many :citation_topics, through: :citations, inverse_of: :source
  has_many :topics, through: :citation_topics, inverse_of: :sources

  # !! must be below has_many :citations
  has_many :asserted_distributions, through: :citations, source: :citation_object, source_type: 'AssertedDistribution'

  has_many :project_sources, dependent: :destroy
  has_many :projects, through: :project_sources

  after_save :set_cached

  validates_presence_of :type
  validates :type, inclusion: {in: ['Source::Bibtex', 'Source::Human', 'Source::Verbatim']} # TODO: not needed
  validate :validate_year_suffix, unless: -> { self.no_year_suffix_validation || (self.type != 'Source::Bibtex') }

  accepts_nested_attributes_for :project_sources, reject_if: :reject_project_sources

  # Redirect type here
  # @param [String] file
  # @return [[Array, message]]
  #   TODO: return a more informative response?
  def self.batch_preview(file)
    begin
      bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
      sources = []
      bibliography.each do |record|
        a = Source::Bibtex.new_from_bibtex(record)#        a.soft_validate() # why?

        sources.push(a)
      end
      return sources, nil
    rescue BibTeX::ParseError => e
      return [], e.message
    rescue
      raise
    end
  end

  # @param [String] file
  # @return [Array, Boolean]
  def self.batch_create(file)
    sources = []
    valid = 0
    begin
      # error_msg = []
      Source.transaction do
        bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
        bibliography.each do |record|
          a = Source::Bibtex.new_from_bibtex(record)
          if a.valid?
            if a.save
              valid += 1
            end#            a.soft_validate()

          else
            # error_msg = a.errors.messages.to_s
          end
          sources.push(a)
        end
      end
    rescue
      return false
    end
    return {records: sources, count: valid}
  end

  # @param used_on [String] a model name 
  # @return [Scope]
  #    the max 10 most recently used (1 week, could parameterize) TaxonName, as used 
  def self.used_recently(used_on = 'TaxonName')
    t = Citation.arel_table
    p = Source.arel_table

    # i is a select manager
    i = t.project(t['source_id'], t['created_at']).from(t)
      .where(t['created_at'].gt(1.weeks.ago))
      .where(t['citation_object_type'].eq(used_on))
      .order(t['created_at'])
      .take(10)
      .distinct

    # z is a table alias
    z = i.as('recent_t')

    Source.joins(
      Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(z['source_id'].eq(p['id'])))
    )
  end

  # @params target [String] a citable model name
  # @return [Hash] sources optimized for user selection
  def self.select_optimized(user_id, project_id, target = 'TaxonName')
    h = {
      quick: [],
      pinboard: Source.pinned_by(user_id).where(pinboard_items: {project_id: project_id}).to_a
    }

    h[:recent] = (
      Source.joins(:citations)
      .where( citations: { project_id: project_id, updated_by_id: user_id } )
      .used_recently(target)
      .limit(5).distinct.to_a +
    Source.where(created_by_id: user_id, updated_at: 2.hours.ago..Time.now )
      .order('created_at DESC')
      .limit(5).to_a
    ).uniq

    h[:recent] ||= []

    h[:quick] = ( Source.pinned_by(user_id).pinboard_inserted.where(pinboard_items: {project_id: project_id}).to_a + h[:recent][0..3]).uniq
    h
  end

  # @return [Array]
  #    objects this source is linked to through citations
  def cited_objects
    self.citations.collect { |t| t.citation_object }
  end

  # @return [Boolean]
  def is_bibtex?
    type == 'Source::Bibtex'
  end

  # @return [Boolean]
  def is_in_project?(project_id)
    projects.where(id: project_id).any?
  end

  # @return [Source, false]
  def clone
    s = dup
    m = "[CLONE of #{id}] "
    begin
      Source.transaction do |t|
        roles.each do |r|
          s.roles << Role.new(person: r.person, type: r.type, position: r.position )
        end

        case type
        when 'Source::Verbatim'
          s.verbatim = m + verbatim
        when 'Source::Bibtex'
          s.title = m + title
        end

        s.save!
      end
    rescue ActiveRecord::RecordInvalid
    end
    s
  end

  protected

  # Defined in subclasses
  # @return [Nil]
  def set_cached
  end

  # @param [Hash] attributed
  # @return [Boolean]
  def reject_project_sources(attributed)
    return true if attributed['project_id'].blank?
    return true if ProjectSource.where(project_id: attributed['project_id'], source_id: id).any?
  end

  def validate_year_suffix
      a = get_author 
    unless year_suffix.blank? || year.blank? || a.blank?
      if new_record?
        s = Source.where(author: a, year: year, year_suffix: year_suffix).first
      else
        s = Source.where(author: a, year: year, year_suffix: year_suffix).not_self(self).first
      end
      errors.add(:year_suffix, " '#{year_suffix}' is already used for #{a} #{year}") unless s.nil?
    end
  end

end

#howpublishedString

Returns:


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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'app/models/source.rb', line 188

class Source < ApplicationRecord
  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::AlternateValues
  include Shared::DataAttributes
  include Shared::Identifiers
  include Shared::Notes
  include Shared::SharedAcrossProjects
  include Shared::Tags
  include Shared::Documentation
  include Shared::HasRoles
  include Shared::IsData
  include Shared::HasPapertrail
  include SoftValidation

  ignore_whitespace_on(:verbatim_contents)

  ALTERNATE_VALUES_FOR = [:address, :annote, :booktitle, :edition, :editor, :institution, :journal, :note, :organization,
                          :publisher, :school, :title, :doi, :abstract, :language, :translator, :author, :url].freeze

  # @return [Boolean]
  #  When true, cached values are not built
  attr_accessor :no_year_suffix_validation

  # Keep this order for citations/topics
  has_many :citations, inverse_of: :source, dependent: :restrict_with_error
  has_many :citation_topics, through: :citations, inverse_of: :source
  has_many :topics, through: :citation_topics, inverse_of: :sources

  # !! must be below has_many :citations
  has_many :asserted_distributions, through: :citations, source: :citation_object, source_type: 'AssertedDistribution'

  has_many :project_sources, dependent: :destroy
  has_many :projects, through: :project_sources

  after_save :set_cached

  validates_presence_of :type
  validates :type, inclusion: {in: ['Source::Bibtex', 'Source::Human', 'Source::Verbatim']} # TODO: not needed
  validate :validate_year_suffix, unless: -> { self.no_year_suffix_validation || (self.type != 'Source::Bibtex') }

  accepts_nested_attributes_for :project_sources, reject_if: :reject_project_sources

  # Redirect type here
  # @param [String] file
  # @return [[Array, message]]
  #   TODO: return a more informative response?
  def self.batch_preview(file)
    begin
      bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
      sources = []
      bibliography.each do |record|
        a = Source::Bibtex.new_from_bibtex(record)#        a.soft_validate() # why?

        sources.push(a)
      end
      return sources, nil
    rescue BibTeX::ParseError => e
      return [], e.message
    rescue
      raise
    end
  end

  # @param [String] file
  # @return [Array, Boolean]
  def self.batch_create(file)
    sources = []
    valid = 0
    begin
      # error_msg = []
      Source.transaction do
        bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
        bibliography.each do |record|
          a = Source::Bibtex.new_from_bibtex(record)
          if a.valid?
            if a.save
              valid += 1
            end#            a.soft_validate()

          else
            # error_msg = a.errors.messages.to_s
          end
          sources.push(a)
        end
      end
    rescue
      return false
    end
    return {records: sources, count: valid}
  end

  # @param used_on [String] a model name 
  # @return [Scope]
  #    the max 10 most recently used (1 week, could parameterize) TaxonName, as used 
  def self.used_recently(used_on = 'TaxonName')
    t = Citation.arel_table
    p = Source.arel_table

    # i is a select manager
    i = t.project(t['source_id'], t['created_at']).from(t)
      .where(t['created_at'].gt(1.weeks.ago))
      .where(t['citation_object_type'].eq(used_on))
      .order(t['created_at'])
      .take(10)
      .distinct

    # z is a table alias
    z = i.as('recent_t')

    Source.joins(
      Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(z['source_id'].eq(p['id'])))
    )
  end

  # @params target [String] a citable model name
  # @return [Hash] sources optimized for user selection
  def self.select_optimized(user_id, project_id, target = 'TaxonName')
    h = {
      quick: [],
      pinboard: Source.pinned_by(user_id).where(pinboard_items: {project_id: project_id}).to_a
    }

    h[:recent] = (
      Source.joins(:citations)
      .where( citations: { project_id: project_id, updated_by_id: user_id } )
      .used_recently(target)
      .limit(5).distinct.to_a +
    Source.where(created_by_id: user_id, updated_at: 2.hours.ago..Time.now )
      .order('created_at DESC')
      .limit(5).to_a
    ).uniq

    h[:recent] ||= []

    h[:quick] = ( Source.pinned_by(user_id).pinboard_inserted.where(pinboard_items: {project_id: project_id}).to_a + h[:recent][0..3]).uniq
    h
  end

  # @return [Array]
  #    objects this source is linked to through citations
  def cited_objects
    self.citations.collect { |t| t.citation_object }
  end

  # @return [Boolean]
  def is_bibtex?
    type == 'Source::Bibtex'
  end

  # @return [Boolean]
  def is_in_project?(project_id)
    projects.where(id: project_id).any?
  end

  # @return [Source, false]
  def clone
    s = dup
    m = "[CLONE of #{id}] "
    begin
      Source.transaction do |t|
        roles.each do |r|
          s.roles << Role.new(person: r.person, type: r.type, position: r.position )
        end

        case type
        when 'Source::Verbatim'
          s.verbatim = m + verbatim
        when 'Source::Bibtex'
          s.title = m + title
        end

        s.save!
      end
    rescue ActiveRecord::RecordInvalid
    end
    s
  end

  protected

  # Defined in subclasses
  # @return [Nil]
  def set_cached
  end

  # @param [Hash] attributed
  # @return [Boolean]
  def reject_project_sources(attributed)
    return true if attributed['project_id'].blank?
    return true if ProjectSource.where(project_id: attributed['project_id'], source_id: id).any?
  end

  def validate_year_suffix
      a = get_author 
    unless year_suffix.blank? || year.blank? || a.blank?
      if new_record?
        s = Source.where(author: a, year: year, year_suffix: year_suffix).first
      else
        s = Source.where(author: a, year: year, year_suffix: year_suffix).not_self(self).first
      end
      errors.add(:year_suffix, " '#{year_suffix}' is already used for #{a} #{year}") unless s.nil?
    end
  end

end

#institutionString

Returns:


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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'app/models/source.rb', line 188

class Source < ApplicationRecord
  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::AlternateValues
  include Shared::DataAttributes
  include Shared::Identifiers
  include Shared::Notes
  include Shared::SharedAcrossProjects
  include Shared::Tags
  include Shared::Documentation
  include Shared::HasRoles
  include Shared::IsData
  include Shared::HasPapertrail
  include SoftValidation

  ignore_whitespace_on(:verbatim_contents)

  ALTERNATE_VALUES_FOR = [:address, :annote, :booktitle, :edition, :editor, :institution, :journal, :note, :organization,
                          :publisher, :school, :title, :doi, :abstract, :language, :translator, :author, :url].freeze

  # @return [Boolean]
  #  When true, cached values are not built
  attr_accessor :no_year_suffix_validation

  # Keep this order for citations/topics
  has_many :citations, inverse_of: :source, dependent: :restrict_with_error
  has_many :citation_topics, through: :citations, inverse_of: :source
  has_many :topics, through: :citation_topics, inverse_of: :sources

  # !! must be below has_many :citations
  has_many :asserted_distributions, through: :citations, source: :citation_object, source_type: 'AssertedDistribution'

  has_many :project_sources, dependent: :destroy
  has_many :projects, through: :project_sources

  after_save :set_cached

  validates_presence_of :type
  validates :type, inclusion: {in: ['Source::Bibtex', 'Source::Human', 'Source::Verbatim']} # TODO: not needed
  validate :validate_year_suffix, unless: -> { self.no_year_suffix_validation || (self.type != 'Source::Bibtex') }

  accepts_nested_attributes_for :project_sources, reject_if: :reject_project_sources

  # Redirect type here
  # @param [String] file
  # @return [[Array, message]]
  #   TODO: return a more informative response?
  def self.batch_preview(file)
    begin
      bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
      sources = []
      bibliography.each do |record|
        a = Source::Bibtex.new_from_bibtex(record)#        a.soft_validate() # why?

        sources.push(a)
      end
      return sources, nil
    rescue BibTeX::ParseError => e
      return [], e.message
    rescue
      raise
    end
  end

  # @param [String] file
  # @return [Array, Boolean]
  def self.batch_create(file)
    sources = []
    valid = 0
    begin
      # error_msg = []
      Source.transaction do
        bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
        bibliography.each do |record|
          a = Source::Bibtex.new_from_bibtex(record)
          if a.valid?
            if a.save
              valid += 1
            end#            a.soft_validate()

          else
            # error_msg = a.errors.messages.to_s
          end
          sources.push(a)
        end
      end
    rescue
      return false
    end
    return {records: sources, count: valid}
  end

  # @param used_on [String] a model name 
  # @return [Scope]
  #    the max 10 most recently used (1 week, could parameterize) TaxonName, as used 
  def self.used_recently(used_on = 'TaxonName')
    t = Citation.arel_table
    p = Source.arel_table

    # i is a select manager
    i = t.project(t['source_id'], t['created_at']).from(t)
      .where(t['created_at'].gt(1.weeks.ago))
      .where(t['citation_object_type'].eq(used_on))
      .order(t['created_at'])
      .take(10)
      .distinct

    # z is a table alias
    z = i.as('recent_t')

    Source.joins(
      Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(z['source_id'].eq(p['id'])))
    )
  end

  # @params target [String] a citable model name
  # @return [Hash] sources optimized for user selection
  def self.select_optimized(user_id, project_id, target = 'TaxonName')
    h = {
      quick: [],
      pinboard: Source.pinned_by(user_id).where(pinboard_items: {project_id: project_id}).to_a
    }

    h[:recent] = (
      Source.joins(:citations)
      .where( citations: { project_id: project_id, updated_by_id: user_id } )
      .used_recently(target)
      .limit(5).distinct.to_a +
    Source.where(created_by_id: user_id, updated_at: 2.hours.ago..Time.now )
      .order('created_at DESC')
      .limit(5).to_a
    ).uniq

    h[:recent] ||= []

    h[:quick] = ( Source.pinned_by(user_id).pinboard_inserted.where(pinboard_items: {project_id: project_id}).to_a + h[:recent][0..3]).uniq
    h
  end

  # @return [Array]
  #    objects this source is linked to through citations
  def cited_objects
    self.citations.collect { |t| t.citation_object }
  end

  # @return [Boolean]
  def is_bibtex?
    type == 'Source::Bibtex'
  end

  # @return [Boolean]
  def is_in_project?(project_id)
    projects.where(id: project_id).any?
  end

  # @return [Source, false]
  def clone
    s = dup
    m = "[CLONE of #{id}] "
    begin
      Source.transaction do |t|
        roles.each do |r|
          s.roles << Role.new(person: r.person, type: r.type, position: r.position )
        end

        case type
        when 'Source::Verbatim'
          s.verbatim = m + verbatim
        when 'Source::Bibtex'
          s.title = m + title
        end

        s.save!
      end
    rescue ActiveRecord::RecordInvalid
    end
    s
  end

  protected

  # Defined in subclasses
  # @return [Nil]
  def set_cached
  end

  # @param [Hash] attributed
  # @return [Boolean]
  def reject_project_sources(attributed)
    return true if attributed['project_id'].blank?
    return true if ProjectSource.where(project_id: attributed['project_id'], source_id: id).any?
  end

  def validate_year_suffix
      a = get_author 
    unless year_suffix.blank? || year.blank? || a.blank?
      if new_record?
        s = Source.where(author: a, year: year, year_suffix: year_suffix).first
      else
        s = Source.where(author: a, year: year, year_suffix: year_suffix).not_self(self).first
      end
      errors.add(:year_suffix, " '#{year_suffix}' is already used for #{a} #{year}") unless s.nil?
    end
  end

end

#isbnString

TODO:

Returns:

  • (String)

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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'app/models/source.rb', line 188

class Source < ApplicationRecord
  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::AlternateValues
  include Shared::DataAttributes
  include Shared::Identifiers
  include Shared::Notes
  include Shared::SharedAcrossProjects
  include Shared::Tags
  include Shared::Documentation
  include Shared::HasRoles
  include Shared::IsData
  include Shared::HasPapertrail
  include SoftValidation

  ignore_whitespace_on(:verbatim_contents)

  ALTERNATE_VALUES_FOR = [:address, :annote, :booktitle, :edition, :editor, :institution, :journal, :note, :organization,
                          :publisher, :school, :title, :doi, :abstract, :language, :translator, :author, :url].freeze

  # @return [Boolean]
  #  When true, cached values are not built
  attr_accessor :no_year_suffix_validation

  # Keep this order for citations/topics
  has_many :citations, inverse_of: :source, dependent: :restrict_with_error
  has_many :citation_topics, through: :citations, inverse_of: :source
  has_many :topics, through: :citation_topics, inverse_of: :sources

  # !! must be below has_many :citations
  has_many :asserted_distributions, through: :citations, source: :citation_object, source_type: 'AssertedDistribution'

  has_many :project_sources, dependent: :destroy
  has_many :projects, through: :project_sources

  after_save :set_cached

  validates_presence_of :type
  validates :type, inclusion: {in: ['Source::Bibtex', 'Source::Human', 'Source::Verbatim']} # TODO: not needed
  validate :validate_year_suffix, unless: -> { self.no_year_suffix_validation || (self.type != 'Source::Bibtex') }

  accepts_nested_attributes_for :project_sources, reject_if: :reject_project_sources

  # Redirect type here
  # @param [String] file
  # @return [[Array, message]]
  #   TODO: return a more informative response?
  def self.batch_preview(file)
    begin
      bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
      sources = []
      bibliography.each do |record|
        a = Source::Bibtex.new_from_bibtex(record)#        a.soft_validate() # why?

        sources.push(a)
      end
      return sources, nil
    rescue BibTeX::ParseError => e
      return [], e.message
    rescue
      raise
    end
  end

  # @param [String] file
  # @return [Array, Boolean]
  def self.batch_create(file)
    sources = []
    valid = 0
    begin
      # error_msg = []
      Source.transaction do
        bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
        bibliography.each do |record|
          a = Source::Bibtex.new_from_bibtex(record)
          if a.valid?
            if a.save
              valid += 1
            end#            a.soft_validate()

          else
            # error_msg = a.errors.messages.to_s
          end
          sources.push(a)
        end
      end
    rescue
      return false
    end
    return {records: sources, count: valid}
  end

  # @param used_on [String] a model name 
  # @return [Scope]
  #    the max 10 most recently used (1 week, could parameterize) TaxonName, as used 
  def self.used_recently(used_on = 'TaxonName')
    t = Citation.arel_table
    p = Source.arel_table

    # i is a select manager
    i = t.project(t['source_id'], t['created_at']).from(t)
      .where(t['created_at'].gt(1.weeks.ago))
      .where(t['citation_object_type'].eq(used_on))
      .order(t['created_at'])
      .take(10)
      .distinct

    # z is a table alias
    z = i.as('recent_t')

    Source.joins(
      Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(z['source_id'].eq(p['id'])))
    )
  end

  # @params target [String] a citable model name
  # @return [Hash] sources optimized for user selection
  def self.select_optimized(user_id, project_id, target = 'TaxonName')
    h = {
      quick: [],
      pinboard: Source.pinned_by(user_id).where(pinboard_items: {project_id: project_id}).to_a
    }

    h[:recent] = (
      Source.joins(:citations)
      .where( citations: { project_id: project_id, updated_by_id: user_id } )
      .used_recently(target)
      .limit(5).distinct.to_a +
    Source.where(created_by_id: user_id, updated_at: 2.hours.ago..Time.now )
      .order('created_at DESC')
      .limit(5).to_a
    ).uniq

    h[:recent] ||= []

    h[:quick] = ( Source.pinned_by(user_id).pinboard_inserted.where(pinboard_items: {project_id: project_id}).to_a + h[:recent][0..3]).uniq
    h
  end

  # @return [Array]
  #    objects this source is linked to through citations
  def cited_objects
    self.citations.collect { |t| t.citation_object }
  end

  # @return [Boolean]
  def is_bibtex?
    type == 'Source::Bibtex'
  end

  # @return [Boolean]
  def is_in_project?(project_id)
    projects.where(id: project_id).any?
  end

  # @return [Source, false]
  def clone
    s = dup
    m = "[CLONE of #{id}] "
    begin
      Source.transaction do |t|
        roles.each do |r|
          s.roles << Role.new(person: r.person, type: r.type, position: r.position )
        end

        case type
        when 'Source::Verbatim'
          s.verbatim = m + verbatim
        when 'Source::Bibtex'
          s.title = m + title
        end

        s.save!
      end
    rescue ActiveRecord::RecordInvalid
    end
    s
  end

  protected

  # Defined in subclasses
  # @return [Nil]
  def set_cached
  end

  # @param [Hash] attributed
  # @return [Boolean]
  def reject_project_sources(attributed)
    return true if attributed['project_id'].blank?
    return true if ProjectSource.where(project_id: attributed['project_id'], source_id: id).any?
  end

  def validate_year_suffix
      a = get_author 
    unless year_suffix.blank? || year.blank? || a.blank?
      if new_record?
        s = Source.where(author: a, year: year, year_suffix: year_suffix).first
      else
        s = Source.where(author: a, year: year, year_suffix: year_suffix).not_self(self).first
      end
      errors.add(:year_suffix, " '#{year_suffix}' is already used for #{a} #{year}") unless s.nil?
    end
  end

end

#issnString

TODO:

Returns:

  • (String)

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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'app/models/source.rb', line 188

class Source < ApplicationRecord
  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::AlternateValues
  include Shared::DataAttributes
  include Shared::Identifiers
  include Shared::Notes
  include Shared::SharedAcrossProjects
  include Shared::Tags
  include Shared::Documentation
  include Shared::HasRoles
  include Shared::IsData
  include Shared::HasPapertrail
  include SoftValidation

  ignore_whitespace_on(:verbatim_contents)

  ALTERNATE_VALUES_FOR = [:address, :annote, :booktitle, :edition, :editor, :institution, :journal, :note, :organization,
                          :publisher, :school, :title, :doi, :abstract, :language, :translator, :author, :url].freeze

  # @return [Boolean]
  #  When true, cached values are not built
  attr_accessor :no_year_suffix_validation

  # Keep this order for citations/topics
  has_many :citations, inverse_of: :source, dependent: :restrict_with_error
  has_many :citation_topics, through: :citations, inverse_of: :source
  has_many :topics, through: :citation_topics, inverse_of: :sources

  # !! must be below has_many :citations
  has_many :asserted_distributions, through: :citations, source: :citation_object, source_type: 'AssertedDistribution'

  has_many :project_sources, dependent: :destroy
  has_many :projects, through: :project_sources

  after_save :set_cached

  validates_presence_of :type
  validates :type, inclusion: {in: ['Source::Bibtex', 'Source::Human', 'Source::Verbatim']} # TODO: not needed
  validate :validate_year_suffix, unless: -> { self.no_year_suffix_validation || (self.type != 'Source::Bibtex') }

  accepts_nested_attributes_for :project_sources, reject_if: :reject_project_sources

  # Redirect type here
  # @param [String] file
  # @return [[Array, message]]
  #   TODO: return a more informative response?
  def self.batch_preview(file)
    begin
      bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
      sources = []
      bibliography.each do |record|
        a = Source::Bibtex.new_from_bibtex(record)#        a.soft_validate() # why?

        sources.push(a)
      end
      return sources, nil
    rescue BibTeX::ParseError => e
      return [], e.message
    rescue
      raise
    end
  end

  # @param [String] file
  # @return [Array, Boolean]
  def self.batch_create(file)
    sources = []
    valid = 0
    begin
      # error_msg = []
      Source.transaction do
        bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
        bibliography.each do |record|
          a = Source::Bibtex.new_from_bibtex(record)
          if a.valid?
            if a.save
              valid += 1
            end#            a.soft_validate()

          else
            # error_msg = a.errors.messages.to_s
          end
          sources.push(a)
        end
      end
    rescue
      return false
    end
    return {records: sources, count: valid}
  end

  # @param used_on [String] a model name 
  # @return [Scope]
  #    the max 10 most recently used (1 week, could parameterize) TaxonName, as used 
  def self.used_recently(used_on = 'TaxonName')
    t = Citation.arel_table
    p = Source.arel_table

    # i is a select manager
    i = t.project(t['source_id'], t['created_at']).from(t)
      .where(t['created_at'].gt(1.weeks.ago))
      .where(t['citation_object_type'].eq(used_on))
      .order(t['created_at'])
      .take(10)
      .distinct

    # z is a table alias
    z = i.as('recent_t')

    Source.joins(
      Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(z['source_id'].eq(p['id'])))
    )
  end

  # @params target [String] a citable model name
  # @return [Hash] sources optimized for user selection
  def self.select_optimized(user_id, project_id, target = 'TaxonName')
    h = {
      quick: [],
      pinboard: Source.pinned_by(user_id).where(pinboard_items: {project_id: project_id}).to_a
    }

    h[:recent] = (
      Source.joins(:citations)
      .where( citations: { project_id: project_id, updated_by_id: user_id } )
      .used_recently(target)
      .limit(5).distinct.to_a +
    Source.where(created_by_id: user_id, updated_at: 2.hours.ago..Time.now )
      .order('created_at DESC')
      .limit(5).to_a
    ).uniq

    h[:recent] ||= []

    h[:quick] = ( Source.pinned_by(user_id).pinboard_inserted.where(pinboard_items: {project_id: project_id}).to_a + h[:recent][0..3]).uniq
    h
  end

  # @return [Array]
  #    objects this source is linked to through citations
  def cited_objects
    self.citations.collect { |t| t.citation_object }
  end

  # @return [Boolean]
  def is_bibtex?
    type == 'Source::Bibtex'
  end

  # @return [Boolean]
  def is_in_project?(project_id)
    projects.where(id: project_id).any?
  end

  # @return [Source, false]
  def clone
    s = dup
    m = "[CLONE of #{id}] "
    begin
      Source.transaction do |t|
        roles.each do |r|
          s.roles << Role.new(person: r.person, type: r.type, position: r.position )
        end

        case type
        when 'Source::Verbatim'
          s.verbatim = m + verbatim
        when 'Source::Bibtex'
          s.title = m + title
        end

        s.save!
      end
    rescue ActiveRecord::RecordInvalid
    end
    s
  end

  protected

  # Defined in subclasses
  # @return [Nil]
  def set_cached
  end

  # @param [Hash] attributed
  # @return [Boolean]
  def reject_project_sources(attributed)
    return true if attributed['project_id'].blank?
    return true if ProjectSource.where(project_id: attributed['project_id'], source_id: id).any?
  end

  def validate_year_suffix
      a = get_author 
    unless year_suffix.blank? || year.blank? || a.blank?
      if new_record?
        s = Source.where(author: a, year: year, year_suffix: year_suffix).first
      else
        s = Source.where(author: a, year: year, year_suffix: year_suffix).not_self(self).first
      end
      errors.add(:year_suffix, " '#{year_suffix}' is already used for #{a} #{year}") unless s.nil?
    end
  end

end

#journalString

Returns:


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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'app/models/source.rb', line 188

class Source < ApplicationRecord
  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::AlternateValues
  include Shared::DataAttributes
  include Shared::Identifiers
  include Shared::Notes
  include Shared::SharedAcrossProjects
  include Shared::Tags
  include Shared::Documentation
  include Shared::HasRoles
  include Shared::IsData
  include Shared::HasPapertrail
  include SoftValidation

  ignore_whitespace_on(:verbatim_contents)

  ALTERNATE_VALUES_FOR = [:address, :annote, :booktitle, :edition, :editor, :institution, :journal, :note, :organization,
                          :publisher, :school, :title, :doi, :abstract, :language, :translator, :author, :url].freeze

  # @return [Boolean]
  #  When true, cached values are not built
  attr_accessor :no_year_suffix_validation

  # Keep this order for citations/topics
  has_many :citations, inverse_of: :source, dependent: :restrict_with_error
  has_many :citation_topics, through: :citations, inverse_of: :source
  has_many :topics, through: :citation_topics, inverse_of: :sources

  # !! must be below has_many :citations
  has_many :asserted_distributions, through: :citations, source: :citation_object, source_type: 'AssertedDistribution'

  has_many :project_sources, dependent: :destroy
  has_many :projects, through: :project_sources

  after_save :set_cached

  validates_presence_of :type
  validates :type, inclusion: {in: ['Source::Bibtex', 'Source::Human', 'Source::Verbatim']} # TODO: not needed
  validate :validate_year_suffix, unless: -> { self.no_year_suffix_validation || (self.type != 'Source::Bibtex') }

  accepts_nested_attributes_for :project_sources, reject_if: :reject_project_sources

  # Redirect type here
  # @param [String] file
  # @return [[Array, message]]
  #   TODO: return a more informative response?
  def self.batch_preview(file)
    begin
      bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
      sources = []
      bibliography.each do |record|
        a = Source::Bibtex.new_from_bibtex(record)#        a.soft_validate() # why?

        sources.push(a)
      end
      return sources, nil
    rescue BibTeX::ParseError => e
      return [], e.message
    rescue
      raise
    end
  end

  # @param [String] file
  # @return [Array, Boolean]
  def self.batch_create(file)
    sources = []
    valid = 0
    begin
      # error_msg = []
      Source.transaction do
        bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
        bibliography.each do |record|
          a = Source::Bibtex.new_from_bibtex(record)
          if a.valid?
            if a.save
              valid += 1
            end#            a.soft_validate()

          else
            # error_msg = a.errors.messages.to_s
          end
          sources.push(a)
        end
      end
    rescue
      return false
    end
    return {records: sources, count: valid}
  end

  # @param used_on [String] a model name 
  # @return [Scope]
  #    the max 10 most recently used (1 week, could parameterize) TaxonName, as used 
  def self.used_recently(used_on = 'TaxonName')
    t = Citation.arel_table
    p = Source.arel_table

    # i is a select manager
    i = t.project(t['source_id'], t['created_at']).from(t)
      .where(t['created_at'].gt(1.weeks.ago))
      .where(t['citation_object_type'].eq(used_on))
      .order(t['created_at'])
      .take(10)
      .distinct

    # z is a table alias
    z = i.as('recent_t')

    Source.joins(
      Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(z['source_id'].eq(p['id'])))
    )
  end

  # @params target [String] a citable model name
  # @return [Hash] sources optimized for user selection
  def self.select_optimized(user_id, project_id, target = 'TaxonName')
    h = {
      quick: [],
      pinboard: Source.pinned_by(user_id).where(pinboard_items: {project_id: project_id}).to_a
    }

    h[:recent] = (
      Source.joins(:citations)
      .where( citations: { project_id: project_id, updated_by_id: user_id } )
      .used_recently(target)
      .limit(5).distinct.to_a +
    Source.where(created_by_id: user_id, updated_at: 2.hours.ago..Time.now )
      .order('created_at DESC')
      .limit(5).to_a
    ).uniq

    h[:recent] ||= []

    h[:quick] = ( Source.pinned_by(user_id).pinboard_inserted.where(pinboard_items: {project_id: project_id}).to_a + h[:recent][0..3]).uniq
    h
  end

  # @return [Array]
  #    objects this source is linked to through citations
  def cited_objects
    self.citations.collect { |t| t.citation_object }
  end

  # @return [Boolean]
  def is_bibtex?
    type == 'Source::Bibtex'
  end

  # @return [Boolean]
  def is_in_project?(project_id)
    projects.where(id: project_id).any?
  end

  # @return [Source, false]
  def clone
    s = dup
    m = "[CLONE of #{id}] "
    begin
      Source.transaction do |t|
        roles.each do |r|
          s.roles << Role.new(person: r.person, type: r.type, position: r.position )
        end

        case type
        when 'Source::Verbatim'
          s.verbatim = m + verbatim
        when 'Source::Bibtex'
          s.title = m + title
        end

        s.save!
      end
    rescue ActiveRecord::RecordInvalid
    end
    s
  end

  protected

  # Defined in subclasses
  # @return [Nil]
  def set_cached
  end

  # @param [Hash] attributed
  # @return [Boolean]
  def reject_project_sources(attributed)
    return true if attributed['project_id'].blank?
    return true if ProjectSource.where(project_id: attributed['project_id'], source_id: id).any?
  end

  def validate_year_suffix
      a = get_author 
    unless year_suffix.blank? || year.blank? || a.blank?
      if new_record?
        s = Source.where(author: a, year: year, year_suffix: year_suffix).first
      else
        s = Source.where(author: a, year: year, year_suffix: year_suffix).not_self(self).first
      end
      errors.add(:year_suffix, " '#{year_suffix}' is already used for #{a} #{year}") unless s.nil?
    end
  end

end

#keyString

Returns:


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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'app/models/source.rb', line 188

class Source < ApplicationRecord
  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::AlternateValues
  include Shared::DataAttributes
  include Shared::Identifiers
  include Shared::Notes
  include Shared::SharedAcrossProjects
  include Shared::Tags
  include Shared::Documentation
  include Shared::HasRoles
  include Shared::IsData
  include Shared::HasPapertrail
  include SoftValidation

  ignore_whitespace_on(:verbatim_contents)

  ALTERNATE_VALUES_FOR = [:address, :annote, :booktitle, :edition, :editor, :institution, :journal, :note, :organization,
                          :publisher, :school, :title, :doi, :abstract, :language, :translator, :author, :url].freeze

  # @return [Boolean]
  #  When true, cached values are not built
  attr_accessor :no_year_suffix_validation

  # Keep this order for citations/topics
  has_many :citations, inverse_of: :source, dependent: :restrict_with_error
  has_many :citation_topics, through: :citations, inverse_of: :source
  has_many :topics, through: :citation_topics, inverse_of: :sources

  # !! must be below has_many :citations
  has_many :asserted_distributions, through: :citations, source: :citation_object, source_type: 'AssertedDistribution'

  has_many :project_sources, dependent: :destroy
  has_many :projects, through: :project_sources

  after_save :set_cached

  validates_presence_of :type
  validates :type, inclusion: {in: ['Source::Bibtex', 'Source::Human', 'Source::Verbatim']} # TODO: not needed
  validate :validate_year_suffix, unless: -> { self.no_year_suffix_validation || (self.type != 'Source::Bibtex') }

  accepts_nested_attributes_for :project_sources, reject_if: :reject_project_sources

  # Redirect type here
  # @param [String] file
  # @return [[Array, message]]
  #   TODO: return a more informative response?
  def self.batch_preview(file)
    begin
      bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
      sources = []
      bibliography.each do |record|
        a = Source::Bibtex.new_from_bibtex(record)#        a.soft_validate() # why?

        sources.push(a)
      end
      return sources, nil
    rescue BibTeX::ParseError => e
      return [], e.message
    rescue
      raise
    end
  end

  # @param [String] file
  # @return [Array, Boolean]
  def self.batch_create(file)
    sources = []
    valid = 0
    begin
      # error_msg = []
      Source.transaction do
        bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
        bibliography.each do |record|
          a = Source::Bibtex.new_from_bibtex(record)
          if a.valid?
            if a.save
              valid += 1
            end#            a.soft_validate()

          else
            # error_msg = a.errors.messages.to_s
          end
          sources.push(a)
        end
      end
    rescue
      return false
    end
    return {records: sources, count: valid}
  end

  # @param used_on [String] a model name 
  # @return [Scope]
  #    the max 10 most recently used (1 week, could parameterize) TaxonName, as used 
  def self.used_recently(used_on = 'TaxonName')
    t = Citation.arel_table
    p = Source.arel_table

    # i is a select manager
    i = t.project(t['source_id'], t['created_at']).from(t)
      .where(t['created_at'].gt(1.weeks.ago))
      .where(t['citation_object_type'].eq(used_on))
      .order(t['created_at'])
      .take(10)
      .distinct

    # z is a table alias
    z = i.as('recent_t')

    Source.joins(
      Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(z['source_id'].eq(p['id'])))
    )
  end

  # @params target [String] a citable model name
  # @return [Hash] sources optimized for user selection
  def self.select_optimized(user_id, project_id, target = 'TaxonName')
    h = {
      quick: [],
      pinboard: Source.pinned_by(user_id).where(pinboard_items: {project_id: project_id}).to_a
    }

    h[:recent] = (
      Source.joins(:citations)
      .where( citations: { project_id: project_id, updated_by_id: user_id } )
      .used_recently(target)
      .limit(5).distinct.to_a +
    Source.where(created_by_id: user_id, updated_at: 2.hours.ago..Time.now )
      .order('created_at DESC')
      .limit(5).to_a
    ).uniq

    h[:recent] ||= []

    h[:quick] = ( Source.pinned_by(user_id).pinboard_inserted.where(pinboard_items: {project_id: project_id}).to_a + h[:recent][0..3]).uniq
    h
  end

  # @return [Array]
  #    objects this source is linked to through citations
  def cited_objects
    self.citations.collect { |t| t.citation_object }
  end

  # @return [Boolean]
  def is_bibtex?
    type == 'Source::Bibtex'
  end

  # @return [Boolean]
  def is_in_project?(project_id)
    projects.where(id: project_id).any?
  end

  # @return [Source, false]
  def clone
    s = dup
    m = "[CLONE of #{id}] "
    begin
      Source.transaction do |t|
        roles.each do |r|
          s.roles << Role.new(person: r.person, type: r.type, position: r.position )
        end

        case type
        when 'Source::Verbatim'
          s.verbatim = m + verbatim
        when 'Source::Bibtex'
          s.title = m + title
        end

        s.save!
      end
    rescue ActiveRecord::RecordInvalid
    end
    s
  end

  protected

  # Defined in subclasses
  # @return [Nil]
  def set_cached
  end

  # @param [Hash] attributed
  # @return [Boolean]
  def reject_project_sources(attributed)
    return true if attributed['project_id'].blank?
    return true if ProjectSource.where(project_id: attributed['project_id'], source_id: id).any?
  end

  def validate_year_suffix
      a = get_author 
    unless year_suffix.blank? || year.blank? || a.blank?
      if new_record?
        s = Source.where(author: a, year: year, year_suffix: year_suffix).first
      else
        s = Source.where(author: a, year: year, year_suffix: year_suffix).not_self(self).first
      end
      errors.add(:year_suffix, " '#{year_suffix}' is already used for #{a} #{year}") unless s.nil?
    end
  end

end

#languageString

Returns:


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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'app/models/source.rb', line 188

class Source < ApplicationRecord
  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::AlternateValues
  include Shared::DataAttributes
  include Shared::Identifiers
  include Shared::Notes
  include Shared::SharedAcrossProjects
  include Shared::Tags
  include Shared::Documentation
  include Shared::HasRoles
  include Shared::IsData
  include Shared::HasPapertrail
  include SoftValidation

  ignore_whitespace_on(:verbatim_contents)

  ALTERNATE_VALUES_FOR = [:address, :annote, :booktitle, :edition, :editor, :institution, :journal, :note, :organization,
                          :publisher, :school, :title, :doi, :abstract, :language, :translator, :author, :url].freeze

  # @return [Boolean]
  #  When true, cached values are not built
  attr_accessor :no_year_suffix_validation

  # Keep this order for citations/topics
  has_many :citations, inverse_of: :source, dependent: :restrict_with_error
  has_many :citation_topics, through: :citations, inverse_of: :source
  has_many :topics, through: :citation_topics, inverse_of: :sources

  # !! must be below has_many :citations
  has_many :asserted_distributions, through: :citations, source: :citation_object, source_type: 'AssertedDistribution'

  has_many :project_sources, dependent: :destroy
  has_many :projects, through: :project_sources

  after_save :set_cached

  validates_presence_of :type
  validates :type, inclusion: {in: ['Source::Bibtex', 'Source::Human', 'Source::Verbatim']} # TODO: not needed
  validate :validate_year_suffix, unless: -> { self.no_year_suffix_validation || (self.type != 'Source::Bibtex') }

  accepts_nested_attributes_for :project_sources, reject_if: :reject_project_sources

  # Redirect type here
  # @param [String] file
  # @return [[Array, message]]
  #   TODO: return a more informative response?
  def self.batch_preview(file)
    begin
      bibliography = BibTeX.parse(file.read.force_encoding('UTF-8'), filter: :latex)
      sources = []
      bibliography.each do |record|
        a = Source::Bibtex.new_from_bibtex(record)#        a.soft_validate() # why?

        sources.push(a)
      end
      return sources, nil
    rescue BibTeX::ParseError => e
      return [], e.message
    rescue
      raise
    end
  end

  # @param [String] file
  # @return [Array, Boolean]
  def self.batch_create(file)
    sources = []
    valid = 0
    begin
      # error_msg = []
      Source.transaction do
        bibliography = BibTeX.parse(file.read.