Class: Serial

Overview

Serial - represents a journal or other serial (repeated) publication. It follows the ISSN model for serials.

TODO handle translations (which are simultaneous)

Constant Summary collapse

ALTERNATE_VALUES_FOR =
[:name, :publisher, :place_published].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 Shared::HasPapertrail

#attribute_updated, #attribute_updater

Methods included from SoftValidation

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

Methods included from Shared::IsData

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

Methods included from Shared::Tags

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

Methods included from Shared::Identifiers

#dwc_occurrence_id, #identified?, #next_by_identifier, #previous_by_identifier, #reject_identifiers, #uri, #uuid

Methods included from Shared::Notes

#concatenated_notes_string, #reject_notes

Methods included from Shared::DataAttributes

#import_attributes, #internal_attributes, #keyword_value_hash, #reject_data_attributes

Methods included from Shared::AlternateValues

#all_values_for, #alternate_valued?

Methods included from Housekeeping::Users

#set_created_by_id, #set_updated_by_id

Methods inherited from ApplicationRecord

transaction_with_retry

Instance Attribute Details

#first_year_of_issueInteger

Returns the first year this serial was published.

Returns:

  • (Integer)

    the first year this serial was published



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

class Serial < ApplicationRecord

  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::AlternateValues
  include Shared::DataAttributes
  include Shared::Notes
  include Shared::Identifiers
  include Shared::Tags
  include Shared::IsData
  include SoftValidation
  include Shared::SharedAcrossProjects
  include Shared::HasPapertrail

  ALTERNATE_VALUES_FOR = [:name, :publisher, :place_published].freeze

  belongs_to :translated_from_serial, foreign_key: :translated_from_serial_id, class_name: 'Serial'
  belongs_to :language, foreign_key: :primary_language_id, inverse_of: :serials
  has_many :sources, class_name: 'Source::Bibtex', inverse_of: :serial, dependent: :restrict_with_error

  has_many :translations, foreign_key: :translated_from_serial_id, class_name: 'Serial', dependent: :destroy
  has_many :succeeding_serial_chronologies, foreign_key: :succeeding_serial_id, inverse_of: :succeeding_serial, class_name: 'SerialChronology', dependent: :restrict_with_error
  has_many :preceding_serial_chronologies, foreign_key: :preceding_serial_id, inverse_of: :preceding_serial, class_name: 'SerialChronology', dependent: :restrict_with_error

  # Single preceding chronology will be multiple serials if there is a merge
  has_many :immediately_preceding_serials, through: :succeeding_serial_chronologies, source: :preceding_serial

  # Single succeeding chronology will be multiple serials if there is a split
  has_many :immediately_succeeding_serials, through: :preceding_serial_chronologies, source: :succeeding_serial

  accepts_nested_attributes_for :alternate_values, reject_if: lambda { |av| av[:value].blank? }, allow_destroy: true

  validates_presence_of :name

  soft_validate(:sv_duplicate?)

  # Force self-referential relations to be included in unify(),
  # These would otherwise all be excluded because they reference 'class_name',
  # which triggers their elimination in the unify code base
  def unify_relations
    ApplicationEnumeration.klass_reflections(self.class).select{|a|
      [
        :translations,
        :sources,
        :succeeding_serial_chronologies,
        :preceding_serial_chronologies
      ].include?(a.name) }
  end

  # @param [String] compared_string
  # @param [String] column
  # @param [Integer] limit
  # @return [Scope]
  #   Levenshtein calculated related records per supplied column
  def nearest_by_levenshtein(compared_string = nil, column = 'name', limit = 10)
    return Serial.none if compared_string.blank?

    # Levenshtein in postgres requires all strings be 255 or fewer
    order_str = Serial.send(
      :sanitize_sql_for_conditions,
      ["levenshtein(Substring(serials.#{column} from 0 for 250), ?)",
       compared_string[0..250]])

    Serial.where('id <> ?', self.to_param)
      .order(Arel.sql(order_str))
      .limit(limit)
  end

  # @return [Boolean]
  #   is there another serial with the same name?  Also checkes alternate values.
  def duplicate?
    # ret_val = false
    if self.new_record?
      ret_val = Serial.exists?(name: self.name)
    else
      name_str = ActiveRecord::Base.send(
        :sanitize_sql_array,
        ['name = ? AND NOT (id = ?)',
         Utilities::Strings.escape_single_quote(self.name),
         self.id])
      ret_val  = Serial.where(name_str).to_a.size > 0
    end

    if ret_val == false
      # check if there is another alternate value with the same name
      a = Serial.with_alternate_value_on(:name, self.name)
      # select alternate value based on alternate_value_object class, alternate_value_object_attribute(column) & value
      if a.count > 0
        ret_val = true
      end
    end
    ret_val
  end

  # @param [Serial] start_serial
  # @return [Array]
  def all_previous(start_serial = self)
    # provides an array of all previous incarnations of me

    out_array = []
    start_serial.immediately_preceding_serials.order(:name).each do |serial|
      out_array.push(serial)
      prev = all_previous(serial)

      out_array.push(prev) unless prev.empty?
    end
    return out_array
  end

  # @param [Serial] start_serial
  # @return [Array]
  def all_succeeding(start_serial = self)
    # provides an array of all succeeding incarnations of me
    out_array = []
    start_serial.immediately_succeeding_serials.order(:name).each do |serial|
      out_array.push(serial)
      succeeding = all_succeeding(serial)

      out_array.push(succeeding) unless succeeding.empty?
    end
    out_array
  end

  def self.used_recently(user_id)
    t = Source.arel_table
    p = Serial.arel_table

    # i is a select manager
    i = t.project(t['serial_id'], t['updated_at']).from(t)
      .where(t['updated_at'].gt(1.months.ago))
      .where(t['updated_by_id'].eq(user_id))
      .order(t['updated_at'].desc)

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

    Serial.joins(
      Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(z['serial_id'].eq(p['id'])))
    ).pluck(:serial_id).uniq
  end

  def self.select_optimized(user_id, project_id)
    r = used_recently(user_id)
    h = {
      recent: (
        Serial.where('"serials"."id" IN (?)', r.first(10) ).order(:name).to_a +
        Serial.where(created_by_id: user_id, created_at: 3.hours.ago..Time.now).limit(5).to_a).uniq,
      pinboard: Serial.pinned_by(user_id).pinned_in_project(project_id).to_a
    }

    h[:quick] = (Serial.pinned_by(user_id).pinboard_inserted.pinned_in_project(project_id).to_a +
                 Serial.where('"serials"."id" IN (?)', r.first(4) ).order(:name).to_a).uniq
    h
  end

  protected

  # @return [Boolean]
  def sv_duplicate?
    if self.duplicate?
      soft_validations.add(:name, 'There is another serial with this name in the database.')
    end
    # TODO soft validation of name matching an alternate value for name of a different serial
  end

