Class: RDFS::Resource

Inherits:
Object
  • Object
show all
Includes:
ResourceLike
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

Methods included from ResourceLike

#<=>, #to_ntriple, #to_s

Constructor Details

#initialize(uri) ⇒ Resource

creates new resource representing an RDF resource



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

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, &block) ⇒ Object

manages invocations such as eyal.age

Raises:



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
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
# File 'lib/active_rdf/objectmanager/resource.rb', line 230

def method_missing(method, *args,&block)
  # 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)

  ActiveRdfLogger.log_debug(self) { "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], false, &block)
    end
  end

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

    # catch the invocation on the namespace
    class <<namespace
      def method_missing(localname, *values, &block)
        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, &block)
        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,  &block)
      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
  ActiveRdfLogger::log_debug(self){ "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.



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

def class_uri
  @class_uri
end

Instance Attribute Details

#uriObject (readonly)

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



24
25
26
# File 'lib/active_rdf/objectmanager/resource.rb', line 24

def uri
  @uri
end

Class Method Details

.==(other) ⇒ Object



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

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

.find(*args) ⇒ Object



152
153
154
# File 'lib/active_rdf/objectmanager/resource.rb', line 152

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)



148
149
150
# File 'lib/active_rdf/objectmanager/resource.rb', line 148

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

.localnameObject



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

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.



131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/active_rdf/objectmanager/resource.rb', line 131

def Resource.method_missing(method, *args)
  if /find_by_(.+)/.match(method.to_s)
    ActiveRdfLogger::log_debug "Constructing dynamic finder for #{method}", self

    # 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



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

def self.uri; class_uri.uri; end

Instance Method Details

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

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



61
62
63
# File 'lib/active_rdf/objectmanager/resource.rb', line 61

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

#[](property) ⇒ Object

quick fix for “direct” getting of a property return a PropertyCollection (see PropertyCollection class)



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

def [](property)
  if !property.instance_of?(RDFS::Resource)
    property = RDFS::Resource.new(property)
  end

  plv = Query.new.distinct(:o).where(self, property, :o).execute

  # make and return new propertis collection
  PropertyList.new(property, plv, self)
end

#abbreviationObject

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

######


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

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)



407
408
409
410
411
412
413
# File 'lib/active_rdf/objectmanager/resource.rb', line 407

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



423
424
425
426
427
# File 'lib/active_rdf/objectmanager/resource.rb', line 423

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



430
431
432
433
434
435
436
# File 'lib/active_rdf/objectmanager/resource.rb', line 430

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

#####


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
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/active_rdf/objectmanager/resource.rb', line 159

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, &block) ⇒ Object



452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
# File 'lib/active_rdf/objectmanager/resource.rb', line 452

def get_predicate(predicate, flatten=false,&block)
  query = Query.new.distinct(:o).where(self, predicate, :o)
  if block_given?
    yield query, :o
  end
  values = query.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



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

def hash; uri.hash; end

#instance_of?(klass) ⇒ Boolean

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

Returns:

  • (Boolean)


417
418
419
# File 'lib/active_rdf/objectmanager/resource.rb', line 417

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

#inverseObject

Simple query shortcut which return a special object created on-the-fly by which is possible to directly obtain every distinct resources where: property ==> specified by the user by [] operator object ==> current Ruby object representing an RDF resource



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/active_rdf/objectmanager/resource.rb', line 213

def inverse
  inverseobj = Object.new
  inverseobj.instance_variable_set(:@obj_uri, self)

  class <<inverseobj     

    def [](property_uri)
      property = RDFS::Resource.new(property_uri)
      Query.new.distinct(:s).where(:s, property, @obj_uri).execute
    end
    private(:type)
  end

  return inverseobj
end

#localnameObject



205
206
207
# File 'lib/active_rdf/objectmanager/resource.rb', line 205

def localname
  Namespace.localname(self)
end

#property_accessorsObject



438
439
440
# File 'lib/active_rdf/objectmanager/resource.rb', line 438

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

#saveObject

saves instance into datastore



378
379
380
381
382
383
384
385
386
387
388
# File 'lib/active_rdf/objectmanager/resource.rb', line 378

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



446
447
448
449
450
# File 'lib/active_rdf/objectmanager/resource.rb', line 446

def set_predicate(predicate, values)
  FederationManager.delete(self, predicate, nil)
  values.flatten.each {|v| FederationManager.add(self, predicate, v) }
  values
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.



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

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