ladder 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +12 -0
- data/README.md +122 -35
- data/ladder.gemspec +6 -6
- data/lib/ladder/resource.rb +45 -29
- data/lib/ladder/resource/dynamic.rb +115 -0
- data/lib/ladder/searchable.rb +1 -1
- data/lib/ladder/version.rb +1 -1
- data/spec/ladder/dynamic_spec.rb +29 -0
- data/spec/ladder/resource_spec.rb +3 -338
- data/spec/ladder/searchable_spec.rb +3 -192
- data/spec/shared/resource.rb +354 -0
- data/spec/shared/searchable.rb +191 -0
- data/spec/spec_helper.rb +3 -0
- metadata +34 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c7375d4f5b507945381d998d3965f4cdcc8e585e
|
4
|
+
data.tar.gz: 01b3d742e5457614366c60d083715fea84b09cc4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dbca5ae82c1668d8faa094d75b4a09f1c63bc882af0f057231ecf228d79044895f9b8a813b962a3f981ff974f9cf20855146fd0126c1a31c9abcc4872f9106db
|
7
|
+
data.tar.gz: 5981572185f3939e1f25843e50b9306d1bb3ec8937e16d11ad90d08e28027e5b755023294965ce8de7d9a8574cc395e7e5f4e2b6b4c7bff4e5a420624e169733
|
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
![Ladder logo](https://github.com/mjsuhonos/ladder/blob/master/logo.png)
|
2
|
+
[![Gem Version](http://img.shields.io/gem/v/ladder.svg)](https://rubygems.org/gems/ladder) [![Build Status](https://travis-ci.org/mjsuhonos/ladder.svg)](https://travis-ci.org/mjsuhonos/ladder)
|
3
|
+
|
2
4
|
# Ladder
|
3
5
|
|
4
|
-
Ladder is a
|
6
|
+
Ladder is a dynamic, scalable metadata framework written in Ruby using well-known components for Linked Data ([ActiveTriples](https://github.com/no-reply/ActiveTriples)/RDF.rb), persistence ([Mongoid](http://mongoid.org)/MongoDB), indexing ([ElasticSearch](http://www.elasticsearch.org)), asynchronicity ([Sidekiq](http://sidekiq.org)/Redis) and HTTP interaction ([Padrino](http://www.padrinorb.com)/Sinatra). It is designed around the following philosophical goals:
|
5
7
|
|
6
|
-
- make it as modular
|
7
|
-
- use as much commodity
|
8
|
-
- make it as easy to use
|
8
|
+
- make it as modular as possible
|
9
|
+
- use as much commodity tooling as possible
|
10
|
+
- make it as easy to use as possible
|
9
11
|
|
10
12
|
## History
|
11
13
|
|
@@ -13,9 +15,7 @@ Ladder was loosely conceived over the course of several years prior to 2011. In
|
|
13
15
|
|
14
16
|
From mid-2014, Ladder is being re-architected as a series of Ruby modules that can be used individually and incorporated within existing Ruby frameworks (eg. [Project Hydra](http://projecthydra.org)), or used together as a comprehensive stack. Ladder is intended to encourage the LAM community to think less dogmatically about our established (often monolithic and/or niche) toolsets and instead embrace a broader vision of using non-LAM specific technologies.
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
-
The original [prototype](https://github.com/mjsuhonos/ladder/tree/prototype) branch is available, as is an [experimental](https://github.com/mjsuhonos/ladder/tree/l2) branch. Core functionality is being refactored as modules in the "ladder" gem (see below).
|
18
|
+
For those interested in the historical code, the original [prototype](https://github.com/mjsuhonos/ladder/tree/prototype) branch is available, as is an [experimental](https://github.com/mjsuhonos/ladder/tree/l2) branch.
|
19
19
|
|
20
20
|
## Installation
|
21
21
|
|
@@ -35,11 +35,12 @@ Or install it yourself as:
|
|
35
35
|
|
36
36
|
## Usage
|
37
37
|
|
38
|
-
* [
|
38
|
+
* [Resources](#resource)
|
39
39
|
* [Configuring Resources](#configuring-resources)
|
40
|
-
* [
|
40
|
+
* [Dynamic Resources](#dynamic-resources)
|
41
|
+
* [Indexing for Search](#indexing-for-search)
|
41
42
|
|
42
|
-
###
|
43
|
+
### Resources
|
43
44
|
|
44
45
|
Much like ActiveTriples, Resources are the core of Ladder. Resources implement all the functionality of a Mongoid::Document and an ActiveTriples::Resource. To add Ladder integration for your model, require
|
45
46
|
and include the main module in your class:
|
@@ -52,16 +53,16 @@ class Person
|
|
52
53
|
|
53
54
|
configure type: RDF::FOAF.Person
|
54
55
|
|
55
|
-
property :
|
56
|
+
property :first_name, predicate: RDF::FOAF.name
|
56
57
|
property :description, predicate: RDF::DC.description
|
57
58
|
end
|
58
59
|
|
59
60
|
steve = Person.new
|
60
|
-
steve.
|
61
|
+
steve.first_name = 'Steve'
|
61
62
|
steve.description = 'Funny-looking'
|
62
63
|
|
63
64
|
steve.as_document
|
64
|
-
=> {"_id"=>BSON::ObjectId('542f0c124169720ea0000000'), "
|
65
|
+
=> {"_id"=>BSON::ObjectId('542f0c124169720ea0000000'), "first_name"=>{"en"=>"Steve"}, "description"=>{"en"=>"Funny-looking"}}
|
65
66
|
|
66
67
|
steve.as_jsonld
|
67
68
|
# => {
|
@@ -90,7 +91,7 @@ class Person
|
|
90
91
|
|
91
92
|
configure type: RDF::FOAF.Person
|
92
93
|
|
93
|
-
property :
|
94
|
+
property :first_name, predicate: RDF::FOAF.name
|
94
95
|
property :books, predicate: RDF::FOAF.made, class_name: 'Book'
|
95
96
|
end
|
96
97
|
|
@@ -106,8 +107,8 @@ end
|
|
106
107
|
b = Book.new(title: 'Heart of Darkness')
|
107
108
|
=> #<Book _id: 542f28d44169721941000000, title: {"en"=>"Heart of Darkness"}, person_ids: nil>
|
108
109
|
|
109
|
-
b.people << Person.new(
|
110
|
-
=> [#<Person _id: 542f28dd4169721941010000,
|
110
|
+
b.people << Person.new(first_name: 'Joseph Conrad')
|
111
|
+
=> [#<Person _id: 542f28dd4169721941010000, first_name: {"en"=>"Joseph Conrad"}, book_ids: [BSON::ObjectId('542f28d44169721941000000')]>]
|
111
112
|
|
112
113
|
b.as_jsonld
|
113
114
|
# => {
|
@@ -170,7 +171,7 @@ class Person
|
|
170
171
|
|
171
172
|
configure type: RDF::FOAF.Person
|
172
173
|
|
173
|
-
property :
|
174
|
+
property :first_name, predicate: RDF::FOAF.name
|
174
175
|
|
175
176
|
embeds_one :address, class_name: 'Place'
|
176
177
|
property :address, predicate: RDF::FOAF.based_near
|
@@ -188,8 +189,8 @@ class Place
|
|
188
189
|
property :resident, predicate: RDF::VCARD.agent
|
189
190
|
end
|
190
191
|
|
191
|
-
steve = Person.new(
|
192
|
-
=> #<Person _id: 542f341e41697219a2000000,
|
192
|
+
steve = Person.new(first_name: 'Steve')
|
193
|
+
=> #<Person _id: 542f341e41697219a2000000, first_name: {"en"=>"Steve"}, address: nil>
|
193
194
|
|
194
195
|
steve.address = Place.new(city: 'Toronto', country: 'Canada')
|
195
196
|
=> #<Place _id: 542f342741697219a2010000, city: {"en"=>"Toronto"}, country: {"en"=>"Canada"}, resident: nil>
|
@@ -233,7 +234,7 @@ steve.as_jsonld
|
|
233
234
|
|
234
235
|
Note in this case that both objects are included in the RDF graph, thanks to embedded relations. This can be useful to avoid additional queries to the database for objects that are tightly coupled.
|
235
236
|
|
236
|
-
|
237
|
+
#### Configuring Resources
|
237
238
|
|
238
239
|
If the LADDER_BASE_URI global constant is set, base URIs are dynamically generated based on the name of the model class. However, you can still set the base URI for a class explicitly just as you would in ActiveTriples:
|
239
240
|
|
@@ -249,7 +250,93 @@ Person.resource_class.base_uri
|
|
249
250
|
=> "http://some.other.uri/"
|
250
251
|
```
|
251
252
|
|
252
|
-
|
253
|
+
#### Dynamic Resources
|
254
|
+
|
255
|
+
In line with ActiveTriples' [Open Model](https://github.com/ActiveTriples/ActiveTriples#open-model) design, you can define properties on any Resource instance similarly to how you would on the class:
|
256
|
+
|
257
|
+
```ruby
|
258
|
+
class Person
|
259
|
+
include Ladder::Resource::Dynamic
|
260
|
+
|
261
|
+
configure type: RDF::FOAF.Person
|
262
|
+
|
263
|
+
property :first_name, predicate: RDF::FOAF.name
|
264
|
+
end
|
265
|
+
|
266
|
+
steve = Person.new(first_name: 'Steve')
|
267
|
+
|
268
|
+
steve.description
|
269
|
+
=> NoMethodError: undefined method 'description' for #<Person:0x007fb54eb1d0b8>
|
270
|
+
|
271
|
+
steve.property :description, predicate: RDF::DC.description
|
272
|
+
steve.description = 'Funny-looking'
|
273
|
+
|
274
|
+
steve.as_document
|
275
|
+
=> {"_id"=>BSON::ObjectId('546669234169720397000000'),
|
276
|
+
"first_name"=>{"en"=>"Steve"},
|
277
|
+
"_context"=>{:description=>"http://purl.org/dc/terms/description"},
|
278
|
+
"description"=>"Funny-looking"}
|
279
|
+
|
280
|
+
steve.as_jsonld
|
281
|
+
# => {
|
282
|
+
# "@context": {
|
283
|
+
# "dc": "http://purl.org/dc/terms/",
|
284
|
+
# "foaf": "http://xmlns.com/foaf/0.1/"
|
285
|
+
# },
|
286
|
+
# "@id": "http://example.org/people/546669234169720397000000",
|
287
|
+
# "@type": "foaf:Person",
|
288
|
+
# "dc:description": "Funny-looking",
|
289
|
+
# "foaf:name": {
|
290
|
+
# "@language": "en",
|
291
|
+
# "@value": "Steve"
|
292
|
+
# }
|
293
|
+
```
|
294
|
+
|
295
|
+
Additionally, you can push RDF statements into a Resource instance like you would with ActiveTriples or RDF::Graph, noting that the subject is ignored since it is implicit:
|
296
|
+
|
297
|
+
```ruby
|
298
|
+
steve << RDF::Statement(nil, RDF::DC.description, 'Tall, dark, and handsome')
|
299
|
+
steve << RDF::Statement(nil, RDF::FOAF.depiction, RDF::URI('http://some.image/pic.jpg'))
|
300
|
+
steve << RDF::Statement(nil, RDF::FOAF.age, 32)
|
301
|
+
|
302
|
+
steve.as_document
|
303
|
+
=> {"_id"=>BSON::ObjectId('546669234169720397000000'),
|
304
|
+
"first_name"=>{"en"=>"Steve"},
|
305
|
+
"_context"=>
|
306
|
+
{:description=>"http://purl.org/dc/terms/description",
|
307
|
+
:depiction=>"http://xmlns.com/foaf/0.1/depiction",
|
308
|
+
:age=>"http://xmlns.com/foaf/0.1/age"},
|
309
|
+
"description"=>"Tall, dark, and handsome",
|
310
|
+
"depiction"=>"http://some.image/pic.jpg",
|
311
|
+
"age"=>32}
|
312
|
+
|
313
|
+
steve.as_jsonld
|
314
|
+
# => {
|
315
|
+
# "@context": {
|
316
|
+
# "dc": "http://purl.org/dc/terms/",
|
317
|
+
# "foaf": "http://xmlns.com/foaf/0.1/",
|
318
|
+
# "xsd": "http://www.w3.org/2001/XMLSchema#"
|
319
|
+
# },
|
320
|
+
# "@id": "http://example.org/people/546669234169720397000000",
|
321
|
+
# "@type": "foaf:Person",
|
322
|
+
# "dc:description": "Tall, dark, and handsome",
|
323
|
+
# "foaf:age": {
|
324
|
+
# "@type": "xsd:integer",
|
325
|
+
# "@value": "32"
|
326
|
+
# },
|
327
|
+
# "foaf:depiction": {
|
328
|
+
# "@id": "http://some.image/pic.jpg"
|
329
|
+
# },
|
330
|
+
# "foaf:name": {
|
331
|
+
# "@language": "en",
|
332
|
+
# "@value": "Steve"
|
333
|
+
# }
|
334
|
+
# }
|
335
|
+
```
|
336
|
+
|
337
|
+
Note that due to the way Mongoid handles dynamic fields, dynamic properties **can not** be localized. They can be any kind of literal, but they **can not** be a related object. They can, however, contain a reference to the related object's URI.
|
338
|
+
|
339
|
+
### Indexing for Search
|
253
340
|
|
254
341
|
You can also index your model classes for keyword searching through ElasticSearch by mixing in the Ladder::Searchable module:
|
255
342
|
|
@@ -260,23 +347,23 @@ class Person
|
|
260
347
|
|
261
348
|
configure type: RDF::FOAF.Person
|
262
349
|
|
263
|
-
property :
|
350
|
+
property :first_name, predicate: RDF::FOAF.name
|
264
351
|
property :description, predicate: RDF::DC.description
|
265
352
|
end
|
266
353
|
|
267
354
|
kimchy = Person.new
|
268
|
-
kimchy.
|
355
|
+
kimchy.first_name = 'Shay'
|
269
356
|
kimchy.description = 'Real genius'
|
270
357
|
```
|
271
358
|
|
272
|
-
In order to enable indexing, call the `#
|
359
|
+
In order to enable indexing, call the `#index_for_search` method on the class:
|
273
360
|
|
274
361
|
```ruby
|
275
|
-
Person.
|
362
|
+
Person.index_for_search
|
276
363
|
=> :as_indexed_json
|
277
364
|
|
278
365
|
kimchy.as_indexed_json
|
279
|
-
=> {"description"=>"Real genius", "
|
366
|
+
=> {"description"=>"Real genius", "first_name"=>"Shay"}
|
280
367
|
|
281
368
|
kimchy.save
|
282
369
|
=> true
|
@@ -294,7 +381,7 @@ results.count
|
|
294
381
|
=> 1
|
295
382
|
|
296
383
|
results.first._source
|
297
|
-
=> {"description"=>"Real genius", "
|
384
|
+
=> {"description"=>"Real genius", "first_name"=>"Shay"}
|
298
385
|
|
299
386
|
results.records.first == kimchy
|
300
387
|
=> true
|
@@ -303,7 +390,7 @@ results.records.first == kimchy
|
|
303
390
|
When indexing, you can control how your model is stored in the index by supplying the `as: :jsonld` or `as: :qname` options:
|
304
391
|
|
305
392
|
```ruby
|
306
|
-
Person.
|
393
|
+
Person.index_for_search as: :jsonld
|
307
394
|
=> :as_indexed_json
|
308
395
|
|
309
396
|
kimchy.as_indexed_json
|
@@ -324,7 +411,7 @@ kimchy.as_indexed_json
|
|
324
411
|
# }
|
325
412
|
# }
|
326
413
|
|
327
|
-
Person.
|
414
|
+
Person.index_for_search as: :qname
|
328
415
|
=> :as_indexed_json
|
329
416
|
|
330
417
|
kimchy.as_indexed_json
|
@@ -350,20 +437,20 @@ class Project
|
|
350
437
|
|
351
438
|
configure type: RDF::DOAP.Project
|
352
439
|
|
353
|
-
property :
|
440
|
+
property :project_name, predicate: RDF::DOAP.name
|
354
441
|
property :description, predicate: RDF::DC.description
|
355
442
|
property :developers, predicate: RDF::DOAP.developer, class_name: 'Person'
|
356
443
|
end
|
357
444
|
|
358
445
|
Person.property :projects, predicate: RDF::FOAF.made, class_name: 'Project'
|
359
446
|
|
360
|
-
es = Project.new(
|
447
|
+
es = Project.new(project_name: 'ElasticSearch', description: 'You know, for search')
|
361
448
|
es.developers << kimchy
|
362
449
|
es.save
|
363
450
|
|
364
|
-
Person.
|
451
|
+
Person.index_for_search as: :jsonld, related: true
|
365
452
|
=> :as_indexed_json
|
366
|
-
Project.
|
453
|
+
Project.index_for_search as: :jsonld, related: true
|
367
454
|
=> :as_indexed_json
|
368
455
|
|
369
456
|
kimchy.as_indexed_json
|
@@ -434,9 +521,9 @@ es.as_indexed_json
|
|
434
521
|
# }
|
435
522
|
# }
|
436
523
|
|
437
|
-
Person.
|
524
|
+
Person.index_for_search as: :qname, related: true
|
438
525
|
=> :as_indexed_json
|
439
|
-
Project.
|
526
|
+
Project.index_for_search as: :qname, related: true
|
440
527
|
=> :as_indexed_json
|
441
528
|
|
442
529
|
kimchy.as_indexed_json
|
data/ladder.gemspec
CHANGED
@@ -24,10 +24,10 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.add_dependency "active-triples", "~> 0.3"
|
25
25
|
spec.add_dependency "elasticsearch-model", "~> 0.1"
|
26
26
|
|
27
|
-
spec.add_development_dependency "bundler"
|
28
|
-
spec.add_development_dependency "pry"
|
29
|
-
spec.add_development_dependency "wirble"
|
30
|
-
spec.add_development_dependency "rspec"
|
31
|
-
spec.add_development_dependency "rake"
|
32
|
-
spec.add_development_dependency "yard"
|
27
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
28
|
+
spec.add_development_dependency "pry", "~> 0.10"
|
29
|
+
spec.add_development_dependency "wirble", "~> 0.1"
|
30
|
+
spec.add_development_dependency "rspec", "~> 3.1"
|
31
|
+
spec.add_development_dependency "rake", "~> 10.3"
|
32
|
+
spec.add_development_dependency "yard", "~> 0.8"
|
33
33
|
end
|
data/lib/ladder/resource.rb
CHANGED
@@ -8,6 +8,8 @@ module Ladder::Resource
|
|
8
8
|
include Mongoid::Document
|
9
9
|
include ActiveTriples::Identifiable
|
10
10
|
|
11
|
+
autoload :Dynamic, 'ladder/resource/dynamic'
|
12
|
+
|
11
13
|
included do
|
12
14
|
configure base_uri: RDF::URI.new(LADDER_BASE_URI) / name.underscore.pluralize if defined? LADDER_BASE_URI
|
13
15
|
end
|
@@ -20,45 +22,59 @@ module Ladder::Resource
|
|
20
22
|
JSON.parse update_resource(opts.slice :related).dump(:jsonld, {standard_prefixes: true}.merge(opts))
|
21
23
|
end
|
22
24
|
|
25
|
+
##
|
26
|
+
# Overload ActiveTriples #rdf_label
|
27
|
+
#
|
28
|
+
# @see ActiveTriples::Resource
|
29
|
+
def rdf_label
|
30
|
+
update_resource
|
31
|
+
resource.rdf_label
|
32
|
+
end
|
33
|
+
|
23
34
|
##
|
24
35
|
# Overload ActiveTriples #update_resource
|
25
36
|
#
|
26
37
|
# @see ActiveTriples::Identifiable
|
27
38
|
def update_resource(opts = {})
|
28
39
|
super() do |name, prop|
|
29
|
-
# this is a literal property
|
30
|
-
|
31
|
-
if field_def.localized?
|
32
|
-
value = read_attribute(name).map { |lang, val| RDF::Literal.new(val, language: lang) }
|
33
|
-
else
|
34
|
-
value = self.send(prop.term)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
# this is a relation property
|
39
|
-
if relation_def = relations[name]
|
40
|
-
objects = self.send(prop.term).to_a
|
41
|
-
|
42
|
-
if opts[:related] or embedded_relations[name]
|
43
|
-
value = objects.map(&:update_resource)
|
44
|
-
|
45
|
-
# update inverse relation properties
|
46
|
-
objects.each { |object| object.resource.set_value(relation_def.inverse, self.rdf_subject) } if relation_def.inverse
|
47
|
-
else
|
48
|
-
value = objects.map(&:rdf_subject)
|
49
|
-
|
50
|
-
# remove inverse relation properties
|
51
|
-
objects.each { |object| resource.delete [object.rdf_subject] }
|
52
|
-
end
|
53
|
-
|
54
|
-
end
|
40
|
+
value = update_from_field(name) if fields[name] # this is a literal property
|
41
|
+
value = update_from_relation(name, opts) if relations[name] # this is a relation property
|
55
42
|
|
56
|
-
|
43
|
+
cast_uri = RDF::URI.new(value)
|
44
|
+
resource.set_value(prop.predicate, cast_uri.valid? ? cast_uri : value) if value
|
57
45
|
end
|
58
46
|
|
59
47
|
resource
|
60
48
|
end
|
61
49
|
|
50
|
+
private
|
51
|
+
|
52
|
+
def update_from_field(name)
|
53
|
+
if fields[name].localized?
|
54
|
+
localized_hash = read_attribute(name)
|
55
|
+
localized_hash.map { |lang, val| RDF::Literal.new(val, language: lang) } unless localized_hash.nil?
|
56
|
+
else
|
57
|
+
self.send(name)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def update_from_relation(name, opts = {})
|
62
|
+
objects = self.send(name).to_a
|
63
|
+
|
64
|
+
if opts[:related] or embedded_relations[name]
|
65
|
+
# update inverse relation properties
|
66
|
+
relation_def = relations[name]
|
67
|
+
objects.each { |object| object.resource.set_value(relation_def.inverse, self.rdf_subject) } if relation_def.inverse
|
68
|
+
objects.map(&:update_resource)
|
69
|
+
else
|
70
|
+
# remove inverse relation properties
|
71
|
+
objects.each { |object| resource.delete [object.rdf_subject] }
|
72
|
+
objects.map(&:rdf_subject)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
public
|
77
|
+
|
62
78
|
module ClassMethods
|
63
79
|
|
64
80
|
##
|
@@ -67,12 +83,12 @@ module Ladder::Resource
|
|
67
83
|
# @see ActiveTriples::Properties
|
68
84
|
def property(name, opts={})
|
69
85
|
if class_name = opts[:class_name]
|
70
|
-
mongoid_opts = opts.except(:predicate, :multivalue)
|
86
|
+
mongoid_opts = {autosave: true, index: true}.merge(opts.except(:predicate, :multivalue))
|
71
87
|
opts.except! *mongoid_opts.keys
|
72
88
|
|
73
89
|
has_and_belongs_to_many(name, mongoid_opts) unless relations.keys.include? name.to_s
|
74
90
|
else
|
75
|
-
field(name, localize: true)
|
91
|
+
field(name, localize: true) unless fields[name.to_s]
|
76
92
|
end
|
77
93
|
|
78
94
|
super
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module Ladder::Resource::Dynamic
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
include Ladder::Resource
|
6
|
+
|
7
|
+
field :_context, type: Hash
|
8
|
+
|
9
|
+
after_find :apply_context
|
10
|
+
|
11
|
+
##
|
12
|
+
# Overload Ladder #update_resource
|
13
|
+
#
|
14
|
+
# @see Ladder::Resource
|
15
|
+
def update_resource(opts = {})
|
16
|
+
# FIXME: for some reason super has to go first or AT clobbers properties
|
17
|
+
super(opts)
|
18
|
+
|
19
|
+
if self._context
|
20
|
+
self._context.each do |field_name, uri|
|
21
|
+
value = self.send(field_name)
|
22
|
+
cast_uri = RDF::URI.new(value)
|
23
|
+
resource.set_value(RDF::Vocabulary.find_term(uri), cast_uri.valid? ? cast_uri : value)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
resource
|
28
|
+
end
|
29
|
+
|
30
|
+
def <<(data)
|
31
|
+
# ActiveTriples::Resource expects: RDF::Statement, Hash, or Array
|
32
|
+
data = RDF::Statement.from(data) unless data.is_a? RDF::Statement
|
33
|
+
|
34
|
+
# Define predicate on object unless it's defined on the class
|
35
|
+
if resource_class.properties.values.map(&:predicate).include? data.predicate
|
36
|
+
field_name = resource_class.properties.select { |name, term| term.predicate == data.predicate }.keys.first.to_sym
|
37
|
+
else
|
38
|
+
qname = data.predicate.qname
|
39
|
+
|
40
|
+
if respond_to? qname.last or :name == qname.last
|
41
|
+
field_name = qname.join('_').to_sym
|
42
|
+
else
|
43
|
+
field_name = qname.last
|
44
|
+
end
|
45
|
+
|
46
|
+
property field_name, predicate: data.predicate
|
47
|
+
end
|
48
|
+
|
49
|
+
# Set the value in Mongoid
|
50
|
+
value = case data.object
|
51
|
+
when RDF::Literal
|
52
|
+
data.object.object
|
53
|
+
when RDF::URI
|
54
|
+
data.object.to_s
|
55
|
+
else
|
56
|
+
data.object
|
57
|
+
end
|
58
|
+
|
59
|
+
self.send("#{field_name}=", value)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
##
|
65
|
+
# Overload ActiveTriples #resource_class
|
66
|
+
#
|
67
|
+
# @see ActiveTriples::Identifiable
|
68
|
+
def resource_class
|
69
|
+
@modified_resource_class ||= self.class.resource_class.clone
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
# Dynamic field definition
|
76
|
+
def property(field_name, *opts)
|
77
|
+
# Store context information
|
78
|
+
self._context ||= Hash.new(nil)
|
79
|
+
self._context[field_name] = opts.first[:predicate].to_s
|
80
|
+
|
81
|
+
apply_context
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
##
|
87
|
+
# Dynamic field accessors (Mongoid)
|
88
|
+
def create_accessors(field_name)
|
89
|
+
define_singleton_method field_name do
|
90
|
+
read_attribute(field_name)
|
91
|
+
end
|
92
|
+
|
93
|
+
define_singleton_method "#{field_name}=" do |value|
|
94
|
+
write_attribute(field_name, value)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
##
|
99
|
+
# Apply dynamic fields and properties to this instance
|
100
|
+
def apply_context
|
101
|
+
return unless self._context
|
102
|
+
|
103
|
+
self._context.each do |field_name, uri|
|
104
|
+
next if fields.keys.include? field_name
|
105
|
+
|
106
|
+
if term = RDF::Vocabulary.find_term(uri)
|
107
|
+
create_accessors field_name
|
108
|
+
|
109
|
+
# Update resource properties
|
110
|
+
resource_class.property(field_name, predicate: term)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|