rdf-mapper 0.0.1

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