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
the record leader
Public Class Methods
# 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
# 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
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
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
alias to append
# File lib/marc/record.rb, line 145 def <<(field) append(field) end
For testing if two records can be considered equal.
# File lib/marc/record.rb, line 337 def ==(other) to_s == other.to_s end
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
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
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() 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
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
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
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
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
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
Return an actual json-encoded string.
# File lib/marc/record.rb, line 300 def to_json_string MARC::JSONLWriter.encode(self) end
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
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
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
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
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
Returns true if there are no error messages associated with the record
# File lib/marc/record.rb, line 126 def valid? errors.none? end