end

#last_year_of_issueInteger

Returns the last year this serial was published.

Returns:

  • (Integer)

    the last year this serial was published



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

class Serial < ApplicationRecord

  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::AlternateValues
  include Shared::DataAttributes
  include Shared::Notes
  include Shared::Identifiers
  include Shared::Tags
  include Shared::IsData
  include SoftValidation
  include Shared::SharedAcrossProjects
  include Shared::HasPapertrail

  ALTERNATE_VALUES_FOR = [:name, :publisher, :place_published].freeze

  belongs_to :translated_from_serial, foreign_key: :translated_from_serial_id, class_name: 'Serial'
  belongs_to :language, foreign_key: :primary_language_id, inverse_of: :serials
  has_many :sources, class_name: 'Source::Bibtex', inverse_of: :serial, dependent: :restrict_with_error

  has_many :translations, foreign_key: :translated_from_serial_id, class_name: 'Serial', dependent: :destroy
  has_many :succeeding_serial_chronologies, foreign_key: :succeeding_serial_id, inverse_of: :succeeding_serial, class_name: 'SerialChronology', dependent: :restrict_with_error
  has_many :preceding_serial_chronologies, foreign_key: :preceding_serial_id, inverse_of: :preceding_serial, class_name: 'SerialChronology', dependent: :restrict_with_error

  # Single preceding chronology will be multiple serials if there is a merge
  has_many :immediately_preceding_serials, through: :succeeding_serial_chronologies, source: :preceding_serial

  # Single succeeding chronology will be multiple serials if there is a split
  has_many :immediately_succeeding_serials, through: :preceding_serial_chronologies, source: :succeeding_serial

  accepts_nested_attributes_for :alternate_values, reject_if: lambda { |av| av[:value].blank? }, allow_destroy: true

  validates_presence_of :name

  soft_validate(:sv_duplicate?)

  # Force self-referential relations to be included in unify(),
  # These would otherwise all be excluded because they reference 'class_name',
  # which triggers their elimination in the unify code base
  def unify_relations
    ApplicationEnumeration.klass_reflections(self.class).select{|a|
      [
        :translations,
        :sources,
        :succeeding_serial_chronologies,
        :preceding_serial_chronologies
      ].include?(a.name) }
  end

  # @param [String] compared_string
  # @param [String] column
  # @param [Integer] limit
  # @return [Scope]
  #   Levenshtein calculated related records per supplied column
  def nearest_by_levenshtein(compared_string = nil, column = 'name', limit = 10)
    return Serial.none if compared_string.blank?

    # Levenshtein in postgres requires all strings be 255 or fewer
    order_str = Serial.send(
      :sanitize_sql_for_conditions,
      ["levenshtein(Substring(serials.#{column} from 0 for 250), ?)",
       compared_string[0..250]])

    Serial.where('id <> ?', self.to_param)
      .order(Arel.sql(order_str))
      .limit(limit)
  end

  # @return [Boolean]
  #   is there another serial with the same name?  Also checkes alternate values.
  def duplicate?
    # ret_val = false
    if self.new_record?
      ret_val = Serial.exists?(name: self.name)
    else
      name_str = ActiveRecord::Base.send(
        :sanitize_sql_array,
        ['name = ? AND NOT (id = ?)',
         Utilities::Strings.escape_single_quote(self.name),
         self.id])
      ret_val  = Serial.where(name_str).to_a.size > 0
    end

    if ret_val == false
      # check if there is another alternate value with the same name
      a = Serial.with_alternate_value_on(:name, self.name)
      # select alternate value based on alternate_value_object class, alternate_value_object_attribute(column) & value
      if a.count > 0
        ret_val = true
      end
    end
    ret_val
  end

  # @param [Serial] start_serial
  # @return [Array]
  def all_previous(start_serial = self)
    # provides an array of all previous incarnations of me

    out_array = []
    start_serial.immediately_preceding_serials.order(:name).each do |serial|
      out_array.push(serial)
      prev = all_previous(serial)

      out_array.push(prev) unless prev.empty?
    end
    return out_array
  end

  # @param [Serial] start_serial
  # @return [Array]
  def all_succeeding(start_serial = self)
    # provides an array of all succeeding incarnations of me
    out_array = []
    start_serial.immediately_succeeding_serials.order(:name).each do |serial|
      out_array.push(serial)
      succeeding = all_succeeding(serial)

      out_array.push(succeeding) unless succeeding.empty?
    end
    out_array
  end

  def self.used_recently(user_id)
    t = Source.arel_table
    p = Serial.arel_table

    # i is a select manager
    i = t.project(t['serial_id'], t['updated_at']).from(t)
      .where(t['updated_at'].gt(1.months.ago))
      .where(t['updated_by_id'].eq(user_id))
      .order(t['updated_at'].desc)

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

    Serial.joins(
      Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(z['serial_id'].eq(p['id'])))
    ).pluck(:serial_id).uniq
  end

  def self.select_optimized(user_id, project_id)
    r = used_recently(user_id)
    h = {
      recent: (
        Serial.where('"serials"."id" IN (?)', r.first(10) ).order(:name).to_a +
        Serial.where(created_by_id: user_id, created_at: 3.hours.ago..Time.now).limit(5).to_a).uniq,
      pinboard: Serial.pinned_by(user_id).pinned_in_project(project_id).to_a
    }

    h[:quick] = (Serial.pinned_by(user_id).pinboard_inserted.pinned_in_project(project_id).to_a +
                 Serial.where('"serials"."id" IN (?)', r.first(4) ).order(:name).to_a).uniq
    h
  end

  protected

  # @return [Boolean]
  def sv_duplicate?
    if self.duplicate?
      soft_validations.add(:name, 'There is another serial with this name in the database.')
    end
    # TODO soft validation of name matching an alternate value for name of a different serial
  end

