Skip to content

Patching ActiveFedora 7.x & 8.x Casting Behavior

David Chandek-Stark edited this page Apr 23, 2015 · 1 revision

Summary

ActiveFedora 7.x/8.x permits loading any repository object into any model, regardless of the model assertions persisted for that object (via hasModel in RELS-EXT). One way to patch this behavior and force casting to a known model for the object is presented.

For example, if you have an object asserting the Collection model with PID test:1, you can retrieve that object via the .find method on another model class:

> Item.find("test:1")
# => returns an Item instance, not a Collection instance.

Background discussion

https://groups.google.com/d/msg/hydra-tech/t4K3rdFIfhg/p0SNT15DnvwJ

Solution

The main part of the patch is to override ActiveFedora::ContentModel.best_model_for(obj). In ActiveFedora 7.x/8.x, when the object's class is not a known model for the object (as asserted in the repository) and it's not ActiveFedora::Base, then it simply returns the object's class -- i.e., the "best model" is not a "known model" in this case, which is why the .find example above works as it does. So, here we patch best_model_for to return only a matching class or subclass for the class of the object:

ActiveFedora::ContentModel.module_eval do
  # Returns the first known model for the object is equal to or a                                               
  # subclass of the object's class.                                                                             
  # This bubbles up, e.g., to prevent mis-casting via `.find`.                                                  
  def self.best_model_for(obj)
    known_models_for(obj).find { |model| model <= obj.class }
  end
end

Note that we have eliminated logic that might be needed in cases that use model class hierarchies. We are assuming that we do not need to find the most specific class model known, and that the first is sufficient.

Also note that, since ActiveFedora::ContentModel.best_model_for may now return nil (unlike AF 7.x/8.x), this will cause a TypeError exception, e.g., in ActiveFedora::Core#adapt_to_cmodel. I have decided to catch that exception and raise a custom error:

def adapt_to_cmodel
  super
rescue ::TypeError
  raise ContentModelError, "Cannot adapt to nil content model."
end

A more subtle change is required in ActiveFedora::FinderMethods#load_from_fedora in order to force casting by default, e.g., when using .find. Instead of the AF 7.x/8.x behavior, which only forces casting if the cast parameter is nil and klass is ActiveFedora::Base, here we force casting unless the cast parameter is explicitly false:

ActiveFedora::FinderMethods.module_eval do
  # Override tries to cast by default                                                                           
  def load_from_fedora(pid, cast)
    inner = ActiveFedora::DigitalObject.find(klass, pid)
    obj = klass.allocate.init_with_object(inner)
    if cast != false
      obj = obj.adapt_to_cmodel
    end
    obj
  end
end
Clone this wiki locally