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.
@@ -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
@@ -1,72 +1,177 @@
1
- module Spira
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
- autoload :DSL, 'spira/resource/dsl'
38
- autoload :ClassMethods, 'spira/resource/class_methods'
39
- autoload :InstanceMethods, 'spira/resource/instance_methods'
40
- autoload :Validations, 'spira/resource/validations'
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
- # When a child class includes Spira::Resource, this does the magic to make
44
- # it a Spira resource.
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
- # @private
47
- def self.included(child)
48
- # Don't do inclusion work twice. Checking for the properties accessor is
49
- # a proxy for a proper check to see if this is a resource already. Ruby
50
- # has already extended the child class' ancestors to include
51
- # Spira::Resource by the time we get here.
52
- # FIXME: Find a 'more correct' check.
53
- unless child.respond_to?(:properties)
54
- child.extend DSL
55
- child.extend ClassMethods
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
- @properties = {}
61
- @lists = {}
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(any)
59
- Spira.type_alias(any, self)
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::Integer
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 explicitly require them or XSD types
19
+ # classes themselves, so we need to require them or XSD types
16
20
  # will show up as not found.
17
- require 'spira/types/integer'
18
- require 'spira/types/boolean'
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
@@ -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
@@ -1,24 +1,23 @@
1
1
  module Spira::Types
2
2
 
3
3
  ##
4
- # A {Spira::Type} for Date values. Values will be associated with the
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.Date`.
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::Date.new(value)
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
@@ -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