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



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

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:



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
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
# File 'lib/active_rdf/objectmanager/resource.rb', line 167

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)
    return predicate_invocation(@predicates[methodname], args, update, flatten)
  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, *args)
          # check if updating or reading predicate value
          if localname.to_s[-1..-1] == '='
            # set value
            predicate = Namespace.lookup(@@uri, localname.to_s[0..-2])
            args.each { |value| FederationManager.add(@@subject, predicate, value) }
          else
            # read value
            predicate = Namespace.lookup(@@uri, localname)
            Query.new.distinct(:o).where(@@subject, predicate, :o).execute(:flatten => @@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
      return predicate_invocation(pred, args, update, flatten)
    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.



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

def class_uri
  @class_uri
end

Instance Attribute Details

#uriObject (readonly)

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



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

def uri
  @uri
end

Class Method Details

.==(other) ⇒ Object



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

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

.find(*args) ⇒ Object



109
110
111
# File 'lib/active_rdf/objectmanager/resource.rb', line 109

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)



105
106
107
# File 'lib/active_rdf/objectmanager/resource.rb', line 105

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

.localnameObject



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

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.



88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/active_rdf/objectmanager/resource.rb', line 88

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)



81
82
83
84
# File 'lib/active_rdf/objectmanager/resource.rb', line 81

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

.uriObject



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

def self.uri; class_uri.uri; end

Instance Method Details

#<=>(other) ⇒ Object

overriding sort based on uri



71
72
73
# File 'lib/active_rdf/objectmanager/resource.rb', line 71

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



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

def ==(other)
  other.respond_to?(:uri) ? other.uri == self.uri : false
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)



335
336
337
338
339
340
341
# File 'lib/active_rdf/objectmanager/resource.rb', line 335

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



351
352
353
354
355
# File 'lib/active_rdf/objectmanager/resource.rb', line 351

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



358
359
360
361
362
363
364
365
366
# File 'lib/active_rdf/objectmanager/resource.rb', line 358

def direct_predicates(distinct = true)
  if distinct
    q = Query.new.distinct(:p)
  else
    q = Query.new.select(:p)
  end
  q.where(self,:p, :o).execute
  #return (direct + direct.collect {|d| ancestors(d)}).flatten.uniq
end

#find(*args) ⇒ Object

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

#####


116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/active_rdf/objectmanager/resource.rb', line 116

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

#hashObject

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



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

def hash
  uri.hash
end

#instance_of?(klass) ⇒ Boolean

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

Returns:

  • (Boolean)


345
346
347
# File 'lib/active_rdf/objectmanager/resource.rb', line 345

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

#localnameObject



162
163
164
# File 'lib/active_rdf/objectmanager/resource.rb', line 162

def localname
  Namespace.localname(self)
end

#property_accessorsObject



368
369
370
# File 'lib/active_rdf/objectmanager/resource.rb', line 368

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

#saveObject

saves instance into datastore



306
307
308
309
310
311
312
313
314
315
316
# File 'lib/active_rdf/objectmanager/resource.rb', line 306

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

#to_sObject

returns uri of resource, can be overridden in subclasses



377
378
379
# File 'lib/active_rdf/objectmanager/resource.rb', line 377

def to_s
  "<#{uri}>"
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.



325
326
327
328
329
# File 'lib/active_rdf/objectmanager/resource.rb', line 325

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