| 1 | require 'active_rdf' |
|---|
| 2 | require 'objectmanager/object_manager' |
|---|
| 3 | require 'objectmanager/namespace' |
|---|
| 4 | require 'queryengine/query' |
|---|
| 5 | require 'instance_exec' |
|---|
| 6 | |
|---|
| 7 | module RDFS |
|---|
| 8 | |
|---|
| 9 | |
|---|
| 10 | # Represents an RDF resource and manages manipulations of that resource, |
|---|
| 11 | # including data lookup (e.g. eyal.age), data updates (e.g. eyal.age=20), |
|---|
| 12 | # class-level lookup (Person.find_by_name 'eyal'), and class-membership |
|---|
| 13 | # (eyal.class ...Person). |
|---|
| 14 | |
|---|
| 15 | class RDFS::Resource |
|---|
| 16 | # adding accessor to the class uri: |
|---|
| 17 | # the uri of the rdf resource being represented by this class |
|---|
| 18 | class << self |
|---|
| 19 | attr_accessor :class_uri |
|---|
| 20 | def reset_cache() |
|---|
| 21 | $triple = Hash.new |
|---|
| 22 | $all_predicates_array =nil |
|---|
| 23 | RDFS::Resource.find_all_predicates() |
|---|
| 24 | end |
|---|
| 25 | end |
|---|
| 26 | |
|---|
| 27 | # uri of the resource (for instances of this class: rdf resources) |
|---|
| 28 | attr_reader :uri |
|---|
| 29 | $triple |
|---|
| 30 | # creates new resource representing an RDF resource |
|---|
| 31 | def initialize uri |
|---|
| 32 | @uri = case uri |
|---|
| 33 | # allow Resource.new(other_resource) |
|---|
| 34 | when RDFS::Resource || BNode |
|---|
| 35 | uri.uri |
|---|
| 36 | # allow Resource.new('<uri>') by stripping out <> |
|---|
| 37 | when /^<([^>]*)>$/ |
|---|
| 38 | $1 |
|---|
| 39 | # allow Resource.new('uri') |
|---|
| 40 | when String |
|---|
| 41 | uri |
|---|
| 42 | else |
|---|
| 43 | raise ActiveRdfError, "cannot create resource <#{uri}>" |
|---|
| 44 | end |
|---|
| 45 | $triple = Hash.new if $triple == nil |
|---|
| 46 | @predicates = Hash.new |
|---|
| 47 | end |
|---|
| 48 | |
|---|
| 49 | # setting our own class uri to rdfs:resource |
|---|
| 50 | # (has to be done after defining our RDFS::Resource.new |
|---|
| 51 | # because it cannot be found in Namespace.lookup otherwise) |
|---|
| 52 | self.class_uri = Namespace.lookup(:rdfs, :Resource) |
|---|
| 53 | |
|---|
| 54 | def self.uri; class_uri.uri; end |
|---|
| 55 | def self.==(other) |
|---|
| 56 | other.respond_to?(:uri) ? other.uri == self.uri : false |
|---|
| 57 | end |
|---|
| 58 | def self.localname; Namespace.localname(self); end |
|---|
| 59 | |
|---|
| 60 | ##### ###### |
|---|
| 61 | ##### start of instance-level code |
|---|
| 62 | ##### ###### |
|---|
| 63 | |
|---|
| 64 | def abbreviation; [Namespace.prefix(uri).to_s, localname]; end |
|---|
| 65 | # a resource is same as another if they both represent the same uri |
|---|
| 66 | def ==(other); |
|---|
| 67 | other.respond_to?(:uri) ? other.uri == self.uri : false |
|---|
| 68 | end |
|---|
| 69 | alias_method 'eql?','==' |
|---|
| 70 | |
|---|
| 71 | # overriding hash to use uri.hash |
|---|
| 72 | # needed for array.uniq |
|---|
| 73 | def hash; uri.hash; end |
|---|
| 74 | |
|---|
| 75 | # overriding sort based on uri |
|---|
| 76 | def <=>(other); uri <=> other.uri; end |
|---|
| 77 | def to_ntriple; to_s; end |
|---|
| 78 | def self.to_ntriple; "<#{class_uri.uri}>"; end |
|---|
| 79 | |
|---|
| 80 | def to_xml |
|---|
| 81 | base = Namespace.expand(Namespace.prefix(self),'').chop |
|---|
| 82 | |
|---|
| 83 | xml = "<?xml version=\"1.0\"?>\n" |
|---|
| 84 | xml += "<rdf:RDF xmlns=\"#{base}\#\"\n" |
|---|
| 85 | Namespace.abbreviations.each { |p| uri = Namespace.expand(p,''); xml += " xmlns:#{p.to_s}=\"#{uri}\"\n" if uri != base + '#' } |
|---|
| 86 | xml += " xml:base=\"#{base}\">\n" |
|---|
| 87 | |
|---|
| 88 | xml += "<rdf:Description rdf:about=\"\##{localname}\">\n" |
|---|
| 89 | direct_predicates.each do |p| |
|---|
| 90 | objects = Query.new.distinct(:o).where(self, p, :o).execute |
|---|
| 91 | objects.each do |obj| |
|---|
| 92 | prefix, localname = Namespace.prefix(p), Namespace.localname(p) |
|---|
| 93 | pred_xml = if prefix |
|---|
| 94 | "%s:%s" % [prefix, localname] |
|---|
| 95 | else |
|---|
| 96 | p.uri |
|---|
| 97 | end |
|---|
| 98 | |
|---|
| 99 | case obj |
|---|
| 100 | when RDFS::Resource |
|---|
| 101 | xml += " <#{pred_xml} rdf:resource=\"#{obj.uri}\"/>\n" |
|---|
| 102 | when LocalizedString |
|---|
| 103 | xml += " <#{pred_xml} xml:lang=\"#{obj.lang}\">#{obj}</#{pred_xml}>\n" |
|---|
| 104 | else |
|---|
| 105 | xml += " <#{pred_xml} rdf:datatype=\"#{obj.xsd_type.uri}\">#{obj}</#{pred_xml}>\n" |
|---|
| 106 | end |
|---|
| 107 | end |
|---|
| 108 | end |
|---|
| 109 | xml += "</rdf:Description>\n" |
|---|
| 110 | xml += "</rdf:RDF>" |
|---|
| 111 | end |
|---|
| 112 | |
|---|
| 113 | ##### ##### |
|---|
| 114 | ##### class level methods ##### |
|---|
| 115 | ##### ##### |
|---|
| 116 | |
|---|
| 117 | # returns the predicates that have this resource as their domain (applicable |
|---|
| 118 | # predicates for this resource) |
|---|
| 119 | def Resource.predicates |
|---|
| 120 | domain = Namespace.lookup(:rdfs, :domain) |
|---|
| 121 | Query.new.distinct(:p).where(:p, domain, class_uri).execute || [] |
|---|
| 122 | end |
|---|
| 123 | |
|---|
| 124 | # manages invocations such as Person.find_by_name, |
|---|
| 125 | # Person.find_by_foaf::name, Person.find_by_foaf::name_and_foaf::knows, etc. |
|---|
| 126 | def Resource.method_missing(method, *args) |
|---|
| 127 | if /find_by_(.+)/.match(method.to_s) |
|---|
| 128 | $activerdflog.debug "constructing dynamic finder for #{method}" |
|---|
| 129 | |
|---|
| 130 | # construct proxy to handle delayed lookups |
|---|
| 131 | # (find_by_foaf::name_and_foaf::age) |
|---|
| 132 | proxy = DynamicFinderProxy.new($1, nil, *args) |
|---|
| 133 | |
|---|
| 134 | # if proxy already found a value (find_by_name) we will not get a more |
|---|
| 135 | # complex query, so return the value. Otherwise, return the proxy so that |
|---|
| 136 | # subsequent lookups are handled |
|---|
| 137 | return proxy.value || proxy |
|---|
| 138 | end |
|---|
| 139 | end |
|---|
| 140 | |
|---|
| 141 | # returns array of all instances of this class (e.g. Person.find_all) |
|---|
| 142 | # (always returns collection) |
|---|
| 143 | def Resource.find_all(*args) |
|---|
| 144 | find(:all, *args) |
|---|
| 145 | end |
|---|
| 146 | |
|---|
| 147 | def Resource.find(*args) |
|---|
| 148 | class_uri.find(*args) |
|---|
| 149 | end |
|---|
| 150 | |
|---|
| 151 | ##### ##### |
|---|
| 152 | ##### instance level methods ##### |
|---|
| 153 | ##### ##### |
|---|
| 154 | def find(*args) |
|---|
| 155 | # extract sort options from args |
|---|
| 156 | options = args.last.is_a?(Hash) ? args.pop : {} |
|---|
| 157 | |
|---|
| 158 | query = Query.new.distinct(:s) |
|---|
| 159 | query.where(:s, Namespace.lookup(:rdf,:type), self) |
|---|
| 160 | |
|---|
| 161 | if options.include? :order |
|---|
| 162 | sort_predicate = options[:order] |
|---|
| 163 | query.sort(:sort_value) |
|---|
| 164 | query.where(:s, sort_predicate, :sort_value) |
|---|
| 165 | end |
|---|
| 166 | |
|---|
| 167 | if options.include? :reverse_order |
|---|
| 168 | sort_predicate = options[:reverse_order] |
|---|
| 169 | query.reverse_sort(:sort_value) |
|---|
| 170 | query.where(:s, sort_predicate, :sort_value) |
|---|
| 171 | end |
|---|
| 172 | |
|---|
| 173 | if options.include? :where |
|---|
| 174 | raise ActiveRdfError, "where clause should be hash of predicate => object" unless options[:where].is_a? Hash |
|---|
| 175 | options[:where].each do |p,o| |
|---|
| 176 | if options.include? :context |
|---|
| 177 | query.where(:s, p, o, options[:context]) |
|---|
| 178 | else |
|---|
| 179 | query.where(:s, p, o) |
|---|
| 180 | end |
|---|
| 181 | end |
|---|
| 182 | else |
|---|
| 183 | if options[:context] |
|---|
| 184 | query.where(:s, :p, :o, options[:context]) |
|---|
| 185 | end |
|---|
| 186 | end |
|---|
| 187 | |
|---|
| 188 | query.limit(options[:limit]) if options[:limit] |
|---|
| 189 | query.offset(options[:offset]) if options[:offset] |
|---|
| 190 | |
|---|
| 191 | if block_given? |
|---|
| 192 | query.execute do |resource| |
|---|
| 193 | yield resource |
|---|
| 194 | end |
|---|
| 195 | else |
|---|
| 196 | query.execute(:flatten => false) |
|---|
| 197 | end |
|---|
| 198 | end |
|---|
| 199 | def self.find_all_predicates |
|---|
| 200 | |
|---|
| 201 | $all_predicates_array = Query.new.distinct(:s).where(:s,Namespace.lookup(:rdf,:type),Namespace.lookup(:rdf,:Property)).execute if $all_predicates_array == nil |
|---|
| 202 | end |
|---|
| 203 | def localname |
|---|
| 204 | Namespace.localname(self) |
|---|
| 205 | end |
|---|
| 206 | |
|---|
| 207 | # manages invocations such as eyal.age |
|---|
| 208 | def method_missing(method, *args) |
|---|
| 209 | # possibilities: |
|---|
| 210 | # 1. eyal.age is a property of eyal (triple exists <eyal> <age> "30") |
|---|
| 211 | # evidence: eyal age ?a, ?a is not nil (only if value exists) |
|---|
| 212 | # action: return ?a |
|---|
| 213 | # |
|---|
| 214 | # 2. eyal's class is in domain of age, but does not have value for eyal |
|---|
| 215 | # explain: eyal is a person and some other person (not eyal) has an age |
|---|
| 216 | # evidence: eyal type ?c, age domain ?c |
|---|
| 217 | # action: return nil |
|---|
| 218 | # |
|---|
| 219 | # 3. eyal.age = 30 (setting a value for a property) |
|---|
| 220 | # explain: eyal has (or could have) a value for age, and we update that value |
|---|
| 221 | # complication: we need to find the full URI for age (by looking at |
|---|
| 222 | # possible predicates to use |
|---|
| 223 | # evidence: eyal age ?o (eyal has a value for age now, we're updating it) |
|---|
| 224 | # evidence: eyal type ?c, age domain ?c (eyal could have a value for age, we're setting it) |
|---|
| 225 | # action: add triple (eyal, age, 30), return 30 |
|---|
| 226 | # |
|---|
| 227 | # 4. eyal.age is a custom-written method in class Person |
|---|
| 228 | # evidence: eyal type ?c, ?c.methods includes age |
|---|
| 229 | # action: inject age into eyal and invoke |
|---|
| 230 | # |
|---|
| 231 | # 5. eyal.age is registered abbreviation |
|---|
| 232 | # evidence: age in @predicates |
|---|
| 233 | # action: return object from triple (eyal, @predicates[age], ?o) |
|---|
| 234 | # |
|---|
| 235 | # 6. eyal.foaf::name, where foaf is a registered abbreviation |
|---|
| 236 | # evidence: foaf in Namespace. |
|---|
| 237 | # action: return namespace proxy that handles 'name' invocation, by |
|---|
| 238 | # rewriting into predicate lookup (similar to case (5) |
|---|
| 239 | |
|---|
| 240 | $activerdflog.debug "method_missing: #{method}" |
|---|
| 241 | |
|---|
| 242 | |
|---|
| 243 | # are we doing an update or not? |
|---|
| 244 | # checking if method ends with '=' |
|---|
| 245 | |
|---|
| 246 | update = method.to_s[-1..-1] == '=' |
|---|
| 247 | methodname = if update |
|---|
| 248 | method.to_s[0..-2] |
|---|
| 249 | else |
|---|
| 250 | method.to_s |
|---|
| 251 | end |
|---|
| 252 | |
|---|
| 253 | # extract single values from array unless user asked for eyal.all_age |
|---|
| 254 | flatten = true |
|---|
| 255 | if method.to_s[0..3] == 'all_' |
|---|
| 256 | flatten = false |
|---|
| 257 | methodname = methodname[4..-1] |
|---|
| 258 | end |
|---|
| 259 | |
|---|
| 260 | # check possibility (5) |
|---|
| 261 | if @predicates.include?(methodname) |
|---|
| 262 | if update |
|---|
| 263 | return set_predicate(@predicates[methodname], args) |
|---|
| 264 | else |
|---|
| 265 | return get_predicate(@predicates[methodname]) |
|---|
| 266 | end |
|---|
| 267 | end |
|---|
| 268 | |
|---|
| 269 | # check possibility (6) |
|---|
| 270 | if Namespace.abbreviations.include?(methodname.to_sym) |
|---|
| 271 | namespace = Object.new |
|---|
| 272 | @@uri = methodname |
|---|
| 273 | @@subject = self |
|---|
| 274 | @@flatten = flatten |
|---|
| 275 | |
|---|
| 276 | # catch the invocation on the namespace |
|---|
| 277 | class <<namespace |
|---|
| 278 | def method_missing(localname, *values) |
|---|
| 279 | update = localname.to_s[-1..-1] == '=' |
|---|
| 280 | predicate = if update |
|---|
| 281 | Namespace.lookup(@@uri, localname.to_s[0..-2]) |
|---|
| 282 | else |
|---|
| 283 | Namespace.lookup(@@uri, localname) |
|---|
| 284 | end |
|---|
| 285 | |
|---|
| 286 | if update |
|---|
| 287 | @@subject.set_predicate(predicate, values) |
|---|
| 288 | else |
|---|
| 289 | @@subject.get_predicate(predicate, @@flatten) |
|---|
| 290 | end |
|---|
| 291 | end |
|---|
| 292 | private(:type) |
|---|
| 293 | end |
|---|
| 294 | return namespace |
|---|
| 295 | end |
|---|
| 296 | |
|---|
| 297 | $all_predicates_array.each do |pred| |
|---|
| 298 | if Namespace.localname(pred) == methodname |
|---|
| 299 | if update |
|---|
| 300 | return set_predicate(pred, args) |
|---|
| 301 | else |
|---|
| 302 | return get_predicate(pred, flatten) |
|---|
| 303 | end |
|---|
| 304 | end |
|---|
| 305 | end |
|---|
| 306 | |
|---|
| 307 | candidates = if update |
|---|
| 308 | (class_level_predicates + direct_predicates).compact.uniq |
|---|
| 309 | else |
|---|
| 310 | direct_predicates |
|---|
| 311 | end |
|---|
| 312 | |
|---|
| 313 | # checking possibility (1) and (3) |
|---|
| 314 | candidates.each do |pred| |
|---|
| 315 | if Namespace.localname(pred) == methodname |
|---|
| 316 | if update |
|---|
| 317 | return set_predicate(pred, args) |
|---|
| 318 | else |
|---|
| 319 | return get_predicate(pred, flatten) |
|---|
| 320 | end |
|---|
| 321 | end |
|---|
| 322 | end |
|---|
| 323 | |
|---|
| 324 | raise ActiveRdfError, "could not set #{methodname} to #{args}: no suitable |
|---|
| 325 | predicate found. Maybe you are missing some schema information?" if update |
|---|
| 326 | |
|---|
| 327 | # get/set attribute value did not succeed, so checking option (2) and (4) |
|---|
| 328 | |
|---|
| 329 | # checking possibility (2), it is not handled correctly above since we use |
|---|
| 330 | # direct_predicates instead of class_level_predicates. If we didn't find |
|---|
| 331 | # anything with direct_predicates, we need to try the |
|---|
| 332 | # class_level_predicates. Only if we don't find either, we |
|---|
| 333 | # throw "method_missing" |
|---|
| 334 | candidates = class_level_predicates |
|---|
| 335 | |
|---|
| 336 | # if any of the class_level candidates fits the sought method, then we |
|---|
| 337 | # found situation (2), so we return nil or [] depending on the {:array => |
|---|
| 338 | # true} value |
|---|
| 339 | if candidates.any?{|c| Namespace.localname(c) == methodname} |
|---|
| 340 | return_ary = args[0][:array] if args[0].is_a? Hash |
|---|
| 341 | if return_ary |
|---|
| 342 | return [] |
|---|
| 343 | else |
|---|
| 344 | return nil |
|---|
| 345 | end |
|---|
| 346 | end |
|---|
| 347 | |
|---|
| 348 | # checking possibility (4) |
|---|
| 349 | # TODO: implement search strategy to select in which class to invoke |
|---|
| 350 | # e.g. if to_s defined in Resource and in Person we should use Person |
|---|
| 351 | $activerdflog.debug "RDFS::Resource: method_missing option 4: custom class method" |
|---|
| 352 | self.type.each do |klass| |
|---|
| 353 | if klass.instance_methods.include?(method.to_s) |
|---|
| 354 | _dup = klass.new(uri) |
|---|
| 355 | return _dup.send(method,*args) |
|---|
| 356 | end |
|---|
| 357 | end |
|---|
| 358 | |
|---|
| 359 | # if none of the three possibilities work out, we don't know this method |
|---|
| 360 | # invocation, but we don't want to throw NoMethodError, instead we return |
|---|
| 361 | # nil, so that eyal.age does not raise error, but returns nil. (in RDFS, |
|---|
| 362 | # we are never sure that eyal cannot have an age, we just dont know the |
|---|
| 363 | # age right now) |
|---|
| 364 | nil |
|---|
| 365 | end |
|---|
| 366 | |
|---|
| 367 | # saves instance into datastore |
|---|
| 368 | def save |
|---|
| 369 | db = ConnectionPool.write_adapter |
|---|
| 370 | rdftype = Namespace.lookup(:rdf, :type) |
|---|
| 371 | types.each do |t| |
|---|
| 372 | db.add(self, rdftype, t) |
|---|
| 373 | end |
|---|
| 374 | |
|---|
| 375 | Query.new.distinct(:p,:o).where(self, :p, :o).execute do |p, o| |
|---|
| 376 | db.add(self, p, o) |
|---|
| 377 | end |
|---|
| 378 | end |
|---|
| 379 | |
|---|
| 380 | # returns all rdf:type of this instance, e.g. [RDFS::Resource, |
|---|
| 381 | # FOAF::Person] |
|---|
| 382 | # |
|---|
| 383 | # Note: this method performs a database lookup for { self rdf:type ?o }. For |
|---|
| 384 | # simple type-checking (to know if you are handling an ActiveRDF object, use |
|---|
| 385 | # self.class, which does not do a database query, but simply returns |
|---|
| 386 | # RDFS::Resource. |
|---|
| 387 | def type |
|---|
| 388 | types.collect do |type| |
|---|
| 389 | ObjectManager.construct_class(type) |
|---|
| 390 | end |
|---|
| 391 | end |
|---|
| 392 | |
|---|
| 393 | # define a localname for a predicate URI |
|---|
| 394 | # |
|---|
| 395 | # localname should be a Symbol or String, fulluri a Resource or String, e.g. |
|---|
| 396 | # add_predicate(:name, FOAF::lastName) |
|---|
| 397 | def add_predicate localname, fulluri |
|---|
| 398 | localname = localname.to_s |
|---|
| 399 | fulluri = RDFS::Resource.new(fulluri) if fulluri.is_a? String |
|---|
| 400 | |
|---|
| 401 | # predicates is a hash from abbreviation string to full uri resource |
|---|
| 402 | @predicates[localname] = fulluri |
|---|
| 403 | end |
|---|
| 404 | |
|---|
| 405 | |
|---|
| 406 | # overrides built-in instance_of? to use rdf:type definitions |
|---|
| 407 | def instance_of?(klass) |
|---|
| 408 | |
|---|
| 409 | if klass.to_s == BNode.to_s || self.class.to_s == BNode.to_s |
|---|
| 410 | |
|---|
| 411 | return (klass.to_s == self.class.to_s) ? true : false |
|---|
| 412 | else |
|---|
| 413 | self.type.include?(klass) |
|---|
| 414 | end |
|---|
| 415 | end |
|---|
| 416 | |
|---|
| 417 | # returns all predicates that fall into the domain of the rdf:type of this |
|---|
| 418 | # resource |
|---|
| 419 | def class_level_predicates |
|---|
| 420 | type = Namespace.lookup(:rdf, 'type') |
|---|
| 421 | domain = Namespace.lookup(:rdfs, 'domain') |
|---|
| 422 | result = Query.new.distinct(:p).where(self,type,:t).where(:p, domain, :t).execute || [] |
|---|
| 423 | $all_predicates_array = $all_predicates_array | result |
|---|
| 424 | result |
|---|
| 425 | end |
|---|
| 426 | |
|---|
| 427 | # returns all predicates that are directly defined for this resource |
|---|
| 428 | def direct_predicates(distinct = true) |
|---|
| 429 | query = nil |
|---|
| 430 | if distinct |
|---|
| 431 | query = Query.new.distinct(:p).where(self, :p, :o) |
|---|
| 432 | else |
|---|
| 433 | query = Query.new.select(:p).where(self, :p, :o) |
|---|
| 434 | end |
|---|
| 435 | result = query.execute |
|---|
| 436 | $all_predicates_array = $all_predicates_array | result |
|---|
| 437 | result |
|---|
| 438 | end |
|---|
| 439 | |
|---|
| 440 | def property_accessors |
|---|
| 441 | direct_predicates.collect {|pred| Namespace.localname(pred) } |
|---|
| 442 | end |
|---|
| 443 | |
|---|
| 444 | # alias include? to ==, so that you can do paper.creator.include?(eyal) |
|---|
| 445 | # without worrying whether paper.creator is single- or multi-valued |
|---|
| 446 | alias include? == |
|---|
| 447 | |
|---|
| 448 | # returns uri of resource, can be overridden in subclasses |
|---|
| 449 | def to_s |
|---|
| 450 | "<#{uri}>" |
|---|
| 451 | end |
|---|
| 452 | |
|---|
| 453 | def set_predicate(predicate, values) |
|---|
| 454 | FederationManager.delete(self, predicate) |
|---|
| 455 | values.flatten.each {|v| FederationManager.add(self, predicate, v) } |
|---|
| 456 | #clear cache |
|---|
| 457 | $triple[self.uri]=nil |
|---|
| 458 | values |
|---|
| 459 | end |
|---|
| 460 | def cache(flatten=false) |
|---|
| 461 | tuple = Hash.new |
|---|
| 462 | properties = Query.new.distinct(:p,:o).where(self, :p, :o).execute(:flatten => flatten) |
|---|
| 463 | if properties == nil |
|---|
| 464 | $triple[self.uri]=tuple |
|---|
| 465 | return |
|---|
| 466 | end |
|---|
| 467 | properties.each do |p,o| |
|---|
| 468 | tuple[p] = Array.new if tuple[p] == nil |
|---|
| 469 | tuple[p] << o |
|---|
| 470 | end |
|---|
| 471 | $triple[self.uri]=tuple |
|---|
| 472 | end |
|---|
| 473 | def get_properties |
|---|
| 474 | cache(true) if $triple[self.uri] == nil |
|---|
| 475 | $triple[self.uri] |
|---|
| 476 | end |
|---|
| 477 | def get_predicate(predicate, flatten=false) |
|---|
| 478 | |
|---|
| 479 | cache(flatten) if $triple[self.uri] == nil |
|---|
| 480 | #original code |
|---|
| 481 | # values = Query.new.distinct(:o).where(self, predicate, :o).execute(:flatten => flatten) |
|---|
| 482 | values = $triple[self.uri][predicate]!= nil && $triple[self.uri][predicate].size == 1 && flatten ? $triple[self.uri][predicate].first : $triple[self.uri][predicate] |
|---|
| 483 | |
|---|
| 484 | values = Array.new if values == nil && flatten==false |
|---|
| 485 | |
|---|
| 486 | # TODO: fix '<<' for Fixnum values etc (we cannot use values.instance_eval |
|---|
| 487 | # because Fixnum cannot do instace_eval, they're not normal classes) |
|---|
| 488 | if values.is_a?(RDFS::Resource) and !values.nil? |
|---|
| 489 | # prepare returned values for accepting << later, eg. in |
|---|
| 490 | # eyal.foaf::knows << knud |
|---|
| 491 | # |
|---|
| 492 | # store @subject, @predicate in returned values |
|---|
| 493 | values.instance_exec(self, predicate) do |s,p| |
|---|
| 494 | @subj = s |
|---|
| 495 | @pred = p |
|---|
| 496 | end |
|---|
| 497 | |
|---|
| 498 | # overwrite << to add triple to db |
|---|
| 499 | values.instance_eval do |
|---|
| 500 | def <<(value) |
|---|
| 501 | FederationManager.add(@subj, @pred, value) |
|---|
| 502 | end |
|---|
| 503 | end |
|---|
| 504 | end |
|---|
| 505 | |
|---|
| 506 | values |
|---|
| 507 | end |
|---|
| 508 | |
|---|
| 509 | private |
|---|
| 510 | # def ancestors(predicate) |
|---|
| 511 | # subproperty = Namespace.lookup(:rdfs,:subPropertyOf) |
|---|
| 512 | # Query.new.distinct(:p).where(predicate, subproperty, :p).execute |
|---|
| 513 | # end |
|---|
| 514 | |
|---|
| 515 | |
|---|
| 516 | |
|---|
| 517 | # returns all rdf:types of this resource but without a conversion to |
|---|
| 518 | # Ruby classes (it returns an array of RDFS::Resources) |
|---|
| 519 | def types |
|---|
| 520 | type = Namespace.lookup(:rdf, :type) |
|---|
| 521 | |
|---|
| 522 | # we lookup the type in the database |
|---|
| 523 | #types = Query.new.distinct(:t).where(self,type,:t).execute |
|---|
| 524 | types = get_predicate(type,false) |
|---|
| 525 | |
|---|
| 526 | # we are also always of type rdfs:resource and of our own class (e.g. foaf:Person) |
|---|
| 527 | defaults = [] |
|---|
| 528 | defaults << Namespace.lookup(:rdfs,:Resource) |
|---|
| 529 | defaults << self.class.class_uri |
|---|
| 530 | |
|---|
| 531 | (types + defaults).uniq |
|---|
| 532 | end |
|---|
| 533 | end |
|---|
| 534 | end |
|---|
| 535 | |
|---|
| 536 | # proxy to manage find_by_ invocations |
|---|
| 537 | class DynamicFinderProxy |
|---|
| 538 | @ns = nil |
|---|
| 539 | @where = nil |
|---|
| 540 | @value = nil |
|---|
| 541 | attr_reader :value |
|---|
| 542 | private(:type) |
|---|
| 543 | |
|---|
| 544 | # construct proxy from find_by text |
|---|
| 545 | # foaf::name |
|---|
| 546 | def initialize(find_string, where, *args) |
|---|
| 547 | @where = where || [] |
|---|
| 548 | parse_attributes(find_string, *args) |
|---|
| 549 | end |
|---|
| 550 | |
|---|
| 551 | def method_missing(method, *args) |
|---|
| 552 | # we store the ns:name for later (we need to wait until we have the |
|---|
| 553 | # arguments before actually constructing the where clause): now we just |
|---|
| 554 | # store that a where clause should appear about foaf:name |
|---|
| 555 | |
|---|
| 556 | # if this method is called name_and_foaf::age we add ourself to the query |
|---|
| 557 | # otherwise, the query is built: we execute it and return the results |
|---|
| 558 | if method.to_s.include?('_and_') |
|---|
| 559 | parse_attributes(method.to_s, *args) |
|---|
| 560 | else |
|---|
| 561 | @where << Namespace.lookup(@ns, method.to_s) |
|---|
| 562 | query(*args) |
|---|
| 563 | end |
|---|
| 564 | end |
|---|
| 565 | |
|---|
| 566 | private |
|---|
| 567 | # split find_string by occurrences of _and_ |
|---|
| 568 | def parse_attributes string, *args |
|---|
| 569 | attributes = string.split('_and_') |
|---|
| 570 | attributes.each do |atr| |
|---|
| 571 | # attribute can be: |
|---|
| 572 | # - a namespace prefix (foaf): store prefix in @ns to prepare for method_missing |
|---|
| 573 | # - name (attribute name): store in where to prepare for method_missing |
|---|
| 574 | if Namespace.abbreviations.include?(atr.to_sym) |
|---|
| 575 | @ns = atr.to_s.downcase.to_sym |
|---|
| 576 | else |
|---|
| 577 | # found simple attribute label, e.g. 'name' |
|---|
| 578 | # find out candidate (full) predicate for this localname: investigate |
|---|
| 579 | # all possible predicates and select first one with matching localname |
|---|
| 580 | candidates = Query.new.distinct(:p).where(:s,:p,:o).execute |
|---|
| 581 | @where << candidates.select {|cand| Namespace.localname(cand) == atr}.first |
|---|
| 582 | end |
|---|
| 583 | end |
|---|
| 584 | |
|---|
| 585 | # if the last attribute was a prefix, return this dynamic finder (we'll |
|---|
| 586 | # catch the following method_missing and construct the real query then) |
|---|
| 587 | # if the last attribute was a localname, construct the query now and return |
|---|
| 588 | # the results |
|---|
| 589 | if Namespace.abbreviations.include?(attributes.last.to_sym) |
|---|
| 590 | return self |
|---|
| 591 | else |
|---|
| 592 | return query(*args) |
|---|
| 593 | end |
|---|
| 594 | end |
|---|
| 595 | |
|---|
| 596 | # construct and execute finder query |
|---|
| 597 | def query(*args) |
|---|
| 598 | # extract options from args or use an empty hash (no options given) |
|---|
| 599 | options = args.last.is_a?(Hash) ? args.last : {} |
|---|
| 600 | |
|---|
| 601 | # build query |
|---|
| 602 | query = Query.new.distinct(:s) |
|---|
| 603 | @where.each_with_index do |predicate, i| |
|---|
| 604 | # specify where clauses, use context if given |
|---|
| 605 | if options[:context] |
|---|
| 606 | query.where(:s, predicate, args[i], options[:context]) |
|---|
| 607 | else |
|---|
| 608 | query.where(:s, predicate, args[i]) |
|---|
| 609 | end |
|---|
| 610 | end |
|---|
| 611 | |
|---|
| 612 | # use sort order if given |
|---|
| 613 | if options.include? :order |
|---|
| 614 | sort_predicate = options[:order] |
|---|
| 615 | query.sort(:sort_value) |
|---|
| 616 | # add sort predicate where clause unless we have it already |
|---|
| 617 | query.where(:s, sort_predicate, :sort_value) unless @where.include? sort_predicate |
|---|
| 618 | end |
|---|
| 619 | |
|---|
| 620 | if options.include? :reverse_order |
|---|
| 621 | sort_predicate = options[:reverse_order] |
|---|
| 622 | query.reverse_sort(:sort_value) |
|---|
| 623 | query.where(:s, sort_predicate, :sort_value) unless @where.include? sort_predicate |
|---|
| 624 | end |
|---|
| 625 | |
|---|
| 626 | query.limit(options[:limit]) if options[:limit] |
|---|
| 627 | query.offset(options[:offset]) if options[:offset] |
|---|
| 628 | |
|---|
| 629 | $activerdflog.debug "executing dynamic finder: #{query.to_sp}" |
|---|
| 630 | |
|---|
| 631 | # store the query results so that caller (Resource.method_missing) can |
|---|
| 632 | # retrieve them (we cannot return them here, since we were invoked from |
|---|
| 633 | # the initialize method so all return values are ignored, instead the proxy |
|---|
| 634 | # itself is returned) |
|---|
| 635 | @value = query.execute |
|---|
| 636 | return @value |
|---|
| 637 | end |
|---|
| 638 | end |
|---|