Class: RDFS::Resource

Inherits:
Object
  • Object
show all
Defined in:
lib/active_rdf/objectmanager/resource.rb

Overview

Represents an RDF resource and manages manipulations of that resource, including data lookup (e.g. eyal.age), data updates (e.g. eyal.age=20), class-level lookup (Person.find_by_name ‘eyal’), and class-membership (eyal.class …Person).

Direct Known Subclasses

BNode, VirtuosoBIF

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(uri) ⇒ Resource

creates new resource representing an RDF resource



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/active_rdf/objectmanager/resource.rb', line 24

def initialize uri
  @uri = case uri
        # allow Resource.new(other_resource)
        when RDFS::Resource
         uri.uri
        # allow Resource.new('<uri>') by stripping out <>
        when /^<([^>]*)>$/
          $1
        # allow Resource.new('uri')
        when String
          uri
        else 
          raise ActiveRdfError, "cannot create resource <#{uri}>"
        end
			@predicates = Hash.new
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *args) ⇒ Object

manages invocations such as eyal.age

Raises:



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
# File 'lib/active_rdf/objectmanager/resource.rb', line 197

def method_missing(method, *args)
    # possibilities:
    # 1. eyal.age is a property of eyal (triple exists <eyal> <age> "30")
    # evidence: eyal age ?a, ?a is not nil (only if value exists)
    # action: return ?a
    #
    # 2. eyal's class is in domain of age, but does not have value for eyal
    # explain: eyal is a person and some other person (not eyal) has an age
    # evidence: eyal type ?c, age domain ?c
    # action: return nil
    #
    # 3. eyal.age = 30 (setting a value for a property)
    # explain: eyal has (or could have) a value for age, and we update that value
    # complication: we need to find the full URI for age (by looking at
    # possible predicates to use
    # evidence: eyal age ?o  (eyal has a value for age now, we're updating it)
    # evidence: eyal type ?c, age domain ?c (eyal could have a value for age, we're setting it)
    # action: add triple (eyal, age, 30), return 30
    #
    # 4. eyal.age is a custom-written method in class Person
    # evidence: eyal type ?c, ?c.methods includes age
    # action: inject age into eyal and invoke
	#
	# 5. eyal.age is registered abbreviation 
	# evidence: age in @predicates
	# action: return object from triple (eyal, @predicates[age], ?o)
	#
	# 6. eyal.foaf::name, where foaf is a registered abbreviation
	# evidence: foaf in Namespace.
	# action: return namespace proxy that handles 'name' invocation, by 
	# rewriting into predicate lookup (similar to case (5)

    $activerdflog.debug "method_missing: #{method}"

    # are we doing an update or not? 
	# checking if method ends with '='

    update = method.to_s[-1..-1] == '='
    methodname = if update 
                   method.to_s[0..-2]
                 else
                   method.to_s
                 end

    # extract single values from array unless user asked for eyal.all_age
    flatten = true
    if method.to_s[0..3] == 'all_'
      flatten = false
      methodname = methodname[4..-1]
    end

	# check possibility (5)
	if @predicates.include?(methodname)
      if update
        return set_predicate(@predicates[methodname], args)
      else
        return get_predicate(@predicates[methodname])
      end
	end

	# check possibility (6)
	if Namespace.abbreviations.include?(methodname.to_sym)
		namespace = Object.new	
		@@uri = methodname
		@@subject = self
      @@flatten = flatten

      # catch the invocation on the namespace
      class <<namespace
        def method_missing(localname, *values)
          update = localname.to_s[-1..-1] == '='
          predicate = if update 
                        Namespace.lookup(@@uri, localname.to_s[0..-2])
                      else
                        Namespace.lookup(@@uri, localname)
                      end
          
          if update
            @@subject.set_predicate(predicate, values)
          else
            @@subject.get_predicate(predicate, @@flatten)
          end
        end
        private(:type)
      end
      return namespace
    end

    candidates = if update
                    (class_level_predicates + direct_predicates).compact.uniq
                  else
                    direct_predicates
                  end

	# checking possibility (1) and (3)
	candidates.each do |pred|
		if Namespace.localname(pred) == methodname
        if update
          return set_predicate(pred, args)
        else
          return get_predicate(pred, flatten)
        end
		end
	end
	
	raise ActiveRdfError, "could not set #{methodname} to #{args}: no suitable 
	predicate found. Maybe you are missing some schema information?" if update

	# get/set attribute value did not succeed, so checking option (2) and (4)
	
	# checking possibility (2), it is not handled correctly above since we use
	# direct_predicates instead of class_level_predicates. If we didn't find
	# anything with direct_predicates, we need to try the
	# class_level_predicates. Only if we don't find either, we
	# throw "method_missing"
	candidates = class_level_predicates

	# if any of the class_level candidates fits the sought method, then we
	# found situation (2), so we return nil or [] depending on the {:array =>
	# true} value
	if candidates.any?{|c| Namespace.localname(c) == methodname}
		return_ary = args[0][:array] if args[0].is_a? Hash
		if return_ary
			return []
		else
			return nil
		end
	end

	# checking possibility (4)
	# TODO: implement search strategy to select in which class to invoke
	# e.g. if to_s defined in Resource and in Person we should use Person
	$activerdflog.debug "RDFS::Resource: method_missing option 4: custom class method"
	self.type.each do |klass|
		if klass.instance_methods.include?(method.to_s)
			_dup = klass.new(uri)
			return _dup.send(method,*args)
		end
	end

	# if none of the three possibilities work out, we don't know this method
	# invocation, but we don't want to throw NoMethodError, instead we return
	# nil, so that eyal.age does not raise error, but returns nil. (in RDFS,
	# we are never sure that eyal cannot have an age, we just dont know the
	# age right now)
	nil
