spira 0.0.12 → 0.5.0
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.
- data/AUTHORS +2 -0
- data/CHANGES.md +6 -3
- data/{README → README.md} +91 -120
- data/lib/rdf/ext/uri.rb +37 -0
- data/lib/spira.rb +47 -86
- data/lib/spira/association_reflection.rb +25 -0
- data/lib/spira/base.rb +353 -4
- data/lib/spira/exceptions.rb +13 -7
- data/lib/spira/persistence.rb +531 -0
- data/lib/spira/reflections.rb +19 -0
- data/lib/spira/resource.rb +165 -60
- data/lib/spira/serialization.rb +27 -0
- data/lib/spira/type.rb +7 -7
- data/lib/spira/types.rb +8 -11
- data/lib/spira/types/boolean.rb +3 -3
- data/lib/spira/types/date.rb +4 -5
- data/lib/spira/types/dateTime.rb +26 -0
- data/lib/spira/types/decimal.rb +2 -2
- data/lib/spira/types/float.rb +3 -3
- data/lib/spira/types/gYear.rb +26 -0
- data/lib/spira/types/int.rb +27 -0
- data/lib/spira/types/integer.rb +3 -3
- data/lib/spira/types/long.rb +27 -0
- data/lib/spira/types/negativeInteger.rb +27 -0
- data/lib/spira/types/nonNegativeInteger.rb +27 -0
- data/lib/spira/types/nonPositiveInteger.rb +27 -0
- data/lib/spira/types/positiveInteger.rb +27 -0
- data/lib/spira/types/string.rb +1 -1
- data/lib/spira/utils.rb +29 -0
- data/lib/spira/validations.rb +77 -0
- data/lib/spira/validations/uniqueness.rb +33 -0
- data/lib/spira/version.rb +3 -6
- metadata +218 -123
- data/lib/spira/errors.rb +0 -94
- data/lib/spira/extensions.rb +0 -14
- data/lib/spira/resource/class_methods.rb +0 -260
- data/lib/spira/resource/dsl.rb +0 -279
- data/lib/spira/resource/instance_methods.rb +0 -567
- data/lib/spira/resource/validations.rb +0 -47
@@ -0,0 +1,19 @@
|
|
1
|
+
require "spira/association_reflection"
|
2
|
+
|
3
|
+
module Spira
|
4
|
+
module Reflections
|
5
|
+
# Returns a hash containing all AssociationReflection objects for the current class
|
6
|
+
# Example:
|
7
|
+
#
|
8
|
+
# Invoice.reflections
|
9
|
+
# Account.reflections
|
10
|
+
#
|
11
|
+
def reflections
|
12
|
+
read_inheritable_attribute(:reflections) || write_inheritable_attribute(:reflections, {})
|
13
|
+
end
|
14
|
+
|
15
|
+
def reflect_on_association(association)
|
16
|
+
reflections[association].is_a?(AssociationReflection) ? reflections[association] : nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/spira/resource.rb
CHANGED
@@ -1,72 +1,177 @@
|
|
1
|
-
|
1
|
+
require "active_support/core_ext/class"
|
2
|
+
require "spira/association_reflection"
|
2
3
|
|
3
|
-
|
4
|
-
# Spira::Resource is the main interface to Spira. Classes and modules
|
5
|
-
# include Spira::Resource to create projections of RDF data as a class. For
|
6
|
-
# an overview, see the {file:README}.
|
7
|
-
#
|
8
|
-
# Projections are a mapping of RDF predicates to fields.
|
9
|
-
#
|
10
|
-
# class Person
|
11
|
-
# include Spira::Resource
|
12
|
-
#
|
13
|
-
# property :name, :predicate => FOAF.name
|
14
|
-
# property :age, :predicate => FOAF.age, :type => Integer
|
15
|
-
# end
|
16
|
-
#
|
17
|
-
# RDF::URI('http://example.org/people/bob').as(Person) #=> <#Person @uri=http://example.org/people/bob>
|
18
|
-
#
|
19
|
-
# Spira resources include the RDF namespace, and can thus reference all of
|
20
|
-
# the default RDF.rb vocabularies without the RDF:: prefix:
|
21
|
-
#
|
22
|
-
# property :name, :predicate => FOAF.name
|
23
|
-
#
|
24
|
-
# The Spira::Resource documentation is broken into several parts, vaguely
|
25
|
-
# related by functionality:
|
26
|
-
# * {Spira::Resource::DSL} contains methods used during the declaration of a class or module
|
27
|
-
# * {Spira::Resource::ClassMethods} contains class methods for use by declared classes
|
28
|
-
# * {Spira::Resource::InstanceMethods} contains methods for use by instances of Spira resource classes
|
29
|
-
# * {Spira::Resource::Validations} contains some default validation functions
|
30
|
-
#
|
31
|
-
# @see Spira::Resource::DSL
|
32
|
-
# @see Spira::Resource::ClassMethods
|
33
|
-
# @see Spira::Resource::InstanceMethods
|
34
|
-
# @see Spira::Resource::Validations
|
4
|
+
module Spira
|
35
5
|
module Resource
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
6
|
+
##
|
7
|
+
# Configuration options for the Spira::Resource:
|
8
|
+
#
|
9
|
+
# @params[Hash] options
|
10
|
+
# :repository_name :: name of the repository to use
|
11
|
+
# :base_uri :: base URI to be used for the resource
|
12
|
+
# :default_vocabulary :: default vocabulary to use for the properties
|
13
|
+
# defined for this resource
|
14
|
+
# All these configuration options are readable via
|
15
|
+
# their respectively named Spira resource methods.
|
16
|
+
#
|
17
|
+
def configure(options = {})
|
18
|
+
singleton_class.class_eval do
|
19
|
+
{ :repository_name => options[:repository_name],
|
20
|
+
:base_uri => options[:base_uri],
|
21
|
+
:default_vocabulary => options[:default_vocabulary]
|
22
|
+
}.each do |name, value|
|
23
|
+
# redefine reader methods only when required,
|
24
|
+
# otherwise, use the ancestor methods
|
25
|
+
if value
|
26
|
+
define_method name do
|
27
|
+
value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
41
33
|
|
42
34
|
##
|
43
|
-
#
|
44
|
-
#
|
35
|
+
# Declare a type for the Spira::Resource.
|
36
|
+
# You can declare multiple types for a resource
|
37
|
+
# with multiple "type" assignments.
|
38
|
+
# If no types are declared for a resource,
|
39
|
+
# they are inherited from the parent resource.
|
45
40
|
#
|
46
|
-
# @
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
child.instance_eval do
|
57
|
-
class << self
|
58
|
-
attr_accessor :properties, :lists
|
41
|
+
# @params[RDF::URI] uri
|
42
|
+
#
|
43
|
+
def type(uri = nil)
|
44
|
+
if uri
|
45
|
+
if uri.is_a?(RDF::URI)
|
46
|
+
ts = @types ? types : Set.new
|
47
|
+
singleton_class.class_eval do
|
48
|
+
define_method :types do
|
49
|
+
ts
|
50
|
+
end
|
59
51
|
end
|
60
|
-
@
|
61
|
-
|
52
|
+
@types = ts << uri
|
53
|
+
else
|
54
|
+
raise TypeError, "Type must be a RDF::URI"
|
62
55
|
end
|
56
|
+
else
|
57
|
+
types.first
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# Add a property to this class. A property is an accessor field that
|
63
|
+
# represents an RDF predicate.
|
64
|
+
#
|
65
|
+
# @example A simple string property
|
66
|
+
# property :name, :predicate => FOAF.name, :type => String
|
67
|
+
# @example A property which defaults to {Spira::Types::Any}
|
68
|
+
# property :name, :predicate => FOAF.name
|
69
|
+
# @example An integer property
|
70
|
+
# property :age, :predicate => FOAF.age, :type => Integer
|
71
|
+
# @param [Symbol] name The name of this property
|
72
|
+
# @param [Hash{Symbol => Any}] opts property options
|
73
|
+
# @option opts [RDF::URI] :predicate The RDF predicate which will refer to this property
|
74
|
+
# @option opts [Spira::Type, String] :type (Spira::Types::Any) The
|
75
|
+
# type for this property. If a Spira::Type is given, that class will be
|
76
|
+
# used to serialize and unserialize values. If a String is given, it
|
77
|
+
# should be the String form of a Spira::Base class name (Strings are
|
78
|
+
# used to prevent issues with load order).
|
79
|
+
# @see Spira::Types
|
80
|
+
# @see Spira::Type
|
81
|
+
# @return [Void]
|
82
|
+
def property(name, opts = {})
|
83
|
+
unset_has_many(name)
|
84
|
+
predicate = predicate_for(opts[:predicate], name)
|
85
|
+
type = type_for(opts[:type])
|
86
|
+
properties[name] = HashWithIndifferentAccess.new(:predicate => predicate, :type => type)
|
87
|
+
|
88
|
+
define_attribute_method name
|
89
|
+
define_method "#{name}=" do |arg|
|
90
|
+
write_attribute name, arg
|
91
|
+
end
|
92
|
+
define_method name do
|
93
|
+
read_attribute name
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# The plural form of `property`. `Has_many` has the same options as
|
99
|
+
# `property`, but instead of a single value, a Ruby Array of objects will
|
100
|
+
# be created instead.
|
101
|
+
#
|
102
|
+
# has_many corresponds to an RDF subject with several triples of the same
|
103
|
+
# predicate. This corresponds to a Ruby Array, which will be returned when
|
104
|
+
# the property is accessed. Arrays will be accepted for new values, but
|
105
|
+
# ordering and duplicate values will be lost on save.
|
106
|
+
#
|
107
|
+
# @see Spira::Base::DSL#property
|
108
|
+
def has_many(name, opts = {})
|
109
|
+
property(name, opts)
|
110
|
+
|
111
|
+
reflections[name] = AssociationReflection.new(:has_many, name, opts)
|
112
|
+
|
113
|
+
define_method "#{name.to_s.singularize}_ids" do
|
114
|
+
records = send(name) || []
|
115
|
+
records.map(&:id).compact
|
116
|
+
end
|
117
|
+
define_method "#{name.to_s.singularize}_ids=" do |ids|
|
118
|
+
records = ids.map {|id| self.class.reflect_on_association(name).klass.unserialize(id) }.compact
|
119
|
+
send "#{name}=", records
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
# Unset a has_many relation if it exists. Allow to redefine the cardinality of a relation in a subClass
|
127
|
+
#
|
128
|
+
# @private
|
129
|
+
def unset_has_many(name)
|
130
|
+
if reflections[name]
|
131
|
+
reflections.delete(name)
|
132
|
+
undef_method "#{name.to_s.singularize}_ids"
|
133
|
+
undef_method "#{name.to_s.singularize}_ids="
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
##
|
138
|
+
# Determine the predicate for a property based on the given predicate, name, and default vocabulary
|
139
|
+
#
|
140
|
+
# @param [#to_s, #to_uri] predicate
|
141
|
+
# @param [Symbol] name
|
142
|
+
# @return [RDF::URI]
|
143
|
+
# @private
|
144
|
+
def predicate_for(predicate, name)
|
145
|
+
case
|
146
|
+
when predicate.respond_to?(:to_uri) && predicate.to_uri.absolute?
|
147
|
+
predicate
|
148
|
+
when default_vocabulary.nil?
|
149
|
+
raise ResourceDeclarationError, "A :predicate option is required for types without a default vocabulary"
|
150
|
+
else
|
151
|
+
# FIXME: use rdf.rb smart separator after 0.3.0 release
|
152
|
+
separator = default_vocabulary.to_s[-1,1] =~ /(\/|#)/ ? '' : '/'
|
153
|
+
RDF::URI.intern(default_vocabulary.to_s + separator + name.to_s)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
##
|
158
|
+
# Determine the type for a property based on the given type option
|
159
|
+
#
|
160
|
+
# @param [nil, Spira::Type, Constant] type
|
161
|
+
# @return Spira::Type
|
162
|
+
# @private
|
163
|
+
def type_for(type)
|
164
|
+
case
|
165
|
+
when type.nil?
|
166
|
+
Spira::Types::Any
|
167
|
+
when type.is_a?(Symbol) || type.is_a?(String)
|
168
|
+
type
|
169
|
+
when Spira.types[type]
|
170
|
+
Spira.types[type]
|
171
|
+
else
|
172
|
+
raise TypeError, "Unrecognized type: #{type}"
|
63
173
|
end
|
64
174
|
end
|
65
|
-
|
66
|
-
# This lets including classes reference vocabularies without the RDF:: prefix
|
67
|
-
include Spira::Types
|
68
|
-
include ::RDF
|
69
|
-
include InstanceMethods
|
70
175
|
|
71
176
|
end
|
72
177
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Spira
|
2
|
+
module Serialization
|
3
|
+
##
|
4
|
+
# Support for Psych (YAML) custom serializer.
|
5
|
+
#
|
6
|
+
# This causes the subject and all attributes to be saved to a YAML or JSON serialization
|
7
|
+
# in such a way that they can be restored in the future.
|
8
|
+
#
|
9
|
+
# @param [Psych::Coder] coder
|
10
|
+
def encode_with(coder)
|
11
|
+
coder["subject"] = subject
|
12
|
+
attributes.each {|p,v| coder[p.to_s] = v if v}
|
13
|
+
end
|
14
|
+
|
15
|
+
##
|
16
|
+
# Support for Psych (YAML) custom de-serializer.
|
17
|
+
#
|
18
|
+
# Updates a previously allocated Spira::Base instance to that of a previously
|
19
|
+
# serialized instance.
|
20
|
+
#
|
21
|
+
# @param [Psych::Coder] coder
|
22
|
+
def init_with(coder)
|
23
|
+
instance_variable_set(:"@subject", coder["subject"])
|
24
|
+
assign_attributes coder.map.except("subject")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/spira/type.rb
CHANGED
@@ -8,17 +8,17 @@ module Spira
|
|
8
8
|
# A simple example:
|
9
9
|
#
|
10
10
|
# class Integer
|
11
|
-
#
|
11
|
+
#
|
12
12
|
# include Spira::Type
|
13
|
-
#
|
13
|
+
#
|
14
14
|
# def self.unserialize(value)
|
15
15
|
# value.object
|
16
16
|
# end
|
17
|
-
#
|
17
|
+
#
|
18
18
|
# def self.serialize(value)
|
19
19
|
# RDF::Literal.new(value)
|
20
20
|
# end
|
21
|
-
#
|
21
|
+
#
|
22
22
|
# register_alias XSD.integer
|
23
23
|
# end
|
24
24
|
#
|
@@ -27,7 +27,7 @@ module Spira
|
|
27
27
|
# integer property on a Spira resource:
|
28
28
|
#
|
29
29
|
# property :age, :predicate => FOAF.age, :type => Integer
|
30
|
-
# property :age, :predicate => FOAF.age, :type => XSD.integer
|
30
|
+
# property :age, :predicate => FOAF.age, :type => RDF::XSD.integer
|
31
31
|
#
|
32
32
|
# `Spira::Type`s include the RDF namespace and thus have all of the base RDF
|
33
33
|
# vocabularies available to them without the `RDF::` prefix.
|
@@ -55,8 +55,8 @@ module Spira
|
|
55
55
|
#
|
56
56
|
# @param [Any] identifier The new alias in property declarations for this class
|
57
57
|
# @return [Void]
|
58
|
-
def register_alias(
|
59
|
-
Spira.type_alias(
|
58
|
+
def register_alias(identifier)
|
59
|
+
Spira.type_alias(identifier, self)
|
60
60
|
end
|
61
61
|
|
62
62
|
##
|
data/lib/spira/types.rb
CHANGED
@@ -4,25 +4,22 @@ module Spira
|
|
4
4
|
# Spira::Types is a set of default Spira::Type classes.
|
5
5
|
#
|
6
6
|
# @see Spira::Type
|
7
|
-
# @see Spira::Types::
|
7
|
+
# @see Spira::Types::Int
|
8
|
+
# @see Spira::Types::Integer
|
8
9
|
# @see Spira::Types::Boolean
|
9
10
|
# @see Spira::Types::String
|
11
|
+
# @see Spira::Types::GYear
|
12
|
+
# @see Spira::Types::Date
|
13
|
+
# @see Spira::Types::DateTime
|
10
14
|
# @see Spira::Types::Float
|
11
15
|
# @see Spira::Types::Any
|
12
16
|
module Types
|
13
17
|
|
14
18
|
# No autoloading here--the associations to XSD types are made by the
|
15
|
-
# classes themselves, so we need to
|
19
|
+
# classes themselves, so we need to require them or XSD types
|
16
20
|
# will show up as not found.
|
17
|
-
|
18
|
-
|
19
|
-
require 'spira/types/any'
|
20
|
-
require 'spira/types/string'
|
21
|
-
require 'spira/types/float'
|
22
|
-
require 'spira/types/uri'
|
23
|
-
require 'spira/types/decimal'
|
24
|
-
require 'spira/types/native'
|
25
|
-
|
21
|
+
|
22
|
+
Dir.glob(File.join(File.dirname(__FILE__), 'types', '*.rb')).each { |file| require file }
|
26
23
|
|
27
24
|
end
|
28
25
|
end
|
data/lib/spira/types/boolean.rb
CHANGED
@@ -19,13 +19,13 @@ module Spira::Types
|
|
19
19
|
|
20
20
|
def self.serialize(value)
|
21
21
|
if value
|
22
|
-
RDF::Literal.new(true, :datatype => XSD.boolean)
|
22
|
+
RDF::Literal.new(true, :datatype => RDF::XSD.boolean)
|
23
23
|
else
|
24
|
-
RDF::Literal.new(false, :datatype => XSD.boolean)
|
24
|
+
RDF::Literal.new(false, :datatype => RDF::XSD.boolean)
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
-
register_alias XSD.boolean
|
28
|
+
register_alias RDF::XSD.boolean
|
29
29
|
|
30
30
|
end
|
31
31
|
end
|
data/lib/spira/types/date.rb
CHANGED
@@ -1,24 +1,23 @@
|
|
1
1
|
module Spira::Types
|
2
2
|
|
3
3
|
##
|
4
|
-
# A {Spira::Type} for
|
4
|
+
# A {Spira::Type} for dates. Values will be associated with the
|
5
5
|
# `XSD.date` type.
|
6
6
|
#
|
7
7
|
# A {Spira::Resource} property can reference this type as
|
8
|
-
# `Spira::Types::Date`, `Date`, or `XSD.
|
8
|
+
# `Spira::Types::Date`, `Date`, or `XSD.date`.
|
9
9
|
#
|
10
10
|
# @see Spira::Type
|
11
11
|
# @see http://rdf.rubyforge.org/RDF/Literal.html
|
12
12
|
class Date
|
13
|
-
|
14
13
|
include Spira::Type
|
15
14
|
|
16
15
|
def self.unserialize(value)
|
17
|
-
value.object
|
16
|
+
object = value.object
|
18
17
|
end
|
19
18
|
|
20
19
|
def self.serialize(value)
|
21
|
-
RDF::Literal
|
20
|
+
RDF::Literal.new(value, :datatype => XSD.date)
|
22
21
|
end
|
23
22
|
|
24
23
|
register_alias XSD.date
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Spira::Types
|
2
|
+
|
3
|
+
##
|
4
|
+
# A {Spira::Type} for dateTimes. Values will be associated with the
|
5
|
+
# `XSD.dateTime` type.
|
6
|
+
#
|
7
|
+
# A {Spira::Resource} property can reference this type as
|
8
|
+
# `Spira::Types::DateTime`, `DateTime`, or `XSD.dateTime`.
|
9
|
+
#
|
10
|
+
# @see Spira::Type
|
11
|
+
# @see http://rdf.rubyforge.org/RDF/Literal.html
|
12
|
+
class DateTime
|
13
|
+
include Spira::Type
|
14
|
+
|
15
|
+
def self.unserialize(value)
|
16
|
+
object = value.object
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.serialize(value)
|
20
|
+
RDF::Literal.new(value, :datatype => XSD.dateTime)
|
21
|
+
end
|
22
|
+
|
23
|
+
register_alias XSD.dateTime
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
data/lib/spira/types/decimal.rb
CHANGED
@@ -20,10 +20,10 @@ module Spira::Types
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def self.serialize(value)
|
23
|
-
RDF::Literal.new(value.is_a?(BigDecimal) ? value.to_s('F') : value.to_s, :datatype => XSD.decimal)
|
23
|
+
RDF::Literal.new(value.is_a?(BigDecimal) ? value.to_s('F') : value.to_s, :datatype => RDF::XSD.decimal)
|
24
24
|
end
|
25
25
|
|
26
|
-
register_alias XSD.decimal
|
26
|
+
register_alias RDF::XSD.decimal
|
27
27
|
|
28
28
|
end
|
29
29
|
end
|