end

#nameString

Returns the name of the serial.

Returns:

  • (String)

    the name of the serial



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

class Serial < ApplicationRecord

  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::AlternateValues
  include Shared::DataAttributes
  include Shared::Notes
  include Shared::Identifiers
  include Shared::Tags
  include Shared::IsData
  include SoftValidation
  include Shared::SharedAcrossProjects
  include Shared::HasPapertrail

  ALTERNATE_VALUES_FOR = [:name, :publisher, :place_published].freeze

  belongs_to :translated_from_serial, foreign_key: :translated_from_serial_id, class_name: 'Serial'
  belongs_to :language, foreign_key: :primary_language_id, inverse_of: :serials
  has_many :sources, class_name: 'Source::Bibtex', inverse_of: :serial, dependent: :restrict_with_error

  has_many :translations, foreign_key: :translated_from_serial_id, class_name: 'Serial', dependent: :destroy
  has_many :succeeding_serial_chronologies, foreign_key: :succeeding_serial_id, inverse_of: :succeeding_serial, class_name: 'SerialChronology', dependent: :restrict_with_error
  has_many :preceding_serial_chronologies, foreign_key: :preceding_serial_id, inverse_of: :preceding_serial, class_name: 'SerialChronology', dependent: :restrict_with_error

  # Single preceding chronology will be multiple serials if there is a merge
  has_many :immediately_preceding_serials, through: :succeeding_serial_chronologies, source: :preceding_serial

  # Single succeeding chronology will be multiple serials if there is a split
  has_many :immediately_succeeding_serials, through: :preceding_serial_chronologies, source: :succeeding_serial

  accepts_nested_attributes_for :alternate_values, reject_if: lambda { |av| av[:value].blank? }, allow_destroy: true

  validates_presence_of :name

  soft_validate(:sv_duplicate?)

  # Force self-referential relations to be included in unify(),
  # These would otherwise all be excluded because they reference 'class_name',
  # which triggers their elimination in the unify code base
  def unify_relations
    ApplicationEnumeration.klass_reflections(self.class).select{|a|
      [
        :translations,
        :sources,
        :succeeding_serial_chronologies,
        :preceding_serial_chronologies
      ].include?(a.name) }
  end

  # @param [String] compared_string
  # @param [String] column
  # @param [Integer] limit
  # @return [Scope]
  #   Levenshtein calculated related records per supplied column
  def nearest_by_levenshtein(compared_string = nil, column = 'name', limit = 10)
    return Serial.none if compared_string.blank?

    # Levenshtein in postgres requires all strings be 255 or fewer
    order_str = Serial.send(
      :sanitize_sql_for_conditions,
      ["levenshtein(Substring(serials.#{column} from 0 for 250), ?)",
       compared_string[0..250]])

    Serial.where('id <> ?', self.to_param)
      .order(Arel.sql(order_str))
      .limit(limit)
  end

  # @return [Boolean]
  #   is there another serial with the same name?  Also checkes alternate values.
  def duplicate?
    # ret_val = false
    if self.new_record?
      ret_val = Serial.exists?(name: self.name)
    else
      name_str = ActiveRecord::Base.send(
        :sanitize_sql_array,
        ['name = ? AND NOT (id = ?)',
         Utilities::Strings.escape_single_quote(self.name),
         self.id])
      ret_val  = Serial.where(name_str).to_a.size > 0
    end

    if ret_val == false
      # check if there is another alternate value with the same name
      a = Serial.with_alternate_value_on(:name, self.name)
      # select alternate value based on alternate_value_object class, alternate_value_object_attribute(column) & value
      if a.count > 0
        ret_val = true
      end
    end
    ret_val
  end

  # @param [Serial] start_serial
  # @return [Array]
  def all_previous(start_serial = self)
    # provides an array of all previous incarnations of me

    out_array = []
    start_serial.immediately_preceding_serials.order(:name).each do |serial|
      out_array.push(serial)
      prev = all_previous(serial)

      out_array.push(prev) unless prev.empty?
    end
    return out_array
  end

  # @param [Serial] start_serial
  # @return [Array]
  def all_succeeding(start_serial = self)
    # provides an array of all succeeding incarnations of me
    out_array = []
    start_serial.immediately_succeeding_serials.order(:name).each do |serial|
      out_array.push(serial)
      succeeding = all_succeeding(serial)

      out_array.push(succeeding) unless succeeding.empty?
    end
    out_array
  end

  def self.used_recently(user_id)
    t = Source.arel_table
    p = Serial.arel_table

    # i is a select manager
    i = t.project(t['serial_id'], t['updated_at']).from(t)
      .where(t['updated_at'].gt(1.months.ago))
      .where(t['updated_by_id'].eq(user_id))
      .order(t['updated_at'].desc)

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

    Serial.joins(
      Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(z['serial_id'].eq(p['id'])))
    ).pluck(:serial_id).uniq
  end

  def self.select_optimized(user_id, project_id)
    r = used_recently(user_id)
    h = {
      recent: (
        Serial.where('"serials"."id" IN (?)', r.first(10) ).order(:name).to_a +
        Serial.where(created_by_id: user_id, created_at: 3.hours.ago..Time.now).limit(5).to_a).uniq,
      pinboard: Serial.pinned_by(user_id).pinned_in_project(project_id).to_a
    }

    h[:quick] = (Serial.pinned_by(user_id).pinboard_inserted.pinned_in_project(project_id).to_a +
                 Serial.where('"serials"."id" IN (?)', r.first(4) ).order(:name).to_a).uniq
    h
  end

  protected

  # @return [Boolean]
  def sv_duplicate?
    if self.duplicate?
      soft_validations.add(:name, 'There is another serial with this name in the database.')
    end
    # TODO soft validation of name matching an alternate value for name of a different serial
  end

