ladder 0.3.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.semver +2 -2
- data/README.md +37 -19
- data/lib/ladder.rb +5 -3
- data/lib/ladder/config.rb +73 -0
- data/lib/ladder/configurable.rb +26 -0
- data/lib/ladder/file.rb +10 -1
- data/lib/ladder/resource.rb +130 -60
- data/lib/ladder/resource/dynamic.rb +14 -10
- data/lib/ladder/resource/serializable.rb +22 -10
- data/lib/ladder/version.rb +1 -1
- data/spec/ladder/file_spec.rb +3 -8
- data/spec/ladder/resource/dynamic_spec.rb +19 -17
- data/spec/ladder/resource_spec.rb +43 -12
- data/spec/ladder/searchable/background_spec.rb +23 -13
- data/spec/ladder/searchable/file_spec.rb +2 -8
- data/spec/ladder/searchable/resource_spec.rb +20 -12
- data/spec/shared/file.rb +4 -2
- data/spec/shared/graph.jsonld +51 -6
- data/spec/shared/resource.rb +45 -180
- data/spec/shared/resource/dynamic.rb +149 -0
- data/spec/spec_helper.rb +16 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1f0ab0c27e81412e4f124be731b6de0abb4ce723
|
4
|
+
data.tar.gz: 3d3e89d64df981809eebb4fdbdc8755033676a6c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6f9e6883c2d960327de40b1d82fe22d38c1e8f8b43072ac6f6bf2ed8b1646b90e4e3ebeb8ed8d8c0006a8a02072ee1f4fddbb4d3787b0c179d72346f77f0d2dd
|
7
|
+
data.tar.gz: f5c77d39b0890fa5a5af2ebc5ea7a361c1f24a6ae6b2e5bc05f6c8d9f5289716ae26f180f922f66c74bf7af4a93af51906b627e0b87893ab15bd85a189e065cd
|
data/.semver
CHANGED
data/README.md
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
|
7
7
|
Ladder is a dynamic framework for [Linked Data](http://en.wikipedia.org/wiki/Linked_data) modelling, persistence, and full-text indexing. It is implemented as a series of Ruby modules that can be used individually and incorporated within existing ActiveModel frameworks (eg. [Project Hydra](http://projecthydra.org)), or combined as a comprehensive stack.
|
8
8
|
|
9
|
-
|
9
|
+
Although conceptually similar to [Spira](https://github.com/ruby-rdf/spira), Ladder takes the opposite approach: instead of making RDF repositories (triple stores) behave like ActiveModel, it allows ActiveModel objects to behave like RDF resources.
|
10
10
|
|
11
11
|
### Components
|
12
12
|
|
@@ -17,7 +17,9 @@ Ladder is intended to encourage the [GLAM](http://en.wikipedia.org/wiki/GLAM_(in
|
|
17
17
|
|
18
18
|
## History
|
19
19
|
|
20
|
-
Ladder was loosely conceived over the course of several years prior to 2011
|
20
|
+
Ladder was loosely conceived over the course of several years prior to 2011 as a way to encourage the [GLAM](http://en.wikipedia.org/wiki/GLAM_(industry_sector)) community to think less dogmatically about established (often monolithic and/or niche) tools and instead embrace a broader vision of adopting more widely-used technologies.
|
21
|
+
|
22
|
+
In early 2012, Ladder began existence as an opportunity to escape from a decade of LAMP development and become familiar with Ruby. From 2012 to late 2013, a closed prototype was built under the auspices of [Deliberate Data](http://deliberatedata.com) as a proof-of-concept to test the feasibility of the design. For those interested in the historical code, the original [prototype](https://github.com/ladder/ladder/tree/prototype) branch is available, as is an [experimental](https://github.com/ladder/ladder/tree/l2) branch.
|
21
23
|
|
22
24
|
## Installation
|
23
25
|
|
@@ -38,13 +40,13 @@ Or install it yourself as:
|
|
38
40
|
## Usage
|
39
41
|
|
40
42
|
* [Resources](#resources)
|
41
|
-
* [Configuring Resources](#configuring-resources)
|
42
43
|
* [Dynamic Resources](#dynamic-resources)
|
43
44
|
* [Files](#files)
|
44
45
|
* [Indexing](#indexing)
|
45
46
|
* [Indexing Resources](#indexing-resources)
|
46
47
|
* [Indexing Files](#indexing-files)
|
47
48
|
* [Background Indexing](#background-indexing)
|
49
|
+
* [Configuration](#configuration)
|
48
50
|
|
49
51
|
### Resources
|
50
52
|
|
@@ -89,7 +91,19 @@ steve.as_jsonld
|
|
89
91
|
# }
|
90
92
|
```
|
91
93
|
|
92
|
-
The `#property` method takes care of setting both Mongoid fields and ActiveTriples properties. Properties with literal values
|
94
|
+
The `#property` method takes care of setting both Mongoid fields and ActiveTriples properties. Properties with literal values (Mongoid fields) can be localized by default. Properties with a supplied `class_name:` will create a many-to-many relation.
|
95
|
+
|
96
|
+
By default, URIs are dynamically generated based on the name of the model class and the configured base URI. However, you can still set the base URI for a class explicitly just as you would in ActiveTriples, eg:
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
Person.base_uri
|
100
|
+
=> #<RDF::URI:0x3fecf69da274 URI:http://example.org/people>
|
101
|
+
|
102
|
+
Person.configure base_uri: 'http://some.other.uri/'
|
103
|
+
=> "http://some.other.uri/"
|
104
|
+
```
|
105
|
+
|
106
|
+
See the [configuration](#configuration) section for more information on configuring default behaviour.
|
93
107
|
|
94
108
|
```ruby
|
95
109
|
class Person
|
@@ -240,20 +254,6 @@ steve.as_jsonld
|
|
240
254
|
|
241
255
|
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.
|
242
256
|
|
243
|
-
#### Configuring Resources
|
244
|
-
|
245
|
-
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:
|
246
|
-
|
247
|
-
```ruby
|
248
|
-
LADDER_BASE_URI = 'http://example.org'
|
249
|
-
|
250
|
-
Person.resource_class.base_uri
|
251
|
-
=> #<RDF::URI:0x3fecf69da274 URI:http://example.org/people>
|
252
|
-
|
253
|
-
Person.configure base_uri: 'http://some.other.uri/'
|
254
|
-
=> "http://some.other.uri/"
|
255
|
-
```
|
256
|
-
|
257
257
|
#### Dynamic Resources
|
258
258
|
|
259
259
|
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:
|
@@ -363,7 +363,7 @@ class Image
|
|
363
363
|
end
|
364
364
|
```
|
365
365
|
|
366
|
-
|
366
|
+
As with Resources, using `#property` will create a many-to-many relation for a File by default; however, because Files must be the target of a one-way relation, the `inverse_of: nil` option is required (unless the `one_sided_relations` [configuration](#configuration) option is set). Note that due to the way GridFS is designed, Files **can not** be embedded.
|
367
367
|
|
368
368
|
```ruby
|
369
369
|
steve = Person.new(first_name: 'Steve')
|
@@ -779,6 +779,24 @@ ActiveJob::Base.queue_adapter = :sidekiq
|
|
779
779
|
|
780
780
|
For more information on available queueing adapters and their features, see the [ActiveJob documentation](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html).
|
781
781
|
|
782
|
+
### Configuration
|
783
|
+
|
784
|
+
Configuration options are set using `Ladder::Config#settings`, eg:
|
785
|
+
|
786
|
+
```ruby
|
787
|
+
Ladder::Config.settings[:base_uri] = 'http://example.org'
|
788
|
+
=> "http://example.org"
|
789
|
+
|
790
|
+
Ladder::Config.settings
|
791
|
+
=> {:base_uri=>"http://example.org", :localize_fields=>false, :one_sided_relations=>false}
|
792
|
+
```
|
793
|
+
|
794
|
+
Ladder currently supports the following configuration options (defaults in parentheses):
|
795
|
+
|
796
|
+
* `:base_uri ('urn:x-ladder')`: Tells Ladder the base (root) URI to use for generating model URIs. For a Rack-based linked data application, this will typically be the HTTP(S) URL, eg. "http://some.domain/my_application/"
|
797
|
+
* `:localize_fields (false)`: When set to `true`, Ladder will set fields defined using `#property` to be localized by default.
|
798
|
+
* `:one_sided_relations (false)`: When set to `true`, Ladder will set relations defined using `#property` to be [one-sided many-to-many](http://mongoid.org/en/mongoid/docs/relations.html#has_and_belongs_to_many) relations. Otherwise, it will define has-and-belongs-to-many (HABTM) relations.
|
799
|
+
|
782
800
|
## Contributing
|
783
801
|
|
784
802
|
Anyone and everyone is welcome to contribute. Go crazy.
|
data/lib/ladder.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
require 'ladder/version'
|
2
|
+
require 'ladder/config'
|
2
3
|
|
3
4
|
module Ladder
|
4
|
-
autoload :File,
|
5
|
-
autoload :Resource,
|
6
|
-
autoload :Searchable,
|
5
|
+
autoload :File, 'ladder/file'
|
6
|
+
autoload :Resource, 'ladder/resource'
|
7
|
+
autoload :Searchable, 'ladder/searchable'
|
8
|
+
autoload :Configurable, 'ladder/configurable'
|
7
9
|
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'mongoid'
|
2
|
+
require 'mongoid/config/validators/option'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
module Ladder
|
6
|
+
module Config
|
7
|
+
extend self
|
8
|
+
extend ::Mongoid::Config::Options
|
9
|
+
|
10
|
+
LOCK = Mutex.new
|
11
|
+
|
12
|
+
option :base_uri, default: URI('urn:x-ladder') # typically a URL (configured)
|
13
|
+
option :localize_fields, default: false # self-explanatory
|
14
|
+
option :one_sided_relations, default: false # otherwise HABTM
|
15
|
+
|
16
|
+
# Get all the models in the application - this is everything that includes
|
17
|
+
# Ladder::Resource or Ladder::File.
|
18
|
+
#
|
19
|
+
# @example Get all the models.
|
20
|
+
# config.models
|
21
|
+
#
|
22
|
+
# @return [ Array<Class> ] All the models in the application.
|
23
|
+
#
|
24
|
+
# @since 3.1.0
|
25
|
+
def models
|
26
|
+
@models ||= []
|
27
|
+
end
|
28
|
+
|
29
|
+
# Register a model in the application with Ladder.
|
30
|
+
#
|
31
|
+
# @example Register a model.
|
32
|
+
# config.register_model(Band)
|
33
|
+
#
|
34
|
+
# @param [ Class ] klass The model to register.
|
35
|
+
#
|
36
|
+
# @since 3.1.0
|
37
|
+
def register_model(klass)
|
38
|
+
LOCK.synchronize do
|
39
|
+
models.push(klass) unless models.include?(klass)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# From a hash of settings, load all the configuration.
|
44
|
+
#
|
45
|
+
# @example Load the configuration.
|
46
|
+
# config.load_configuration(settings)
|
47
|
+
#
|
48
|
+
# @param [ Hash ] settings The configuration settings.
|
49
|
+
#
|
50
|
+
# @since 3.1.0
|
51
|
+
def load_configuration(settings)
|
52
|
+
configuration = settings.with_indifferent_access
|
53
|
+
self.options = configuration[:options]
|
54
|
+
end
|
55
|
+
|
56
|
+
# Set the configuration options. Will validate each one individually.
|
57
|
+
#
|
58
|
+
# @example Set the options.
|
59
|
+
# config.options = { raise_not_found_error: true }
|
60
|
+
#
|
61
|
+
# @param [ Hash ] options The configuration options.
|
62
|
+
#
|
63
|
+
# @since 3.0.0
|
64
|
+
def options=(options)
|
65
|
+
if options
|
66
|
+
options.each_pair do |option, value|
|
67
|
+
::Mongoid::Config::Validators::Option.validate(option)
|
68
|
+
send("#{option}=", value)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Ladder
|
2
|
+
module Configurable
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
##
|
7
|
+
# Set a default base URI based on the Ladder::Config settings
|
8
|
+
#
|
9
|
+
# @return [RDF::URI]
|
10
|
+
def base_uri
|
11
|
+
RDF::URI.new(Ladder::Config.settings[:base_uri]) / name.underscore.pluralize
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
##
|
17
|
+
# Register with Ladder and set the default base URI
|
18
|
+
#
|
19
|
+
# @return [void]
|
20
|
+
def configure_model
|
21
|
+
configure base_uri: base_uri
|
22
|
+
Ladder::Config.register_model self unless Ladder::Config.models.include? self
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/ladder/file.rb
CHANGED
@@ -7,15 +7,24 @@ module Ladder
|
|
7
7
|
|
8
8
|
include Mongoid::Document
|
9
9
|
include ActiveTriples::Identifiable
|
10
|
+
include Ladder::Configurable
|
10
11
|
|
11
12
|
included do
|
12
|
-
|
13
|
+
configure_model
|
13
14
|
|
14
15
|
store_in collection: "#{ grid.prefix }.files"
|
15
16
|
|
16
17
|
# Define accessor methods for attributes
|
17
18
|
define_method(:content_type) { read_attribute(:contentType) }
|
18
19
|
|
20
|
+
# Attributes are:
|
21
|
+
# length -> RDF::DC.extent
|
22
|
+
# chunkSize -> (internal) ?
|
23
|
+
# uploadDate -> RDF::DC.created
|
24
|
+
# md5 -> premis:hasMessageDigest ? with premis:hasMessageDigestAlgorithm = 'MD5'
|
25
|
+
# contentType -> RDF::DC.format
|
26
|
+
# filename -> RDF::RDFS.label
|
27
|
+
|
19
28
|
grid::File.fields.keys.map(&:to_sym).each do |attr|
|
20
29
|
define_method(attr) { read_attribute(attr) }
|
21
30
|
end
|
data/lib/ladder/resource.rb
CHANGED
@@ -10,11 +10,10 @@ module Ladder
|
|
10
10
|
|
11
11
|
include Mongoid::Document
|
12
12
|
include ActiveTriples::Identifiable
|
13
|
+
include Ladder::Configurable
|
13
14
|
include Ladder::Resource::Serializable
|
14
15
|
|
15
|
-
included
|
16
|
-
configure base_uri: RDF::URI.new(LADDER_BASE_URI) / name.underscore.pluralize if defined? LADDER_BASE_URI
|
17
|
-
end
|
16
|
+
included { configure_model }
|
18
17
|
|
19
18
|
delegate :rdf_label, to: :update_resource
|
20
19
|
|
@@ -27,10 +26,10 @@ module Ladder
|
|
27
26
|
# @return [ActiveTriples::Resource] resource for the object
|
28
27
|
def update_resource(opts = {})
|
29
28
|
resource_class.properties.each do |field_name, property|
|
30
|
-
|
31
|
-
|
29
|
+
values = update_from_field(field_name) if fields[field_name]
|
30
|
+
values = update_from_relation(field_name, opts[:related]) if relations[field_name]
|
32
31
|
|
33
|
-
resource.set_value(property.predicate,
|
32
|
+
resource.set_value(property.predicate, values)
|
34
33
|
end
|
35
34
|
|
36
35
|
resource
|
@@ -40,7 +39,7 @@ module Ladder
|
|
40
39
|
# Push an RDF::Statement into the object
|
41
40
|
#
|
42
41
|
# @param [RDF::Statement, Hash, Array] statement @see RDF::Statement#from
|
43
|
-
# @return [
|
42
|
+
# @return [Object, nil] the value inserted into the object
|
44
43
|
def <<(statement)
|
45
44
|
# ActiveTriples::Resource expects: RDF::Statement, Hash, or Array
|
46
45
|
statement = RDF::Statement.from(statement) unless statement.is_a? RDF::Statement
|
@@ -49,24 +48,10 @@ module Ladder
|
|
49
48
|
field_name = field_from_predicate(statement.predicate)
|
50
49
|
return unless field_name
|
51
50
|
|
52
|
-
|
53
|
-
value = Ladder::Resource.from_uri(statement.object) if statement.object.is_a? RDF::URI
|
54
|
-
|
55
|
-
# TODO: tidy this code
|
56
|
-
# subject (RDF::Term) - A symbol is converted to an interned Node.
|
57
|
-
# predicate (RDF::URI)
|
58
|
-
# object (RDF::Resource) - if not a Resource, it is coerced to Literal or Node
|
59
|
-
# depending on if it is a symbol or something other than a Term.
|
60
|
-
value = yield if block_given?
|
61
|
-
value ||= statement.object.to_s
|
51
|
+
objects = statement.object.is_a?(RDF::Node) && block_given? ? yield : statement.object
|
62
52
|
|
63
|
-
|
64
|
-
|
65
|
-
if enum.is_a?(Enumerable)
|
66
|
-
enum.send(:push, value) unless enum.include? value
|
67
|
-
else
|
68
|
-
send("#{field_name}=", value)
|
69
|
-
end
|
53
|
+
update_field(field_name, *objects) if fields[field_name]
|
54
|
+
update_relation(field_name, *objects) if relations[field_name]
|
70
55
|
end
|
71
56
|
|
72
57
|
##
|
@@ -84,8 +69,6 @@ module Ladder
|
|
84
69
|
relation.class_name.constantize
|
85
70
|
end
|
86
71
|
|
87
|
-
private
|
88
|
-
|
89
72
|
##
|
90
73
|
# Retrieve the attribute name for a field or relation,
|
91
74
|
# based on its defined RDF predicate
|
@@ -99,23 +82,87 @@ module Ladder
|
|
99
82
|
defined_prop.first
|
100
83
|
end
|
101
84
|
|
85
|
+
private
|
86
|
+
|
102
87
|
##
|
103
|
-
#
|
88
|
+
# Set values on a defined relation
|
104
89
|
#
|
105
90
|
# @param [String] field_name ActiveModel attribute name for the field
|
106
|
-
# @
|
107
|
-
|
91
|
+
# @param [Array<Object>] obj objects (usually Ladder::Resources) to be set
|
92
|
+
# @return [Ladder::Resource, nil]
|
93
|
+
def update_relation(field_name, *obj)
|
94
|
+
# Should be an Array of RDF::Term objects
|
95
|
+
return unless obj
|
96
|
+
|
97
|
+
obj.map! { |item| item.is_a?(RDF::URI) ? Ladder::Resource.from_uri(item) : item }
|
98
|
+
relation = send(field_name)
|
99
|
+
|
100
|
+
if Mongoid::Relations::Targets::Enumerable == relation.class
|
101
|
+
obj.map { |item| relation.send(:push, item) unless relation.include? item }
|
102
|
+
else
|
103
|
+
send("#{field_name}=", obj.size > 1 ? obj : obj.first)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
##
|
108
|
+
# Set values on a field; this will cast values
|
109
|
+
# from RDF types to persistable Mongoid types
|
110
|
+
#
|
111
|
+
# @param [String] field_name ActiveModel attribute name for the field
|
112
|
+
# @param [Array<Object>] obj objects (usually RDF::Terms) to be set
|
113
|
+
# @return [Object, nil]
|
114
|
+
def update_field(field_name, *obj)
|
115
|
+
# Should be an Array of RDF::Term objects
|
116
|
+
return unless obj
|
117
|
+
|
108
118
|
if fields[field_name].localized?
|
109
|
-
|
119
|
+
trans = {}
|
110
120
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
end
|
121
|
+
obj.each do |item|
|
122
|
+
lang = item.is_a?(RDF::Literal) && item.has_language? ? item.language.to_s : I18n.locale.to_s
|
123
|
+
value = item.is_a?(RDF::URI) ? item.to_s : item.object # TODO: tidy this up
|
124
|
+
trans[lang] = trans[lang] ? [*trans[lang]] << value : value
|
116
125
|
end
|
126
|
+
|
127
|
+
send("#{field_name}_translations=", trans) unless trans.empty?
|
117
128
|
else
|
118
|
-
|
129
|
+
objects = obj.map { |item| item.is_a?(RDF::URI) ? item.to_s : item.object } # TODO: tidy this up
|
130
|
+
send("#{field_name}=", objects.size > 1 ? objects : objects.first)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
##
|
135
|
+
# Cast values from Mongoid types to RDF types
|
136
|
+
#
|
137
|
+
# @param [Object] value ActiveModel attribute to be cast
|
138
|
+
# @param [Hash] opts options to pass to RDF::Literal
|
139
|
+
# @return [RDF::Term]
|
140
|
+
def cast_value(value, opts = {})
|
141
|
+
case value
|
142
|
+
when Array
|
143
|
+
value.map { |v| cast_value(v, opts) }
|
144
|
+
when String
|
145
|
+
cast_uri = RDF::URI.new(value)
|
146
|
+
cast_uri.valid? ? cast_uri : RDF::Literal.new(value, opts)
|
147
|
+
when Time
|
148
|
+
# Cast DateTimes with 00:00:00 or Date stored as Times in Mongoid to xsd:date
|
149
|
+
# FIXME: this should NOT be applied for fields that are typed as Time
|
150
|
+
value.midnight == value ? RDF::Literal.new(value.to_date) : RDF::Literal.new(value.to_datetime)
|
151
|
+
else
|
152
|
+
RDF::Literal.new(value, opts)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
##
|
157
|
+
# Update the delegated ActiveTriples::Resource from a field
|
158
|
+
#
|
159
|
+
# @param [String] field_name ActiveModel attribute name for the field
|
160
|
+
# @return [Object]
|
161
|
+
def update_from_field(field_name)
|
162
|
+
if fields[field_name].localized?
|
163
|
+
read_attribute(field_name).to_a.map { |lang, value| cast_value(value, language: lang) }.flatten
|
164
|
+
else
|
165
|
+
cast_value send(field_name)
|
119
166
|
end
|
120
167
|
end
|
121
168
|
|
@@ -124,7 +171,7 @@ module Ladder
|
|
124
171
|
#
|
125
172
|
# @param [String] field_name ActiveModel attribute name for the relation
|
126
173
|
# @param [Boolean] related whether to include related objects
|
127
|
-
# @return [
|
174
|
+
# @return [Enumerable]
|
128
175
|
def update_from_relation(field_name, related = false)
|
129
176
|
objects = send(field_name).to_a
|
130
177
|
|
@@ -133,8 +180,8 @@ module Ladder
|
|
133
180
|
methods.select { |i| i[/autosave_documents/] }.each { |m| send m }
|
134
181
|
|
135
182
|
# update inverse relation properties
|
136
|
-
|
137
|
-
objects.each { |object| object.resource.set_value(
|
183
|
+
relation = relations[field_name]
|
184
|
+
objects.each { |object| object.resource.set_value(relation.inverse, rdf_subject) } if relation.inverse
|
138
185
|
objects.map(&:update_resource)
|
139
186
|
else
|
140
187
|
# remove inverse relation properties
|
@@ -159,9 +206,12 @@ module Ladder
|
|
159
206
|
def property(field_name, opts = {})
|
160
207
|
if opts[:class_name]
|
161
208
|
mongoid_opts = { autosave: true, index: true }.merge(opts.except(:predicate, :multivalue))
|
162
|
-
|
209
|
+
# TODO: add/fix tests for this behaviour when true
|
210
|
+
mongoid_opts[:inverse_of] = nil if Ladder::Config.settings[:one_sided_relations]
|
211
|
+
|
212
|
+
has_and_belongs_to_many(field_name, mongoid_opts) unless relations[field_name.to_s]
|
163
213
|
else
|
164
|
-
mongoid_opts = { localize:
|
214
|
+
mongoid_opts = { localize: Ladder::Config.settings[:base_uri] }.merge(opts.except(:predicate, :multivalue))
|
165
215
|
field(field_name, mongoid_opts) unless fields[field_name.to_s]
|
166
216
|
end
|
167
217
|
|
@@ -180,7 +230,8 @@ module Ladder
|
|
180
230
|
#
|
181
231
|
# As nodes are traversed in the graph, the instantiated objects
|
182
232
|
# will be added to a Hash that is passed recursively, in order to
|
183
|
-
# prevent infinite traversal in the case of cyclic graphs.
|
233
|
+
# prevent infinite traversal in the case of cyclic graphs (ie.
|
234
|
+
# mark-and-sweep graph traversal).
|
184
235
|
#
|
185
236
|
# @param [RDF::Graph] graph an RDF::Graph to traverse
|
186
237
|
# @param [Hash] objects a keyed Hash of already-created objects in the graph
|
@@ -200,30 +251,50 @@ module Ladder
|
|
200
251
|
# Add object to stack for recursion
|
201
252
|
objects[root_subject] = new_object
|
202
253
|
|
203
|
-
graph.query([root_subject])
|
204
|
-
next if objects[statement.object]
|
254
|
+
subgraph = graph.query([root_subject])
|
205
255
|
|
206
|
-
|
207
|
-
#
|
208
|
-
|
256
|
+
subgraph.each_statement do |statement|
|
257
|
+
# Group statements for this predicate
|
258
|
+
stmts = subgraph.query([root_subject, statement.predicate])
|
209
259
|
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
next unless klass
|
260
|
+
if stmts.size > 1
|
261
|
+
# We have already set this value in a prior pass
|
262
|
+
next if new_object.read_attribute new_object.field_from_predicate statement.predicate
|
214
263
|
|
215
|
-
|
216
|
-
|
264
|
+
# If there are multiple statements for this predicate, pass an array
|
265
|
+
statement.object = RDF::Node.new
|
266
|
+
new_object.send(:<<, statement) { stmts.objects.to_a } # TODO: implement #set_value instead
|
217
267
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
268
|
+
elsif statement.object.is_a? RDF::Node
|
269
|
+
next if objects[statement.object]
|
270
|
+
|
271
|
+
# If the object is a BNode, dereference the relation
|
272
|
+
objects[statement.object] = new_object.send(:<<, statement) do # TODO: implement #set_value instead
|
273
|
+
klass = new_object.klass_from_predicate(statement.predicate)
|
274
|
+
klass.new_from_graph(graph, objects, [statement.object]) if klass
|
275
|
+
end
|
276
|
+
|
277
|
+
else new_object << statement
|
222
278
|
end
|
223
279
|
end # end each_statement
|
224
280
|
|
225
281
|
new_object
|
226
282
|
end
|
283
|
+
|
284
|
+
protected
|
285
|
+
|
286
|
+
##
|
287
|
+
# Propagate base URI and properties to subclasses
|
288
|
+
#
|
289
|
+
# @return [void]
|
290
|
+
def inherited(subclass)
|
291
|
+
# Copy properties from parent to subclass
|
292
|
+
resource_class.properties.each do |_name, config|
|
293
|
+
subclass.property config.term, predicate: config.predicate, class_name: config.class_name
|
294
|
+
end
|
295
|
+
|
296
|
+
subclass.configure_model
|
297
|
+
end
|
227
298
|
end
|
228
299
|
|
229
300
|
##
|
@@ -236,14 +307,13 @@ module Ladder
|
|
236
307
|
# @param [RDF::URI] uri RDF subject URI for the resource
|
237
308
|
# @return [Ladder::Resource] a resource instance
|
238
309
|
def self.from_uri(uri)
|
239
|
-
|
240
|
-
klass = klasses.find { |k| uri.to_s.include? k.base_uri.to_s }
|
310
|
+
klass = Ladder::Config.models.find { |k| uri.to_s.include? k.resource_class.base_uri.to_s }
|
241
311
|
|
242
312
|
if klass
|
243
313
|
object_id = uri.to_s.match(/[0-9a-fA-F]{24}/).to_s
|
244
314
|
|
245
315
|
# Retrieve the object if it's persisted, otherwise return a new one (eg. embedded)
|
246
|
-
return klass.
|
316
|
+
return klass.where(id: object_id).exists? ? klass.find(object_id) : klass.new
|
247
317
|
end
|
248
318
|
end
|
249
319
|
end
|