Class: ActiveFedora::Base

Inherits:
Object
  • Object
show all
Includes:
SemanticNode
Defined in:
lib/active_fedora/base.rb

Overview

This class ties together many of the lower-level modules, and implements something akin to an ActiveRecord-alike interface to fedora. If you want to represent a fedora object in the ruby space, this is the class you want to extend.

The Basics

class Oralhistory < ActiveFedora::Base
   :name => "properties", :type => ActiveFedora::MetadataDatastream do |m|
    m.field "narrator",  :string
    m.field "narrator",  :text
  end
end

The above example creates a Fedora object with a metadata datastream named “properties”, which is composed of a narrator and bio field.

Datastreams defined with has_metadata are accessed via the datastreams member hash.

Direct Known Subclasses

ContentModel

Instance Attribute Summary

Attributes included from SemanticNode

#load_from_solr, #relationships_loaded, #subject

Class Method Summary collapse

Instance Method Summary collapse

Methods included from SemanticNode

#add_relationship, #assert_kind_of, #build_statement, #conforms_to?, #ids_for_outbound, #inbound_relationship_predicates, #inbound_relationships, #load_relationships, #object_relations, #outbound_relationship_predicates, #outbound_relationships, #relationship_predicates, #relationships, #relationships_are_dirty, #relationships_are_dirty=, #relationships_desc, #remove_relationship

Constructor Details

#initialize(attrs = nil) ⇒ Base

Constructor. You may supply a custom :pid, or we call the Fedora Rest API for the next available Fedora pid, and mark as new object. Also, if attrs does not contain :pid but does contain :namespace it will pass the :namespace value to Fedora::Repository.nextid to generate the next pid available within the given namespace.



79
80
81
82
83
84
85
86
87
88
89
# File 'lib/active_fedora/base.rb', line 79

def initialize(attrs = nil)
  attrs = {} if attrs.nil?
  attributes = attrs.dup
  @inner_object = UnsavedDigitalObject.new(self.class, attributes.delete(:namespace), attributes.delete(:pid))
  self.relationships_loaded = true
  load_datastreams

  [:new_object,:create_date, :modified_date].each { |k| attributes.delete(k)}
  self.attributes=attributes
  run_callbacks :initialize
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args) ⇒ Object



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/active_fedora/base.rb', line 31

def method_missing(name, *args)
  # if [:collection_members, :part_of, :parts, :part_of_append, :file_objects].include? name 
  #   ActiveSupport::Deprecation.warn("Deprecation: FileManagement will not be included by default in the next version.   To use #{name} add 'include ActiveFedora::FileManagement' to your model")
  #   self.class.send :include, FileManagement
  #   send name, *args
  # else 
    dsid = corresponding_datastream_name(name)
    if dsid
      ### Create and invoke a proxy method 
      self.class.send :define_method, name do
          datastreams[dsid]
      end
      self.send(name)
    else 
      super
    end
end

Class Method Details

.assign_pid(obj) ⇒ String

if you are doing sharding, override this method to do something other than use a sequence

Returns:

  • (String)

    the unique pid for a new object

Raises:

  • (RuntimeError)


155
156
157
158
159
160
161
# File 'lib/active_fedora/base.rb', line 155

def self.assign_pid(obj)
    args = {}
    args[:namespace] = obj.namespace if obj.namespace
    raise RuntimeError, "When using shards, you must override #{self}.assign_pid()" if ActiveFedora.config.sharded?
    d = REXML::Document.new(connection_for_pid('0').next_pid(args))
    d.elements['//pid'].text
end

.connection_for_pid(pid) ⇒ Rubydora::Repository

Uses shard_index to find or create the rubydora connection for this pid

Parameters:

  • pid (String)

    the identifier of the object to get the connection for

Returns:

  • (Rubydora::Repository)

    The repository that the identifier exists in.



123
124
125
126
127
128
129
130
131
132
133
# File 'lib/active_fedora/base.rb', line 123

