class MARC::Record

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

Attributes

leader[RW]

the record leader

Public Class Methods

new() click to toggle source
# 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
new_from_hash(h) click to toggle source
# 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 = {}) click to toggle source

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)
# File lib/marc/record.rb, line 223
def self.new_from_marc(raw, params = {})
  MARC::Reader.decode(raw, params)
end
new_from_marchash(mh) click to toggle source

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

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

# 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

Public Instance Methods

<<(field) click to toggle source

alias to append

# File lib/marc/record.rb, line 145
def <<(field)
  append(field)
end
==(other) click to toggle source

For testing if two records can be considered equal.

# File lib/marc/record.rb, line 337
def ==(other)
  to_s == other.to_s
end
=~(regex) click to toggle source

Handy for using a record in a regex:

if record =~ /Gravity's Rainbow/ then print "Slothrop" end
# File lib/marc/record.rb, line 344
def =~(regex)
  to_s =~ regex
end
[](tag) click to toggle source

You can lookup fields using this shorthand:

title = record['245']
# File lib/marc/record.rb, line 176
def [](tag)
  find { |f| f.tag == tag }
end
append(field) click to toggle source

add a field to the record

record.append(MARC::DataField.new( '100', '2', '0', ['a', 'Fred']))
# File lib/marc/record.rb, line 138
def append(field)
  @fields.push(field)
  @fields.clean = false
end
each() { |field| ... } click to toggle source

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}
# File lib/marc/record.rb, line 161
def each
  @fields.each do |field|
    yield field
  end
end
each_by_tag(filter) { |tag| ... } click to toggle source

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

# File lib/marc/record.rb, line 169
def each_by_tag(filter)
  @fields.each_by_tag(filter) { |tag| yield tag }
end
errors() click to toggle source

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

# File lib/marc/record.rb, line 131
def errors
  @fields.flat_map(&:errors)
end
fields(filter = nil) click to toggle source

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.

# 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
tags() click to toggle source

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

# File lib/marc/record.rb, line 207
def tags
  @fields.tag_list
end
to_dublin_core() click to toggle source

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

dc = record.to_dublin_core()
print dc['title']
# File lib/marc/record.rb, line 264
def to_dublin_core
  MARC::DublinCore.map(self)
end
to_hash() click to toggle source

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

# 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_string() click to toggle source

Return an actual json-encoded string.

# File lib/marc/record.rb, line 300
def to_json_string
  MARC::JSONLWriter.encode(self)
end
to_marc() click to toggle source

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

marc = record.to_marc()
# File lib/marc/record.rb, line 232
def to_marc
  MARC::Writer.encode(self)
end
to_marchash() click to toggle source

Return a marc-hash version of the record

# 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_s() click to toggle source

Returns a string version of the record, suitable for printing

# 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) click to toggle source

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()
# 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) click to toggle source

Create the actual XML string (as opposed to to_xml which, for historic reasons, returns an REXML document) @param [Boolean] fast_but_unsafe Use the fast MARC::UnsafeXMLWriter code @param [Boolean] include_namespace Include namespaces on the <record> tag? @return [String] MARC-XML encoding of the record

# 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?() click to toggle source

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

# File lib/marc/record.rb, line 126
def valid?
  errors.none?
end