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, #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.



72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/active_fedora/base.rb', line 72

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



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

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



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/active_fedora/base.rb', line 176

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 = Nokogiri::XML( fedora_connection[0].connection.next_pid(args))
  pid = d.xpath('//fedora:pid', 'fedora' => 'http://www.fedora.info/definitions/1/0/management/').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.



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

.datastream_class_for_name(dsid) ⇒ Object



151
152
153
# File 'lib/active_fedora/base.rb', line 151

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

.pids_from_uris(uris) ⇒ Object



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

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



142
143
144
145
146
147
148
# File 'lib/active_fedora/base.rb', line 142

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



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

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



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

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



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

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



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

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



162
163
164
165
166
167
168
169
170
171
172
# File 'lib/active_fedora/base.rb', line 162

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)



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

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

#idObject

Needed for the nested form helper



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

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'


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

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



192
193
194
# File 'lib/active_fedora/base.rb', line 192

def inner_object # :nodoc
  @inner_object
end

#internal_uriObject

return the internal fedora URI



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

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

#modified_dateObject

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



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

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

#new?Boolean

Returns:

  • (Boolean)


49
50
51
# File 'lib/active_fedora/base.rb', line 49

def new?
  new_object?
end

#new_object?Boolean

Has this object been saved?

Returns:

  • (Boolean)


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

def new_object?
  inner_object.new?
end

#new_record?Boolean

Required by associations

Returns:

  • (Boolean)


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

def new_record?
  self.new_object?
end

#owner_idObject

return the owner id



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

def owner_id
  @inner_object.ownerId
end

#owner_id=(owner_id) ⇒ Object



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

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

#persisted?Boolean

Returns:

  • (Boolean)


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

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



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

def pid
   @inner_object.pid
end

#pretty_pidObject



248
249
250
251
252
253
254
# File 'lib/active_fedora/base.rb', line 248

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.



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

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.



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

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.



86
87
88
89
# File 'lib/active_fedora/base.rb', line 86

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

#to_keyObject



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

def to_key
  persisted? ? [pid] : nil
end

#to_paramObject



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

def to_param
  persisted? ? to_key.join('-') : nil
end