Class: MARC::Record

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/marc/record.rb

Overview

A class that represents an individual MARC record. Every record is made up of a collection of MARC::DataField objects.

MARC::Record mixes in Enumerable to enable access to constituent DataFields. For example, to return a list of all subject DataFields:

record.find_all {|field| field.tag =~ /^6../}

The accessor ‘fields’ is also an Array of MARC::DataField objects which the client can modify if neccesary.

record.fields.delete(field)

Other accessor attribute: ‘leader’ for record leader as String

High-performance lookup by tag

A frequent use case is looking up fields in a MARC record by tag, such as ‘all the 500 fields’. Certain methods can use a hash keyed by tag name for higher performance lookup by tag. The hash is lazily created on first access – there is some cost of creating the hash, testing shows you get a performance advantage to using the hash-based methods if you are doing at least a dozen lookups.

record.fields("500")  # returns an array
record.each_by_tag("500") {|field| ... }
record.fields(['100', '700'])   # can also use an array in both methods
record.each_by_tag( 600..699 )  # or a range

Freezing for thread-safety and high performance

MARC::Record is not generally safe for sharing between threads. Even if you think you are just acccessing it read-only, you may accidentally trigger a reindex of the by-tag cache (see above).

However, after you are done constructing a Record, you can mark the ‘fields` array as immutable. This makes a Record safe for sharing between threads for read-only use, and also helps you avoid accidentally triggering a reindex, as accidental reindexes can harm by-tag lookup performance.

record.fields.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeRecord

Returns a new instance of Record.



115
116
117
118
119
120
121
122
123
# File 'lib/marc/record.rb', line 115

def initialize
  @fields = FieldMap.new
  # leader is 24 bytes
  @leader = " " * 24
  # leader defaults:
  # http://www.loc.gov/marc/bibliographic/ecbdldrd.html
  @leader[10..11] = "22"
  @leader[20..23] = "4500"
end

Instance Attribute Details

#leaderObject

the record leader



113
114
115
# File 'lib/marc/record.rb', line 113

def leader
  @leader
end

Class Method Details

.new_from_hash(h) ⇒ Object



304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/marc/record.rb', line 304

def self.new_from_hash(h)
  r = new
  r.leader = h["leader"]
  h["fields"]&.each do |position|
    position.each_pair do |tag, field|
      if field.is_a?(Hash)
        f = MARC::DataField.new(tag, field["ind1"], field["ind2"])
        field["subfields"].each do |pos|
          pos.each_pair do |code, value|
            f.append MARC::Subfield.new(code, value)
          end
        end
        r << f
      else
        r << MARC::ControlField.new(tag, field)
      end
    end
  end
  r
end

.new_from_marc(raw, params = {}) ⇒ Object

Factory method for creating a MARC::Record from MARC21 in transmission format.

record = MARC::Record.new_from_marc(marc21)

in cases where you might be working with somewhat flawed MARC data you may want to use the :forgiving parameter which will bypass using field byte offsets and simply look for the end of field byte to figure out the end of fields.

record = MARC::Record.new_from_marc(marc21, :forgiving => true)


223
224
225
# File 'lib/marc/record.rb', line 223

def self.new_from_marc(raw, params = {})
  MARC::Reader.decode(raw, params)
end

.new_from_marchash(mh) ⇒ Object

Factory method for creating a new MARC::Record from a marchash object

record = MARC::Record->new_from_marchash(mh)



278
279
280
281
282
283
284
285
286
287
288
# File 'lib/marc/record.rb', line 278

def self.new_from_marchash(mh)
  r = new
  r.leader = mh["leader"]
  mh["fields"].each do |f|
    if f.length == 2
      r << MARC::ControlField.new(f[0], f[1])
    elsif r << MARC::DataField.new(f[0], f[1], f[2], *f[3])
    end
  end
  r
end

Instance Method Details

#<<(field) ⇒ Object

alias to append



145
146
147
# File 'lib/marc/record.rb', line 145

def <<(field)
  append(field)
end

#==(other) ⇒ Object

For testing if two records can be considered equal.



337
338
339
# File 'lib/marc/record.rb', line 337

def ==(other)
  to_s == other.to_s
end

#=~(regex) ⇒ Object

Handy for using a record in a regex:

if record =~ /Gravity's Rainbow/ then print "Slothrop" end


344
345
346
# File 'lib/marc/record.rb', line 344

def =~(regex)
  to_s =~ regex
end

#[](tag) ⇒ Object

You can lookup fields using this shorthand:

title = record['245']


176
177
178
# File 'lib/marc/record.rb', line 176

def [](tag)
  find { |f| f.tag == tag }
end

#append(field) ⇒ Object

add a field to the record

record.append(MARC::DataField.new( '100', '2', '0', ['a', 'Fred']))


138
139
140
141
# File 'lib/marc/record.rb', line 138

def append(field)
  @fields.push(field)
  @fields.clean = false
end

#eachObject

each() is here to support iterating and searching since MARC::Record mixes in Enumerable

iterating through the fields in a record:

record.each { |f| print f }

getting the 245

title = record.find {|f| f.tag == '245'}

getting all subjects

subjects = record.find_all {|f| ('600'..'699') === f.tag}


161
162
163
164
165
# File 'lib/marc/record.rb', line 161

def each
  @fields.each do |field|
    yield field
  end
end

#each_by_tag(filter) ⇒ Object

A more convenient way to iterate over each field with a given tag. The filter argument can be a string, array or range.



169
170
171
# File 'lib/marc/record.rb', line 169

def each_by_tag(filter)
  @fields.each_by_tag(filter) { |tag| yield tag }
end

#errorsObject

Returns an array of validation errors for all fields in the record



131
132
133
# File 'lib/marc/record.rb', line 131

def errors
  @fields.flat_map(&:errors)
end

#fields(filter = nil) ⇒ Object

Provides a backwards compatible means to access the FieldMap. No argument returns the FieldMap array in entirety. Providing a string, array or range of tags will return an array of fields in the order they appear in the record.



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/marc/record.rb', line 184

def fields(filter = nil)
  unless filter
    # Since we're returning the FieldMap object, which the caller
    # may mutate, we precautionarily mark dirty -- unless it's frozen
    # immutable.
    @fields.clean = false unless @fields.frozen?
    return @fields
  end
  @fields.reindex unless @fields.clean
  flds = []
  if filter.is_a?(String) && @fields.tags[filter]
    @fields.tags[filter].each do |idx|
      flds << @fields[idx]
    end
  elsif filter.is_a?(Array) || filter.is_a?(Range)
    @fields.each_by_tag(filter) do |tag|
      flds << tag
    end
  end
  flds
end

#tagsObject

Returns an array of all of the tags that appear in the record (not necessarily in the order they appear).



207
208
209
# File 'lib/marc/record.rb', line 207

def tags
  @fields.tag_list
end

#to_dublin_coreObject

Handy method for returning a hash mapping this records values to the Dublin Core.

dc = record.to_dublin_core()
print dc['title']


264
265
266
# File 'lib/marc/record.rb', line 264

def to_dublin_core
  MARC::DublinCore.map(self)
end

#to_hashObject

Returns a (roundtrippable) hash representation for MARC-in-JSON



291
292
293
294
295
296
297
# File 'lib/marc/record.rb', line 291

def to_hash
  record_hash = {"leader" => @leader, "fields" => []}
  @fields.each do |field|
    record_hash["fields"] << field.to_hash
  end
  record_hash
end

#to_json_stringObject

Return an actual json-encoded string.



300
301
302
# File 'lib/marc/record.rb', line 300

def to_json_string
  MARC::JSONLWriter.encode(self)
end

#to_marcObject

Returns a record in MARC21 transmission format (ANSI Z39.2). Really this is just a wrapper around MARC::MARC21::encode

marc = record.to_marc()


232
233
234
# File 'lib/marc/record.rb', line 232

def to_marc
  MARC::Writer.encode(self)
end

#to_marchashObject

Return a marc-hash version of the record



269
270
271
# File 'lib/marc/record.rb', line 269

def to_marchash
  {"type" => "marc-hash", "version" => [MARCHASH_MAJOR_VERSION, MARCHASH_MINOR_VERSION], "leader" => leader, "fields" => map { |f| f.to_marchash }}
end

#to_sObject

Returns a string version of the record, suitable for printing



327
328
329
330
331
332
333
# File 'lib/marc/record.rb', line 327

def to_s
  str = "LEADER #{leader}\n"
  each do |field|
    str += field.to_s + "\n"
  end
  str
end

#to_xml(include_namespace: true) ⇒ Object

Handy method for returning the MARCXML serialization for a MARC::Record object. You’ll get back a REXML::Document object. Really this is just a wrapper around MARC::XMLWriter::encode

xml_doc = record.to_xml()


241
242
243
# File 'lib/marc/record.rb', line 241

def to_xml(include_namespace: true)
  MARC::XMLWriter.encode(self, include_namespace: include_namespace)
end

#to_xml_string(fast_but_unsafe: false, include_namespace: true) ⇒ String

Create the actual XML string (as opposed to #to_xml which, for historic reasons, returns an REXML document)

Parameters:

  • fast_but_unsafe (Boolean) (defaults to: false)

    Use the fast MARC::UnsafeXMLWriter code

  • include_namespace (Boolean) (defaults to: true)

    Include namespaces on the <record> tag?

Returns:

  • (String)

    MARC-XML encoding of the record



250
251
252
253
254
255
256
# File 'lib/marc/record.rb', line 250

def to_xml_string(fast_but_unsafe: false, include_namespace: true)
  if fast_but_unsafe
    MARC::UnsafeXMLWriter.encode(self, include_namespace: include_namespace)
  else
    MARC::XMLWriter.encode(self, include_namespace: include_namespace).to_s
  end
end

#valid?Boolean

Returns true if there are no error messages associated with the record

Returns:

  • (Boolean)


126
127
128
# File 'lib/marc/record.rb', line 126

def valid?
  errors.none?
end