module Spira::Persistence::ClassMethods

Public Instance Methods

[](identifier, attributes = {}, &block)
Alias for: for
all(*args) click to toggle source
# File lib/spira/persistence.rb, line 36
def all(*args)
  find(:all, *args)
end
count() click to toggle source

The number of URIs projectable as a given class in the repository. This method is only valid for classes which declare a `type` with the `type` method in the Resource.

@raise [Spira::NoTypeError] if the resource class does not have an RDF type declared @return [Integer] the count

# File lib/spira/persistence.rb, line 100
def count
  each.count
end
create(attributes = nil, options = {}, &block) click to toggle source

Creates an object (or multiple objects) and saves it to the database, if validations pass. The resulting object is returned whether the object was saved successfully to the database 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.

create respects mass-assignment security and accepts either :as or :without_protection options in the options parameter.

Examples

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

# Create a single new object using the :admin mass-assignment security role
User.create({ first_name: 'Jamie', is_admin: true }, as: :admin)

# Create a single new object bypassing mass-assignment security
User.create({ first_name: 'Jamie', is_admin: true }, without_protection: true)

# 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
# File lib/spira/persistence.rb, line 135
def create(attributes = nil, options = {}, &block)
  if attributes.is_a?(Array)
    attributes.collect { |attr| create(attr, options, &block) }
  else
    object = new(attributes, options, &block)
    object.save
    object
  end
end
each(*args) { |unserialize(solution)| ... } click to toggle source

Enumerate over all resources projectable as this class. This method is only valid for classes which declare a `type` with the `type` method in the Resource.

Note that the instantiated records are “promises” not real instances.

@raise [Spira::NoTypeError] if the resource class does not have an RDF type declared @overload each

@yield [instance] A block to perform for each available projection of this class
@yieldparam [self] instance
@yieldreturn [Void]
@return [Void]

@overload each

@return [Enumerator]
# File lib/spira/persistence.rb, line 60
def each(*args)
  raise Spira::NoTypeError, "Cannot count a #{self} without a reference type URI" unless type

  options = args.extract_options!
  conditions = options.delete(:conditions) || {}

  raise Spira::SpiraError, "Cannot accept :type in query conditions" if conditions.delete(:type) || conditions.delete("type")

  if block_given?
    limit = options[:limit] || -1
    offset = options[:offset] || 0
    # TODO: ideally, all types should be joined in a conjunction
    #       within "conditions_to_query", but since RDF::Query
    #       cannot handle such patterns, we iterate across types "manually"
    types.each do |tp|
      break if limit.zero?
      q = conditions_to_query(conditions.merge(type: tp))
      repository.query(q) do |solution|
        break if limit.zero?
        if offset.zero?
          yield unserialize(solution[:subject])
          limit -= 1
        else
          offset -= 1
        end
      end
    end
  else
    enum_for(:each, *args)
  end
end
Also aliased as: find_each
find(scope, *args) click to toggle source

Simple finder method.

@param [Symbol, ID] scope

scope can be :all, :first or an ID

@param [Hash] args

args can contain:
  :conditions - Hash of properties and values
  :limit      - Integer, limiting the amount of returned records

@return [Spira::Base, Array]

# File lib/spira/persistence.rb, line 25
def find(scope, *args)
  case scope
  when :first
    find_each(*args).first
  when :all
    find_all(*args)
  else
    instantiate_record(scope)
  end
end
find_each(*args)
Alias for: each
first(*args) click to toggle source
# File lib/spira/persistence.rb, line 40
def first(*args)
  find(:first, *args)
end
for(identifier, attributes = {}, &block) click to toggle source

Create a new projection instance of this class for the given URI. If a class has a base_uri given, and the argument is not an `RDF::URI`, the given identifier will be appended to the base URI.

Spira does not have 'find' or 'create' functions. As RDF identifiers are globally unique, they all simply 'are'.

On calling `for`, a new projection is created for the given URI. The first time access is attempted on a field, the repository will be queried for existing attributes, which will be used for the given URI. Underlying repositories are not accessed at the time of calling `for`.

A class with a base URI may still be projected for any URI, whether or not it uses the given resource class' base URI.

