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
# File 'lib/active_fedora/base.rb', line 69

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



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



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/active_fedora/base.rb', line 203

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.



117
118
119
120
121
122
123
124
125
126
127
# File 'lib/active_fedora/base.rb', line 117

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


171
172
173
174
175
176
177
178
179
180
# File 'lib/active_fedora/base.rb', line 171

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



145
146
147
# File 'lib/active_fedora/base.rb', line 145

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.



391
392
393
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
# File 'lib/active_fedora/base.rb', line 391

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['has_model_s']
    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



426
427
428
429
430
431
432
433
434
435
436
# File 'lib/active_fedora/base.rb', line 426

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



136
137
138
139
140
141
142
# File 'lib/active_fedora/base.rb', line 136

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



281
282
283
284
285
286
# File 'lib/active_fedora/base.rb', line 281

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



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

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



355
356
357
358
# File 'lib/active_fedora/base.rb', line 355

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



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

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



189
190
191
192
193
194
195
196
197
198
199
# File 'lib/active_fedora/base.rb', line 189

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)



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

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

#idObject

Needed for the nested form helper



231
232
233
# File 'lib/active_fedora/base.rb', line 231

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'


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

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].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



219
220
221
# File 'lib/active_fedora/base.rb', line 219

def inner_object # :nodoc
  @inner_object
end

#internal_uriObject

return the internal fedora URI



244
245
246
# File 'lib/active_fedora/base.rb', line 244

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

#labelObject

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



273
274
275
# File 'lib/active_fedora/base.rb', line 273

def label
  @inner_object.label
end

#label=(new_label) ⇒ Object



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

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)



268
269
270
# File 'lib/active_fedora/base.rb', line 268

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



254
255
256
# File 'lib/active_fedora/base.rb', line 254

def owner_id
  @inner_object.ownerId
end

#owner_id=(owner_id) ⇒ Object



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

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



226
227
228
# File 'lib/active_fedora/base.rb', line 226

def pid
   @inner_object.pid
end

#pretty_pidObject



289
290
291
292
293
294
295
# File 'lib/active_fedora/base.rb', line 289

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.



363
364
365
366
367
368
# File 'lib/active_fedora/base.rb', line 363

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.



373
374
375
376
377
378
# File 'lib/active_fedora/base.rb', line 373

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.



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

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

#solrize_profile(solr_doc = Hash.new) ⇒ Object

:nodoc:



317
318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/active_fedora/base.rb', line 317

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



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

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



249
250
251
# File 'lib/active_fedora/base.rb', line 249

def state 
  @inner_object.state
end

#to_keyObject



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

def to_key
  persisted? ? [pid] : nil
end

#to_paramObject



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

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)



301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
# File 'lib/active_fedora/base.rb', line 301

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