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,623 @@
1
+ module RDFMapper
2
+
3
+ require 'lib/model/association'
4
+ require 'lib/model/output'
5
+ require 'lib/model/attribute'
6
+ require 'lib/model/property'
7
+
8
+ ##
9
+ # [-]
10
+ ##
11
+ class Model
12
+
13
+ class << self
14
+
15
+ alias_method :original_name, :name #nodoc
16
+
17
+ ##
18
+ # Sets or returns model's namespace. It is intended to operate as a shortcut:
19
+ # model and its attributes will calculate their RDF type and predicates
20
+ # automatically. The following two examples produce identical models:
21
+ #
22
+ # class Person < RDFMapper::Model
23
+ # namespace 'http://example.org/schema#'
24
+ # attribute :name
25
+ # attribute :age
26
+ # end
27
+ #
28
+ # class Person < RDFMapper::Model
29
+ # type 'http://example.org/schema#Person'
30
+ # attribute :name, :predicate => 'http://example.org/schema#name'
31
+ # attribute :age, :predicate => 'http://example.org/schema#age'
32
+ # end
33
+ #
34
+ # Person.type #=> 'http://xmlns.com/foaf/0.1/Person'
35
+ #
36
+ # @overload namespace(value)
37
+ # Sets model's namespace
38
+ # @param [RDF::Vocabulary, RDF::URI, String] value
39
+ #
40
+ # @overload namespace
41
+ # Returns model's namespace
42
+ # @param [nil]
43
+ #
44
+ # @see type
45
+ # @return [RDF::Vocabulary]
46
+ ##
47
+ def namespace(value = nil, options = {})
48
+ @ns = options[:name] || 'myrdf'
49
+ case value
50
+ when NilClass
51
+ @namespace
52
+ when RDF::Vocabulary
53
+ @namespace = value
54
+ else
55
+ @namespace = RDF::Vocabulary.new(value.to_s)
56
+ end
57
+ end
58
+
59
+ def ns
60
+ @ns || 'myrdf'
61
+ end
62
+
63
+ ##
64
+ # Sets or returns model's RDF type
65
+ #
66
+ # class Company < RDFMapper::Model
67
+ # type RDF::URI.new('http://example.org/schema#Company')
68
+ # end
69
+ #
70
+ # class Person < RDFMapper::Model
71
+ # type 'http://example.org/schema#Person'
72
+ # end
73
+ #
74
+ # Company.type #=> #<RDF::URI(http://example.org/schema#Company)>
75
+ # Person.type #=> #<RDF::URI(http://example.org/schema#Person)>
76
+ #
77
+ # @overload type(value)
78
+ # Sets model's RDF type
79
+ # @param [RDF::URI, String] value
80
+ #
81
+ # @overload type
82
+ # @param [nil]
83
+ #
84
+ # @see namespace
85
+ # @return [RDF::URI]
86
+ ##
87
+ def type(value = nil)
88
+ unless value.nil?
89
+ return @type = RDF::URI.new(value.to_s)
90
+ end
91
+ unless @type.nil?
92
+ return @type
93
+ end
94
+ (nil == namespace) == true ? nil : namespace[name]
95
+ end
96
+
97
+ ##
98
+ # Returns model's name without modules. Original class name is stored
99
+ # as 'original_name'
100
+ #
101
+ # module TestModule
102
+ # class Person < RDFMapper::Model; end
103
+ # end
104
+ #
105
+ # Person.name => 'Person'
106
+ # Person.original_name => 'TestModule::Person'
107
+ #
108
+ # @return [String]
109
+ ##
110
+ def name
111
+ original_name.split('::').last
112
+ end
113
+
114
+ ##
115
+ # Sets or returns model's connection adapter.
116
+ #
117
+ # @overload adapter(instance)
118
+ # Sets model's connection adapter
119
+ # @param [Symbol] name adapter name (`:rails`, `:rest` or `:sparql`)
120
+ # @param [Hash] options options to pass on to the adapter constructor
121
+ #
122
+ # @overload adapter
123
+ # Returns model's connection adapter
124
+ #
125
+ # @return [Object] an instance of RDFMapper adapter
126
+ ##
127
+ def adapter(name = nil, options = {})
128
+ return @adapter if name.nil?
129
+ @adapter = RDFMapper::Adapters.register(name, self, options)
130
+ end
131
+
132
+ ##
133
+ # Returns a model that subclassed {RDFMapper::Model} and has specified
134
+ # URI as its rdf:type
135
+ #
136
+ # class Person < RDFMapper::Model
137
+ # type 'http://example.org/schema#Person'
138
+ # end
139
+ #
140
+ # class Company < RDFMapper::Model
141
+ # namespace 'http://example.org/schema#'
142
+ # end
143
+ #
144
+ # RDFMapper::Model['http://example.org/schema#Person'] #=> Person
145
+ # RDFMapper::Model['http://example.org/schema#Company'] #=> Company
146
+ # RDFMapper::Model['http://unknown-url.com/'] #=> nil
147
+ #
148
+ # @param [String] URI
149
+ # @param [RDF::URI] URI
150
+ #
151
+ # @return [Object] an RDFMapper model
152
+ ##
153
+ def [](uri)
154
+ return nil if uri.nil?
155
+ @@subclasses.select do |model|
156
+ model.type.to_s == uri.to_s
157
+ end.first
158
+ end
159
+
160
+ ##
161
+ # Returns RDFMapper::Attribute that is assigned to the specified name.
162
+ # Accepts symbol, string, RDF::URI as a parameter. Value is optional
163
+ # and is used for associations.
164
+ #
165
+ # class Person < RDFMapper::Model
166
+ # namespace 'http://example.org/schema#'
167
+ # attribute :name, :type => :text
168
+ # has_many :contacts, :predicate => 'http://example.org/schema#has'
169
+ # has_many :friends, :predicate => 'http://example.org/schema#has'
170
+ # end
171
+ #
172
+ # Person.has?(:name) #=> #<RDFMapper::Model::Attribute>
173
+ # Person.has?('http://example.org/schema#name') #=> #<RDFMapper::Model::Attribute>
174
+ # Person.has?('http://example.org/schema#unknown') #=> nil
175
+ #
176
+ # Person.has?('http://example.org/schema#has', Contact) #=> #<RDFMapper::Model::Attribute>
177
+ # Person.has?('http://example.org/schema#has', Contact.new) #=> #<RDFMapper::Model::Attribute>
178
+ # Person.has?(nil, Contact) #=> #<RDFMapper::Model::Attribute>
179
+ #
180
+ # @param [Symbol, RDF::URI, String] name
181
+ # @param [Object] value
182
+ #
183
+ # @return [RDFMapper::Attribute]
184
+ # @return [nil] if attribute was not found
185
+ ##
186
+ def has?(name, value = nil)
187
+ if name.kind_of? String
188
+ return has?(RDF::URI.new(name), value)
189
+ end
190
+ if name.kind_of? Symbol
191
+ return attributes[name]
192
+ end
193
+ attributes.values.select do |att|
194
+ att.matches?(name, value)
195
+ end.first
196
+ end
197
+
198
+ ##
199
+ # Returns the association name for the supplied predicate and / or value
200
+ # @see has?
201
+ #
202
+ # @param [Symbol, RDF::URI, String] name
203
+ # @param [Object] value
204
+ #
205
+ # @return [Symbol]
206
+ # @return [nil] if attribute was not found
207
+ ##
208
+ def symbol(name, value = nil)
209
+ att = has?(name, value)
210
+ att.nil? ? nil : att.name
211
+ end
212
+
213
+ ##
214
+ # Returns a hash of all attributes with their names as keys and
215
+ # RDFMapper::Attribute instances as values.
216
+ #
217
+ # @return [Hash]
218
+ ##
219
+ def attributes
220
+ @attributes ||= {}
221
+ end
222
+
223
+ ##
224
+ # Returns a hash of all properties with their names as keys and
225
+ # RDFMapper::Attribute instances as values.
226
+ #
227
+ # @return [Hash]
228
+ ##
229
+ def properties
230
+ Hash[attributes.select { |name, att| att.property? }]
231
+ end
232
+
233
+ ##
234
+ # Returns a hash of all associations with their names as keys and
235
+ # RDFMapper::Attribute instances as values.
236
+ #
237
+ # @return [Hash]
238
+ ##
239
+ def associations
240
+ Hash[attributes.reject { |name, att| att.property? }]
241
+ end
242
+
243
+ ##
244
+ # Defines an attribute within a model.
245
+ #
246
+ # @param [Symbol] name attribute name
247
+ # @param [Symbol] options[:type] attribute type (:text, :uri, :integer, :float)
248
+ # @param [RDF::URI, String] options[:predicate] RDF predicate
249
+ #
250
+ # @return [Object] instance of RDFMapper::Model::Attribute
251
+ ##
252
+ def attribute(name, options = {})
253
+ attributes[name.to_sym] = Attribute.new(self, name.to_sym, options)
254
+ class_eval <<-EOF
255
+ def #{name}(*args, &block)
256
+ get_attribute(:#{name}, *args, &block)
257
+ end
258
+ def #{name}=(value)
259
+ set_attribute(:#{name}, value)
260
+ end
261
+ EOF
262
+ end
263
+
264
+ ##
265
+ # Creates an object and saves it via the assigned adapter.
266
+ # The resulting object is returned whether the object was saved
267
+ # successfully to the database or not.
268
+ #
269
+ # @param [Hash] attributes attributes of the new object
270
+ # @param [RDF::URI, String] id object's ID
271
+ #
272
+ # @return [Object] instance of RDFMapper::Model
273
+ # @return [nil] if save was unsuccessful
274
+ ##
275
+ def create(attributes, id)
276
+ new(attributes).save(id)
277
+ end
278
+
279
+ ##
280
+ # Find operates similarly to Rails' ActiveRecord::Base.find function. It has
281
+ # the same four retrieval approaches:
282
+ #
283
+ # * Find by id -- This can either be a specific id, a list of ids, or
284
+ # an array of ids ([5, 6, 10]).
285
+ #
286
+ # * Find first -- This will return the first record matched by the
287
+ # options used. These options can either be specific conditions or
288
+ # merely an order. If no record can be matched, `nil` is returned.
289
+ # Use Model.find(:first, *args) or its shortcut Model.first(*args).
290
+ #
291
+ # * Find last - This will return the last record matched by the options
292
+ # used. These options can either be specific conditions or merely an
293
+ # order. If no record can be matched, `nil` is returned. Use
294
+ # Model.find(:last, *args) or its shortcut Model.last(*args).
295
+ #
296
+ # * Find all - This will return all the records matched by the options
297
+ # used. If no records are found, an empty array is returned. Use
298
+ # Model.find(:all, *args) or its shortcut Model.all(*args).
299
+ ##
300
+ def find(*args)
301
+ options = args.last.is_a?(::Hash) ? args.pop : {}
302
+ case args.first
303
+ when :first then find_every(options.merge(:limit => 1)).first
304
+ when :last then find_every(options).last
305
+ when :all then find_every(options)
306
+ else find_from_ids(args, options)
307
+ end
308
+ end
309
+
310
+ ##
311
+ # Either finds or creates an object with the specified ID.
312
+ #
313
+ # @param [RDF::URI, String] id object's ID
314
+ # @param [Hash] attributes attributes of the new object
315
+ #
316
+ # @return [Object] instance of RDFMapper::Model
317
+ # @return [nil] if save was unsuccessful
318
+ ##
319
+ def find_or_create(id, attributes = {})
320
+ instance = find(id)
321
+ instance.nil? ? create(id, attributes) : instance
322
+ end
323
+
324
+ ##
325
+ # A convenience wrapper for find(:first, *args). You can pass in
326
+ # all the same arguments to this method as you can to find(:first).
327
+ #
328
+ # @see find
329
+ ##
330
+ def first(*args)
331
+ find(:first, *args)
332
+ end
333
+
334
+ ##
335
+ # A convenience wrapper for find(:last, *args). You can pass in
336
+ # all the same arguments to this method as you can to find(:last).
337
+ #
338
+ # @see find
339
+ ##
340
+ def last(*args)
341
+ find(:last, *args)
342
+ end
343
+
344
+ ##
345
+ # This is an alias for find(:all). You can pass in all the same
346
+ # arguments to this method as you can to find(:all).
347
+ #
348
+ # @see find
349
+ ##
350
+ def all(*args)
351
+ find(:all, *args)
352
+ end
353
+
354
+
355
+ private
356
+
357
+ ##
358
+ # Returns an Array of instances that match specified conditions.
359
+ # Note that they are not loaded until they are accessed (lazy loading)
360
+ ##
361
+ def find_every(options) #nodoc
362
+ RDFMapper::Scope::Collection.new(self, options)
363
+ end
364
+
365
+ ##
366
+ # Returns instances with specified IDs. Depending on the number
367
+ # of IDs it returns either an Array or a single instance (or nil
368
+ # if nothing was found)
369
+ ##
370
+ def find_from_ids(ids, options) #nodoc
371
+ unless ids.kind_of?(Array)
372
+ ids = [ids]
373
+ end
374
+ options[:conditions] ||= { }
375
+ options[:conditions][:id] = ids
376
+ result = find_every(options)
377
+ case ids.size
378
+ when 0 then []
379
+ when 1 then result.first
380
+ else result
381
+ end
382
+ end
383
+
384
+ ##
385
+ # Keeps track of all models that subclass RDFMapper::Model
386
+ ##
387
+ def inherited(subclass) #nodoc
388
+ @@subclasses ||= []
389
+ @@subclasses << subclass
390
+ end
391
+
392
+ end
393
+
394
+ include RDFMapper::Logger
395
+
396
+ ##
397
+ # Creates a new instance of a model with specified attributes.
398
+ # Note that attributes include properties as well as associations.
399
+ # It also accepts URIs in addition to symbols:
400
+ #
401
+ # class Company << RDFMapper::Model
402
+ # namespace 'http://myschema.com/#'
403
+ # has_many :people
404
+ # end
405
+ #
406
+ # class Person << RDFMapper::Model
407
+ # namespace 'http://myschema.com/#'
408
+ # attribute :name, :type => text
409
+ # belongs_to :company, :predicate => 'http://myschema.com/#employer'
410
+ # end
411
+ #
412
+ # The following two examples create identical models:
413
+ #
414
+ # Person.new(:name => 'John')
415
+ # Person.new('http://myschema.com/#name' => 'John')
416
+ #
417
+ # And so do the following two examples:
418
+ #
419
+ # @company = Company.new(:name => 'MyCo Inc.')
420
+ #
421
+ # Person.new(:company => @company)
422
+ # Person.new('http://myschema.com/#employer' => @company)
423
+ #
424
+ # @param [Hash] attributes attributes of the new object
425
+ # @return [Object] instance of RDFMapper::Model
426
+ ##
427
+ def initialize(attributes = {})
428
+ @arbitrary = {}
429
+ @attributes = {}
430
+ @id = nil
431
+
432
+ self.class.attributes.map do |name, att|
433
+ @attributes[name] = att.value(self)
434
+ end
435
+
436
+ self.attributes = attributes
437
+ yield self if block_given?
438
+ end
439
+
440
+ ##
441
+ # Returns objects's unique ID.
442
+ #
443
+ # @return [RDF::URI] object's ID
444
+ ##
445
+ def id(*args)
446
+ @id.nil? ? nil : @id.dup
447
+ end
448
+
449
+ ##
450
+ # Compares instances based on their IDs.
451
+ #
452
+ # @return [Boolean]
453
+ ##
454
+ def ==(other)
455
+ (other.nil? or other.id.nil?) ? false : (id == other.id)
456
+ end
457
+
458
+ alias_method :eql?, :==
459
+ alias_method :equal?, :==
460
+
461
+ ##
462
+ # Returns the value of the attribute identified by `name` after it
463
+ # has been typecast (for example, "2004-12-12" is cast to a date
464
+ # object, like Date.new(2004, 12, 12)). (Alias for the private
465
+ # get_attribute method).
466
+ #
467
+ # @param [Symbol, String, RDF::URI] name attribute name or predicate URI
468
+ # @return [Object] instance of a property or an association
469
+ ##
470
+ def [](name)
471
+ unless name.kind_of? Symbol
472
+ name = self.class.symbol(name)
473
+ end
474
+ name.nil? ? nil : get_attribute(name)
475
+ end
476
+
477
+ ##
478
+ # Updates the attribute identified by `name` with the specified
479
+ # value. (Alias for the private set_attribute method).
480
+ #
481
+ #
482
+ # @param [Symbol, String, RDF::URI] name attribute name or predicate URI
483
+ # @param [Object] value new value of the attribute
484
+ #
485
+ # @return [Object] instance of a property or an association
486
+ ##
487
+ def []=(name, value)
488
+ unless name.kind_of? Symbol
489
+ name = self.class.symbol(name)
490
+ end
491
+ name.nil? ? nil : set_attribute(name, value)
492
+ end
493
+
494
+ ##
495
+ # Returns a hash of all the attributes with their names as keys and
496
+ # the attributes' values as values.
497
+ #
498
+ # @return [Hash] all attributes of an instance (name => value)
499
+ ##
500
+ def attributes(*args)
501
+ @attributes.merge(@arbitrary)
502
+ end
503
+
504
+ ##
505
+ # Allows you to set all the attributes at once by passing in a hash
506
+ # with keys matching attribute names or RDF predicates.
507
+ #
508
+ # @param [Hash] attributes object's new attributes
509
+ # @return [Hash] hash of all attributes (name => value)
510
+ ##
511
+ def attributes=(hash)
512
+ return unless hash.kind_of? Hash
513
+ hash.nil? ? nil : hash.each { |name, value| self[name] = value }
514
+ end
515
+
516
+ ##
517
+ # Checks whether the model originated from or was saved to
518
+ # a data source (in other word, whether it has RDF ID).
519
+ #
520
+ # @return [Boolean]
521
+ ##
522
+ def new?
523
+ id.nil?
524
+ end
525
+
526
+ alias_method :new_record?, :==
527
+
528
+ ##
529
+ # Saves the instance. If the model is new, a record gets created
530
+ # via the specified adapter (ID must be supplied in this case),
531
+ # otherwise the existing record gets updated.
532
+ #
533
+ # @param [RDF::URI, String] id object's ID
534
+ # @return [Object] self
535
+ # @return [nil] if save was unsuccessful
536
+ ##
537
+ def save(id = nil)
538
+ # Raise error if adapter is unspecified
539
+ check_for_adapter
540
+
541
+ if new? and id.nil?
542
+ raise RuntimeError, 'Save failed. ID must be specified'
543
+ end
544
+ if new?
545
+ self.id = id
546
+ end
547
+ self.attributes = self.class.adapter.save(self)
548
+ self
549
+ end
550
+
551
+ ##
552
+ # [-]
553
+ ##
554
+ def reload
555
+ # Raise error if adapter is unspecified
556
+ check_for_adapter
557
+
558
+ if id.nil?
559
+ raise RuntimeError, 'Reload failed. Model has no ID'
560
+ end
561
+
562
+ self.attributes = self.class.adapter.reload(self)
563
+ self
564
+ end
565
+
566
+ ##
567
+ # Developer-friendly representation of the instance.
568
+ #
569
+ # @return [String]
570
+ ##
571
+ def inspect #nodoc
572
+ "#<%s:%s>" % [self.class, object_id]
573
+ end
574
+
575
+
576
+ private
577
+
578
+ ##
579
+ # Raises an error if adapter is undefined.
580
+ ##
581
+ def check_for_adapter #nodoc
582
+ if self.class.adapter.nil?
583
+ raise RuntimeError, 'Save failed. Model adapter is undefined'
584
+ end
585
+ end
586
+
587
+ ##
588
+ # Sets ID of this object (must be RDF::URI or a String).
589
+ ##
590
+ def id=(value) #nodoc
591
+ @id = RDF::URI.new(value.to_s)
592
+ end
593
+
594
+ ##
595
+ # Returns the value of an attribute identified by `name` after it
596
+ # has been typecast (for example, "2004-12-12" is cast to a date
597
+ # object, like Date.new(2004, 12, 12)).
598
+ ##
599
+ def get_attribute(name, *args, &block) #nodoc
600
+ if @attributes.key?(name)
601
+ @attributes[name].object(*args, &block)
602
+ else
603
+ @arbitrary[name]
604
+ end
605
+ end
606
+
607
+ ##
608
+ # Updates the attribute identified by `name` with the specified value.
609
+ ##
610
+ def set_attribute(name, value) #nodoc
611
+ if name == :id
612
+ return nil
613
+ end
614
+ if @attributes.key?(name)
615
+ @attributes[name].replace(value)
616
+ else
617
+ @arbitrary[name] = value
618
+ end
619
+ end
620
+
621
+ end # Model
622
+ end # RDFMapper
623
+
@@ -0,0 +1,70 @@
1
+ module RDFMapper
2
+ class Model
3
+
4
+ ##
5
+ # RDF XML representaion of the instance.
6
+ #
7
+ # @todo. Not implemented
8
+ #
9
+ # @param [Hash] options [TODO]
10
+ # @return [String]
11
+ ##
12
+ def to_xml(options = {})
13
+ RDF::Writer.for(:xml).buffer({ :declaration => false }) do |writer|
14
+ if self.class.namespace
15
+ writer.namespace!(self.class.namespace, self.class.ns)
16
+ end
17
+ to_triples.each do |triple|
18
+ writer << triple
19
+ end
20
+ end
21
+ end
22
+
23
+ ##
24
+ # [-]
25
+ ##
26
+ def to_triples(options = {})
27
+ to_statements(options).map do |statement|
28
+ [ statement[:subject], statement[:predicate], statement[:object] ]
29
+ end
30
+ end
31
+
32
+ ##
33
+ # options[:short] - class declaration only
34
+ # options[:full] - include associations
35
+ ##
36
+ def to_statements(options = {})
37
+ if options[:full]
38
+ atts = attribute_statements(options)
39
+ elsif options[:short]
40
+ return type_statement
41
+ else
42
+ atts = attribute_statements
43
+ end
44
+ type_statement + atts
45
+ end
46
+
47
+ private
48
+
49
+ ##
50
+ # [-]
51
+ ##
52
+ def attribute_statements(options = {})
53
+ @attributes.map do |name, att|
54
+ att.to_statements(options)
55
+ end.flatten.compact
56
+ end
57
+
58
+ def associations_statements
59
+
60
+ end
61
+
62
+ def type_statement
63
+ [{ :subject => id,
64
+ :predicate => RDF.type,
65
+ :object => self.class.type }]
66
+ end
67
+
68
+ end # Model
69
+ end # RDFMapper
70
+