end

#place_publishedString

Returns The name of the place(s) where the serial is published.

Returns:

  • (String)

    The name of the place(s) where the serial is published.



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

class Serial < ApplicationRecord

  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::AlternateValues
  include Shared::DataAttributes
  include Shared::Notes
  include Shared::Identifiers
  include Shared::Tags
  include Shared::IsData
  include SoftValidation
  include Shared::SharedAcrossProjects
  include Shared::HasPapertrail

  ALTERNATE_VALUES_FOR = [:name, :publisher, :place_published].freeze

  belongs_to :translated_from_serial, foreign_key: :translated_from_serial_id, class_name: 'Serial'
  belongs_to :language, foreign_key: :primary_language_id, inverse_of: :serials
  has_many :sources, class_name: 'Source::Bibtex', inverse_of: :serial, dependent: :restrict_with_error

  has_many :translations, foreign_key: :translated_from_serial_id, class_name: 'Serial', dependent: :destroy
  has_many :succeeding_serial_chronologies, foreign_key: :succeeding_serial_id, inverse_of: :succeeding_serial, class_name: 'SerialChronology', dependent: :restrict_with_error
  has_many :preceding_serial_chronologies, foreign_key: :preceding_serial_id, inverse_of: :preceding_serial, class_name: 'SerialChronology', dependent: :restrict_with_error

  # Single preceding chronology will be multiple serials if there is a merge
  has_many :immediately_preceding_serials, through: :succeeding_serial_chronologies, source: :preceding_serial

  # Single succeeding chronology will be multiple serials if there is a split
  has_many :immediately_succeeding_serials, through: :preceding_serial_chronologies, source: :succeeding_serial

  accepts_nested_attributes_for :alternate_values, reject_if: lambda { |av| av[:value].blank? }, allow_destroy: true

  validates_presence_of :name

  soft_validate(:sv_duplicate?)

  # Force self-referential relations to be included in unify(),
  # These would otherwise all be excluded because they reference 'class_name',
  # which triggers their elimination in the unify code base
  def unify_relations
    ApplicationEnumeration.klass_reflections(self.class).select{|a|
      [
        :translations,
        :sources,
        :succeeding_serial_chronologies,
        :preceding_serial_chronologies
      ].include?(a.name) }
  end

  # @param [String] compared_string
  # @param [String] column
  # @param [Integer] limit
  # @return [Scope]
  #   Levenshtein calculated related records per supplied column
  def nearest_by_levenshtein(compared_string = nil, column = 'name', limit = 10)
    return Serial.none if compared_string.blank?

    # Levenshtein in postgres requires all strings be 255 or fewer
    order_str = Serial.send(
      :sanitize_sql_for_conditions,
      ["levenshtein(Substring(serials.#{column} from 0 for 250), ?)",
       compared_string[0..250]])

    Serial.where('id <> ?', self.to_param)
      .order(Arel.sql(order_str))
      .limit(limit)
  end

  # @return [Boolean]
  #   is there another serial with the same name?  Also checkes alternate values.
  def duplicate?
    # ret_val = false
    if self.new_record?
      ret_val = Serial.exists?(name: self.name)
    else
      name_str = ActiveRecord::Base.send(
        :sanitize_sql_array,
        ['name = ? AND NOT (id = ?)',
         Utilities::Strings.escape_single_quote(self.name),
         self.id])
      ret_val  = Serial.where(name_str).to_a.size > 0
    end

    if ret_val == false
      # check if there is another alternate value with the same name
      a = Serial.with_alternate_value_on(:name, self.name)
      # select alternate value based on alternate_value_object class, alternate_value_object_attribute(column) & value
      if a.count > 0
        ret_val = true
      end
    end
    ret_val
  end

  # @param [Serial] start_serial
  # @return [Array]
  def all_previous(start_serial = self)
    # provides an array of all previous incarnations of me

    out_array = []
    start_serial.immediately_preceding_serials.order(:name).each do |serial|
      out_array.push(serial)
      prev = all_previous(serial)

      out_array.push(prev) unless prev.empty?
    end
    return out_array
  end

  # @param [Serial] start_serial
  # @return [Array]
  def all_succeeding(start_serial = self)
    # provides an array of all succeeding incarnations of me
    out_array = []
    start_serial.immediately_succeeding_serials.order(:name).each do |serial|
      out_array.push(serial)
      succeeding = all_succeeding(serial)

      out_array.push(succeeding) unless succeeding.empty?
    end
    out_array
  end

  def self.used_recently(user_id)
    t = Source.arel_table
    p = Serial.arel_table

    # i is a select manager
    i = t.project(t['serial_id'], t['updated_at']).from(t)
      .where(t['updated_at'].gt(1.months.ago))
      .where(t['updated_by_id'].eq(user_id))
      .order(t['updated_at'].desc)

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

    Serial.joins(
      Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(z['serial_id'].eq(p['id'])))
    ).pluck(:serial_id).uniq
  end

  def self.select_optimized(user_id, project_id)
    r = used_recently(user_id)
    h = {
      recent: (
        Serial.where('"serials"."id" IN (?)', r.first(10) ).order(:name).to_a +
        Serial.where(created_by_id: user_id, created_at: 3.hours.ago..Time.now).limit(5).to_a).uniq,
      pinboard: Serial.pinned_by(user_id).pinned_in_project(project_id).to_a
    }

    h[:quick] = (Serial.pinned_by(user_id).pinboard_inserted.pinned_in_project(project_id).to_a +
                 Serial.where('"serials"."id" IN (?)', r.first(4) ).order(:name).to_a).uniq
    h
  end

  protected

  # @return [Boolean]
  def sv_duplicate?
    if self.duplicate?
      soft_validations.add(:name, 'There is another serial with this name in the database.')
    end
    # TODO soft validation of name matching an alternate value for name of a different serial
  end