def self.connection_for_pid(pid)
  idx = shard_index(pid)
  unless fedora_connection.has_key? idx
    if ActiveFedora.config.sharded?
      fedora_connection[idx] = RubydoraConnection.new(ActiveFedora.config.credentials[idx])
    else
      fedora_connection[idx] = RubydoraConnection.new(ActiveFedora.config.credentials)
    end
  end
  fedora_connection[idx].connection
end

.create(args = {}) ⇒ Object



146
147
148
149
150
# File 'lib/active_fedora/base.rb', line 146

def self.create(args = {})
  obj = self.new(args)
  obj.save
  obj
end

.datastream_class_for_name(dsid) ⇒ Object



142
143
144
# File 'lib/active_fedora/base.rb', line 142

def self.datastream_class_for_name(dsid)
  ds_specs[dsid] ? ds_specs[dsid][:type] : ActiveFedora::Datastream
end

.load_instance_from_solr(pid, solr_doc = nil) ⇒ Object

This method can be used instead of ActiveFedora::Model::ClassMethods.find.

It works similarly except it populates an object from Solr instead of Fedora. It is most useful for objects used in read-only displays in order to speed up loading time. If only a pid is passed in it will query solr for a corresponding solr document and then use it to populate this object.

If a value is passed in for optional parameter solr_doc it will not query solr again and just use the one passed to populate the object.

It will anything stored within solr such as metadata and relationships. Non-metadata datastreams will not be loaded and if needed you should use find instead.



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
394
395
396
397
398
399
# File 'lib/active_fedora/base.rb', line 364

def self.load_instance_from_solr(pid,solr_doc=nil)
  if solr_doc.nil?
    result = find_by_solr(pid)
    raise ActiveFedora::ObjectNotFoundError, "Object #{pid} not found in solr" if result.nil?
    solr_doc = result.first
    #double check pid and id in record match
    raise ActiveFedora::ObjectNotFoundError, "Object #{pid} not found in Solr" unless !result.nil? && !solr_doc.nil? && pid == solr_doc[SOLR_DOCUMENT_ID]
  else
    raise "Solr document record id and pid do not match" unless pid == solr_doc[SOLR_DOCUMENT_ID]
  end
  klass = if class_str = solr_doc['active_fedora_model_s']
    class_str.first.constantize
  else
    ActiveFedora::Base
  end

  profile_json = Array(solr_doc[ActiveFedora::Base.profile_solr_name]).first
  unless profile_json.present?
    raise ActiveFedora::ObjectNotFoundError, "Object #{pid} does not contain a solrized profile"
  end
  profile_hash = ActiveSupport::JSON.decode(profile_json)
  obj = klass.allocate.init_with(SolrDigitalObject.new(solr_doc, profile_hash, klass))
  #set by default to load any dependent relationship objects from solr as well
  #need to call rels_ext once so it exists when iterating over datastreams
  obj.rels_ext
  obj.datastreams.each_value do |ds|
    if ds.respond_to?(:profile_from_hash) and (ds_prof = profile_hash['datastreams'][ds.dsid])
      ds.profile_from_hash(ds_prof)
    end
    if ds.respond_to?(:from_solr)
      ds.from_solr(solr_doc) if ds.kind_of?(ActiveFedora::MetadataDatastream) || ds.kind_of?(ActiveFedora::NokogiriDatastream) || ( ds.kind_of?(ActiveFedora::RelsExtDatastream))
    end
  end
  obj.inner_object.freeze
  obj
end

.pids_from_uris(uris) ⇒ Object



465
466
467
468
469
470
471
472
473
474
475
# File 'lib/active_fedora/base.rb', line 465

def self.pids_from_uris(uris) 
  if uris.class == String
    return uris.gsub("info:fedora/", "")
  elsif uris.class == Array
    arr = []
    uris.each do |uri|
      arr << uri.gsub("info:fedora/", "")
    end
    return arr
  end
