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::SimpleDatastream 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, #clear_relationship, #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.



69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/active_fedora/base.rb', line 69

def initialize(attrs = nil)
  attrs = {} if attrs.nil?
  @association_cache = {}
  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



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

def method_missing(name, *args)
  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



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/active_fedora/base.rb', line 206

def self.assign_pid(obj)
  args = {}
  args[:namespace] = obj.namespace if obj.namespace
  # TODO: This juggling of Fedora credentials & establishing connections should be handled by 
  # an establish_fedora_connection method,possibly wrap it all into a fedora_connection method - MZ 06-05-2012
  if ActiveFedora.config.sharded?
    credentials = ActiveFedora.config.credentials[0]
  else
    credentials = ActiveFedora.config.credentials
  end
  fedora_connection[0] ||= ActiveFedora::RubydoraConnection.new(credentials)
  d = REXML::Document.new( fedora_connection[0].connection.next_pid(args))
  pid =d.elements['//pid'].text
  pid
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.



120
121
122
123
124
125
126
127
128
129
130
# File 'lib/active_fedora/base.rb', line 120

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(attributes = nil, &block) ⇒ Object

Creates an object (or multiple objects) and saves it to the repository, if validations pass. The resulting object is returned whether the object was saved successfully to the repository or not.

The attributes parameter can be either be a Hash or an Array of Hashes. These Hashes describe the attributes on the objects that are to be created.

Examples

# Create a single new object
User.create(:first_name => 'Jamie')

# Create an Array of new objects
User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }])

# Create a single object and pass it into a block to set other attributes.
User.create(:first_name => 'Jamie') do |u|
  u.is_admin = false
end

# Creating an Array of new objects using a block, where the block is executed for each object:
User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }]) do |u|
  u.is_admin = false
end


174
175
176
177
178
179
180
181
182
183
# File 'lib/active_fedora/base.rb', line 174

def self.create(attributes = nil, &block)
  if attributes.is_a?(Array)
    attributes.collect { |attr| create(attr, &block) }
  else
    object = new(attributes)
    yield(object) if block_given?
    object.save
    object
  end
end

.datastream_class_for_name(dsid) ⇒ Object



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

def self.datastream_class_for_name(dsid)
  ds_specs[dsid] ? ds_specs[dsid].fetch(:type, ActiveFedora::Datastream) : 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.



394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
# File 'lib/active_fedora/base.rb', line 394

def self.load_instance_from_solr(pid,solr_doc=nil)
  if solr_doc.nil?
    result = find_with_conditions(:id=>pid)
    raise ActiveFedora::ObjectNotFoundError, "Object #{pid} not found in solr" if result.empty?
    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[ActiveFedora::SolrService.solr_name('has_model', :symbol)]
    ActiveFedora::SolrService.class_from_solr_document(solr_doc)
  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
    ds.from_solr(solr_doc) if ds.respond_to?(:from_solr)
  end
  obj.inner_object.freeze
  obj
end

.pids_from_uris(uris) ⇒ Object



429
430
431
432
433
434
435
436
437
438
439
# File 'lib/active_fedora/base.rb', line 429

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

This is where your sharding strategy is implemented – it’s how we figure out which shard an object will be (or was) written to. Given a pid, it decides which shard that pid will be written to (and thus retrieved from). For a given pid, as long as your shard configuration remains the same it will always return the same value. If you’re not using sharding, this will always return 0, meaning use the first/only Fedora Repository in your configuration. Default strategy runs a modulo of the md5 of the pid against the number of shards. If you want to use a different sharding strategy, override this method. Make sure that it will always return the same value for a given pid and shard configuration.

Returns:

  • (Integer)

    the index of the shard this object is stored in



139
140
141
142
143
144
145
# File 'lib/active_fedora/base.rb', line 139

def self.shard_index(pid)
  if ActiveFedora.config.sharded?
    Digest::MD5.hexdigest(pid).hex % ActiveFedora.config.credentials.length
  else
    0
  end
end

Instance Method Details

#==(comparison_object) ⇒ Object



284
285
286
287
288
289
# File 'lib/active_fedora/base.rb', line 284

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



