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,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