ActiveFedora in depth

What are your goals?

What is ActiveFedora?

Where did it come from?

Whole cloth

https://github.com/projecthydra/active_fedora/commit/29d0c09eef32dcda4120a5bb82c23596d33363dc

@blame flyingzumwalt

zoia -- flyingzumwalt reminds jcoyne of a bug in Rubydora

Early Contributors (in order of apperance)

So, jcoyne, what's your involvement?

How did we do?

Best improvements (according to jcoyne)

Worst parts (according to jcoyne)

What does the code look like?

/lib/active_fedora/base.rb#L27-L54

Compare to ActiveRecord

/lib/active_record/base.rb#L269-L316

How does ActiveRecord differ from ActiveFedora?

Multiple inheritance architecture

What happens when we call save() on an ActiveFedora object?


          ActiveFedora::Base.ancestors
          => [ActiveFedora::Base, ActiveFedora::LoadableFromJson, ActiveFedora::Versionable, ActiveFedora::Attributes::PrimaryKey, ActiveFedora::Attributes::Serializers, ActiveFedora::Attributes, ActiveModel::ForbiddenAttributesProtection, ActiveModel::Dirty, ActiveFedora::AttributeMethods::Dirty, ActiveFedora::AttributeMethods::Write, ActiveFedora::AttributeMethods::Read, #, ActiveFedora::AttributeMethods, ActiveModel::AttributeMethods, ActiveTriples::Reflection, ActiveFedora::Base::GeneratedPropertyMethods, ActiveTriples::Properties, ActiveFedora::FedoraAttributes, ActiveFedora::InheritableAccessors, ActiveFedora::AttachedFiles, ActiveFedora::Serialization, ActiveModel::Serializers::JSON, ActiveModel::Serialization, ActiveFedora::Reflection, ActiveFedora::NestedAttributes, ActiveFedora::AutosaveAssociation, ActiveFedora::Associations, ActiveFedora::Validations, ActiveModel::Validations::HelperMethods, ActiveModel::Validations, ActiveModel::Validations::Callbacks, ActiveSupport::Callbacks, ActiveFedora::Callbacks, ActiveModel::Conversion, ActiveFedora::Scoping::Named, ActiveFedora::Scoping::Default, ActiveFedora::Scoping, ActiveFedora::Indexing, ActiveFedora::Persistence, ActiveFedora::Core, Object, PP::ObjectMixin, JSON::Ext::Generator::GeneratorMethods::Object, Kernel, BasicObject]

Multiple inheritance architecture

What happens when we call save() on an ActiveFedora object?

Querying

Querying means to define a scope (an ActiveFedora::Relation)

The most common scope is all


    > Book.all
    => []
    > Book.all.class
    => ActiveFedora::Relation
      

ActiveFedora::Relation

/lib/active_fedora/relation.rb

Has a @klass and @values Hash (:where, :order, :limit, :offset)

These are the constraints of a Relationship

Value accessors are here: /lib/active_fedora/relation/query_methods.rb#L4-L47

ActiveFedora::Relation

In our book example all the values are empty


    > Book.all.where_values
    => []
    > Book.all.order_values
    => []
    > Book.all.limit_value
    => nil
    > Book.all.offset_value
    => nil
      

How does it load the records?

You can refine the scope

By using where()
> Book.where(title: 'foo').where_values
=> ["title_tesim:foo"]
and chain scopes:
> Book.where(title: 'foo').
       where(author: 'Betty').where_values
=> ["title_tesim:foo", "author_tesim:Betty"]

How do associations work?


# lib/active_fedora/associations.rb

def belongs_to(name, options = {})
  raise "You must specify a property name for #{name}" if !options[:property]
  Builder::BelongsTo.build(self, name, options)
end

Builder creates a Reflection


Book.create_reflection(:has_and_belongs_to_many, 'pages', { property: 'hasPages'}, Book)

# Adds a reflection:
Book.reflections
#=> { 'pages' => <#AssociationReflection
#           @options={ :property => :has_pages }
#           @macro=:has_and_belongs_to_many ...> }

Builder creates the accessors


def "#{name}_id"
  association(name).id_reader
end

def "#{name}_id="(id)
  association(name).id_writer(id)
end

Builder creates the accessors


def "#{name}"
  association(name).reader
end

def "#{name}="(value)
  association(name).writer(value)
end

The association method


def association(name)
  reflect = self.class.reflect_on_association(name)
  reflect.association_class.new(self, reflect)
end

The SingularAssociation

The CollectionAssociation

class Book < ActiveFedora::Base
 belongs_to :library, property: :has_constituent
end

class Library < ActiveFedora::Base
  has_many :books
end

CollectionProxy


library = Library.new
library.books.build
library.books.build
library.books.class
=> ActiveFedora::Associations::CollectionProxy
library.books

=> [#<Book pid: nil, library_id: nil>, #<Book pid: nil, library_id: nil>]

CollectionProxy is a Relation

Challenges with Associations

Attribute tracking

ActiveFedora keeps track of attributes in memory, like ActiveRecord. This enables dirty tracking on a per-attribute basis.

Challenge: Track attributes regardless of whether they are persisted on the RDF resource representing the object, or in a NonRDFSource subnode (f.k.a. Datastream)

How does indexing work?

Fin.

/