rdf-mapper 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.
@@ -0,0 +1,105 @@
1
+ module RDFMapper
2
+ module Adapters
3
+ ##
4
+ # [-]
5
+ ##
6
+ class SPARQL < Base
7
+
8
+ ##
9
+ # [-]
10
+ ##
11
+ def initialize(cls, options = {})
12
+ @rdf, @options = cls, options
13
+ end
14
+
15
+ ##
16
+ # [-]
17
+ ##
18
+ def load(query)
19
+ Query.new(query, @options).find
20
+ end
21
+
22
+ class Query
23
+
24
+ include RDFMapper::Logger
25
+
26
+ ##
27
+ # [-]
28
+ ##
29
+ def initialize(query, options = {})
30
+ @query, @options = query, options
31
+ @rdf = @query.cls
32
+ end
33
+
34
+ ##
35
+ # [-]
36
+ ##
37
+ def find
38
+ describe
39
+ end
40
+
41
+
42
+ private
43
+
44
+ ##
45
+ # [-]
46
+ ##
47
+ def describe
48
+ data = sparql_writer(:describe) do |writer|
49
+ triples = @query.to_triples
50
+ writer.write_triples(triples)
51
+ # Target always comes first in first triple
52
+ primary = triples.first.first
53
+ writer.targets << primary
54
+ end
55
+ repository = {}
56
+ download(data).each_triple do |triple|
57
+ s, p, o = triple
58
+ repository[s.to_s] ||= {}
59
+ repository[s.to_s][p.to_s] ||= []
60
+ repository[s.to_s][p.to_s] << o
61
+ end
62
+ objects(repository)
63
+ end
64
+
65
+ ##
66
+ # [-]
67
+ ##
68
+ def objects(repository)
69
+ repository.select do |uri, atts|
70
+ atts.key?(RDF.type.to_s)
71
+ end.select do |uri, atts|
72
+ @rdf.type == atts[RDF.type.to_s].first
73
+ end.map do |uri, atts|
74
+ atts.delete(RDF.type.to_s)
75
+ atts[:id] = uri
76
+ atts
77
+ end
78
+ end
79
+
80
+ ##
81
+ # [-]
82
+ ##
83
+ def sparql_writer(type, &block)
84
+ RDF::Writer.for(:sparql).buffer({ :type => type }, &block)
85
+ end
86
+
87
+ ##
88
+ # [-]
89
+ ##
90
+ def download(request)
91
+ data = RDFMapper::HTTP.post(@options[:server], request)
92
+
93
+ if data =~ /\<sparql/
94
+ RDF::Reader.for(:sparql_results)
95
+ elsif data =~ /\<rdf:RDF/
96
+ RDF::Reader.for(:xml)
97
+ else
98
+ raise RuntimeError, 'Unknown content type'
99
+ end.new(data)
100
+ end
101
+
102
+ end # Query
103
+ end # SPARQL
104
+ end # Adapters
105
+ end # RDFMapper
@@ -0,0 +1,95 @@
1
+ module RDFMapper
2
+ module Associations
3
+
4
+ autoload :BelongsTo, 'lib/associations/belongs_to'
5
+ autoload :HasMany, 'lib/associations/has_many'
6
+ autoload :HasOne, 'lib/associations/has_one'
7
+ autoload :HasAndBelongs, 'lib/associations/has_and_belongs'
8
+
9
+ ##
10
+ # Base class for all association types. Contains default constructor.
11
+ ##
12
+ class Base
13
+
14
+ include RDFMapper::Logger
15
+
16
+ def initialize(instance, options = {})
17
+ @instance = instance
18
+ @association = options[:cls]
19
+ @options = options
20
+ end
21
+
22
+ ##
23
+ # [-]
24
+ ##
25
+ def replace(value)
26
+ raise NotImplementedError, 'Expected association to override `replace`'
27
+ end
28
+
29
+ ##
30
+ # [-]
31
+ ##
32
+ def object(force = false)
33
+ value.nil? and value.empty? if force
34
+ self
35
+ end
36
+
37
+ ##
38
+ # [-]
39
+ ##
40
+ def to_statements(options = {})
41
+ options[:skip] ||= []
42
+ if value.kind_of? Array
43
+ items = value
44
+ else
45
+ items = [value]
46
+ end
47
+ items.reject do |item|
48
+ options[:skip].include?(items)
49
+ end.map do |item|
50
+ node = if options[:full]
51
+ item.to_statements(:skip => @instance)
52
+ else
53
+ item.to_statements(:short => true)
54
+ end
55
+ node + [{
56
+ :subject => @instance.id,
57
+ :predicate => @options[:type],
58
+ :object => item.id
59
+ }]
60
+ end.flatten
61
+ end
62
+
63
+ ##
64
+ # Developer-friendly representation of the instance
65
+ #
66
+ # @return [String]
67
+ ##
68
+ def inspect #nodoc
69
+ value.inspect
70
+ end
71
+
72
+
73
+ private
74
+
75
+ ##
76
+ # [-]
77
+ ##
78
+ def value
79
+ raise NotImplementedError, 'Expected association to override `value`'
80
+ end
81
+
82
+ ##
83
+ # [-]
84
+ ##
85
+ def method_missing(symbol, *args, &block)
86
+ if value.respond_to? symbol
87
+ value.send(symbol, *args, &block)
88
+ else
89
+ raise RuntimeError, 'Undefined method `%s`' % symbol
90
+ end
91
+ end
92
+
93
+ end # Base
94
+ end # Associations
95
+ end # RDFMapper
@@ -0,0 +1,64 @@
1
+ module RDFMapper
2
+ module Associations
3
+ ##
4
+ # [-]
5
+ ##
6
+ class BelongsTo < Base
7
+
8
+ ##
9
+ # [-]
10
+ ##
11
+ def id
12
+ value.id
13
+ end
14
+
15
+ ##
16
+ # [-]
17
+ ##
18
+ def nil?
19
+ value.nil?
20
+ end
21
+
22
+ ##
23
+ # [-]
24
+ ##
25
+ def kind_of?(type)
26
+ value.kind_of?(type)
27
+ end
28
+
29
+ ##
30
+ # Replaces current association with a new object
31
+ ##
32
+ def replace(value)
33
+ if value.kind_of? String
34
+ @key = RDF::URI.new(value)
35
+ end
36
+ if value.kind_of? RDF::URI
37
+ @key = value
38
+ end
39
+ if value.kind_of? RDFMapper::Model
40
+ @value = value
41
+ else
42
+ nil
43
+ end
44
+ end
45
+
46
+
47
+ private
48
+
49
+ ##
50
+ # [-]
51
+ ##
52
+ def value
53
+ unless @value.nil?
54
+ return @value
55
+ end
56
+ if @key.nil?
57
+ return nil
58
+ end
59
+ replace(@association.find(@key))
60
+ end
61
+
62
+ end # BelongsTo
63
+ end # Associations
64
+ end # RDFMapper
@@ -0,0 +1,17 @@
1
+ module RDFMapper
2
+ module Associations
3
+ ##
4
+ # [-]
5
+ ##
6
+ class HasAndBelongs
7
+
8
+ ##
9
+ # [-]
10
+ ##
11
+ def initialize(instance, options = {})
12
+ raise NotImplementedError, 'has_and_belongs_to is not yet implemented'
13
+ end
14
+
15
+ end # HasAndBelongs
16
+ end # Associations
17
+ end # RDFMapper
@@ -0,0 +1,147 @@
1
+ module RDFMapper
2
+ module Associations
3
+ ##
4
+ # [-]
5
+ ##
6
+ class HasMany < Base
7
+
8
+ ##
9
+ # Replaces the collections content by deleting and adding
10
+ # objects as appropriate.
11
+ ##
12
+ def replace(objects)
13
+ new_objects = filter(objects.to_a)
14
+ return @value if new_objects.empty?
15
+
16
+ new_objects.each do |child|
17
+ self << child
18
+ end
19
+
20
+ @value ||= []
21
+ @value.each do |child|
22
+ delete(child) unless new_objects.include?(child)
23
+ end
24
+
25
+ @value
26
+ end
27
+
28
+ ##
29
+ # Adds one or more objects to the collection by setting their
30
+ # foreign keys to the collection's primary key.
31
+ ##
32
+ def <<(*objects)
33
+ objects.to_a.select do |child|
34
+ child.kind_of? RDFMapper::Model
35
+ end.each do |child|
36
+ unless include?(child)
37
+ child[reverse] = @instance
38
+ @value << child
39
+ end
40
+ end
41
+ self
42
+ end
43
+
44
+ alias_method :push, :<<
45
+
46
+ ##
47
+ # Removes one or more objects from the collection by removing
48
+ # the association between objects
49
+ ##
50
+ def delete(*objects)
51
+ objects.each do |child|
52
+ if include?(child)
53
+ child[reverse] = nil
54
+ @value.delete(child)
55
+ end
56
+ end
57
+ self
58
+ end
59
+
60
+ ##
61
+ # Removes every object from the collection.
62
+ ##
63
+ def clear
64
+ delete(@value)
65
+ end
66
+
67
+ ##
68
+ # Finds an associated object according to the same rules as
69
+ # RDFMapper::Model.find.
70
+ ##
71
+ def find
72
+ raise NotImplementedError, '`find` not yet implemented' # TODO
73
+ end
74
+
75
+ ##
76
+ # Returns one or more new objects of the collection type that
77
+ # have been instantiated with attributes and linked to this object
78
+ # through a foreign key, but have not yet been saved.
79
+ ##
80
+ def build
81
+ raise NotImplementedError, '`build` not yet implemented' # TODO
82
+ end
83
+
84
+ ##
85
+ # Returns a new object of the collection type that has been
86
+ # instantiated with attributes, linked to this object through
87
+ # a foreign key, and that has already been saved.
88
+ ##
89
+ def create
90
+ raise NotImplementedError, '`create` not yet implemented' # TODO
91
+ end
92
+
93
+ ##
94
+ # Returns true if a given object is present in the collection
95
+ ##
96
+ def include?(object)
97
+ @value ||= []
98
+ @value.include?(object)
99
+ end
100
+
101
+ ##
102
+ # [-]
103
+ ##
104
+ def to_a
105
+ value
106
+ end
107
+
108
+
109
+ private
110
+
111
+ def filter(objects)
112
+ objects.select do |child|
113
+ child.kind_of? RDFMapper::Model
114
+ end
115
+ end
116
+
117
+ ##
118
+ # [-]
119
+ ##
120
+ def value
121
+ unless @value.nil?
122
+ return @value
123
+ end
124
+ if @instance.id.nil?
125
+ return []
126
+ end
127
+ replace @association.find(:all, {
128
+ :conditions => { reverse => @instance },
129
+ :skip => [reverse]
130
+ })
131
+ end
132
+
133
+ ##
134
+ # [-]
135
+ ##
136
+ def reverse
137
+ @reverse ||= @association.has?(nil, @instance)
138
+ if @reverse.nil?
139
+ raise RuntimeError, 'Expected %s to belong to %s' % [@association, @instance.class]
140
+ else
141
+ @reverse.name
142
+ end
143
+ end
144
+
145
+ end # HasMany
146
+ end # Associations
147
+ end # RDFMapper
@@ -0,0 +1,17 @@
1
+ module RDFMapper
2
+ module Associations
3
+ ##
4
+ # [-]
5
+ ##
6
+ class HasOne
7
+
8
+ ##
9
+ # [-]
10
+ ##
11
+ def initialize(instance, options = {})
12
+ raise NotImplementedError, 'has_one is not yet implemented'
13
+ end
14
+
15
+ end # HasOne
16
+ end # Associations
17
+ end # RDFMapper
@@ -0,0 +1,59 @@
1
+ module RDFMapper
2
+ class Model
3
+ class << self
4
+
5
+ ##
6
+ # Specifies a one-to-many association. The following methods for retrieval
7
+ # and query of collections of associated objects will be added:
8
+ #
9
+ # * collection(force_load = false) -- Returns an array of all the associated
10
+ # objects. An empty array is returned if none are found.
11
+ #
12
+ # * collection<<(object, ...) -- Adds one or more objects to the collection by
13
+ # setting their foreign keys to the collection‘s primary key.
14
+ #
15
+ # * collection.delete(object, ...) -- Removes one or more objects from the
16
+ # collection by removing the association between objects.
17
+ #
18
+ # * collection=objects -- Replaces the collections content by deleting and
19
+ # adding objects as appropriate.
20
+ #
21
+ # * collection.clear -- Removes every object from the collection.
22
+ #
23
+ # * collection.empty? -- Returns true if there are no associated objects.
24
+ #
25
+ # * collection.size -- Returns the number of associated objects.
26
+ #
27
+ # @param [Symbol] name name of the association
28
+ #
29
+ # @param [Symbol] options[:class_name] class name of the association. Use it
30
+ # only if that name can't be inferred from the association name
31
+ #
32
+ # @return [Object] instance of RDFMapper::Attribute
33
+ ##
34
+ def has_many(name, options = {})
35
+ attribute(name, options.merge(:association => :has_many))
36
+ end
37
+
38
+ ##
39
+ # Specifies a one-to-one association with another class. The following
40
+ # methods for retrieval and query of the associated object will be added:
41
+ #
42
+ # * association(force_reload = false) -- Returns the associated object.
43
+ # nil is returned if none is found.
44
+ #
45
+ # * association=(associate) -- Assigns the associate object.
46
+ #
47
+ # @param [Symbol] name name of the association
48
+ #
49
+ # @param [Symbol] options[:class_name] class name of the association. Use it
50
+ # only if that name can't be inferred from the association name
51
+ ##
52
+ def belongs_to(name, options = {})
53
+ attribute(name, options.merge(:association => :belongs_to))
54
+ end
55
+
56
+ end
57
+ end # Model
58
+ end # RDFMapper
59
+
@@ -0,0 +1,186 @@
1
+ module RDFMapper
2
+ class Model
3
+ ##
4
+ # Contains configuration and convenience methods for model attributes.
5
+ # Instances of this class are assigned to classes (not instances!) of
6
+ # RDFMapper::Model.
7
+ ##
8
+ class Attribute
9
+
10
+ include RDFMapper::Logger
11
+
12
+ attr_reader :name
13
+
14
+ ##
15
+ # Constructor is called for each attribute of a model at the time
16
+ # the model is defined.
17
+ #
18
+ # @param [Object] cls class of the model
19
+ # @param [String] name name of the attribute
20
+ # @param [Hash] options options to pass on to the property / association constructor
21
+ #
22
+ # @return [self]
23
+ ##
24
+ def initialize(cls, name, options)
25
+ @cls = cls
26
+ @name = name
27
+ @options = options.dup
28
+ end
29
+
30
+ ##
31
+ # Checks if this attribute is a `belongs_to` association.
32
+ #
33
+ # @return [Boolean]
34
+ ##
35
+ def belongs_to?
36
+ not property? and not multiple?
37
+ end
38
+
39
+ ##
40
+ # Checks if this attribute is a property (i.e. not an association).
41
+ #
42
+ # @return [Boolean]
43
+ ##
44
+ def property?
45
+ @options[:association].nil?
46
+ end
47
+
48
+ ##
49
+ # Returns attribute's RDF predicate.
50
+ #
51
+ # @return [RDF::URI]
52
+ # @return [nil] if not specified and model has no namespace
53
+ ##
54
+ def type
55
+ # Type is 'cached': lookups based on namespace can be quite slow
56
+ @type ||= unless @options[:predicate].nil?
57
+ RDF::URI.new(@options[:predicate].to_s)
58
+ else
59
+ # Keep this weird comparison. RDF::Vocabulary doesn't recognize `nil?` or `==`
60
+ (nil == @cls.namespace) ? nil : @cls.namespace[@name]
61
+ end
62
+ end
63
+
64
+ ##
65
+ # Returns class of the associated model. Uses either the `:class_name`
66
+ # option or relies on association name. It follows ActiveRecord naming
67
+ # conventions(e.g. has_many should be plural, belongs_to - singular).
68
+ #
69
+ # @return [Object] a subclass of RDFMapper::Model
70
+ # @return [nil] if association is not found or this is a property attribute
71
+ ##
72
+ def model
73
+ # A bit of 'caching': lookups based on namespace can be quite slow
74
+ unless @model.nil?
75
+ return @model
76
+ end
77
+
78
+ # Should return nil if this is not an association
79
+ if property?
80
+ return nil
81
+ end
82
+
83
+ if @model = model_from_options || model_from_namespace
84
+ @model
85
+ else
86
+ raise RuntimeError, 'Could not find association model for %s (%s :%s)' % [@cls, @options[:association], @name]
87
+ end
88
+ end
89
+
90
+ ##
91
+ # Returns the class or an instance of a class associated with this
92
+ # attribute.
93
+
94
+ # When model instance is not specified, it will return Associations::HasMany,
95
+ # Associations::BelongsTo, Property, etc. Alternatively, when RDFMapper::Model
96
+ # is specified, it will return an instance of these classes.
97
+ #
98
+ # @param [Object] instance instance of RDFMapper::Model
99
+ # @return [Object] class of this attribute
100
+ # @return [Object] instance of this attribute
101
+ ##
102
+ def value(instance = nil)
103
+ if nil == instance
104
+ return attribute_type
105
+ end
106
+ attribute_type.new(instance, @options.merge({
107
+ :cls => model,
108
+ :type => type,
109
+ :name => name
110
+ }))
111
+ end
112
+
113
+ ##
114
+ # Checks if this attribute has the same predicate and / or value.
115
+ # Value is accepted both as an instance and a class.
116
+ ##
117
+ def matches?(predicate, value = nil)
118
+ if type.nil? # Always false if attribute predicate is not defined
119
+ return false
120
+ end
121
+ if value == nil # Checking predicates
122
+ return type.to_s == predicate.to_s
123
+ end
124
+ unless value.respond_to? :new # Converting instance to a class
125
+ value = value.class
126
+ end
127
+ if model.nil? # Value is not nil, but model is undefined
128
+ return false
129
+ end
130
+ if value.type != model.type # Value type and model type should match
131
+ return false
132
+ end
133
+ if predicate.nil? # Value and model types match
134
+ true
135
+ else
136
+ predicate == type
137
+ end
138
+ end
139
+
140
+
141
+ private
142
+
143
+ ##
144
+ # Returns attribute's class (e.g. Associations::HasMany, Property) based
145
+ # on the supplied `options[:association]`
146
+ ##
147
+ def attribute_type #nodoc
148
+ case @options[:association]
149
+ when :has_many then Associations::HasMany
150
+ when :belongs_to then Associations::BelongsTo
151
+ when :has_one then Association::HasOne
152
+ when :has_and_belongs then Association::HasAndBelongs
153
+ else Property
154
+ end
155
+ end
156
+
157
+ ##
158
+ # Derives the name of associated model from `options[:class_name]` and
159
+ # returns its class.
160
+ ##
161
+ def model_from_options #nodoc
162
+ @options[:class_name].nil? ? nil : @options[:class_name].constantize
163
+ end
164
+
165
+ ##
166
+ # Derives the name of associated model from association name and
167
+ # returns its class
168
+ ##
169
+ def model_from_namespace #nodoc
170
+ name = multiple? ? @name.to_s.classify : @name.to_s.pluralize.classify
171
+ # Keep this weird comparison. RDF::Vocabulary doesn't recognize `nil?` or `==`
172
+ (nil == @cls.namespace) ? nil : @cls[@cls.namespace[name]]
173
+ end
174
+
175
+ ##
176
+ # Checks if this attribute is an association with multiple objects
177
+ # (i.e. `has_many` or `has_and_belongs_to`). Used for deriving association
178
+ # name (plural / singular)
179
+ ##
180
+ def multiple? #nodoc
181
+ @options[:association] == :has_many || @options[:association] == :has_and_belongs
182
+ end
183
+
184
+ end # Attribute
185
+ end # Model
186
+ end # RDFMapper