end

.shard_index(pid) ⇒ Integer

If you want to use sharding override this method with your strategy

Returns:

  • (Integer)

    the index of the shard this object is stored in



137
138
139
# File 'lib/active_fedora/base.rb', line 137

def self.shard_index(pid)
  0
end

Instance Method Details

#==(comparison_object) ⇒ Object



257
258
259
260
261
262
# File 'lib/active_fedora/base.rb', line 257

def ==(comparison_object)
     comparison_object.equal?(self) ||
       (comparison_object.instance_of?(self.class) &&
         comparison_object.pid == pid &&
         !comparison_object.new_record?)
end

#adapt_to(klass) ⇒ Object

This method adapts the inner_object to a new ActiveFedora::Base implementation This is intended to minimize redundant interactions with Fedora



320
321
322
323
324
325
# File 'lib/active_fedora/base.rb', line 320

def adapt_to(klass)
  unless klass.ancestors.include? ActiveFedora::Base
    raise "Cannot adapt #{self.class.name} to #{klass.name}: Not a ActiveFedora::Base subclass"
  end
  klass.allocate.init_with(inner_object)
end

#adapt_to_cmodelObject

Examine the :has_model assertions in the RELS-EXT. Adapt this class to the first first known model



328
329
330
331
# File 'lib/active_fedora/base.rb', line 328

def adapt_to_cmodel
  the_model = ActiveFedora::ContentModel.known_models_for( self ).first
  self.class != the_model ? self.adapt_to(the_model) : self
end

#attributes=(properties) ⇒ Object



68
69
70
71
72
# File 'lib/active_fedora/base.rb', line 68

def attributes=(properties)
  properties.each do |k, v|
    respond_to?(:"#{k}=") ? send(:"#{k}=", v) : raise(UnknownAttributeError, "unknown attribute: #{k}")
  end
end

#create_dateObject

return the create_date of the inner object (unless it’s a new object)



203
204
205
# File 'lib/active_fedora/base.rb', line 203

def create_date
  @inner_object.new? ? Time.now : @inner_object.profile["objCreateDate"]
end

#fieldsObject

Return a hash of all available metadata fields for all ActiveFedora::MetadataDatastream datastreams, as well as system_create_date, system_modified_date, active_fedora_model_field, and the object id.



225
226
227
228
229
230
231
232
# File 'lib/active_fedora/base.rb', line 225

def fields
# TODO this can likely be removed once find_by_fields_by_solr is removed
  fields = {:id => {:values => [pid]}, :system_create_date => {:values => [self.create_date], :type=>:date}, :system_modified_date => {:values => [self.modified_date], :type=>:date}, :active_fedora_model => {:values => [self.class.inspect], :type=>:symbol}}
  datastreams.values.each do |ds|        
    fields.merge!(ds.fields) if [ActiveFedora::MetadataDatastream, ActiveFedora::QualifiedDublinCoreDatastream].include?(ds.class)
  end
  return fields
end

#get_values_from_datastream(dsid, field_key, default = []) ⇒ Object



457
458
459
460
461
462
463
# File 'lib/active_fedora/base.rb', line 457

def get_values_from_datastream(dsid,field_key,default=[])
  if datastreams.include?(dsid)
    return datastreams[dsid].get_values(field_key,default)
  else
    return nil
  end
end

#idObject

Needed for the nested form helper



175
176
177
# File 'lib/active_fedora/base.rb', line 175

def id   ### Needed for the nested form helper
  self.pid
end

#init_with(inner_obj) ⇒ Object

Initialize an empty model object and set the inner_obj example:

class Post < ActiveFedora::Base
   :name => "properties", :type => ActiveFedora::MetadataDatastream
end

post = Post.allocate
post.init_with(DigitalObject.find(pid))
post.properties.title # => 'hello world'


102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/active_fedora/base.rb', line 102