end

Class Attribute Details

.class_uriObject

Returns the value of attribute class_uri.



17
18
19
# File 'lib/active_rdf/objectmanager/resource.rb', line 17

def class_uri
  @class_uri
end

Instance Attribute Details

#uriObject (readonly)

uri of the resource (for instances of this class: rdf resources)



21
22
23
# File 'lib/active_rdf/objectmanager/resource.rb', line 21

def uri
  @uri
end

Class Method Details

.==(other) ⇒ Object



47
48
49
# File 'lib/active_rdf/objectmanager/resource.rb', line 47

def self.==(other)
  other.respond_to?(:uri) ? other.uri == self.uri : false
end

.find(*args) ⇒ Object



139
140
141
# File 'lib/active_rdf/objectmanager/resource.rb', line 139

def Resource.find(*args)
  class_uri.find(*args)
end

.find_all(*args) ⇒ Object

returns array of all instances of this class (e.g. Person.find_all) (always returns collection)



135
136
137
# File 'lib/active_rdf/objectmanager/resource.rb', line 135

def Resource.find_all(*args)
  find(:all, *args)
end

.localnameObject



50
# File 'lib/active_rdf/objectmanager/resource.rb', line 50

def self.localname; Namespace.localname(self); end

.method_missing(method, *args) ⇒ Object

manages invocations such as Person.find_by_name, Person.find_by_foaf::name, Person.find_by_foaf::name_and_foaf::knows, etc.



118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/active_rdf/objectmanager/resource.rb', line 118

def Resource.method_missing(method, *args)
  if /find_by_(.+)/.match(method.to_s)
    $activerdflog.debug "constructing dynamic finder for #{method}"

    # construct proxy to handle delayed lookups 
    # (find_by_foaf::name_and_foaf::age)
    proxy = DynamicFinderProxy.new($1, nil, *args)

    # if proxy already found a value (find_by_name) we will not get a more 
    # complex query, so return the value. Otherwise, return the proxy so that 
    # subsequent lookups are handled
    return proxy.value || proxy
  end
end

.predicatesObject

returns the predicates that have this resource as their domain (applicable predicates for this resource)



111
112
113
114
# File 'lib/active_rdf/objectmanager/resource.rb', line 111

def Resource.predicates
  domain = Namespace.lookup(:rdfs, :domain)
  Query.new.distinct(:p).where(:p, domain, class_uri).execute || []
end

.to_ntripleObject



70
# File 'lib/active_rdf/objectmanager/resource.rb', line 70

def self.to_ntriple; "<#{class_uri.uri}>"; end

.uriObject



