active-triples 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.travis.yml +12 -0
- data/AUTHORS +2 -0
- data/Gemfile +3 -0
- data/LICENSE +24 -0
- data/README.md +38 -0
- data/active-triples.gemspec +35 -0
- data/lib/active_triples.rb +29 -0
- data/lib/active_triples/configurable.rb +61 -0
- data/lib/active_triples/indexing.rb +76 -0
- data/lib/active_triples/list.rb +155 -0
- data/lib/active_triples/nested_attributes.rb +132 -0
- data/lib/active_triples/node_config.rb +56 -0
- data/lib/active_triples/properties.rb +96 -0
- data/lib/active_triples/repositories.rb +36 -0
- data/lib/active_triples/resource.rb +369 -0
- data/lib/active_triples/term.rb +188 -0
- data/lib/active_triples/version.rb +3 -0
- data/spec/rdf_list_spec.rb +183 -0
- data/spec/rdf_nested_attributes_spec.rb +183 -0
- data/spec/rdf_properties_spec.rb +81 -0
- data/spec/rdf_repositories_spec.rb +28 -0
- data/spec/rdf_resource_spec.rb +447 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/support/active_model_lint.rb +81 -0
- metadata +197 -0
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_support/concern'
|
3
|
+
require 'active_support/core_ext/class'
|
4
|
+
|
5
|
+
module ActiveTriples
|
6
|
+
module NestedAttributes
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
class_attribute :nested_attributes_options, :instance_writer => false
|
11
|
+
self.nested_attributes_options = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
UNASSIGNABLE_KEYS = %w(_destroy )
|
17
|
+
|
18
|
+
# @param [Symbol] association_name
|
19
|
+
# @param [Hash, Array] attributes_collection
|
20
|
+
# @example
|
21
|
+
#
|
22
|
+
# assign_nested_attributes_for_collection_association(:people, {
|
23
|
+
# '1' => { id: '1', name: 'Peter' },
|
24
|
+
# '2' => { name: 'John' },
|
25
|
+
# '3' => { id: '2', _destroy: true }
|
26
|
+
# })
|
27
|
+
#
|
28
|
+
# Will update the name of the Person with ID 1, build a new associated
|
29
|
+
# person with the name 'John', and mark the associated Person with ID 2
|
30
|
+
# for destruction.
|
31
|
+
#
|
32
|
+
# Also accepts an Array of attribute hashes:
|
33
|
+
#
|
34
|
+
# assign_nested_attributes_for_collection_association(:people, [
|
35
|
+
# { id: '1', name: 'Peter' },
|
36
|
+
# { name: 'John' },
|
37
|
+
# { id: '2', _destroy: true }
|
38
|
+
# ])
|
39
|
+
def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
|
40
|
+
options = self.nested_attributes_options[association_name]
|
41
|
+
|
42
|
+
# TODO
|
43
|
+
#check_record_limit!(options[:limit], attributes_collection)
|
44
|
+
|
45
|
+
if attributes_collection.is_a?(Hash)
|
46
|
+
attributes_collection = attributes_collection.values
|
47
|
+
end
|
48
|
+
|
49
|
+
association = self.send(association_name)
|
50
|
+
|
51
|
+
attributes_collection.each do |attributes|
|
52
|
+
attributes = attributes.with_indifferent_access
|
53
|
+
|
54
|
+
if attributes['id'] && existing_record = association.detect { |record| record.rdf_subject.to_s == attributes['id'].to_s }
|
55
|
+
if !call_reject_if(association_name, attributes)
|
56
|
+
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
|
57
|
+
end
|
58
|
+
else
|
59
|
+
attributes = attributes.with_indifferent_access
|
60
|
+
association.build(attributes.except(*UNASSIGNABLE_KEYS))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Updates a record with the +attributes+ or marks it for destruction if
|
66
|
+
# +allow_destroy+ is +true+ and has_destroy_flag? returns +true+.
|
67
|
+
def assign_to_or_mark_for_destruction(record, attributes, allow_destroy)
|
68
|
+
record.attributes = attributes.except(*UNASSIGNABLE_KEYS)
|
69
|
+
record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy
|
70
|
+
end
|
71
|
+
|
72
|
+
def call_reject_if(association_name, attributes)
|
73
|
+
return false if has_destroy_flag?(attributes)
|
74
|
+
case callback = self.nested_attributes_options[association_name][:reject_if]
|
75
|
+
when Symbol
|
76
|
+
method(callback).arity == 0 ? send(callback) : send(callback, attributes)
|
77
|
+
when Proc
|
78
|
+
callback.call(attributes)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Determines if a hash contains a truthy _destroy key.
|
83
|
+
def has_destroy_flag?(hash)
|
84
|
+
["1", "true"].include?(hash['_destroy'].to_s)
|
85
|
+
end
|
86
|
+
|
87
|
+
module ClassMethods
|
88
|
+
def accepts_nested_attributes_for *attr_names
|
89
|
+
options = { :allow_destroy => false, :update_only => false }
|
90
|
+
options.update(attr_names.extract_options!)
|
91
|
+
options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only)
|
92
|
+
options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank
|
93
|
+
|
94
|
+
attr_names.each do |association_name|
|
95
|
+
nested_attributes_options = self.nested_attributes_options.dup
|
96
|
+
nested_attributes_options[association_name] = options
|
97
|
+
self.nested_attributes_options = nested_attributes_options
|
98
|
+
|
99
|
+
generate_association_writer(association_name)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
# Generates a writer method for this association. Serves as a point for
|
106
|
+
# accessing the objects in the association. For example, this method
|
107
|
+
# could generate the following:
|
108
|
+
#
|
109
|
+
# def pirate_attributes=(attributes)
|
110
|
+
# assign_nested_attributes_for_collection_association(:pirate, attributes)
|
111
|
+
# end
|
112
|
+
#
|
113
|
+
# This redirects the attempts to write objects in an association through
|
114
|
+
# the helper methods defined below. Makes it seem like the nested
|
115
|
+
# associations are just regular associations.
|
116
|
+
def generate_association_writer(association_name)
|
117
|
+
class_eval <<-eoruby, __FILE__, __LINE__ + 1
|
118
|
+
if method_defined?(:#{association_name}_attributes=)
|
119
|
+
remove_method(:#{association_name}_attributes=)
|
120
|
+
end
|
121
|
+
def #{association_name}_attributes=(attributes)
|
122
|
+
assign_nested_attributes_for_collection_association(:#{association_name}, attributes)
|
123
|
+
## in lieu of autosave_association_callbacks just save all of em.
|
124
|
+
send(:#{association_name}).each {|obj| obj.marked_for_destruction? ? obj.destroy : nil}
|
125
|
+
send(:#{association_name}).reset!
|
126
|
+
end
|
127
|
+
eoruby
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module ActiveTriples
|
2
|
+
class NodeConfig
|
3
|
+
attr_accessor :predicate, :term, :class_name, :type, :behaviors, :multivalue
|
4
|
+
|
5
|
+
def initialize(term, predicate, args={})
|
6
|
+
self.term = term
|
7
|
+
self.predicate = predicate
|
8
|
+
self.class_name = args.delete(:class_name)
|
9
|
+
self.multivalue = args.delete(:multivalue) { true }
|
10
|
+
raise ArgumentError, "Invalid arguments for Rdf Node configuration: #{args} on #{predicate}" unless args.empty?
|
11
|
+
end
|
12
|
+
|
13
|
+
def [](value)
|
14
|
+
value = value.to_sym
|
15
|
+
self.respond_to?(value) ? self.send(value) : nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def class_name
|
19
|
+
if @class_name.kind_of?(String)
|
20
|
+
begin
|
21
|
+
new_class = @class_name.constantize
|
22
|
+
@class_name = new_class
|
23
|
+
rescue NameError
|
24
|
+
end
|
25
|
+
end
|
26
|
+
@class_name
|
27
|
+
end
|
28
|
+
|
29
|
+
def with_index (&block)
|
30
|
+
# needed for solrizer integration
|
31
|
+
iobj = IndexObject.new
|
32
|
+
yield iobj
|
33
|
+
self.type = iobj.data_type
|
34
|
+
self.behaviors = iobj.behaviors
|
35
|
+
end
|
36
|
+
|
37
|
+
# this enables a cleaner API for solr integration
|
38
|
+
class IndexObject
|
39
|
+
attr_accessor :data_type, :behaviors
|
40
|
+
def initialize
|
41
|
+
@behaviors = []
|
42
|
+
@data_type = :string
|
43
|
+
end
|
44
|
+
def as(*args)
|
45
|
+
@behaviors = args
|
46
|
+
end
|
47
|
+
def type(sym)
|
48
|
+
@data_type = sym
|
49
|
+
end
|
50
|
+
def defaults
|
51
|
+
:noop
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'deprecation'
|
2
|
+
require 'active_support/core_ext/hash'
|
3
|
+
|
4
|
+
module ActiveTriples
|
5
|
+
##
|
6
|
+
# Implements property configuration common to Rdf::Resource,
|
7
|
+
# RDFDatastream, and others. It does its work at the class level,
|
8
|
+
# and is meant to be extended.
|
9
|
+
#
|
10
|
+
# Define properties at the class level with:
|
11
|
+
#
|
12
|
+
# property :title, predicate: RDF::DC.title, class_name: ResourceClass
|
13
|
+
#
|
14
|
+
# or with the 'old' style:
|
15
|
+
#
|
16
|
+
# map_predicates do |map|
|
17
|
+
# map.title(in: RDF::DC)
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# You can pass a block to either to set index behavior.
|
21
|
+
module Properties
|
22
|
+
extend Deprecation
|
23
|
+
attr_accessor :config
|
24
|
+
|
25
|
+
##
|
26
|
+
# Registers properties for Resource-like classes
|
27
|
+
# @param [Symbol] name of the property (and its accessor methods)
|
28
|
+
# @param [Hash] opts for this property, must include a :predicate
|
29
|
+
# @yield [index] index sets solr behaviors for the property
|
30
|
+
def property(name, opts={}, &block)
|
31
|
+
self.config[name] = NodeConfig.new(name, opts[:predicate], opts.except(:predicate)).tap do |config|
|
32
|
+
config.with_index(&block) if block_given?
|
33
|
+
end
|
34
|
+
behaviors = config[name].behaviors.flatten if config[name].behaviors and not config[name].behaviors.empty?
|
35
|
+
register_property(name)
|
36
|
+
end
|
37
|
+
|
38
|
+
def config
|
39
|
+
@config ||= if superclass.respond_to? :config
|
40
|
+
superclass.config.dup
|
41
|
+
else
|
42
|
+
{}.with_indifferent_access
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
alias_method :properties, :config
|
47
|
+
alias_method :properties=, :config=
|
48
|
+
|
49
|
+
def config_for_term_or_uri(term)
|
50
|
+
return config[term.to_sym] unless term.kind_of? RDF::Resource
|
51
|
+
config.each { |k, v| return v if v.predicate == term.to_uri }
|
52
|
+
end
|
53
|
+
|
54
|
+
def fields
|
55
|
+
properties.keys.map(&:to_sym)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
##
|
61
|
+
# Private method for creating accessors for a given property.
|
62
|
+
# @param [#to_s] name Name of the accessor to be created, get/set_value is called on the resource using this.
|
63
|
+
def register_property(name)
|
64
|
+
parent = Proc.new{self}
|
65
|
+
# parent = Proc.new{resource} if self < ActiveFedora::Datastream
|
66
|
+
define_method "#{name}=" do |*args|
|
67
|
+
instance_eval(&parent).set_value(name.to_sym, *args)
|
68
|
+
end
|
69
|
+
define_method name do
|
70
|
+
instance_eval(&parent).get_values(name.to_sym)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
public
|
75
|
+
# Mapper is for backwards compatibility with ActiveFedora::RDFDatastream
|
76
|
+
class Mapper
|
77
|
+
attr_accessor :parent
|
78
|
+
def initialize(parent)
|
79
|
+
@parent = parent
|
80
|
+
end
|
81
|
+
def method_missing(name, *args, &block)
|
82
|
+
properties = args.first || {}
|
83
|
+
vocab = properties.delete(:in)
|
84
|
+
to = properties.delete(:to) || name
|
85
|
+
predicate = vocab.send(to)
|
86
|
+
parent.property(name, properties.merge(predicate: predicate), &block)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
def map_predicates
|
90
|
+
Deprecation.warn Properties, "map_predicates is deprecated and will be removed in active-fedora 8.0.0. Use property :name, predicate: predicate instead.", caller
|
91
|
+
mapper = Mapper.new(self)
|
92
|
+
yield(mapper)
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module ActiveTriples
|
2
|
+
##
|
3
|
+
# Defines module methods for registering an RDF::Repository for
|
4
|
+
# persistence of Resources.
|
5
|
+
#
|
6
|
+
# This allows any triplestore (or other storage platform) with an
|
7
|
+
# RDF::Repository implementation to be used for persistence of
|
8
|
+
# resources that will be shared between ActiveFedora::Base objects.
|
9
|
+
#
|
10
|
+
# ActiveFedora::Rdf::Repositories.add_repository :blah, RDF::Repository.new
|
11
|
+
#
|
12
|
+
# Multiple repositories can be registered to keep different kinds of
|
13
|
+
# resources seperate. This is configurable on subclasses of Resource
|
14
|
+
# at the class level.
|
15
|
+
#
|
16
|
+
# @see Configurable
|
17
|
+
module Repositories
|
18
|
+
|
19
|
+
def add_repository(name, repo)
|
20
|
+
raise "Repositories must be an RDF::Repository" unless repo.kind_of? RDF::Repository
|
21
|
+
repositories[name] = repo
|
22
|
+
end
|
23
|
+
module_function :add_repository
|
24
|
+
|
25
|
+
def clear_repositories!
|
26
|
+
@repositories = {}
|
27
|
+
end
|
28
|
+
module_function :clear_repositories!
|
29
|
+
|
30
|
+
def repositories
|
31
|
+
@repositories ||= {}
|
32
|
+
end
|
33
|
+
module_function :repositories
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,369 @@
|
|
1
|
+
require 'deprecation'
|
2
|
+
require 'active_model'
|
3
|
+
require 'active_support/core_ext/hash'
|
4
|
+
|
5
|
+
module ActiveTriples
|
6
|
+
##
|
7
|
+
# Defines a generic RDF `Resource` as an RDF::Graph with property
|
8
|
+
# configuration, accessors, and some other methods for managing
|
9
|
+
# resources as discrete subgraphs which can be maintained by a Hydra
|
10
|
+
# datastream model.
|
11
|
+
#
|
12
|
+
# Resources can be instances of ActiveTriples::Resource
|
13
|
+
# directly, but more often they will be instances of subclasses with
|
14
|
+
# registered properties and configuration. e.g.
|
15
|
+
#
|
16
|
+
# class License < Resource
|
17
|
+
# configure repository: :default
|
18
|
+
# property :title, predicate: RDF::DC.title, class_name: RDF::Literal do |index|
|
19
|
+
# index.as :displayable, :facetable
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
class Resource < RDF::Graph
|
23
|
+
@@type_registry
|
24
|
+
extend Configurable
|
25
|
+
extend Properties
|
26
|
+
extend Deprecation
|
27
|
+
extend ActiveModel::Naming
|
28
|
+
include ActiveModel::Conversion
|
29
|
+
include ActiveModel::Serialization
|
30
|
+
include ActiveModel::Serializers::JSON
|
31
|
+
include NestedAttributes
|
32
|
+
attr_accessor :parent
|
33
|
+
|
34
|
+
class << self
|
35
|
+
def type_registry
|
36
|
+
@@type_registry ||= {}
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Adapter for a consistent interface for creating a new node from a URI.
|
41
|
+
# Similar functionality should exist in all objects which can become a node.
|
42
|
+
def from_uri(uri,vals=nil)
|
43
|
+
new(uri, vals)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def writable?
|
48
|
+
!frozen?
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# Initialize an instance of this resource class. Defaults to a
|
53
|
+
# blank node subject. In addition to RDF::Graph parameters, you
|
54
|
+
# can pass in a URI and/or a parent to build a resource from a
|
55
|
+
# existing data.
|
56
|
+
#
|
57
|
+
# You can pass in only a parent with:
|
58
|
+
# Resource.new(nil, parent)
|
59
|
+
#
|
60
|
+
# @see RDF::Graph
|
61
|
+
def initialize(*args, &block)
|
62
|
+
resource_uri = args.shift unless args.first.is_a?(Hash)
|
63
|
+
self.parent = args.shift unless args.first.is_a?(Hash)
|
64
|
+
set_subject!(resource_uri) if resource_uri
|
65
|
+
super(*args, &block)
|
66
|
+
reload
|
67
|
+
# Append type to graph if necessary.
|
68
|
+
self.get_values(:type) << self.class.type if self.class.type.kind_of?(RDF::URI) && type.empty?
|
69
|
+
end
|
70
|
+
|
71
|
+
def graph
|
72
|
+
Deprecation.warn Resource, "graph is redundant & deprecated. It will be removed in active-fedora 8.0.0.", caller
|
73
|
+
self
|
74
|
+
end
|
75
|
+
|
76
|
+
def final_parent
|
77
|
+
@final_parent ||= begin
|
78
|
+
parent = self.parent
|
79
|
+
while parent && parent.parent && parent.parent != parent
|
80
|
+
parent = parent.parent
|
81
|
+
end
|
82
|
+
parent
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def attributes
|
87
|
+
attrs = {}
|
88
|
+
attrs['id'] = id if id
|
89
|
+
fields.map { |f| attrs[f.to_s] = get_values(f) }
|
90
|
+
unregistered_predicates.map { |uri| attrs[uri.to_s] = get_values(uri) }
|
91
|
+
attrs
|
92
|
+
end
|
93
|
+
|
94
|
+
def serializable_hash(options = nil)
|
95
|
+
attrs = (fields.map { |f| f.to_s }) << 'id'
|
96
|
+
hash = super(:only => attrs)
|
97
|
+
unregistered_predicates.map { |uri| hash[uri.to_s] = get_values(uri) }
|
98
|
+
hash
|
99
|
+
end
|
100
|
+
|
101
|
+
def attributes=(values)
|
102
|
+
raise ArgumentError, "values must be a Hash, you provided #{values.class}" unless values.kind_of? Hash
|
103
|
+
values = values.with_indifferent_access
|
104
|
+
set_subject!(values.delete(:id)) if values.has_key?(:id) and node?
|
105
|
+
values.each do |key, value|
|
106
|
+
if properties.keys.include?(key)
|
107
|
+
set_value(rdf_subject, key, value)
|
108
|
+
elsif self.singleton_class.nested_attributes_options.keys.map{ |k| "#{k}_attributes"}.include?(key)
|
109
|
+
send("#{key}=".to_sym, value)
|
110
|
+
else
|
111
|
+
raise ArgumentError, "No association found for name `#{key}'. Has it been defined yet?"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def rdf_subject
|
117
|
+
@rdf_subject ||= RDF::Node.new
|
118
|
+
end
|
119
|
+
|
120
|
+
def id
|
121
|
+
node? ? nil : rdf_subject.to_s
|
122
|
+
end
|
123
|
+
|
124
|
+
def node?
|
125
|
+
return true if rdf_subject.kind_of? RDF::Node
|
126
|
+
false
|
127
|
+
end
|
128
|
+
|
129
|
+
def to_term
|
130
|
+
rdf_subject
|
131
|
+
end
|
132
|
+
|
133
|
+
def base_uri
|
134
|
+
self.class.base_uri
|
135
|
+
end
|
136
|
+
|
137
|
+
def type
|
138
|
+
self.get_values(:type).to_a.map{|x| x.rdf_subject}
|
139
|
+
end
|
140
|
+
|
141
|
+
def type=(type)
|
142
|
+
raise "Type must be an RDF::URI" unless type.kind_of? RDF::URI
|
143
|
+
self.update(RDF::Statement.new(rdf_subject, RDF.type, type))
|
144
|
+
end
|
145
|
+
|
146
|
+
##
|
147
|
+
# Look for labels in various default fields, prioritizing
|
148
|
+
# configured label fields
|
149
|
+
def rdf_label
|
150
|
+
labels = Array(self.class.rdf_label)
|
151
|
+
labels += default_labels
|
152
|
+
labels.each do |label|
|
153
|
+
values = get_values(label)
|
154
|
+
return values unless values.empty?
|
155
|
+
end
|
156
|
+
node? ? [] : [rdf_subject.to_s]
|
157
|
+
end
|
158
|
+
|
159
|
+
def fields
|
160
|
+
properties.keys.map(&:to_sym).reject{|x| x == :type}
|
161
|
+
end
|
162
|
+
|
163
|
+
##
|
164
|
+
# Load data from URI
|
165
|
+
def fetch
|
166
|
+
load(rdf_subject)
|
167
|
+
self
|
168
|
+
end
|
169
|
+
|
170
|
+
def persist!
|
171
|
+
raise "failed when trying to persist to non-existant repository or parent resource" unless repository
|
172
|
+
repository.delete [rdf_subject,nil,nil] unless node?
|
173
|
+
if node?
|
174
|
+
repository.statements.each do |statement|
|
175
|
+
repository.send(:delete_statement, statement) if statement.subject == rdf_subject
|
176
|
+
end
|
177
|
+
end
|
178
|
+
repository << self
|
179
|
+
@persisted = true
|
180
|
+
end
|
181
|
+
|
182
|
+
def persisted?
|
183
|
+
@persisted ||= false
|
184
|
+
return (@persisted and parent.persisted?) if parent
|
185
|
+
@persisted
|
186
|
+
end
|
187
|
+
|
188
|
+
##
|
189
|
+
# Repopulates the graph from the repository or parent resource.
|
190
|
+
def reload
|
191
|
+
@term_cache ||= {}
|
192
|
+
if self.class.repository == :parent
|
193
|
+
return false if final_parent.nil?
|
194
|
+
end
|
195
|
+
self << repository.query(subject: rdf_subject)
|
196
|
+
unless empty?
|
197
|
+
@persisted = true
|
198
|
+
end
|
199
|
+
true
|
200
|
+
end
|
201
|
+
|
202
|
+
##
|
203
|
+
# Adds or updates a property with supplied values.
|
204
|
+
#
|
205
|
+
# Handles two argument patterns. The recommended pattern is:
|
206
|
+
# set_value(property, values)
|
207
|
+
#
|
208
|
+
# For backwards compatibility, there is support for explicitly
|
209
|
+
# passing the rdf_subject to be used in the statement:
|
210
|
+
# set_value(uri, property, values)
|
211
|
+
#
|
212
|
+
# @note This method will delete existing statements with the correct subject and predicate from the graph
|
213
|
+
def set_value(*args)
|
214
|
+
# Add support for legacy 3-parameter syntax
|
215
|
+
if args.length > 3 || args.length < 2
|
216
|
+
raise ArgumentError("wrong number of arguments (#{args.length} for 2-3)")
|
217
|
+
end
|
218
|
+
values = args.pop
|
219
|
+
get_term(args).set(values)
|
220
|
+
end
|
221
|
+
|
222
|
+
##
|
223
|
+
# Returns an array of values belonging to the property
|
224
|
+
# requested. Elements in the array may RdfResource objects or a
|
225
|
+
# valid datatype.
|
226
|
+
#
|
227
|
+
# Handles two argument patterns. The recommended pattern is:
|
228
|
+
# get_values(property)
|
229
|
+
#
|
230
|
+
# For backwards compatibility, there is support for explicitly
|
231
|
+
# passing the rdf_subject to be used in th statement:
|
232
|
+
# get_values(uri, property)
|
233
|
+
def get_values(*args)
|
234
|
+
get_term(args)
|
235
|
+
end
|
236
|
+
|
237
|
+
def get_term(args)
|
238
|
+
@term_cache ||= {}
|
239
|
+
term = Term.new(self, args)
|
240
|
+
@term_cache["#{term.rdf_subject}/#{term.property}"] ||= term
|
241
|
+
@term_cache["#{term.rdf_subject}/#{term.property}"]
|
242
|
+
end
|
243
|
+
|
244
|
+
##
|
245
|
+
# Set a new rdf_subject for the resource.
|
246
|
+
#
|
247
|
+
# This raises an error if the current subject is not a blank node,
|
248
|
+
# and returns false if it can't figure out how to make a URI from
|
249
|
+
# the param. Otherwise it creates a URI for the resource and
|
250
|
+
# rebuilds the graph with the updated URI.
|
251
|
+
#
|
252
|
+
# Will try to build a uri as an extension of the class's base_uri
|
253
|
+
# if appropriate.
|
254
|
+
#
|
255
|
+
# @param [#to_uri, #to_s] uri_or_str the uri or string to use
|
256
|
+
def set_subject!(uri_or_str)
|
257
|
+
raise "Refusing update URI when one is already assigned!" unless node?
|
258
|
+
# Refusing set uri to an empty string.
|
259
|
+
return false if uri_or_str.nil? or uri_or_str.to_s.empty?
|
260
|
+
# raise "Refusing update URI! This object is persisted to a datastream." if persisted?
|
261
|
+
old_subject = rdf_subject
|
262
|
+
@rdf_subject = get_uri(uri_or_str)
|
263
|
+
|
264
|
+
each_statement do |statement|
|
265
|
+
if statement.subject == old_subject
|
266
|
+
delete(statement)
|
267
|
+
self << RDF::Statement.new(rdf_subject, statement.predicate, statement.object)
|
268
|
+
elsif statement.object == old_subject
|
269
|
+
delete(statement)
|
270
|
+
self << RDF::Statement.new(statement.subject, statement.predicate, rdf_subject)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def destroy
|
276
|
+
clear
|
277
|
+
persist! if repository
|
278
|
+
parent.destroy_child(self) if parent
|
279
|
+
@destroyed = true
|
280
|
+
end
|
281
|
+
alias_method :destroy!, :destroy
|
282
|
+
|
283
|
+
def destroyed?
|
284
|
+
@destroyed ||= false
|
285
|
+
end
|
286
|
+
|
287
|
+
def destroy_child(child)
|
288
|
+
statements.each do |statement|
|
289
|
+
delete_statement(statement) if statement.subject == child.rdf_subject || statement.object == child.rdf_subject
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
def new_record?
|
294
|
+
not persisted?
|
295
|
+
end
|
296
|
+
|
297
|
+
##
|
298
|
+
# @return [String] the string to index in solr
|
299
|
+
def solrize
|
300
|
+
node? ? rdf_label : rdf_subject.to_s
|
301
|
+
end
|
302
|
+
|
303
|
+
def mark_for_destruction
|
304
|
+
@marked_for_destruction = true
|
305
|
+
end
|
306
|
+
|
307
|
+
def marked_for_destruction?
|
308
|
+
@marked_for_destruction
|
309
|
+
end
|
310
|
+
|
311
|
+
private
|
312
|
+
|
313
|
+
def properties
|
314
|
+
self.singleton_class.properties
|
315
|
+
end
|
316
|
+
|
317
|
+
def registered_predicates
|
318
|
+
properties.values.map { |config| config.predicate }
|
319
|
+
end
|
320
|
+
|
321
|
+
def unregistered_predicates
|
322
|
+
preds = registered_predicates
|
323
|
+
preds << RDF.type
|
324
|
+
predicates.select { |p| !preds.include? p }
|
325
|
+
end
|
326
|
+
|
327
|
+
def property_for_predicate(predicate)
|
328
|
+
properties.each do |property, values|
|
329
|
+
return property if values[:predicate] == predicate
|
330
|
+
end
|
331
|
+
return nil
|
332
|
+
end
|
333
|
+
|
334
|
+
def default_labels
|
335
|
+
[RDF::SKOS.prefLabel,
|
336
|
+
RDF::DC.title,
|
337
|
+
RDF::RDFS.label,
|
338
|
+
RDF::SKOS.altLabel,
|
339
|
+
RDF::SKOS.hiddenLabel]
|
340
|
+
end
|
341
|
+
|
342
|
+
##
|
343
|
+
# Return the repository (or parent) that this resource should
|
344
|
+
# write to when persisting.
|
345
|
+
def repository
|
346
|
+
@repository ||=
|
347
|
+
if self.class.repository == :parent
|
348
|
+
final_parent
|
349
|
+
else
|
350
|
+
Repositories.repositories[self.class.repository]
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
##
|
355
|
+
# Takes a URI or String and aggressively tries to create a valid RDF URI.
|
356
|
+
# Combines the input with base_uri if appropriate.
|
357
|
+
#
|
358
|
+
# @TODO: URI.scheme_list is naive and incomplete. Find a better way to check for an existing scheme.
|
359
|
+
def get_uri(uri_or_str)
|
360
|
+
return uri_or_str.to_uri if uri_or_str.respond_to? :to_uri
|
361
|
+
return uri_or_str if uri_or_str.kind_of? RDF::Node
|
362
|
+
uri_or_str = uri_or_str.to_s
|
363
|
+
return RDF::Node(uri_or_str[2..-1]) if uri_or_str.start_with? '_:'
|
364
|
+
return RDF::URI(uri_or_str) if RDF::URI(uri_or_str).valid? and (URI.scheme_list.include?(RDF::URI.new(uri_or_str).scheme.upcase) or RDF::URI.new(uri_or_str).scheme == 'info')
|
365
|
+
return RDF::URI(self.base_uri.to_s + (self.base_uri.to_s[-1,1] =~ /(\/|#)/ ? '' : '/') + uri_or_str) if base_uri && !uri_or_str.start_with?(base_uri.to_s)
|
366
|
+
raise RuntimeError, "could not make a valid RDF::URI from #{uri_or_str}"
|
367
|
+
end
|
368
|
+
end
|
369
|
+
end
|