def init_with(inner_obj)
  @inner_object = inner_obj
  unless @inner_object.is_a? SolrDigitalObject
    @inner_object.original_class = self.class
    ## Replace existing unchanged datastreams with the definitions found in this class if they have a different type.
    ## Any datastream that is deleted here will cause a reload from fedora, so avoid it whenever possible
    ds_specs.keys.each do |key|
      if !@inner_object.datastreams[key].changed.include?(:content) && @inner_object.datastreams[key].class != self.class.ds_specs[key][:type]
        @inner_object.datastreams.delete(key)
      end
    end
  end
  load_datastreams
  run_callbacks :find
  run_callbacks :initialize
  self
end

#inner_objectObject

:nodoc



163
164
165
# File 'lib/active_fedora/base.rb', line 163

def inner_object # :nodoc
  @inner_object
end

#inspectObject



264
265
266
# File 'lib/active_fedora/base.rb', line 264

def inspect
  "#<#{self.class}:#{self.hash} @pid=\"#{pid}\" >"
end

#internal_uriObject

return the internal fedora URI



184
185
186
# File 'lib/active_fedora/base.rb', line 184

def internal_uri
  "info:fedora/#{pid}"
end

#labelObject

return the label of the inner object (unless it’s a new object)



213
214
215
# File 'lib/active_fedora/base.rb', line 213

def label
  @inner_object.label
end

#label=(new_label) ⇒ Object



217
218
219
# File 'lib/active_fedora/base.rb', line 217

def label=(new_label)
  @inner_object.label = new_label
end

#modified_dateObject

return the modification date of the inner object (unless it’s a new object)



208
209
210
# File 'lib/active_fedora/base.rb', line 208

def modified_date
  @inner_object.new? ? Time.now : @inner_object.profile["objLastModDate"]
end

#new_object=(bool) ⇒ Object



55
56
57
# File 'lib/active_fedora/base.rb', line 55

def new_object=(bool)
  ActiveSupport::Deprecation.warn("ActiveFedora::Base.new_object= has been deprecated and nolonger has any effect")
end

#new_object?Boolean

Has this object been saved?

Returns:

  • (Boolean)


51
52
53
# File 'lib/active_fedora/base.rb', line 51

def new_object?
  inner_object.new?
end

#new_record?Boolean

Required by associations

Returns:

  • (Boolean)


60
61
62
# File 'lib/active_fedora/base.rb', line 60

def new_record?
  self.new_object?
end

#owner_idObject

return the owner id



194
195
196
# File 'lib/active_fedora/base.rb', line 194

def owner_id
  @inner_object.ownerId
end

#owner_id=(owner_id) ⇒ Object



198
199
200
# File 'lib/active_fedora/base.rb', line 198

def owner_id=(owner_id)
  @inner_object.ownerId=(owner_id)
end

#persisted?Boolean

Returns:

  • (Boolean)


64
65
66
# File 'lib/active_fedora/base.rb', line 64

def persisted?
  !new_object?
end

#pidObject

return the pid of the Fedora Object if there is no fedora object (loaded from solr) get the instance var TODO make inner_object a proxy that can hold the pid



170
171
172
# File 'lib/active_fedora/base.rb', line 170

def pid
   @inner_object.pid
end

#reifyObject

** EXPERIMENTAL ** This method returns a new object of the same class, with the internal SolrDigitalObject replaced with an actual DigitalObject.



336
337
338
339
340
341
# File 'lib/active_fedora/base.rb', line 336

def reify
  if self.inner_object.is_a? DigitalObject
    raise "#{self.inspect} is already a full digital object"
  end
  self.class.find self.pid
end

#reify!Object

** EXPERIMENTAL ** This method reinitializes a lightweight, loaded-from-solr object with an actual DigitalObject inside.



346
347
348
349
350
351
# File 'lib/active_fedora/base.rb', line 346

