spira 0.0.12 → 0.5.0

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