46
# File 'lib/active_rdf/objectmanager/resource.rb', line 46

def self.uri; class_uri.uri; end

Instance Method Details

#<=>(other) ⇒ Object

overriding sort based on uri



68
# File 'lib/active_rdf/objectmanager/resource.rb', line 68

def <=>(other); uri <=> other.uri; end

#==(other) ⇒ Object Also known as: eql?, include?

a resource is same as another if they both represent the same uri



58
59
60
# File 'lib/active_rdf/objectmanager/resource.rb', line 58

def ==(other);
  other.respond_to?(:uri) ? other.uri == self.uri : false
end

#abbreviationObject

###### start of instance-level code

######


56
# File 'lib/active_rdf/objectmanager/resource.rb', line 56

def abbreviation; [Namespace.prefix(uri).to_s, localname]; end

#add_predicate(localname, fulluri) ⇒ Object

define a localname for a predicate URI

localname should be a Symbol or String, fulluri a Resource or String, e.g. add_predicate(:name, FOAF::lastName)



375
376
377
378
379
380
381
# File 'lib/active_rdf/objectmanager/resource.rb', line 375

def add_predicate localname, fulluri
	localname = localname.to_s
	fulluri = RDFS::Resource.new(fulluri) if fulluri.is_a? String

	# predicates is a hash from abbreviation string to full uri resource
	@predicates[localname] = fulluri
end

#class_level_predicatesObject

returns all predicates that fall into the domain of the rdf:type of this resource



391
392
393
394
395
# File 'lib/active_rdf/objectmanager/resource.rb', line 391

def class_level_predicates
	type = Namespace.lookup(:rdf, 'type')
	domain = Namespace.lookup(:rdfs, 'domain')
	Query.new.distinct(:p).where(self,type,:t).where(:p, domain, :t).execute || []
end

#direct_predicates(distinct = true) ⇒ Object

returns all predicates that are directly defined for this resource



398
399
400
401
402
403
404
# File 'lib/active_rdf/objectmanager/resource.rb', line 398

def direct_predicates(distinct = true)
	if distinct
		Query.new.distinct(:p).where(self, :p, :o).execute
	else
		Query.new.select(:p).where(self, :p, :o).execute
	end
end

#find(*args) ⇒ Object

##### instance level methods #####

#####


146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/active_rdf/objectmanager/resource.rb', line 146

def find(*args)
  # extract sort options from args
  options = args.last.is_a?(Hash) ? args.pop : {}

  query = Query.new.distinct(:s)
  query.where(:s, Namespace.lookup(:rdf,:type), self)

  if options.include? :order
    sort_predicate = options[:order]
    query.sort(:sort_value)
    query.where(:s, sort_predicate, :sort_value)
  end

  if options.include? :reverse_order
    sort_predicate = options[:reverse_order]
    query.reverse_sort(:sort_value)
    query.where(:s, sort_predicate, :sort_value)
  end

  if options.include? :where
    raise ActiveRdfError, "where clause should be hash of predicate => object" unless options[:where].is_a? Hash
    options[:where].each do |p,o|
      if options.include? :context
        query.where(:s, p, o, options[:context])
      else
        query.where(:s, p, o)
      end
    end
  else
    if options[:context]
      query.where(:s, :p, :o, options[:context])
    end
  end

  query.limit(options[:limit]) if options[:limit]
  query.offset(options[:offset]) if options[:offset]

  if block_given?
    query.execute do |resource|
      yield resource
    end
  else
    query.execute(:flatten => false)
  end
end

#get_predicate(predicate, flatten = false) ⇒ Object



425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
# File 'lib/active_rdf/objectmanager/resource.rb', line 425

def get_predicate(predicate, flatten=false)
  values = Query.new.distinct(:o).where(self, predicate, :o).execute(:flatten => flatten)

  # TODO: fix '<<' for Fixnum values etc (we cannot use values.instance_eval 
  # because Fixnum cannot do instace_eval, they're not normal classes)
  if values.is_a?(RDFS::Resource) and !values.nil?
    # prepare returned values for accepting << later, eg. in
    # eyal.foaf::knows << knud
    #
    # store @subject, @predicate in returned values
    values.instance_exec(self, predicate) do |s,p|
      @subj = s
      @pred = p
    end

    # overwrite << to add triple to db
    values.instance_eval do
      def <<(value)
        FederationManager.add(@subj, @pred, value)
      end
    end
  end

  values