350
351
352
353
354
355
# File 'lib/active_fedora/base.rb', line 350

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



358
359
360
361
# File 'lib/active_fedora/base.rb', line 358

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

#cloneObject



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

def clone
  new_object = self.class.create
  clone_into(new_object)
end

#clone_into(new_object) ⇒ Object

Clone the datastreams from this object into the provided object, while preserving the pid of the provided object

Parameters:

  • new_object (Base)

    clone into this object



192
193
194
195
196
197
198
199
200
201
202
# File 'lib/active_fedora/base.rb', line 192

def clone_into(new_object)
  rels = Nokogiri::XML( rels_ext.content)
  rels.xpath("//rdf:Description/@rdf:about").first.value = new_object.internal_uri
  new_object.rels_ext.content = rels.to_xml

  datastreams.each do |k, v|
    next if k == 'RELS-EXT'
    new_object.datastreams[k].content = v.content
  end
  new_object if new_object.save
end

#create_dateObject

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



266
267
268
# File 'lib/active_fedora/base.rb', line 266

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

#idObject

Needed for the nested form helper



234
235
236
# File 'lib/active_fedora/base.rb', line 234

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::SimpleDatastream
end

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


98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/active_fedora/base.rb', line 98

def init_with(inner_obj)
  @association_cache = {}
  @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].content_changed? && @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



222
223
224
# File 'lib/active_fedora/base.rb', line 222

def inner_object # :nodoc
  @inner_object
end

#internal_uriObject

return the internal fedora URI



247
248
249
# File 'lib/active_fedora/base.rb', line 247

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

#labelObject

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



276
277
278
# File 'lib/active_fedora/base.rb', line 276

def label
  @inner_object.label
end

#label=(new_label) ⇒ Object



280
281
282
# File 'lib/active_fedora/base.rb', line 280

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)



271
272
273
# File 'lib/active_fedora/base.rb', line 271

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

#new?Boolean

Returns:

  • (Boolean)


46
47
48
# File 'lib/active_fedora/base.rb', line 46

def new?
  new_object?
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)


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

def new_record?
  self.new_object?
end

#owner_idObject

return the owner id



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

def owner_id
  @inner_object.ownerId
end

#owner_id=(owner_id) ⇒ Object



261
262
263
# File 'lib/active_fedora/base.rb', line 261

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

#persisted?Boolean

Returns:

  • (Boolean)


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

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



229
230
231
# File 'lib/active_fedora/base.rb', line 229

def pid
   @inner_object.pid
end

#pretty_pidObject



292
293
294
295
296
297
298
# File 'lib/active_fedora/base.rb', line 292

def pretty_pid
  if self.pid == UnsavedDigitalObject::PLACEHOLDER
    nil
  else
    self.pid
  end
end

#reifyObject

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



366
367
368
369
370
371
# File 'lib/active_fedora/base.rb', line 366

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.



376
377
378
379
380
381
# File 'lib/active_fedora/base.rb', line 376

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

#reloadObject

Reloads the object from Fedora.



83
84
85
86
# File 'lib/active_fedora/base.rb', line 83

def reload
  clear_association_cache
  init_with(self.class.find(self.pid).inner_object)
end

#solrize_profile(solr_doc = Hash.new) ⇒ Object

:nodoc:



320
321
322
323
324
325
326
327
328
329
330
331
332
333
# File 'lib/active_fedora/base.rb', line 320

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



337
338
339
340
341
342
343
344
345
# File 'lib/active_fedora/base.rb', line 337

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



252
253
254
# File 'lib/active_fedora/base.rb', line 252

def state 
  @inner_object.state
end

#to_keyObject



242
243
244
# File 'lib/active_fedora/base.rb', line 242

def to_key
  persisted? ? [pid] : nil
end

#to_paramObject



238
239
240
# File 'lib/active_fedora/base.rb', line 238

def to_param
  persisted? ? to_key.join('-') : 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)



304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/active_fedora/base.rb', line 304

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|
    solr_doc = ds.to_solr(solr_doc)
  end
  solr_doc = solrize_relationships(solr_doc) unless opts[:model_only]
  solr_doc
end