end

#primary_language_idInteger

The id of the Language - language of this serial. According to the ISSN a new ISSN is minted for a journal that

changes languages.

Returns:

  • (Integer)


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

class Serial < ApplicationRecord

  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::AlternateValues
  include Shared::DataAttributes
  include Shared::Notes
  include Shared::Identifiers
  include Shared::Tags
  include Shared::IsData
  include SoftValidation
  include Shared::SharedAcrossProjects
  include Shared::HasPapertrail

  ALTERNATE_VALUES_FOR = [:name, :publisher, :place_published].freeze

  belongs_to :translated_from_serial, foreign_key: :translated_from_serial_id, class_name: 'Serial'
  belongs_to :language, foreign_key: :primary_language_id, inverse_of: :serials
  has_many :sources, class_name: 'Source::Bibtex', inverse_of: :serial, dependent: :restrict_with_error

  has_many :translations, foreign_key: :translated_from_serial_id, class_name: 'Serial', dependent: :destroy
  has_many :succeeding_serial_chronologies, foreign_key: :succeeding_serial_id, inverse_of: :succeeding_serial, class_name: 'SerialChronology', dependent: :restrict_with_error
  has_many :preceding_serial_chronologies, foreign_key: :preceding_serial_id, inverse_of: :preceding_serial, class_name: 'SerialChronology', dependent: :restrict_with_error

  # Single preceding chronology will be multiple serials if there is a merge
  has_many :immediately_preceding_serials, through: :succeeding_serial_chronologies, source: :preceding_serial

  # Single succeeding chronology will be multiple serials if there is a split
  has_many :immediately_succeeding_serials, through: :preceding_serial_chronologies, source: :succeeding_serial

  accepts_nested_attributes_for :alternate_values, reject_if: lambda { |av| av[:value].blank? }, allow_destroy: true

  validates_presence_of :name

  soft_validate(:sv_duplicate?)

  # Force self-referential relations to be included in unify(),
  # These would otherwise all be excluded because they reference 'class_name',
  # which triggers their elimination in the unify code base
  def unify_relations
    ApplicationEnumeration.klass_reflections(self.class).select{|a|
      [
        :translations,
        :sources,
        :succeeding_serial_chronologies,
        :preceding_serial_chronologies
      ].include?(a.name) }
  end

  # @param [String] compared_string
  # @param [String] column
  # @param [Integer] limit
  # @return [Scope]
  #   Levenshtein calculated related records per supplied column
  def nearest_by_levenshtein(compared_string = nil, column = 'name', limit = 10)
    return Serial.none if compared_string.blank?

    # Levenshtein in postgres requires all strings be 255 or fewer
    order_str = Serial.send(
      :sanitize_sql_for_conditions,
      ["levenshtein(Substring(serials.#{column} from 0 for 250), ?)",
       compared_string[0..250]])

    Serial.where('id <> ?', self.to_param)
      .order(Arel.sql(order_str))
      .limit(limit)
  end

  # @return [Boolean]
  #   is there another serial with the same name?  Also checkes alternate values.
  def duplicate?
    # ret_val = false
    if self.new_record?
      ret_val = Serial.exists?(name: self.name)
    else
      name_str = ActiveRecord::Base.send(
        :sanitize_sql_array,
        ['name = ? AND NOT (id = ?)',
         Utilities::Strings.escape_single_quote(self.name),
         self.id])
      ret_val  = Serial.where(name_str).to_a.size > 0
    end

    if ret_val == false
      # check if there is another alternate value with the same name
      a = Serial.with_alternate_value_on(:name, self.name)
      # select alternate value based on alternate_value_object class, alternate_value_object_attribute(column) & value
      if a.count > 0
        ret_val = true
      end
    end
    ret_val
  end

  # @param [Serial] start_serial
  # @return [Array]
  def all_previous(start_serial = self)
    # provides an array of all previous incarnations of me

    out_array = []
    start_serial.immediately_preceding_serials.order(:name).each do |serial|
      out_array.push(serial)
      prev = all_previous(serial)

      out_array.push(prev) unless prev.empty?
    end
    return out_array
  end

  # @param [Serial] start_serial
  # @return [Array]
  def all_succeeding(start_serial = self)
    # provides an array of all succeeding incarnations of me
    out_array = []
    start_serial.immediately_succeeding_serials.order(:name).each do |serial|
      out_array.push(serial)
      succeeding = all_succeeding(serial)

      out_array.push(succeeding) unless succeeding.empty?
    end
    out_array
  end

  def self.used_recently(user_id)
    t = Source.arel_table
    p = Serial.arel_table

    # i is a select manager
    i = t.project(t['serial_id'], t['updated_at']).from(t)
      .where(t['updated_at'].gt(1.months.ago))
      .where(t['updated_by_id'].eq(user_id))
      .order(t['updated_at'].desc)

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

    Serial.joins(
      Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(z['serial_id'].eq(p['id'])))
    ).pluck(:serial_id).uniq
  end

  def self.select_optimized(user_id, project_id)
    r = used_recently(user_id)
    h = {
      recent: (
        Serial.where('"serials"."id" IN (?)', r.first(10) ).order(:name).to_a +
        Serial.where(created_by_id: user_id, created_at: 3.hours.ago..Time.now).limit(5).to_a).uniq,
      pinboard: Serial.pinned_by(user_id).pinned_in_project(project_id).to_a
    }

    h[:quick] = (Serial.pinned_by(user_id).pinboard_inserted.pinned_in_project(project_id).to_a +
                 Serial.where('"serials"."id" IN (?)', r.first(4) ).order(:name).to_a).uniq
    h
  end

  protected

  # @return [Boolean]
  def sv_duplicate?
    if self.duplicate?
      soft_validations.add(:name, 'There is another serial with this name in the database.')
    end
    # TODO soft validation of name matching an alternate value for name of a different serial
  end

