[Activerdf] RoR and ActiveRDF integration, plus some extensions
Jean-Francois Cloutier
jean.f.cloutier at gmail.com
Sat Jul 15 16:02:52 IST 2006
Hi,
I made some extensions and modifications to ActiveRDF about a month ago in
an attempt to
1- better integrate it with Rails: testing (with Rake and RDF fixtures),
callbacks (similar to ActiveRecord) and validation
2- add capabilities I needed :generating classes from schema into a module,
inheritance based on the schema class hierarchy and ability to add
(inherited) functionality to the generated classes.
However much I'd like to work with an RDF store on my current project, it
has become clear that I'll have to go with MySQL and ActiveRecord until
production-grade support for RDF is fully integrated into Rails. I am
hopeful that the work initiated by Hansson (Rails' creator) on
ActiveResource (google it), by extending it to support RDF resources, will
lead to fully integrated RDF support in Rails.
The following code is barely documented and minimally tested. Since I am
putting this work aside indefinitely, I thought I'd share it in its current
state. I hope someone will find it useful.
WARNING: The code is experimental and is not up to date with the latest
development release. Also I renamed a few things that were specific to my
project and I have not tested those changes. Don't expect this code to work
for you as is.
Best,
Jean-Francois
README.txt
-------------------
I made the following enhancements to activerdf
- Optimized Ruby class generation from RDFS schema classes (activerdf
does not recreate the RDFS inheritance structure)
- Added the option to create these classes in a dedicated module to
avoid name conflicts
- Implemented the functional equivalent of inheritance between generated
classes via mixins
- Added validation callbacks and all other ActiveRecord-style callbacks
on creation, saving and updating
- Fixed the find method so that the instances retrieved are of the
correct, schema-generated classes
The RDF database used for the protoype thus far is YARS. YARS only accepts
Notation3 as input and as a query language. I had to write a simple shell
script to convert and massage an XML-RDFS file into a valid N3 encoding. (I
use Protege to build the schema and model and then save it as XML.-RDFS).
The Ruby source code:
- activerdf_fixes.rb are fixes to the development build as of June 07
- activerdf_extensions.rb are the actual extensions to activerdf
- mixins.rb is sample code that is injected into generated classes when
faking inheritance
The extensions I introduced make a number of assumptions:
- The YARS connection is established with class model generation turned
on and classes created within a "model module", say ACME (for example an
RDFS class called <http://domain.com/ACME/Foo> would yield the class
ACME::Foo, a subclass of IdentifiedResource)
- When a model class is created, a sub-module of the model module is
mixed into the class to add class methods if module <model module>::<class
name>ClassMixin (e.g. ACME::FooClassMixin) is found.
- When an instance of a model class is created, the instance is extended
by modules <model module>::<class name>Mixin where <class name> is the local
name of the instance's class (e.g. Foo) or the name of a class that ought to
be its superclass according to the RDFS schema (e.g.SuperFoo). This is how
RDFS-schema-directed inheritance is "faked" in the generated Ruby class
model. (Doing it right would require a significant rewrite of activerdf
which is actually planned by its developers)
- the URIs of individuals are expected to follow the following pattern
<class URI>#<id> (e.g. http://domain.com/ACME/Foo#myfoo)
Shell script to convert RDFS-XML into NTriples
-------------------------------------------------------------------
#! /bin/sh
# parameters = directory (no trailing /) and file prefix (e.g. acme)
# assumes that the schema is saved as <prefix>_schema.rdfs (e.g.
acme_schema.rdfs)
# assumes that the model (individuals) is saved as <prefix>_model.rdfs
# requires the CWM python library
cd ~/tools/cwm
echo "Converting schema"
python cwm.py -rdf $1/$2_schema.rdfs -ntriples > $1/$2_schema.pre
sed -e s/\"\"\"/\"/g $1/$2_schema.pre > $1/$2_schema.nt
rm $1/$2_schema.pre
echo "Converting model"
python cwm.py -rdf $1/$2_model.rdfs -ntriples > $1/$2_model.pre
sed -e s/\"\"\"/\"/g $1/$2_model.pre > $1/$2_model.nt
rm $1/$2_model.pre
<railsAppDir>/config/environment.rb
----------------------------------------------------
# Be sure to restart your web server when you modify this file.
# Uncomment below to force Rails into production mode when
# you don't control web/app server and can't set it the proper way
# ENV['RAILS_ENV'] ||= 'production'
# Specifies gem version of Rails to use when vendor/rails is not present
RAILS_GEM_VERSION = '1.1.2'
# Bootstrap the Rails environment, frameworks, and default configuration
require File.join(File.dirname(__FILE__), 'boot')
Rails::Initializer.run do |config|
# Settings in config/environments/* take precedence those specified here
# Skip frameworks you're not going to use
config.frameworks -= [ :action_web_service, :action_mailer, :active_record
]
# Add additional load paths for your own custom dirs
# config.load_paths += %W( #{RAILS_ROOT}/extras )
config.load_paths += %W( "#{RAILS_ROOT}/lib/activerdf" )
# Force all environments to use the same logger level
# (by default production uses :info, the others :debug)
# config.log_level = :debug
# Use the database for sessions instead of the file system
# (create the session table with 'rake db:sessions:create')
# config.action_controller.session_store = :active_record_store
# Use SQL instead of Active Record's schema dumper when creating the test
database.
# This is necessary if your schema can't be completely dumped by the
schema dumper,
# like if you have constraints or database-specific column types
# config.active_record.schema_format = :sql
# Activate observers that should always be running
# config.active_record.observers = :cacher, :garbage_collector
# Make Active Record use UTC-base instead of local time
# config.active_record.default_timezone = :utc
# See Rails::Configuration for more options
# ActiveRDF config
require 'activerdf/active_rdf'
require 'activerdf_fixes'
require 'activerdf_extensions'
require 'mixins'
end
# Add new inflection rules using the following format
# (all these examples are active by default):
# Inflector.inflections do |inflect|
# inflect.plural /^(ox)$/i, '\1en'
# inflect.singular /^(ox)en/i, '\1'
# inflect.irregular 'person', 'people'
# inflect.uncountable %w( fish sheep )
# end
# Include your application configuration below
<railsAppDir>/config/development.rb
----------------------------------------------------
# Settings specified here will take precedence over those in
config/environment.rb
# In the development environment your application's code is reloaded on
# every request. This slows down response time but is perfect for
development
# since you don't have to restart the webserver when you make code changes.
config.cache_classes = false
# Log error messages when you accidentally call methods on nil.
config.whiny_nils = true
# Enable the breakpoint server that script/breakpointer connects to
config.breakpoint_server = true
# Show full error reports and disable caching
config.action_controller.consider_all_requests_local = true
config.action_controller.perform_caching = false
config.action_view.cache_template_extensions = false
config.action_view.debug_rjs = true
# Don't care if the mailer can't send
config.action_mailer.raise_delivery_errors = false
#ActiveRDF
#Load YARS configuration from yaml file
yaml = YAML::load_file File.join(File.dirname(config.environment_path),'
activerdf.yml')
yaml = yaml['development']
NodeFactory.connection yaml.symbolize_keys
<railsAppDir>/config/test.rb
---------------------------------------
# Settings specified here will take precedence over those in
config/environment.rb
# The test environment is used exclusively to run your application's
# test suite. You never need to work with it otherwise. Remember that
# your test database is "scratch space" for the test suite and is wiped
# and recreated between test runs. Don't rely on the data there!
config.cache_classes = true
# Log error messages when you accidentally call methods on nil.
config.whiny_nils = true
# Show full error reports and disable caching
config.action_controller.consider_all_requests_local = true
config.action_controller.perform_caching = false
# Tell ActionMailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test
#ActiveRDF
#Load YARS configuration from yaml file
yaml = YAML::load_file File.join(File.dirname(config.environment_path),'
activerdf.yml')
yaml = yaml['test']
NodeFactory.connection yaml.symbolize_keys
# Dirty patch to bug in ActiveRDF...
# $logger = Logger.new '/tmp/activerdf.log', 'daily'
# $logger.level = Logger::DEBUG
<railsAppDir>/config/activerdf.yml
-------------------------------------------------
development:
cache_server: !ruby/symbol memory
adapter: !ruby/symbol yars
context: 'yars'
host: 'localhost'
construct_class_model: true
logger: '/tmp/activerdf.log'
log_level: 0
class_model_module: 'ACME'
test:
cache_server: !ruby/symbol memory
adapter: !ruby/symbol yars
context: 'yars-test'
host: 'localhost'
construct_class_model: true
logger: '/tmp/activerdf.log'
log_level: 0
class_model_module: 'ACME'
<railsAppDir>/app/activerdf_extensions.rb
--------------------------------------------------------------
###########################
# Extensions to ActiveRDF #
###########################
################### Extending NodeFactory ####################
# Adding a callback to model construction
module NodeFactoryMixin
def construct_class_model_with_callback(module_name)
construct_class_model_without_callback(module_name)
class_model_constructed_callback(module_name)
end
end
NodeFactory.extend(NodeFactoryMixin)
class NodeFactory
# Adds class-level and instance-level mixins if defined to classes
generated from RDF schema
# Only works if classes generated within a named module
def self.class_model_constructed_callback(module_name)
$logger.info "Callback: Model constructed in module #{module_name}"
if module_name
module_regexp = Regexp.new("^#{module_name}::")
IdentifiedResource.subclasses.each do |klass_name|
if klass_name =~ module_regexp
if eval("defined? #{klass_name}ClassMixin")
$logger.info "*** extending #{klass_name} with
#{klass_name}ClassMixin"
# p "*** extending #{klass_name} with
#{klass_name}ClassMixin"
eval("#{klass_name}.extend(#{klass_name}ClassMixin)")
else
# p "did NOT extend class #{klass_name} with
#{klass_name}ClassMixin"
end
superklass_names =
find_superclasses(eval(klass_name).class_URI).collect {|s|
"#{module_name}::#{s}"}
IdentifiedResource.set_superclass_names(klass_name,
superklass_names)
end
end
end
end
# Efficiently finds all superclasses of a schema class
def self.find_superclasses(uri)
superclass_names = []
qe = QueryEngine.new
qe.add_binding_variables(:o)
qe.add_condition(uri, NamespaceFactory.get(:rdfs,:subClassOf), :o)
# Execute the query
qe.execute.each do |o|
unless superclass_names.include? o
superclass_names << o.local_part
superclass_names += find_superclasses(o)
end
end
superclass_names.uniq.reverse # top-down
end
class << self
alias_method :construct_class_model_without_callback,
:construct_class_model
alias_method :construct_class_model,
:construct_class_model_with_callback
end
end
################### IdentifiedResource extensions ####################
# IdentifiedResource does not create a class hierarchy of identified
resources that reflects the schema class hierarchy.
# This mixin module fixes that by creating a parallel hierarchy of modules
that are mixed into the generated classes.
# This module also implements a callback framework similar to that of
ActiveRecord.
# There's also the beginning of a validation framework similar to
ActiveRecord.
# IMPORTANT: Assumes the URI pattern #{class_URI}#id for individuals
module IdentifiedResourceClassMixin
def create_with_callbacks(uri)
pre_create(uri)
# Assumes the URI pattern #{class_URI}#id for individuals
klass = get_class_from_uri(uri)
individual = klass.create_without_callbacks(uri)
individual.post_create
individual
end
end
# Add class methods
IdentifiedResource.extend(IdentifiedResourceClassMixin)
class IdentifiedResource
@@superclass_names = {}
@@class_uris = {}
def self.get_class_uris()
@@class_uris
end
def self.get_class_from_uri(uri)
if uri =~ /^(.+)#.+$/
class_uri = $1
@@class_uris[class_uri] || IdentifiedResource
else
IdentifiedResource
end
end
def self.set_superclass_names(klass_name, super_names)
@@superclass_names[klass_name] = super_names
end
def self.get_superclass_names(klass_name)
@@superclass_names[klass_name] || []
end
def self.remember_class_uri(klass)
@@class_uris[klass.class_URI.uri] = klass
end
def self.pre_create(uri)
before_create(uri)
end
# callback
def self.before_create(uri)
$logger.info "Before creating #{self.name} with URI #{uri}"
end
attr :errors
attr :created
# alias class methods
class << self
alias_method :create_without_callbacks, :create
alias_method :create, :create_with_callbacks
end
def save_with_callbacks()
pre_save
save_without_callbacks
post_save
end
alias_method :save_without_callbacks, :save
alias_method :save, :save_with_callbacks
def post_create()
# $logger.info "was created with #{uri}"
# Extend instance with all available mixins associated with RDFS
class and RDFS superclasses of instance
# $logger.info IdentifiedResource.get_superclass_names(
self.class.to_s)
(IdentifiedResource.get_superclass_names(self.class.to_s) <<
self.class.to_s).each do |sn|
if eval("defined? #{sn}Mixin")
eval("self.extend #{sn}Mixin")
$logger.info "Extended instance of #{self.class} with
#{sn}Mixin"
end
end
@created = true
@errors = Hash.new
after_create
end
def pre_save()
before_validation
validate
after_validation
if @created
before_validation_on_create
validate_on_create
after_validation_on_create
else
before_validation_on_update
validate_on_update
after_validation_on_update
end
before_save
end
def post_save()
@created = false
after_save
end
## Default noop callbacks
def after_create()
$logger.info "After creating #{self}"
end
def before_save()
$logger.info "Before saving #{self}"
end
def after_save()
$logger.info "After saving #{self}"
end
def validate()
$logger.info "Validating #{self}"
end
def validate_on_create()
$logger.info "Validating #{self} on create"
end
def validate_on_update()
$logger.info "Validating #{self} on create"
end
def before_validation()
$logger.info "Before validation of #{self}"
end
def after_validation()
$logger.info "After validation of #{self}"
end
def before_validation_on_create()
$logger.info "Before validation on create of #{self}"
end
def after_validation_on_create()
$logger.info "After validation on create of #{self}"
end
def before_validation_on_update()
$logger.info "Before validation on update of #{self}"
end
def after_validation_on_update()
$logger.info "After validation on update of #{self}"
end
end
<railsAppDir>/app/activerdf_fixes.rb
---------------------------------------------------
######################
# Fixes to ActiveRDF #
######################
module AttributesContainer
# save all property values. we use self.predicates hash to know the
original
# URIs of all predicates (we lost those when converting to attributes)
def save_attributes()
# If _attributes is nil, we need to load it from the DB
if _attributes.nil?
initialize_attributes
end
self.class.predicates.each do |attr_localname, attr_fullname|
object = @_attributes[attr_localname]
# to save the triple we need the full URI of the predicate
predicate = attr_fullname
# if an attribute is an array, we save all constituents
sequentially,
# e.g. person.publications = ['article1', 'article2']
# First, we remove all triples related to (subject, predicate)
only if
# the attribute values have changed
if not object.nil? and object[1]
# [FIX] Ignore exception on removal
begin
NodeFactory.connection.remove(self, predicate, nil)
rescue
$logger.error "Remove #{predicate} failed: #{$!}"
end
end
# then save the new value, if the value have changed and the
value is not nil
if not object.nil? and object[1] and not object[0].nil? and
object[0].is_a?(Array)
object[0].each do |realvalue|
NodeFactory.connection.add(self, predicate, realvalue)
end
elsif not object.nil? and object[1] and not object[0].nil?
NodeFactory.connection.add(self, predicate, object[0])
end
end
end
end
class NodeFactory
# Initialise cache and connection to data source. If no parameter
given, we
# return the previous instantiated connection.
#
# Available parameters, possible values and default values
# * +:adapter+ => :yars, :redland, :sparql, :jena (default :yars)
# * +:construct_class_model+ => boolean (default true)
# * +:class_model_module+ => string (default nil)
# * +:construct_schema+ => boolean (default false)
# * +:cache_server+ => 'host-url' or :memory (default :memory)
# * +:proxy+ => 'proxy-url' or ProxyClass (default nil)
# * +:logger+ => File (default tmpdir/activerdf.log)
# * +:logger_level+ => Logger::Level (default Logger::FATAL)
#
# Hash of parameters for adapter yars:
# * +:host+ => Host of the yars server
# * +:port+ => y default 8080
# * +:context+ => 'context-url', by default the root context
# Hash of parameters for adapter redland:
# * +:location+ => 'file-path' or :memory (default /tmp/test-store)
def self.connection(params = {})
# if no parameters given, and connection already established earlier,
return that connection
if params.empty?
return @@current_connection unless @@current_connection.nil?
end
# use default parameters for the unspecified parameters
params = default_parameters.merge(params)
# setup the logger
# [FIX] added second parameter
$logger = Logger.new params[:logger] , 'daily'
$logger.level = params[:logger_level]
# Initialize cache system
init_cache(params[:cache_server] || params[:host])
# Initialize DB adapter
connection = init_adapter(params)
# Save the parameter
@@default_host_parameters = params
return connection
end
private
# constructs the class model from a RDF dataset
def self.construct_class_model(module_name)
qe = QueryEngine.new
qe.add_binding_variables :s
qe.add_condition(:s, NamespaceFactory.get(:rdf,:type),
NamespaceFactory.get(:rdfs,:Class))
all_types = qe.execute
$logger.info "found #{all_types.size} types in #{connection.context
}"
# [EXTENSION] Added support for generating classes from schema into
named module
if module_name
if eval "defined? #{module_name}"
mod = eval "#{module_name}"
# $logger.warn "Module #{module_name} is already defined"
else
mod = Object.module_eval "#{module_name} = Module.new"
# $logger.info "Defining module #{module_name}"
end
else
mod = Object
end
klasses = all_types.collect do |type|
construct_class(mod, type, qe) if type.kind_of?(Resource)
end
##for type in all_types do
## unless type.kind_of?(Resource)
## raise(NodeFactoryError, "received literal #{type} instead
as resource type")
## end
## klasses << construct_class(type, qe)
##end
klasses.uniq.each do |klass|
# and loading all attributes into the class
get_class_attributes_from_data klass, qe
end
end
# constructs a class from an RDF resource (using its local_name as class
name)
# [EXTENSION] Support for generating classes from schema into named
module
def self.construct_class(mod, type, qe)
## TODO: setup context inside class
class_name = type.to_class_name
unless mod.const_defined? class_name.to_sym
klass = mod.module_eval("#{class_name} =
Class.newIdentifiedResource")
klass.set_class_uri type.uri
# Remember mapping class -to -uri
IdentifiedResource.remember_class_uri(klass)
$logger.info "created class #{class_name}"
klass
else
mod.const_get(class_name)
end
end
end
# Optimized finding predicates
class Resource
@@predicates_cache = Hash.new
@@thing_predicates = nil
def self.find_thing_predicates()
unless @@thing_predicates
predicates = Hash.new
preds = Resource.find({ NamespaceFactory.get(:rdfs,:domain) =>
NamespaceFactory.get(:owl,:Thing) })
preds.each do |predicate|
attribute = predicate.local_part
predicates[attribute] = predicate.uri
# $logger.debug "added OWL Thing predicate #{attribute}"
end unless preds.nil?
@@thing_predicates = predicates
end
@@thing_predicates
end
# Find all predicates for a resource from the schema definition
# returns a hash containing from localname to full predicate URI (e.g.
# 'firstName' => 'http://foaf.org/firstName')
def self.find_predicates(class_uri)
raise(ActiveRdfError, "In #{__FILE__}:#{__LINE__}, class uri is
nil.") if class_uri.nil?
raise(ActiveRdfError, "In #{__FILE__}:#{__LINE__}, class uri is not
a Resource, it's a #{class_uri.class}.") unless
class_uri.kind_of?(IdentifiedResource)
unless @@predicates_cache[class_uri]
predicates = Hash.new
preds = Resource.find({ NamespaceFactory.get(:rdfs,:domain) =>
class_uri })
preds.each do |predicate|
attribute = predicate.local_part
predicates[attribute] = predicate.uri
$logger.debug "found predicate #{attribute}"
end unless preds.nil?
predicates.update(find_thing_predicates)
# Generate the query string
qe = QueryEngine.new
qe.add_binding_variables(:o)
qe.add_condition(class_uri,
NamespaceFactory.get(:rdfs,:subClassOf),
:o)
$logger.debug "Before call Find superpredicate for #{class_uri}"
# Execute the query
qe.execute.each do |o|
$logger.debug "Find superpredicate #{o.to_s} for
#{class_uri}"
superclass = o
superpredicates = find_predicates(superclass)
# $logger.debug "Found superpredicate : " +
superpredicates.inspect
predicates.update(superpredicates)
end
$logger.debug "Find predicate result : " + predicates.inspect
@@predicates_cache[class_uri] = predicates
end # unless
@@predicates_cache[class_uri]
end
end
<railsAppDir>/app/mixins.rb
-----------------------------------------
####################################
# Resource class extension modules #
####################################
# Assuming that NodFactory generated classes from schema into ACME module
module ACME
# Assuming a schema class named UniqueIDResource
# Class mixin
module UniqueIDResourceClassMixin
def make_guid
"#{Object::Time.now.to_f}#{rand}"
end
end
# Instance mixin
module UniqueIDResourceMixin
def after_create
super
self.has_guid = ACME::UniqueIDResource.make_guid
self.created_on = Object::Time.now.utc.strftime
('%Y-%m-%dT%H:%M:%S')
end
end
# Assuming a schema class named TestResource as subclass of
UniqueIDResource
module TestResourceMixin
def after_create
super
$logger.info "After creating #{self.has_guid} in TestResource"
end
end
module UserMixin
def is_admin?
is_admin == 'true'
end
end
end
<railsAppDir>/test/test_helper.rb
-----------------------------------------------
ENV["RAILS_ENV"] = "test"
require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
# From Rails Recipe, Rails without a database
# Instead of require test_help.rb which redefines setup and teardown for
databases, we cherry-pick.
require 'application'
require 'test/unit'
require 'action_controller/test_process'
require 'breakpoint'
class Test::Unit::TestCase
# RDF fixtures
@@rdf_fixtures = Hash.new
alias __initialize__ initialize
# Assign the fixtures to an instance variable of the same name
def initialize(test)
__initialize__(test)
@@rdf_fixtures.each do |fixture, value|
eval("@#{fixture} = {}")
self.class.send(:attr_writer, fixture.to_sym)
self.send("#{fixture}=", value)
end
end
# Load the RDF fixtures in file <name>.yml in ./test/fixtures/rdf
def self.rdf_fixtures(name)
filename = File.expand_path(File.join(File.dirname(__FILE__),
"fixtures/rdf/#{name}.yml"))
yaml = YAML::load_file filename
@@rdf_fixtures[name] = yaml2rdf(yaml)
end
private
# Process YAML file containing RDF resource specifications
# The YAML file defining the RDF fixtures has a simple format.
#Example:
# ISNA:
# - 'http://mindalliance.com/isna'
# - User:
# admin:
# find_one:
# has_uid: 'admin'
# participant:
# create:
# has_uid: 'jd'
# has_password: 'secret'
# In this example,
# * ISNA is the module name for the schema-generated classes
# * http... is the prefix for the model
# * User is the name of a class
# * admin and participant are the keys for the resources, allowing
@users[:admin] etc.
# * find_one and create are the methods by which the resources are
instantiated (retrieved or created).
# o find_all is also supported and retrieves into an Array all that
match the criteria.
# * has_uid, has_password etc. are resource properties to bet set on
creation or used as conditions for retrieval depending on the methods
specified.
#Note that the RDF fixtures file can define multiple modules and, in
each one, multiple classes with their instances. The names of the instances
must be unique.
def self.yaml2rdf(yaml)
hash = Hash.new
begin
yaml.each do |module_name, module_content|
uri = module_content[0]
class_sets = module_content[1]
class_sets.each do |class_name, individuals|
individuals.each do |name, individual|
action = individual.keys[0]
case action
when 'create'
hash[name] = create_rdf_individual(module_name,
uri, class_name, individual[action])
when 'find_one'
inds = find_rdf_individual(module_name, uri,
class_name, individual[action])
if inds.size != 1
raise "More than one individual found"
else
hash[name] = inds[0]
end
when 'find_all'
hash[name] = find_rdf_individual(module_name,
uri, class_name, individual[action])
else
raise "Invalid action #{action}"
end
end
end
end
rescue
raise "Invalid fixture: #{$!}"
end
hash.symbolize_keys!
end
# Create rdf resource and initialize properties in params
def self.create_rdf_individual(module_name, uri, class_name, params)
klass = eval("#{module_name}::#{class_name}")
ind = klass.create("#{uri}/#{class_name}##{rand}")
if params
params.each do |predicate, value|
method_name = predicate + '='
ind.send(method_name.intern, value)
end
end
ind
end
# Find all resources that meet conditions in params
def self.find_rdf_individual(module_name, uri, class_name, params)
klass = eval("#{module_name}::#{class_name}")
conditions = Hash.new
if params
params.each do |predicate, value|
conditions[predicate.intern] = Literal.create(value)
end
end
klass.find(conditions)
end
end
<railsAppDir>/test/unit/user_test.rb
-----------------------------------------------------
# Assumes a schema class User from which a class User was generated in
module ACME
require File.dirname(__FILE__) + '/../test_helper'
class UserTest < Test::Unit::TestCase
rdf_fixtures :users
def setup
@admin = @users[:admin]
@participant = @users[:participant]
@all = @users[:all]
end
# Tests that single fixture was retrieved (find_one)
def test_found_one
assert_kind_of ACME::User, @admin
end
# Test that fixture was properly created
def test_created
assert_kind_of ACME::User, @participant
assert_equal @participant.has_uid, 'jd'
assert_equal @participant.has_password, 'secret'
end
# Test that all fixtures were retrieved (find_all)
def test_all_found
assert_kind_of Array, @all
@all.each do |ind|
assert_kind_of ACME::User, ind
end
end
# Tests that User mixin was properly applied
def test_admin
assert @admin.is_admin?
assert !@participant.is_admin?
end
end
<railsAppDir>/test/unit/test/fixtures/rdf/users.yml
-----------------------------------------------------------------------
# Don't forget: no tabs
# In this example,
# * ACME is the module name for the schema-generated classes
# * http... is the prefix for the model
# * User is the name of a class
# * admin and participant are the keys for the resources, allowing
@users[:admin] etc.
# * find_one and create are the methods by which individual resources are
instantiated (retrieved or created).
# o find_all is also supported and retrieves into an Array all that
match the criteria.
# * has_uid, has_password etc. are resource properties to bet set on
creation or used as conditions
# for retrieval depending on the methods specified.
# Note that the RDF fixtures file can define multiple modules and, in each
one,
# multiple classes with their instances. The names of the instances must be
unique.
ACME:
- 'http://domain.com/acme'
- User:
# assumes that an administrative User 'admin' exists
admin:
find_one:
has_uid: 'admin'
participant:
create:
has_uid: 'jd'
has_password: 'secret'
# no conditions
all:
find_all:
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.deri.org/pipermail/activerdf/attachments/20060715/238d443c/attachment-0001.htm
More information about the Activerdf
mailing list