end

#hashObject

overriding hash to use uri.hash needed for array.uniq



65
# File 'lib/active_rdf/objectmanager/resource.rb', line 65

def hash; uri.hash; end

#instance_of?(klass) ⇒ Boolean

overrides built-in instance_of? to use rdf:type definitions

Returns:

  • (Boolean)


385
386
387
# File 'lib/active_rdf/objectmanager/resource.rb', line 385

def instance_of?(klass)
	self.type.include?(klass)
end

#localnameObject



192
193
194
# File 'lib/active_rdf/objectmanager/resource.rb', line 192

def localname
  Namespace.localname(self)
end

#property_accessorsObject



406
407
408
# File 'lib/active_rdf/objectmanager/resource.rb', line 406

def property_accessors
	direct_predicates.collect {|pred| Namespace.localname(pred) }
end

#saveObject

saves instance into datastore



346
347
348
349
350
351
352
353
354
355
356
# File 'lib/active_rdf/objectmanager/resource.rb', line 346

def save
	db = ConnectionPool.write_adapter
	rdftype = Namespace.lookup(:rdf, :type)
	types.each do |t|
		db.add(self, rdftype, t)
	end

	Query.new.distinct(:p,:o).where(self, :p, :o).execute do |p, o|
		db.add(self, p, o)
	end
end

#set_predicate(predicate, values) ⇒ Object



419
420
421
422
423
# File 'lib/active_rdf/objectmanager/resource.rb', line 419

def set_predicate(predicate, values)
  FederationManager.delete(self, predicate)
  values.flatten.each {|v| FederationManager.add(self, predicate, v) }
  values
end

#to_ntripleObject



69
# File 'lib/active_rdf/objectmanager/resource.rb', line 69

def to_ntriple; "<#{uri}>"; end

#to_sObject

returns uri of resource, can be overridden in subclasses



415
416
417
# File 'lib/active_rdf/objectmanager/resource.rb', line 415

def to_s
	"<#{uri}>"
end

#to_xmlObject



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/active_rdf/objectmanager/resource.rb', line 72

def to_xml
  base = Namespace.expand(Namespace.prefix(self),'').chop

  xml = "<?xml version=\"1.0\"?>\n"
  xml += "<rdf:RDF xmlns=\"#{base}\#\"\n"
  Namespace.abbreviations.each { |p| uri = Namespace.expand(p,''); xml += "  xmlns:#{p.to_s}=\"#{uri}\"\n" if uri != base + '#' }
  xml += "  xml:base=\"#{base}\">\n"

  xml += "<rdf:Description rdf:about=\"\##{localname}\">\n"
  direct_predicates.each do |p|
    objects = Query.new.distinct(:o).where(self, p, :o).execute
    objects.each do |obj|
      prefix, localname = Namespace.prefix(p), Namespace.localname(p)
      pred_xml = if prefix
                   "%s:%s" % [prefix, localname]
                 else
                   p.uri
                 end

      case obj
      when RDFS::Resource
        xml += "  <#{pred_xml} rdf:resource=\"#{obj.uri}\"/>\n"
      when LocalizedString
        xml += "  <#{pred_xml} xml:lang=\"#{obj.lang}\">#{obj}</#{pred_xml}>\n"
      else
        xml += "  <#{pred_xml} rdf:datatype=\"#{obj.xsd_type.uri}\">#{obj}</#{pred_xml}>\n"
      end
    end
  end
  xml += "</rdf:Description>\n"
  xml += "</rdf:RDF>"
end

#typeObject

returns all rdf:type of this instance, e.g. [RDFS::Resource, FOAF::Person]

Note: this method performs a database lookup for { self rdf:type ?o }. For simple type-checking (to know if you are handling an ActiveRDF object, use self.class, which does not do a database query, but simply returns RDFS::Resource.



365
366
367
368
369
# File 'lib/active_rdf/objectmanager/resource.rb', line 365

def type
	types.collect do |type|
		ObjectManager.construct_class(type)
	end
end