end

#publisherString

Returns the serial publisher.

Returns:

  • (String)

    the serial publisher



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

class Serial < ApplicationRecord

  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::AlternateValues
  include Shared::DataAttributes
  include Shared::Notes
  include Shared::Identifiers
  include Shared::Tags
  include Shared::IsData
  include SoftValidation
  include Shared::SharedAcrossProjects
  include Shared::HasPapertrail

  ALTERNATE_VALUES_FOR = [:name, :publisher, :place_published].freeze

  belongs_to :translated_from_serial, foreign_key: :translated_from_serial_id, class_name: 'Serial'
  belongs_to :language, foreign_key: :primary_language_id, inverse_of: :serials
  has_many :sources, class_name: 'Source::Bibtex', inverse_of: :serial, dependent: :restrict_with_error

  has_many :translations, foreign_key: :translated_from_serial_id, class_name: 'Serial', dependent: :destroy
  has_many :succeeding_serial_chronologies, foreign_key: :succeeding_serial_id, inverse_of: :succeeding_serial, class_name: 'SerialChronology', dependent: :restrict_with_error
  has_many :preceding_serial_chronologies, foreign_key: :preceding_serial_id, inverse_of: :preceding_serial, class_name: 'SerialChronology', dependent: :restrict_with_error

  # Single preceding chronology will be multiple serials if there is a merge
  has_many :immediately_preceding_serials, through: :succeeding_serial_chronologies, source: :preceding_serial

  # Single succeeding chronology will be multiple serials if there is a split
  has_many :immediately_succeeding_serials, through: :preceding_serial_chronologies, source: :succeeding_serial

  accepts_nested_attributes_for :alternate_values, reject_if: lambda { |av| av[:value].blank? }, allow_destroy: true

  validates_presence_of :name

  soft_validate(:sv_duplicate?)

  # Force self-referential relations to be included in unify(),
  # These would otherwise all be excluded because they reference 'class_name',
  # which triggers their elimination in the unify code base
  def unify_relations
    ApplicationEnumeration.klass_reflections(self.class).select{|a|
      [
        :translations,
        :sources,
        :succeeding_serial_chronologies,
        :preceding_serial_chronologies
      ].include?(a.name) }
  end

  # @param [String] compared_string
  # @param [String] column
  # @param [Integer] limit
  # @return [Scope]
  #   Levenshtein calculated related records per supplied column
  def nearest_by_levenshtein(compared_string = nil, column = 'name', limit = 10)
    return Serial.none if compared_string.blank?

    # Levenshtein in postgres requires all strings be 255 or fewer
    order_str = Serial.send(
      :sanitize_sql_for_conditions,
      ["levenshtein(Substring(serials.#{column} from 0 for 250), ?)",
       compared_string[0..250]])

    Serial.where('id <> ?', self.to_param)
      .order(Arel.sql(order_str))
      .limit(limit)
  end

  # @return [Boolean]
  #   is there another serial with the same name?  Also checkes alternate values.
  def duplicate?
    # ret_val = false
    if self.new_record?
      ret_val = Serial.exists?(name: self.name)
    else
      name_str = ActiveRecord::Base.send(
        :sanitize_sql_array,
        ['name = ? AND NOT (id = ?)',
         Utilities::Strings.escape_single_quote(self.name),
         self.id])
      ret_val  = Serial.where(name_str).to_a.size > 0
    end

    if ret_val == false
      # check if there is another alternate value with the same name
      a = Serial.with_alternate_value_on(:name, self.name)
      # select alternate value based on alternate_value_object class, alternate_value_object_attribute(column) & value
      if a.count > 0
        ret_val = true
      end
    end
    ret_val
  end

  # @param [Serial] start_serial
  # @return [Array]
  def all_previous(start_serial = self)
    # provides an array of all previous incarnations of me

    out_array = []
    start_serial.immediately_preceding_serials.order(:name).each do |serial|
      out_array.push(serial)
      prev = all_previous(serial)

      out_array.push(prev) unless prev.empty?
    end
    return out_array
  end

  # @param [Serial] start_serial
  # @return [Array]
  def all_succeeding(start_serial = self)
    # provides an array of all succeeding incarnations of me
    out_array = []
    start_serial.immediately_succeeding_serials.order(:name).each do |serial|
      out_array.push(serial)
      succeeding = all_succeeding(serial)

      out_array.push(succeeding) unless succeeding.empty?
    end
    out_array
  end

  def self.used_recently(user_id)
    t = Source.arel_table
    p = Serial.arel_table

    # i is a select manager
    i = t.project(t['serial_id'], t['updated_at']).from(t)
      .where(t['updated_at'].gt(1.months.ago))
      .where(t['updated_by_id'].eq(user_id))
      .order(t['updated_at'].desc)

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

    Serial.joins(
      Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(z['serial_id'].eq(p['id'])))
    ).pluck(:serial_id).uniq
  end

  def self.select_optimized(user_id, project_id)
    r = used_recently(user_id)
    h = {
      recent: (
        Serial.where('"serials"."id" IN (?)', r.first(10) ).order(:name).to_a +
        Serial.where(created_by_id: user_id, created_at: 3.hours.ago..Time.now).limit(5).to_a).uniq,
      pinboard: Serial.pinned_by(user_id).pinned_in_project(project_id).to_a
    }

    h[:quick] = (Serial.pinned_by(user_id).pinboard_inserted.pinned_in_project(project_id).to_a +
                 Serial.where('"serials"."id" IN (?)', r.first(4) ).order(:name).to_a).uniq
    h
  end

  protected

  # @return [Boolean]
  def sv_duplicate?
    if self.duplicate?
      soft_validations.add(:name, 'There is another serial with this name in the database.')
    end
    # TODO soft validation of name matching an alternate value for name of a different serial
  end