def reify!
  if self.inner_object.is_a? DigitalObject
    raise "#{self.inspect} is already a full digital object"
  end
  self.init_with DigitalObject.find(self.class,self.pid)
end

#solrize_profile(solr_doc = Hash.new) ⇒ Object

:nodoc:



290
291
292
293
294
295
296
297
298
299
300
301
302
303
# File 'lib/active_fedora/base.rb', line 290

def solrize_profile(solr_doc = Hash.new) # :nodoc:
  profile_hash = { 'datastreams' => {} }
  if inner_object.respond_to? :profile
    inner_object.profile.each_pair do |property,value|
      if property =~ /Date/
        value = Time.parse(value) unless value.is_a?(Time)
        value = value.xmlschema
      end
      profile_hash[property] = value
    end
  end
  self.datastreams.each_pair { |dsid,ds| profile_hash['datastreams'][dsid] = ds.solrize_profile }
  solr_doc[self.class.profile_solr_name] = profile_hash.to_json
end

#solrize_relationships(solr_doc = Hash.new) ⇒ Object

Serialize the datastream’s RDF relationships to solr

Parameters:

  • solr_doc (Hash) (defaults to: Hash.new)

    @deafult an empty Hash



307
308
309
310
311
312
313
314
315
# File 'lib/active_fedora/base.rb', line 307

def solrize_relationships(solr_doc = Hash.new)
  relationships.each_statement do |statement|
    predicate = RelsExtDatastream.short_predicate(statement.predicate)
    literal = statement.object.kind_of?(RDF::Literal)
    val = literal ? statement.object.value : statement.object.to_str
    ::Solrizer::Extractor.insert_solr_field_value(solr_doc, solr_name(predicate, :symbol), val )
  end
  return solr_doc
end

#stateObject

return the state of the inner object



189
190
191
# File 'lib/active_fedora/base.rb', line 189

def state 
  @inner_object.state
end

#to_keyObject



179
180
181
# File 'lib/active_fedora/base.rb', line 179

def to_key
  persisted? ? [pid] : nil
end

#to_solr(solr_doc = Hash.new, opts = {}) ⇒ Object

Return a Hash representation of this object where keys in the hash are appropriate Solr field names. If opts == true, the base object metadata and the RELS-EXT datastream will be omitted. This is mainly to support shelver, which calls .to_solr for each model an object subscribes to.

Parameters:

  • solr_doc (Hash) (defaults to: Hash.new)

    (optional) Hash to insert the fields into

  • opts (Hash) (defaults to: {})

    (optional)



272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/active_fedora/base.rb', line 272

def to_solr(solr_doc = Hash.new, opts={})
  unless opts[:model_only]
    c_time = create_date
    c_time = Time.parse(c_time) unless c_time.is_a?(Time)
    m_time = modified_date
    m_time = Time.parse(m_time) unless m_time.is_a?(Time)
    solr_doc.merge!(SOLR_DOCUMENT_ID.to_sym => pid, ActiveFedora::SolrService.solr_name(:system_create, :date) => c_time.utc.xmlschema, ActiveFedora::SolrService.solr_name(:system_modified, :date) => m_time.utc.xmlschema, ActiveFedora::SolrService.solr_name(:active_fedora_model, :symbol) => self.class.inspect)
    solrize_profile(solr_doc)
  end
  datastreams.each_value do |ds|
    ds.ensure_xml_loaded if ds.respond_to? :ensure_xml_loaded  ### Can't put this in the model because it's often implemented in Solrizer::XML::TerminologyBasedSolrizer 
    #puts "\n\nQDC #{ds.to_solr(solr_doc).inspect}" if ds.kind_of?(ActiveFedora::QualifiedDublinCoreDatastream)
    solr_doc = ds.to_solr(solr_doc) if ds.kind_of?(ActiveFedora::RDFDatastream) || ds.kind_of?(ActiveFedora::NokogiriDatastream) || ds.kind_of?(ActiveFedora::MetadataDatastream)
  end
  solr_doc = solrize_relationships(solr_doc) unless opts[:model_only]
  solr_doc
