spira 0.0.12 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|