end

#translated_from_serial_idInteger

Returns the id of the serial that this serial is a direct translation of.

Returns:

  • (Integer)

    the id of the serial that this serial is a direct translation of



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

class Serial < ApplicationRecord

  include Housekeeping::Users
  include Housekeeping::Timestamps
  include Shared::AlternateValues
  include Shared::DataAttributes
  include Shared::Notes
  include Shared::Identifiers
  include Shared::Tags
  include Shared::IsData
  include SoftValidation
  include Shared::SharedAcrossProjects
  include Shared::HasPapertrail

  ALTERNATE_VALUES_FOR = [:name, :publisher, :place_published].freeze

  belongs_to :translated_from_serial, foreign_key: :translated_from_serial_id, class_name: 'Serial'
  belongs_to :language, foreign_key: :primary_language_id, inverse_of: :serials
  has_many :sources, class_name: 'Source::Bibtex', inverse_of: :serial, dependent: :restrict_with_error

  has_many :translations, foreign_key: :translated_from_serial_id, class_name: 'Serial', dependent: :destroy
  has_many :succeeding_serial_chronologies, foreign_key: :succeeding_serial_id, inverse_of: :succeeding_serial, class_name: 'SerialChronology', dependent: :restrict_with_error
  has_many :preceding_serial_chronologies, foreign_key: :preceding_serial_id, inverse_of: :preceding_serial, class_name: 'SerialChronology', dependent: :restrict_with_error

  # Single preceding chronology will be multiple serials if there is a merge
  has_many :immediately_preceding_serials, through: :succeeding_serial_chronologies, source: :preceding_serial

  # Single succeeding chronology will be multiple serials if there is a split
  has_many :immediately_succeeding_serials, through: :preceding_serial_chronologies, source: :succeeding_serial

  accepts_nested_attributes_for :alternate_values, reject_if: lambda { |av| av[:value].blank? }, allow_destroy: true

  validates_presence_of :name

  soft_validate(:sv_duplicate?)

  # Force self-referential relations to be included in unify(),
  # These would otherwise all be excluded because they reference 'class_name',
  # which triggers their elimination in the unify code base
  def unify_relations
    ApplicationEnumeration.klass_reflections(self.class).select{|a|
      [
        :translations,
        :sources,
        :succeeding_serial_chronologies,
        :preceding_serial_chronologies
      ].include?(a.name) }
  end

  # @param [String] compared_string
  # @param [String] column
  # @param [Integer] limit
  # @return [Scope]
  #   Levenshtein calculated related records per supplied column
  def nearest_by_levenshtein(compared_string = nil, column = 'name', limit = 10)
    return Serial.none if compared_string.blank?

    # Levenshtein in postgres requires all strings be 255 or fewer
    order_str = Serial.send(
      :sanitize_sql_for_conditions,
      ["levenshtein(Substring(serials.#{column} from 0 for 250), ?)",
       compared_string[0..250]])

    Serial.where('id <> ?', self.to_param)
      .order(Arel.sql(order_str))
      .limit(limit)
  end

  # @return [Boolean]
  #   is there another serial with the same name?  Also checkes alternate values.
  def duplicate?
    # ret_val = false
    if self.new_record?
      ret_val = Serial.exists?(name: self.name)
    else
      name_str = ActiveRecord::Base.send(
        :sanitize_sql_array,
        ['name = ? AND NOT (id = ?)',
         Utilities::Strings.escape_single_quote(self.name),
         self.id])
      ret_val  = Serial.where(name_str).to_a.size > 0
    end

    if ret_val == false
      # check if there is another alternate value with the same name
      a = Serial.with_alternate_value_on(:name, self.name)
      # select alternate value based on alternate_value_object class, alternate_value_object_attribute(column) & value
      if a.count > 0
        ret_val = true
      end
    end
    ret_val
  end

  # @param [Serial] start_serial
  # @return [Array]
  def all_previous(start_serial = self)
    # provides an array of all previous incarnations of me

    out_array = []
    start_serial.immediately_preceding_serials.order(:name).each do |serial|
      out_array.push(serial)
      prev = all_previous(serial)

      out_array.push(prev) unless prev.empty?
    end
    return out_array
  end

  # @param [Serial] start_serial
  # @return [Array]
  def all_succeeding(start_serial = self)
    # provides an array of all succeeding incarnations of me
    out_array = []
    start_serial.immediately_succeeding_serials.order(:name).each do |serial|
      out_array.push(serial)
      succeeding = all_succeeding(serial)

      out_array.push(succeeding) unless succeeding.empty?
    end
    out_array
  end

  def self.used_recently(user_id)
    t = Source.arel_table
    p = Serial.arel_table

    # i is a select manager
    i = t.project(t['serial_id'], t['updated_at']).from(t)
      .where(t['updated_at'].gt(1.months.ago))
      .where(t['updated_by_id'].eq(user_id))
      .order(t['updated_at'].desc)

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

    Serial.joins(
      Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(z['serial_id'].eq(p['id'])))
    ).pluck(:serial_id).uniq
  end

  def self.select_optimized(user_id, project_id)
    r = used_recently(user_id)
    h = {
      recent: (
        Serial.where('"serials"."id" IN (?)', r.first(10) ).order(:name).to_a +
        Serial.where(created_by_id: user_id, created_at: 3.hours.ago..Time.now).limit(5).to_a).uniq,
      pinboard: Serial.pinned_by(user_id).pinned_in_project(project_id).to_a
    }

    h[:quick] = (Serial.pinned_by(user_id).pinboard_inserted.pinned_in_project(project_id).to_a +
                 Serial.where('"serials"."id" IN (?)', r.first(4) ).order(:name).to_a).uniq
    h
  end

  protected

  # @return [Boolean]
  def sv_duplicate?
    if self.duplicate?
      soft_validations.add(:name, 'There is another serial with this name in the database.')
    end
    # TODO soft validation of name matching an alternate value for name of a different serial
  end