end

#to_xml(xml = Nokogiri::XML::Document.parse("<xml><fields/><content/></xml>")) ⇒ Object

Returns the xml version of this object as a string.



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/active_fedora/base.rb', line 235

def to_xml(xml=Nokogiri::XML::Document.parse("<xml><fields/><content/></xml>"))
  fields_xml = xml.xpath('//fields').first
  builder = Nokogiri::XML::Builder.with(fields_xml) do |fields_xml|
    fields_xml.id_ pid
    fields_xml.system_create_date self.create_date
    fields_xml.system_modified_date self.modified_date
    fields_xml.active_fedora_model self.class.inspect
  end
  
  # {:id => pid, :system_create_date => self.create_date, :system_modified_date => self.modified_date, :active_fedora_model => self.class.inspect}.each_pair do |attribute_name, value|
  #   el = REXML::Element.new(attribute_name.to_s)
  #   el.text = value
  #   fields_xml << el
  # end
  
  datastreams.each_value do |ds|  
    ds.to_xml(fields_xml) if ds.class.included_modules.include?(ActiveFedora::MetadataDatastreamHelper)
    ds.to_rels_ext if ds.kind_of?(ActiveFedora::RelsExtDatastream)
  end
  return xml.to_s
end

#update_datastream_attributes(params = {}, opts = {}) ⇒ Object

Updates the attributes for each datastream named in the params Hash

Examples:

Update the descMetadata and properties datastreams with new values

article = HydrangeaArticle.new
ds_values_hash = {
  "descMetadata"=>{ [{:person=>0}, :role]=>{"0"=>"role1", "1"=>"role2", "2"=>"role3"} },
  "properties"=>{ "notes"=>"foo" }
}
article.update_datastream_attributes( ds_values_hash )

Parameters:

  • params (Hash) (defaults to: {})

    A Hash whose keys correspond to datastream ids and whose values are appropriate Hashes to submit to update_indexed_attributes on that datastream

  • opts (Hash) (defaults to: {})

    (currently ignored.)



445
446
447
448
449
450
451
452
453
454
455
# File 'lib/active_fedora/base.rb', line 445

def update_datastream_attributes(params={}, opts={})
  result = params.dup
  params.each_pair do |dsid, ds_params| 
    if datastreams.include?(dsid)
      result[dsid] = datastreams[dsid].update_indexed_attributes(ds_params)
    else
      result.delete(dsid)
    end
  end
  return result
end

#update_indexed_attributes(params = {}, opts = {}) ⇒ Object

A convenience method for updating indexed attributes. The passed in hash must look like this :

{{:name=>{"0"=>"a","1"=>"b"}}

This will result in any datastream field of name :name having the value [a,b]

An index of -1 will insert a new value. any existing value at the relevant index will be overwritten.

As in update_attributes, this overwrites all available fields by default.

If you want to specify which datastream(s) to update, use the :datastreams argument like so:

m.update_attributes({"fubar"=>{"-1"=>"mork", "0"=>"york", "1"=>"mangle"}}, :datastreams=>"my_ds")

or

m.update_attributes({"fubar"=>{"-1"=>"mork", "0"=>"york", "1"=>"mangle"}}, :datastreams=>["my_ds", "my_other_ds"])


418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
# File 'lib/active_fedora/base.rb', line 418

def update_indexed_attributes(params={}, opts={})
  if ds = opts[:datastreams]
    ds_array = []
    ds = [ds] unless ds.respond_to? :each
    ds.each do |dsname|
      ds_array << datastreams[dsname]
    end
  else
    ds_array = 
  end
  result = {}
  ds_array.each do |d|
    result[d.dsid] = d.update_indexed_attributes(params,opts)
  end
  return result
end