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

def initialize uri
  @uri = case uri
        when RDFS::Resource
         uri.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:



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

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



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

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

.find(*args) ⇒ Object



103
104
105
# File 'lib/active_rdf/objectmanager/resource.rb', line 103

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)



99
100
101
# File 'lib/active_rdf/objectmanager/resource.rb', line 99

def Resource.find_all(*args)
  find(:all, *args)
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.



82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/active_rdf/objectmanager/resource.rb', line 82

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)



75
76
77
78
# File 'lib/active_rdf/objectmanager/resource.rb', line 75

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

.to_sObject



375
376
377
# File 'lib/active_rdf/objectmanager/resource.rb', line 375

def Resource.to_s
  "<#{uri}>"
end

.uriObject



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

def self.uri; class_uri.uri; end

Instance Method Details

#<=>(other) ⇒ Object

overriding sort based on uri



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

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



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

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)



329
330
331
332
333
334
335
# File 'lib/active_rdf/objectmanager/resource.rb', line 329

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



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

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



352
353
354
355
356
357
358
359
360
# File 'lib/active_rdf/objectmanager/resource.rb', line 352

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

#####


110
111
112
113
114
115
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
# File 'lib/active_rdf/objectmanager/resource.rb', line 110

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



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

def hash
  uri.hash
end

#instance_of?(klass) ⇒ Boolean

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

Returns:

  • (Boolean)


339
340
341
# File 'lib/active_rdf/objectmanager/resource.rb', line 339

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

#localnameObject



156
157
158
# File 'lib/active_rdf/objectmanager/resource.rb', line 156

def localname
  Namespace.localname(self)
end

#property_accessorsObject



362
363
364
# File 'lib/active_rdf/objectmanager/resource.rb', line 362

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

#saveObject

saves instance into datastore



300
301
302
303
304
305
306
307
308
309
310
# File 'lib/active_rdf/objectmanager/resource.rb', line 300

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



371
372
373
# File 'lib/active_rdf/objectmanager/resource.rb', line 371

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.



319
320
321
322
323
# File 'lib/active_rdf/objectmanager/resource.rb', line 319

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