end

Class Method Details

.select_optimized(user_id, project_id) ⇒ Object



174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'app/models/serial.rb', line 174

def self.select_optimized(user_id, project_id)
  r = used_recently(user_id)
  h = {
    recent: (
      Serial.where('"serials"."id" IN (?)', r.first(10) ).order(:name).to_a +
      Serial.where(created_by_id: user_id, created_at: 3.hours.ago..Time.now).limit(5).to_a).uniq,
    pinboard: Serial.pinned_by(user_id).pinned_in_project(project_id).to_a
  }

  h[:quick] = (Serial.pinned_by(user_id).pinboard_inserted.pinned_in_project(project_id).to_a +
               Serial.where('"serials"."id" IN (?)', r.first(4) ).order(:name).to_a).uniq
  h
end

.used_recently(user_id) ⇒ Object



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'app/models/serial.rb', line 156

def self.used_recently(user_id)
  t = Source.arel_table
  p = Serial.arel_table

  # i is a select manager
  i = t.project(t['serial_id'], t['updated_at']).from(t)
    .where(t['updated_at'].gt(1.months.ago))
    .where(t['updated_by_id'].eq(user_id))
    .order(t['updated_at'].desc)

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

  Serial.joins(
    Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(z['serial_id'].eq(p['id'])))
  ).pluck(:serial_id).uniq
end

Instance Method Details

#all_previous(start_serial = self) ⇒ Array

Parameters:

  • start_serial (Serial) (defaults to: self)

Returns:

  • (Array)


129
130
131
132
133
134
135
136
137
138
139
140
# File 'app/models/serial.rb', line 129

def all_previous(start_serial = self)
  # provides an array of all previous incarnations of me

  out_array = []
  start_serial.immediately_preceding_serials.order(:name).each do |serial|
    out_array.push(serial)
    prev = all_previous(serial)

    out_array.push(prev) unless prev.empty?
  end
  return out_array
end

#all_succeeding(start_serial = self) ⇒ Array

Parameters:

  • start_serial (Serial) (defaults to: self)

Returns:

  • (Array)


144
145
146
147
148
149
150
151
152
153
154
# File 'app/models/serial.rb', line 144

def all_succeeding(start_serial = self)
  # provides an array of all succeeding incarnations of me
  out_array = []
  start_serial.immediately_succeeding_serials.order(:name).each do |serial|
    out_array.push(serial)
    succeeding = all_succeeding(serial)

    out_array.push(succeeding) unless succeeding.empty?
  end
  out_array
end

#duplicate?Boolean

Returns is there another serial with the same name? Also checkes alternate values.

Returns:

  • (Boolean)

    is there another serial with the same name? Also checkes alternate values.



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'app/models/serial.rb', line 103

def duplicate?
  # ret_val = false
  if self.new_record?
    ret_val = Serial.exists?(name: self.name)
  else
    name_str = ActiveRecord::Base.send(
      :sanitize_sql_array,
      ['name = ? AND NOT (id = ?)',
       Utilities::Strings.escape_single_quote(self.name),
       self.id])
    ret_val  = Serial.where(name_str).to_a.size > 0
  end

  if ret_val == false
    # check if there is another alternate value with the same name
    a = Serial.with_alternate_value_on(:name, self.name)
    # select alternate value based on alternate_value_object class, alternate_value_object_attribute(column) & value
    if a.count > 0
      ret_val = true
    end
  end
  ret_val
end

#nearest_by_levenshtein(compared_string = nil, column = 'name', limit = 10) ⇒ Scope

Returns Levenshtein calculated related records per supplied column.

Parameters:

  • compared_string (String) (defaults to: nil)
  • column (String) (defaults to: 'name')
  • limit (Integer) (defaults to: 10)

Returns:

  • (Scope)

    Levenshtein calculated related records per supplied column



87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'app/models/serial.rb', line 87

def nearest_by_levenshtein(compared_string = nil, column = 'name', limit = 10)
  return Serial.none if compared_string.blank?

  # Levenshtein in postgres requires all strings be 255 or fewer
  order_str = Serial.send(
    :sanitize_sql_for_conditions,
    ["levenshtein(Substring(serials.#{column} from 0 for 250), ?)",
     compared_string[0..250]])

  Serial.where('id <> ?', self.to_param)
    .order(Arel.sql(order_str))
    .limit(limit)
end

#sv_duplicate?Boolean (protected)

Returns:

  • (Boolean)


191
192
193
194
195
196
# File 'app/models/serial.rb', line 191

def sv_duplicate?
  if self.duplicate?
    soft_validations.add(:name, 'There is another serial with this name in the database.')
  end
  # TODO soft validation of name matching an alternate value for name of a different serial
end

#unify_relationsObject

Force self-referential relations to be included in unify(), These would otherwise all be excluded because they reference ‘class_name’, which triggers their elimination in the unify code base



72
73
74
75
76
77
78
79
80
# File 'app/models/serial.rb', line 72

def unify_relations
  ApplicationEnumeration.klass_reflections(self.class).select{|a|
    [
      :translations,
      :sources,
      :succeeding_serial_chronologies,
      :preceding_serial_chronologies
    ].include?(a.name) }
end