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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ec1270a31250bb0419ba153d1dee8db81788bed7
4
- data.tar.gz: ffb8adb9b1ab0adcd83ff542642ebbb6342d4572
3
+ metadata.gz: c7375d4f5b507945381d998d3965f4cdcc8e585e
4
+ data.tar.gz: 01b3d742e5457614366c60d083715fea84b09cc4
5
5
  SHA512:
6
- metadata.gz: 041d289b8105df18cb2dd1f3728a513abc5964807950bf6927ea2b73d85a9e2a060a9f962bf419b18260627554aa895028695c9857e08f3183334e208dc1b32e
7
- data.tar.gz: 73f4d0acec4fadc96c77c29302a58ab2cdd7d2309e69fc3f29df6a6794b645d5888ad350d0cc25263fba7d083aa58583ba7966df8cecbcf23ed920eed6bb5f3d
6
+ metadata.gz: dbca5ae82c1668d8faa094d75b4a09f1c63bc882af0f057231ecf228d79044895f9b8a813b962a3f981ff974f9cf20855146fd0126c1a31c9abcc4872f9106db
7
+ data.tar.gz: 5981572185f3939e1f25843e50b9306d1bb3ec8937e16d11ad90d08e28027e5b755023294965ce8de7d9a8574cc395e7e5f4e2b6b4c7bff4e5a420624e169733
@@ -0,0 +1,12 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.1.1
5
+ # - jruby-19mode
6
+ # uncomment this line if your project needs to run something other than `rake`:
7
+ script: bundle exec rspec spec
8
+ services:
9
+ - mongodb
10
+ - elasticsearch
11
+ before_script:
12
+ - sleep 15
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 highly 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:
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 (eg. the Ruby Way) as possible
7
- - use as much commodity (ie. non-LAM-specific) tooling as possible
8
- - make it as easy to use (ie. little programming required) as possible
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
- ### There's Not Much Here!
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
- * [Ladder::Resource](#ladderresource)
38
+ * [Resources](#resource)
39
39
  * [Configuring Resources](#configuring-resources)
40
- * [Ladder::Searchable](#laddersearchable)
40
+ * [Dynamic Resources](#dynamic-resources)
41
+ * [Indexing for Search](#indexing-for-search)
41
42
 
42
- ### Ladder::Resource
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 :name, predicate: RDF::FOAF.name
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.name = 'Steve'
61
+ steve.first_name = 'Steve'
61
62
  steve.description = 'Funny-looking'
62
63
 
63
64
  steve.as_document
64
- => {"_id"=>BSON::ObjectId('542f0c124169720ea0000000'), "name"=>{"en"=>"Steve"}, "description"=>{"en"=>"Funny-looking"}}
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 :name, predicate: RDF::FOAF.name
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(name: 'Joseph Conrad')
110
- => [#<Person _id: 542f28dd4169721941010000, name: {"en"=>"Joseph Conrad"}, book_ids: [BSON::ObjectId('542f28d44169721941000000')]>]
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 :name, predicate: RDF::FOAF.name
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(name: 'Steve')
192
- => #<Person _id: 542f341e41697219a2000000, name: {"en"=>"Steve"}, address: nil>
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
- ### Configuring Resources
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
- ### Ladder::Searchable
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 :name, predicate: RDF::FOAF.name
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.name = 'Shay'
355
+ kimchy.first_name = 'Shay'
269
356
  kimchy.description = 'Real genius'
270
357
  ```
271
358
 
272
- In order to enable indexing, call the `#index` method on the class:
359
+ In order to enable indexing, call the `#index_for_search` method on the class:
273
360
 
274
361
  ```ruby
275
- Person.index
362
+ Person.index_for_search
276
363
  => :as_indexed_json
277
364
 
278
365
  kimchy.as_indexed_json
279
- => {"description"=>"Real genius", "name"=>"Shay"}
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", "name"=>"Shay"}
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.index as: :jsonld
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.index as: :qname
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 :name, predicate: RDF::DOAP.name
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(name: 'ElasticSearch', description: 'You know, for search')
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.index as: :jsonld, related: true
451
+ Person.index_for_search as: :jsonld, related: true
365
452
  => :as_indexed_json
366
- Project.index as: :jsonld, related: true
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.index as: :qname, related: true
524
+ Person.index_for_search as: :qname, related: true
438
525
  => :as_indexed_json
439
- Project.index as: :qname, related: true
526
+ Project.index_for_search as: :qname, related: true
440
527
  => :as_indexed_json
441
528
 
442
529
  kimchy.as_indexed_json
@@ -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
@@ -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
- if field_def = fields[name]
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
- resource.set_value(prop.predicate, value)
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).merge(autosave: true)
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