@raise [TypeError] if an RDF type is given in the attributes and one is given in the attributes. @raise [ArgumentError] if a non-URI is given and the class does not have a base URI. @overload for(uri, attributes = {})

@param [RDF::URI] uri The URI to create an instance for
@param [Hash{Symbol => Any}] attributes Initial attributes

@overload for(identifier, attributes = {})

@param [Any] uri The identifier to append to the base URI for this class
@param [Hash{Symbol => Any}] attributes Initial attributes

@yield [self] Executes a given block and calls `#save!` @yieldparam [self] self The newly created instance @return [Spira::Base] The newly created instance @see rdf.rubyforge.org/RDF/URI.html

# File lib/spira/persistence.rb, line 175
def for(identifier, attributes = {}, &block)
  self.project(id_for(identifier), attributes, &block)
end
Also aliased as: []
id_for(identifier) click to toggle source

Creates a URI or RDF::Node based on a potential base_uri and string, URI, or Node, or Addressable::URI. If not a URI or Node, the given identifier should be a string representing an absolute URI, or something responding to to_s which can be appended to a base URI, which this class must have.

@param [Any] identifier @return [RDF::URI, RDF::Node] @raise [ArgumentError] If this class cannot create an identifier from the given argument @see rdf.rubyforge.org/RDF/URI.html @see Spira.base_uri @see Spira.for

# File lib/spira/persistence.rb, line 211
def id_for(identifier)
  case
    # Absolute URI's go through unchanged
  when identifier.is_a?(RDF::URI) && identifier.absolute?
    identifier
    # We don't have a base URI to join this fragment with, so go ahead and instantiate it as-is.
  when identifier.is_a?(RDF::URI) && self.base_uri.nil?
    identifier
    # Blank nodes go through unchanged
  when identifier.respond_to?(:node?) && identifier.node?
    identifier
    # Anything that can be an RDF::URI, we re-run this case statement
    # on it for the fragment logic above.
  when identifier.respond_to?(:to_uri) && !identifier.is_a?(RDF::URI)
    id_for(identifier.to_uri)
    # see comment with #to_uri above, this might be a fragment
  else
    uri = identifier.is_a?(RDF::URI) ? identifier : RDF::URI.intern(identifier.to_s)
    case
    when uri.absolute?
      uri
    when self.base_uri.nil?
      raise ArgumentError, "Cannot create identifier for #{self} by String without base_uri; an RDF::URI is required"
    else
      separator = self.base_uri.to_s[-1,1] =~ /(\/|#)/ ? '' : '/'
      RDF::URI.intern(self.base_uri.to_s + separator + identifier.to_s)
    end
  end
end
project(subject, attributes = {}, &block) click to toggle source

Create a new instance with the given subject without any modification to the given subject at all. This method exists to provide an entry point for implementing classes that want to create a more intelligent .for and/or .id_for for their given use cases, such as simple string appending to base URIs or calculated URIs from other representations.

@example Using simple string concatentation with base_uri in .for instead of joining delimiters

def for(identifier, attributes = {}, &block)
  self.project(RDF::URI(self.base_uri.to_s + identifier.to_s), attributes, &block)
end

@param [RDF::URI, RDF::Node] subject @param [Hash{Symbol => Any}] attributes Initial attributes @return [Spira::Base] the newly created instance

# File lib/spira/persistence.rb, line 194
def project(subject, attributes = {}, &block)
  new(attributes.merge(_subject: subject), &block)
end
repository() click to toggle source

The current repository for this class

@return [RDF::Repository, nil]

# File lib/spira/persistence.rb, line 11
def repository
  Spira.repository || raise(NoRepositoryError)
end

Private Instance Methods

conditions_to_query(conditions) click to toggle source
# File lib/spira/persistence.rb, line 252
def conditions_to_query(conditions)
  patterns = []
  conditions.each do |name, value|
    if name.to_s == "type"
      patterns << [:subject, RDF.type, value]
    else
      patterns << [:subject, properties[name][:predicate], value]
    end
  end

  RDF::Query.new do
    patterns.each { |pat| pattern(pat) }
  end
end
find_all(*args) click to toggle source
# File lib/spira/persistence.rb, line 244
def find_all(*args)
  [].tap do |records|
    find_each(*args) do |record|
      records << record
    end
  end
end