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,78 @@
1
+ module RDFMapper
2
+ class Model
3
+ class Property
4
+
5
+ def initialize(instance, options = {}, *args)
6
+ @instance = instance
7
+ @options = options
8
+ @value = nil
9
+ @new = true
10
+ end
11
+
12
+ ##
13
+ # Checks if property has default (nil) value.
14
+ #
15
+ # @params [Boolean]
16
+ ##
17
+ def new?
18
+ @new
19
+ end
20
+
21
+ ##
22
+ # Assigns a new value to the property.
23
+ #
24
+ # @param [Object] value
25
+ # @return [Object]
26
+ ##
27
+ def replace(value)
28
+ @new = false
29
+ if value.kind_of? Array
30
+ value = value.first
31
+ end
32
+ if value.kind_of? RDF::Literal
33
+ value = value.object
34
+ end
35
+ @value = case @options[:type]
36
+ when :text then value.to_s
37
+ when :integer then value.to_i
38
+ when :float then value.to_f
39
+ when :uri then RDF::URI.new(value.to_s)
40
+ else value.to_s
41
+ end
42
+ end
43
+
44
+ ##
45
+ # [-]
46
+ ##
47
+ def object(*args)
48
+ @value
49
+ end
50
+
51
+ ##
52
+ # [-]
53
+ ##
54
+ def to_statements(options = {})
55
+ { :subject => @instance.id,
56
+ :predicate => @options[:type],
57
+ :object => RDF::Literal.new(@value) }
58
+ end
59
+
60
+ ##
61
+ # Developer-friendly representation of the instance
62
+ #
63
+ # @return [String]
64
+ ##
65
+ def inspect #nodoc
66
+ @value.inspect
67
+ end
68
+
69
+ ##
70
+ # [-]
71
+ ##
72
+ def method_missing(symbol, *args, &block)
73
+ @value.send(symbol, *args, &block)
74
+ end
75
+
76
+ end # Property
77
+ end # Model
78
+ end # RDFMapper
@@ -0,0 +1,165 @@
1
+ module RDFMapper
2
+ module Scope
3
+ ##
4
+ # This class contains collections of models. It is primarily used in
5
+ # search queries (find(:all) queries will yield instances of this
6
+ # class) and associations.
7
+ #
8
+ # It implements most commonly used Array and Enumerable methods.
9
+ ##
10
+ class Collection
11
+
12
+ attr_reader :loader #Temporary
13
+
14
+ def initialize(cls, options)
15
+ @loader = Loader.new(cls, options)
16
+ @models = []
17
+ @cls = cls
18
+ end
19
+
20
+ ##
21
+ # Set data adapter for the query and return self. This will override
22
+ # the default model adapter. It is intended to be used as a chain method:
23
+ #
24
+ # Person.find(:all).from(:rest) #=> #<PersonCollection:217132856>
25
+ # Person.find(:all).from(:rest).length #=> 10
26
+ #
27
+ # @param [Symbol] adapter (:rails, :sparql, :rest)
28
+ # @param [Hash] options options to pass on to the adapter constructor
29
+ #
30
+ # @return [self]
31
+ ##
32
+ def from(adapter, options = {})
33
+ @loader.from(adapter, options)
34
+ self
35
+ end
36
+
37
+ ##
38
+ # Returns first object of the collection. Note that the
39
+ # object is not yet loaded at this point.
40
+ #
41
+ # @return [Object]
42
+ ##
43
+ def first
44
+ at(0)
45
+ end
46
+
47
+ ##
48
+ # Returns first object of the collection. Note that the
49
+ # object is not yet loaded at this point.
50
+ #
51
+ # @return [Object]
52
+ ##
53
+ def last
54
+ at(-1)
55
+ end
56
+
57
+ ##
58
+ # Returns the object at `index`. Note that the object is
59
+ # not yet loaded at this point.
60
+ #
61
+ # @param [Integer] index
62
+ # @return [Object]
63
+ ##
64
+ def [](index)
65
+ at(index)
66
+ end
67
+
68
+ ##
69
+ # Returns the object at `index`. Note that the object is
70
+ # not yet loaded at this point.
71
+ #
72
+ # @param [Integer] index
73
+ # @return [Object]
74
+ ##
75
+ def at(index)
76
+ @models[index] ||= @loader.get(index)
77
+ end
78
+
79
+ ##
80
+ # Returns true if collection has no objects.
81
+ #
82
+ # @param [Boolean]
83
+ ##
84
+ def empty?
85
+ length == 0
86
+ end
87
+
88
+ ##
89
+ # Returns the number of objects in a collection.
90
+ #
91
+ # @return [Integer]
92
+ ##
93
+ def length
94
+ @loader.length
95
+ end
96
+
97
+ ##
98
+ # Calls block once for each object in a collection, passing
99
+ # that element as a parameter.
100
+ #
101
+ # @yield [Object]
102
+ # @return [self]
103
+ ##
104
+ def each(&block)
105
+ items.each { |x| block.call(x) }
106
+ self
107
+ end
108
+
109
+ ##
110
+ # Invokes block once for each object in a collection. Creates
111
+ # a new array containing the values returned by the block
112
+ #
113
+ # @yield [Object]
114
+ # @return [Array]
115
+ ##
116
+ def map(&block)
117
+ items.map { |x| block.call(x) }
118
+ end
119
+
120
+ ##
121
+ # Returns true if collection contains specified object.
122
+ #
123
+ # @param [Object] object instance of RDFMapper::Model
124
+ # @return [Boolean]
125
+ ##
126
+ def exists?(object)
127
+ items.include?(object)
128
+ end
129
+
130
+ ##
131
+ # Converts collection into Array.
132
+ #
133
+ # @return [Array]
134
+ ##
135
+ def to_a
136
+ items
137
+ end
138
+
139
+ alias_method :size, :length
140
+ alias_method :collect, :map
141
+ alias_method :slice, :[]
142
+ alias_method :include?, :exists?
143
+
144
+ ##
145
+ # Developer-friendly representation of the instance
146
+ #
147
+ # @return [String]
148
+ ##
149
+ def inspect #nodoc
150
+ "#<%sCollection:%s>" % [@cls, object_id]
151
+ end
152
+
153
+
154
+ private
155
+
156
+ ##
157
+ # Loads entire collection (if needed) and returns all objects.
158
+ ##
159
+ def items #nodoc
160
+ (0..length-1).map { |x| at(x) }
161
+ end
162
+
163
+ end # Collection
164
+ end # Scope
165
+ end # RDFMapper
@@ -0,0 +1,132 @@
1
+ module RDFMapper
2
+ module Scope
3
+ ##
4
+ # [-]
5
+ ##
6
+ class Condition
7
+
8
+ attr_reader :eq
9
+
10
+ def initialize(cls, att, value, eq = '=')
11
+ @cls, @att, @value, @eq = cls, att, value, eq
12
+ end
13
+
14
+ ##
15
+ # [-]
16
+ ##
17
+ def name
18
+ if @att == :id
19
+ return @att
20
+ end
21
+ unless att = @cls.has?(@att)
22
+ raise RuntimeError, 'Undefined attribute %s for %s' % [@att, @cls]
23
+ else
24
+ att.name
25
+ end
26
+ end
27
+
28
+ ##
29
+ # [-]
30
+ ##
31
+ def value(required = [])
32
+ association? ? association(required) : literal
33
+ end
34
+
35
+ alias_method :check, :value
36
+
37
+ ##
38
+ # [-]
39
+ ##
40
+ def to_triples(subject)
41
+ to_statements(subject).map do |statement|
42
+ [ statement[:subject], statement[:predicate], statement[:object] ]
43
+ end
44
+ end
45
+
46
+ ##
47
+ # [-]
48
+ ##
49
+ def to_statements(subject)
50
+ if association?
51
+ object = association.id
52
+ rdf_type = association.to_triples(:short => true)
53
+ else
54
+ object = RDF::Query::Variable.new
55
+ object.bind(value, eq)
56
+ rdf_type = []
57
+ end
58
+ rdf_type + [{
59
+ :subject => subject,
60
+ :predicate => @cls.has?(@att).type,
61
+ :object => object
62
+ }]
63
+ end
64
+
65
+ ##
66
+ # Developer-friendly representation of the instance.
67
+ #
68
+ # @return [String]
69
+ ##
70
+ def inspect #nodoc
71
+ "#<Condition:(%s%s%s)>" % [name, eq, value]
72
+ end
73
+
74
+
75
+ private
76
+
77
+ ##
78
+ # [-]
79
+ ##
80
+ def association(required = [], value = nil) #nodoc
81
+ if value.nil?
82
+ value = @value
83
+ end
84
+ if value.kind_of? Array or value.kind_of? RDFMapper::Scope::Collection
85
+ return value.map do |item|
86
+ association(required, item)
87
+ end
88
+ end
89
+ unless value.kind_of? RDFMapper::Model
90
+ att = @cls.associations[name]
91
+ value = att.model.find(value.to_s)
92
+ end
93
+ required.each do |att|
94
+ if value[att].nil?
95
+ raise RuntimeError, 'Expected %s to have %s' % [value, att] if value.reload[att].nil?
96
+ end
97
+ end
98
+ value
99
+ end
100
+
101
+ ##
102
+ # [-]
103
+ ##
104
+ def literal(value = nil) #nodoc
105
+ if value.nil?
106
+ value = @value
107
+ end
108
+ if value.kind_of? Array
109
+ return value.map do |item|
110
+ literal(item)
111
+ end
112
+ end
113
+ if value.kind_of? RDF::Literal
114
+ value.object
115
+ elsif value.kind_of? RDF::URI
116
+ value.to_s
117
+ else
118
+ value
119
+ end
120
+ end
121
+
122
+ ##
123
+ # Return the association name that has `name_or_key` as foreign key or name.
124
+ ##
125
+ def association? #nodoc
126
+ att = @cls.associations[name]
127
+ not (att.nil? or att.property?)
128
+ end
129
+
130
+ end # Condition
131
+ end # Scope
132
+ end # RDFMapper
@@ -0,0 +1,111 @@
1
+ module RDFMapper
2
+ module Scope
3
+ ##
4
+ # Loader is responsible for loading and updating model attributes. An instance
5
+ # of Loader is assigned to each search query and association.
6
+ ##
7
+ class Loader
8
+
9
+ def initialize(cls, options = {})
10
+ @conditions = Query.new(cls, options)
11
+ @objects = []
12
+ @cls = cls
13
+ end
14
+
15
+ ##
16
+ # Checks if model ID is specified within conditions. Returns
17
+ # RDF::URI if found, nil otherwise.
18
+ #
19
+ # @return [RDF::URI]
20
+ # @return [nil]
21
+ ##
22
+ def has_id?
23
+ @conditions[:id].nil? ? nil : RDF::URI.new(@conditions[:id])
24
+ end
25
+
26
+ ##
27
+ # Sets data adapter for this loader. This will override default model adapter.
28
+ #
29
+ # @param [Symbol] adapter (:rails, :sparql, :rest)
30
+ # @param [Hash] options options to pass on to the adapter constructor
31
+ # @return [Object] adapter instance
32
+ ##
33
+ def from(adapter, options = {})
34
+ @adapter = RDFMapper::Adapters.register(adapter, @cls, options)
35
+ end
36
+
37
+ ##
38
+ # Returns the number of loaded objects.
39
+ #
40
+ # @return [Integer]
41
+ ##
42
+ def length
43
+ load.length
44
+ end
45
+
46
+ ##
47
+ # Creates a new 'scoped' instance of RDFMapper::Model.
48
+ #
49
+ # @param [Integer] index
50
+ # @return [Object]
51
+ ##
52
+ def get(index)
53
+ instance = @cls.new(@objects[index])
54
+ RDFMapper::Scope.apply(instance, self, index)
55
+ end
56
+
57
+ ##
58
+ # Updates an existing 'scoped' instance of RDFMapper::Model
59
+ # (sets ID and attributes).
60
+ #
61
+ # @param [Integer] index
62
+ # @param [Object] instance
63
+ # @return [Object]
64
+ ##
65
+ def update(index, instance = nil) #nodoc
66
+ atts = load[index]
67
+ if atts.nil?
68
+ return instance.send(:nil!)
69
+ end
70
+ instance.send(:id=, atts[:id])
71
+ instance.attributes = atts
72
+ instance
73
+ end
74
+
75
+ ##
76
+ # Developer-friendly representation of the instance
77
+ #
78
+ # @return [String]
79
+ ##
80
+ def inspect #nodoc
81
+ "#<%sLoader:%s>" % [@cls, object_id]
82
+ end
83
+
84
+ private
85
+
86
+ ##
87
+ # Returns adapter class to be used when loading. It's either the
88
+ # default model adapter or the one explicitly specified via `from`.
89
+ ##
90
+ def adapter #nodoc
91
+ @adapter || @cls.adapter
92
+ end
93
+
94
+ ##
95
+ # Loads and returns objects. Objects are 'cached' and not reloaded
96
+ # upon subsequent requests.
97
+ ##
98
+ def load #nodoc
99
+ if @loaded
100
+ return @objects
101
+ end
102
+ if adapter.nil?
103
+ raise RuntimeError, "No adapter specified for %s" % @cls
104
+ end
105
+ @loaded = true
106
+ @objects = adapter.load(@conditions)
107
+ end
108
+
109
+ end # Loader
110
+ end # Scope
111
+ end # RDFMapper
@@ -0,0 +1,129 @@
1
+ module RDFMapper
2
+ module Scope
3
+
4
+ require 'lib/scope/loader'
5
+ require 'lib/scope/collection'
6
+ require 'lib/scope/query'
7
+ require 'lib/scope/condition'
8
+
9
+
10
+ def self.apply(instance, loader, index)
11
+ instance.extend(Model)
12
+ instance.send(:scoped!, loader, index)
13
+ end
14
+
15
+ ##
16
+ # Extension of RDFMapper::Model that implements lazy-loading. All models
17
+ # in collections and search queries will implement this module by default.
18
+ ##
19
+ module Model
20
+
21
+ ##
22
+ # Returns instance's unique ID (as RDF::URI).
23
+ ##
24
+ def id
25
+ super || @loader.has_id? || load.id
26
+ end
27
+
28
+ ##
29
+ # Set data adapter for the query and return self. This will override
30
+ # the default model adapter. It is intended to be used as a chain method:
31
+ #
32
+ # Person.find(:first).from(:rails) #=> #<Person:217132856>
33
+ # Person.find(:first).from(:rails).name #=> 'John'
34
+ #
35
+ # @param [Symbol] adapter (:rails, :sparql, :rest)
36
+ # @param [Hash] options options to pass on to the adapter constructor
37
+ #
38
+ # @return [self]
39
+ ##
40
+ def from(adapter, options = {})
41
+ @loader.from(adapter, options)
42
+ self
43
+ end
44
+
45
+ ##
46
+ # In addition to the original method, preloads the model.
47
+ ##
48
+ def [](name)
49
+ super || (@loaded ? nil : load[name])
50
+ end
51
+
52
+ ##
53
+ # Returns a hash of all the attributes with their names as keys and
54
+ # the attributes' values as values. If model is unloaded, it will
55
+ # preload it before returning its attributes.
56
+ #
57
+ # @return [Hash] all attributes of an instance (name => value)
58
+ ##
59
+ def attributes
60
+ check_for_nil_error
61
+ @loaded ? super : load.attributes
62
+ end
63
+
64
+ ##
65
+ # Checks if the instance is `nil`. Will load the instance to find it out.
66
+ #
67
+ # @return [Boolean]
68
+ ##
69
+ def nil?
70
+ @nil or (not @loaded and load.nil?)
71
+ end
72
+
73
+ ##
74
+ # Developer-friendly representation of the instance.
75
+ #
76
+ # @return [String]
77
+ ##
78
+ def inspect #nodoc
79
+ "#<Scoped|%s:%s>" % [self.class, object_id]
80
+ end
81
+
82
+
83
+ private
84
+
85
+ ##
86
+ # Sets Loader instance and object's index in a collection. Called
87
+ # automatically when lazy-loading module is applied.
88
+ ##
89
+ def scoped!(loader, index) #nodoc
90
+ @loader, @index = loader, index
91
+ @loaded, @nil = false, false
92
+ self
93
+ end
94
+
95
+ ##
96
+ # Flags the instance as `nil`. Returns self.
97
+ ##
98
+ def nil!
99
+ @nil = true
100
+ self
101
+ end
102
+
103
+ ##
104
+ # In addition to the original method, preloads the model.
105
+ ##
106
+ def get_attribute(name, *args)
107
+ @loaded ? super : load.send(name, *args)
108
+ end
109
+
110
+ ##
111
+ # Loads the model.
112
+ ##
113
+ def load
114
+ check_for_nil_error
115
+ return self if @loaded
116
+ @loaded = true
117
+ @loader.update(@index, self)
118
+ end
119
+
120
+ ##
121
+ # Checks if the instance is `nil`. Raises an error if true.
122
+ ##
123
+ def check_for_nil_error
124
+ raise RuntimeError, 'Instance %s is NIL' % self if @nil
125
+ end
126
+
127
+ end
128
+ end # Scope
129
+ end # RDFMapper