ladder 0.3.1 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +4 -1
- data/.semver +5 -0
- data/Gemfile +1 -1
- data/README.md +11 -11
- data/Rakefile +1 -2
- data/ladder.gemspec +26 -23
- data/lib/ladder.rb +1 -1
- data/lib/ladder/file.rb +49 -40
- data/lib/ladder/resource.rb +204 -60
- data/lib/ladder/resource/dynamic.rb +134 -83
- data/lib/ladder/resource/serializable.rb +56 -43
- data/lib/ladder/searchable.rb +14 -12
- data/lib/ladder/searchable/background.rb +40 -31
- data/lib/ladder/searchable/file.rb +33 -26
- data/lib/ladder/searchable/resource.rb +26 -15
- data/lib/ladder/version.rb +2 -2
- data/spec/ladder/file_spec.rb +9 -7
- data/spec/ladder/resource/dynamic_spec.rb +13 -143
- data/spec/ladder/resource_spec.rb +47 -226
- data/spec/ladder/searchable/background_spec.rb +37 -42
- data/spec/ladder/searchable/file_spec.rb +8 -5
- data/spec/ladder/searchable/resource_spec.rb +30 -38
- data/spec/shared/file.rb +9 -9
- data/spec/shared/graph.jsonld +31 -0
- data/spec/shared/resource.rb +397 -14
- data/spec/shared/searchable/file.rb +2 -4
- data/spec/shared/searchable/resource.rb +137 -145
- data/spec/spec_helper.rb +3 -2
- metadata +49 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 94262dd2e8eb69badfa00eeb2671a19f8637614c
|
4
|
+
data.tar.gz: 0d2b3f22370d591e2c0aa12ccb803156c9712ba6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f8afcec27c1a5f503137ebbfba804f7392fed957a62058d2d1c2a3632121b72607cfb96bc2ef44ebc55049666fe55bc45620de08f93fc1b2b308303191ae51fe
|
7
|
+
data.tar.gz: 2c7025c69534858a30b0c5717bf4575a98f3800f85b92111100f28185b6a891394066eab0bbd75f348658e837d836edcf18b5b58e5b188036878d9366adf4459
|
data/.gitignore
CHANGED
data/.semver
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -64,7 +64,7 @@ end
|
|
64
64
|
|
65
65
|
steve = Person.new(first_name: 'Steve', description: 'Funny-looking')
|
66
66
|
=> #<Person _id: 542f0c124169720ea0000000, first_name: {"en"=>"Steve"}, description: {"en"=>"Funny-looking"}>
|
67
|
-
|
67
|
+
|
68
68
|
steve.as_document
|
69
69
|
=> {"_id"=>BSON::ObjectId('542f0c124169720ea0000000'),
|
70
70
|
"first_name"=>{"en"=>"Steve"},
|
@@ -190,7 +190,7 @@ class Place
|
|
190
190
|
|
191
191
|
property :city, predicate: RDF::VCARD.locality
|
192
192
|
property :country, predicate: RDF::VCARD.send('country-name')
|
193
|
-
|
193
|
+
|
194
194
|
embedded_in :resident, class_name: 'Person', inverse_of: :address
|
195
195
|
property :resident, predicate: RDF::VCARD.agent
|
196
196
|
end
|
@@ -448,8 +448,8 @@ results = Person.search 'shay'
|
|
448
448
|
# #<Elasticsearch::Model::Searching::SearchRequest:0x007fa2ca830a58
|
449
449
|
# @definition={:index=>"people", :type=>"person", :q=>"Shay"},
|
450
450
|
# @klass=[PROXY] Person,
|
451
|
-
# @
|
452
|
-
|
451
|
+
# @params={}>>
|
452
|
+
|
453
453
|
results.count
|
454
454
|
=> 1
|
455
455
|
|
@@ -677,8 +677,8 @@ results = OCR.search 'Moomintroll'
|
|
677
677
|
# #<Elasticsearch::Model::Searching::SearchRequest:0x007fa2ca830a58
|
678
678
|
# @definition={:index=>"ocrs", :type=>"ocr", :q=>"Moomintroll"},
|
679
679
|
# @klass=[PROXY] OCR,
|
680
|
-
# @
|
681
|
-
|
680
|
+
# @params={}>>
|
681
|
+
|
682
682
|
results.count
|
683
683
|
=> 1
|
684
684
|
|
@@ -708,8 +708,8 @@ results = OCR.search 'Moomintroll', fields: '*'
|
|
708
708
|
# #<Elasticsearch::Model::Searching::SearchRequest:0x007fc36cadab10
|
709
709
|
# @definition={:index=>"ocrs", :type=>"ocr", :body=>{:query=>{:query_string=>{:query=>"Moomintroll"}}, :fields=>"*"}},
|
710
710
|
# @klass=[PROXY] OCR,
|
711
|
-
# @
|
712
|
-
|
711
|
+
# @params={}>>
|
712
|
+
|
713
713
|
results.count
|
714
714
|
=> 1
|
715
715
|
|
@@ -734,8 +734,8 @@ results = OCR.search query: { query_string: { query: 'his' } }, highlight: { fie
|
|
734
734
|
# @definition={:index=>"ocrs", :type=>"ocr", :body=>{:query=>{:query_string=>"Moomintroll"},
|
735
735
|
# :highlight=>{:fields=>{:file=>{}}}}},
|
736
736
|
# @klass=[PROXY] OCR,
|
737
|
-
# @
|
738
|
-
|
737
|
+
# @params={}>>
|
738
|
+
|
739
739
|
results.count
|
740
740
|
=> 1
|
741
741
|
|
@@ -760,7 +760,7 @@ class OCR
|
|
760
760
|
end
|
761
761
|
|
762
762
|
# ...
|
763
|
-
|
763
|
+
|
764
764
|
class Person
|
765
765
|
include Ladder::Resource
|
766
766
|
include Ladder::Searchable::Background
|
data/Rakefile
CHANGED
@@ -1,2 +1 @@
|
|
1
|
-
require
|
2
|
-
|
1
|
+
require 'bundler/gem_tasks'
|
data/ladder.gemspec
CHANGED
@@ -4,33 +4,36 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
require 'ladder/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name =
|
7
|
+
spec.name = 'ladder'
|
8
8
|
spec.version = Ladder::VERSION
|
9
9
|
spec.platform = Gem::Platform::RUBY
|
10
|
-
spec.authors =
|
11
|
-
spec.email =
|
12
|
-
spec.summary =
|
13
|
-
spec.description =
|
14
|
-
spec.homepage =
|
15
|
-
spec.license =
|
10
|
+
spec.authors = 'MJ Suhonos'
|
11
|
+
spec.email = 'mj@suhonos.ca'
|
12
|
+
spec.summary = 'ActiveModel Linked Data framework.'
|
13
|
+
spec.description = 'Dynamic framework for Linked Data modelling, persistence, and full-text indexing.'
|
14
|
+
spec.homepage = 'https://github.com/ladder/ladder'
|
15
|
+
spec.license = 'APACHE2'
|
16
16
|
spec.required_ruby_version = '>= 2.0.0'
|
17
17
|
|
18
18
|
spec.files = `git ls-files -z`.split("\x0")
|
19
|
-
spec.executables = spec.files.grep(
|
20
|
-
spec.test_files = spec.files.grep(
|
21
|
-
spec.require_paths = [
|
19
|
+
spec.executables = spec.files.grep(/^bin/) { |f| File.basename(f) }
|
20
|
+
spec.test_files = spec.files.grep(/^(test|spec|features)/)
|
21
|
+
spec.require_paths = ['lib']
|
22
22
|
|
23
|
-
spec.add_dependency
|
24
|
-
spec.add_dependency
|
25
|
-
spec.add_dependency
|
26
|
-
spec.add_dependency
|
27
|
-
spec.add_dependency
|
23
|
+
spec.add_dependency 'active-triples', '~> 0.6'
|
24
|
+
spec.add_dependency 'activejob', '~> 4.2'
|
25
|
+
spec.add_dependency 'elasticsearch-model', '~> 0.1'
|
26
|
+
spec.add_dependency 'mongoid', '~> 4.0'
|
27
|
+
spec.add_dependency 'mongoid-grid_fs', '~> 2.1'
|
28
28
|
|
29
|
-
spec.add_development_dependency
|
30
|
-
spec.add_development_dependency
|
31
|
-
spec.add_development_dependency
|
32
|
-
spec.add_development_dependency
|
33
|
-
spec.add_development_dependency
|
34
|
-
spec.add_development_dependency
|
35
|
-
spec.add_development_dependency
|
36
|
-
|
29
|
+
spec.add_development_dependency 'awesome_print', '~> 1.6'
|
30
|
+
spec.add_development_dependency 'bundler', '~> 1.7'
|
31
|
+
spec.add_development_dependency 'mimemagic', '~> 0.2'
|
32
|
+
spec.add_development_dependency 'pry', '~> 0.10'
|
33
|
+
spec.add_development_dependency 'rspec', '~> 3.2'
|
34
|
+
spec.add_development_dependency 'rubocop', '~> 0.29'
|
35
|
+
spec.add_development_dependency 'semver', '~> 1.0'
|
36
|
+
spec.add_development_dependency 'simplecov', '~> 0.9'
|
37
|
+
spec.add_development_dependency 'wirble', '~> 0.1'
|
38
|
+
spec.add_development_dependency 'yard', '~> 0.8'
|
39
|
+
end
|
data/lib/ladder.rb
CHANGED
data/lib/ladder/file.rb
CHANGED
@@ -1,62 +1,71 @@
|
|
1
1
|
require 'mongoid/grid_fs'
|
2
2
|
require 'active_triples'
|
3
3
|
|
4
|
-
module Ladder
|
5
|
-
|
4
|
+
module Ladder
|
5
|
+
module File
|
6
|
+
extend ActiveSupport::Concern
|
6
7
|
|
7
|
-
|
8
|
-
|
8
|
+
include Mongoid::Document
|
9
|
+
include ActiveTriples::Identifiable
|
9
10
|
|
10
|
-
|
11
|
-
|
11
|
+
included do
|
12
|
+
configure base_uri: RDF::URI.new(LADDER_BASE_URI) / name.underscore.pluralize if defined? LADDER_BASE_URI
|
12
13
|
|
13
|
-
|
14
|
+
store_in collection: "#{ grid.prefix }.files"
|
14
15
|
|
15
|
-
|
16
|
-
|
16
|
+
# Define accessor methods for attributes
|
17
|
+
define_method(:content_type) { read_attribute(:contentType) }
|
17
18
|
|
18
|
-
|
19
|
-
|
19
|
+
grid::File.fields.keys.map(&:to_sym).each do |attr|
|
20
|
+
define_method(attr) { read_attribute(attr) }
|
21
|
+
end
|
22
|
+
|
23
|
+
around_save :save_file
|
20
24
|
end
|
21
25
|
|
22
|
-
|
23
|
-
end
|
26
|
+
attr_accessor :file
|
24
27
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
28
|
+
##
|
29
|
+
# Output content of object from stored file or readable input
|
30
|
+
#
|
31
|
+
# @return [String] string-encoded copy of binary data
|
32
|
+
def data
|
33
|
+
@grid_file ||= self.class.grid.get(id) if persisted?
|
34
|
+
return @grid_file.data if @grid_file
|
32
35
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
resource
|
41
|
-
|
42
|
-
|
43
|
-
|
36
|
+
file.rewind if file.respond_to? :rewind
|
37
|
+
file.read
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# Return an empty ActiveTriples resource for serializing related resources
|
42
|
+
#
|
43
|
+
# @return [ActiveTriples::Resource] resource for the object
|
44
|
+
def update_resource
|
45
|
+
resource
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
44
49
|
|
45
50
|
##
|
46
51
|
# Make save behave like Mongoid::Document as much as possible
|
47
|
-
|
52
|
+
#
|
53
|
+
# @return result of #run_callbacks, @see ActiveSupport::Callbacks
|
54
|
+
def save_file
|
48
55
|
attributes[:content_type] = file.content_type if file.respond_to? :content_type
|
49
|
-
@grid_file ? @grid_file.save :
|
56
|
+
@grid_file ? @grid_file.save : @grid_file = self.class.grid.put(file, attributes.symbolize_keys)
|
50
57
|
|
51
58
|
persisted? ? run_callbacks(:update) : run_callbacks(:create)
|
52
59
|
end
|
53
60
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
61
|
+
module ClassMethods
|
62
|
+
##
|
63
|
+
# Create a namespaced GridFS module for this class
|
64
|
+
#
|
65
|
+
# @return [Module] a Mongoid::GridFs module for this class
|
66
|
+
def grid
|
67
|
+
@grid ||= Mongoid::GridFs.build_namespace_for name
|
68
|
+
end
|
59
69
|
end
|
60
70
|
end
|
61
|
-
|
62
|
-
end
|
71
|
+
end
|
data/lib/ladder/resource.rb
CHANGED
@@ -1,56 +1,112 @@
|
|
1
1
|
require 'mongoid'
|
2
2
|
require 'active_triples'
|
3
3
|
|
4
|
-
module Ladder
|
5
|
-
|
6
|
-
|
4
|
+
module Ladder
|
5
|
+
module Resource
|
6
|
+
autoload :Dynamic, 'ladder/resource/dynamic'
|
7
|
+
autoload :Serializable, 'ladder/resource/serializable'
|
7
8
|
|
8
|
-
|
9
|
+
extend ActiveSupport::Concern
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
11
|
+
include Mongoid::Document
|
12
|
+
include ActiveTriples::Identifiable
|
13
|
+
include Ladder::Resource::Serializable
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
15
|
+
included do
|
16
|
+
configure base_uri: RDF::URI.new(LADDER_BASE_URI) / name.underscore.pluralize if defined? LADDER_BASE_URI
|
17
|
+
end
|
17
18
|
|
18
|
-
|
19
|
+
delegate :rdf_label, to: :update_resource
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
21
|
+
##
|
22
|
+
# Update the delegated ActiveTriples::Resource from
|
23
|
+
# ActiveModel properties & relations
|
24
|
+
#
|
25
|
+
# @param [Hash] opts options to pass to Mongoid / ActiveTriples
|
26
|
+
# @option opts [Boolean] :related whether to include related resources
|
27
|
+
# @return [ActiveTriples::Resource] resource for the object
|
28
|
+
def update_resource(opts = {})
|
29
|
+
resource_class.properties.each do |field_name, property|
|
30
|
+
value = update_from_field(field_name) if fields[field_name]
|
31
|
+
value = update_from_relation(field_name, opts[:related]) if relations[field_name]
|
32
|
+
|
33
|
+
resource.set_value(property.predicate, value) # if value
|
34
|
+
end
|
26
35
|
|
27
|
-
resource
|
36
|
+
resource
|
28
37
|
end
|
29
38
|
|
30
|
-
|
31
|
-
|
39
|
+
##
|
40
|
+
# Push an RDF::Statement into the object
|
41
|
+
#
|
42
|
+
# @param [RDF::Statement, Hash, Array] statement @see RDF::Statement#from
|
43
|
+
# @return [void]
|
44
|
+
def <<(statement)
|
45
|
+
# ActiveTriples::Resource expects: RDF::Statement, Hash, or Array
|
46
|
+
statement = RDF::Statement.from(statement) unless statement.is_a? RDF::Statement
|
47
|
+
|
48
|
+
# Only push statement if the statement's predicate is defined on the class
|
49
|
+
field_name = field_from_predicate(statement.predicate)
|
50
|
+
return unless field_name
|
51
|
+
|
52
|
+
# If the object is a URI, see if it is a retrievable model object
|
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
|
62
|
+
|
63
|
+
enum = send(field_name)
|
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
|
70
|
+
end
|
32
71
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
#
|
37
|
-
|
72
|
+
##
|
73
|
+
# Retrieve the class for a relation, based on its defined RDF predicate
|
74
|
+
#
|
75
|
+
# @param [RDF::URI] predicate a URI for the RDF::Term
|
76
|
+
# @return [Ladder::Resource, Ladder::File, nil] related class
|
77
|
+
def klass_from_predicate(predicate)
|
78
|
+
field_name = field_from_predicate(predicate)
|
79
|
+
return unless field_name
|
38
80
|
|
39
|
-
|
40
|
-
|
41
|
-
field_name = resource_class.properties.select { |name, term| term.predicate == data.predicate }.keys.first.to_sym
|
81
|
+
relation = relations[field_name]
|
82
|
+
return unless relation
|
42
83
|
|
43
|
-
|
44
|
-
value = data.object.is_a?(RDF::Literal) ? data.object.object : data.object.to_s
|
45
|
-
self.send("#{field_name}=", value)
|
84
|
+
relation.class_name.constantize
|
46
85
|
end
|
47
|
-
end
|
48
86
|
|
49
|
-
|
87
|
+
private
|
88
|
+
|
89
|
+
##
|
90
|
+
# Retrieve the attribute name for a field or relation,
|
91
|
+
# based on its defined RDF predicate
|
92
|
+
#
|
93
|
+
# @param [RDF::URI] predicate a URI for the RDF::Term
|
94
|
+
# @return [String, nil] name for the attribute
|
95
|
+
def field_from_predicate(predicate)
|
96
|
+
defined_prop = resource_class.properties.find { |_field_name, term| term.predicate == predicate }
|
97
|
+
return unless defined_prop
|
98
|
+
|
99
|
+
defined_prop.first
|
100
|
+
end
|
50
101
|
|
51
|
-
|
52
|
-
|
53
|
-
|
102
|
+
##
|
103
|
+
# Update the delegated ActiveTriples::Resource from a field
|
104
|
+
#
|
105
|
+
# @param [String] field_name ActiveModel attribute name for the field
|
106
|
+
# @return [void]
|
107
|
+
def update_from_field(field_name)
|
108
|
+
if fields[field_name].localized?
|
109
|
+
localized_hash = read_attribute(field_name)
|
54
110
|
|
55
111
|
unless localized_hash.nil?
|
56
112
|
localized_hash.map do |lang, value|
|
@@ -59,20 +115,26 @@ module Ladder::Resource
|
|
59
115
|
end
|
60
116
|
end
|
61
117
|
else
|
62
|
-
|
118
|
+
send(field_name)
|
63
119
|
end
|
64
120
|
end
|
65
|
-
|
66
|
-
def update_from_relation(name, opts = {})
|
67
|
-
objects = self.send(name).to_a
|
68
121
|
|
69
|
-
|
122
|
+
##
|
123
|
+
# Update the delegated ActiveTriples::Resource from a relation
|
124
|
+
#
|
125
|
+
# @param [String] field_name ActiveModel attribute name for the relation
|
126
|
+
# @param [Boolean] related whether to include related objects
|
127
|
+
# @return [void]
|
128
|
+
def update_from_relation(field_name, related = false)
|
129
|
+
objects = send(field_name).to_a
|
130
|
+
|
131
|
+
if related || embedded_relations[field_name]
|
70
132
|
# Force autosave of related documents to ensure correct serialization
|
71
|
-
methods.select{|i| i[/autosave_documents/] }.each{|m| send m}
|
133
|
+
methods.select { |i| i[/autosave_documents/] }.each { |m| send m }
|
72
134
|
|
73
135
|
# update inverse relation properties
|
74
|
-
relation_def = relations[
|
75
|
-
objects.each { |object| object.resource.set_value(relation_def.inverse,
|
136
|
+
relation_def = relations[field_name]
|
137
|
+
objects.each { |object| object.resource.set_value(relation_def.inverse, rdf_subject) } if relation_def.inverse
|
76
138
|
objects.map(&:update_resource)
|
77
139
|
else
|
78
140
|
# remove inverse relation properties
|
@@ -81,26 +143,108 @@ module Ladder::Resource
|
|
81
143
|
end
|
82
144
|
end
|
83
145
|
|
84
|
-
|
146
|
+
public
|
147
|
+
|
148
|
+
module ClassMethods
|
149
|
+
##
|
150
|
+
# Define a Mongoid field/relation on the class as well as
|
151
|
+
# an RDF property on the delegated resource
|
152
|
+
#
|
153
|
+
# @see ActiveTriples::Resource#property
|
154
|
+
# @see ActiveTriples::Properties
|
155
|
+
#
|
156
|
+
# @param [String] field_name ActiveModel attribute name for the field
|
157
|
+
# @param [Hash] opts options to pass to Mongoid / ActiveTriples
|
158
|
+
# @return [ActiveTriples::Resource] a modified resource
|
159
|
+
def property(field_name, opts = {})
|
160
|
+
if opts[:class_name]
|
161
|
+
mongoid_opts = { autosave: true, index: true }.merge(opts.except(:predicate, :multivalue))
|
162
|
+
has_and_belongs_to_many(field_name, mongoid_opts) unless relations.keys.include? field_name.to_s
|
163
|
+
else
|
164
|
+
mongoid_opts = { localize: true }.merge(opts.except(:predicate, :multivalue))
|
165
|
+
field(field_name, mongoid_opts) unless fields[field_name.to_s]
|
166
|
+
end
|
167
|
+
|
168
|
+
opts.except!(*mongoid_opts.keys)
|
169
|
+
|
170
|
+
super
|
171
|
+
end
|
172
|
+
|
173
|
+
##
|
174
|
+
# Create a new instance of this class, populated with values
|
175
|
+
# and related objects from a given RDF::Graph for this model.
|
176
|
+
#
|
177
|
+
# By default, the graph will be traversed starting with the first
|
178
|
+
# node that matches the same RDF.type as this class; however, an
|
179
|
+
# optional RDF::Queryable pattern can be provided, @see RDF::Queryable#query
|
180
|
+
#
|
181
|
+
# As nodes are traversed in the graph, the instantiated objects
|
182
|
+
# will be added to a Hash that is passed recursively, in order to
|
183
|
+
# prevent infinite traversal in the case of cyclic graphs.
|
184
|
+
#
|
185
|
+
# @param [RDF::Graph] graph an RDF::Graph to traverse
|
186
|
+
# @param [Hash] objects a keyed Hash of already-created objects in the graph
|
187
|
+
# @param [RDF::Query, RDF::Statement, Array(RDF::Term), Hash] pattern a query pattern
|
188
|
+
# @return [Ladder::Resource, nil] an instance of this class
|
189
|
+
def new_from_graph(graph, objects = {}, pattern = nil)
|
190
|
+
# Default to getting the first object in the graph with the same RDF type as this class
|
191
|
+
pattern ||= [nil, RDF.type, resource_class.type]
|
192
|
+
|
193
|
+
root_subject = graph.query(pattern).first_subject
|
194
|
+
return unless root_subject
|
195
|
+
|
196
|
+
# If the subject is an existing model, just retrieve it
|
197
|
+
new_object = Ladder::Resource.from_uri(root_subject) if root_subject.is_a? RDF::URI
|
198
|
+
new_object ||= new
|
199
|
+
|
200
|
+
# Add object to stack for recursion
|
201
|
+
objects[root_subject] = new_object
|
202
|
+
|
203
|
+
graph.query([root_subject]).each_statement do |statement|
|
204
|
+
next if objects[statement.object]
|
205
|
+
|
206
|
+
# TODO: If the object is a list, process members individually
|
207
|
+
# list = RDF::List.new statement.object, graph
|
208
|
+
# binding.pry unless list.empty?
|
209
|
+
|
210
|
+
# If the object is a BNode, dereference the relation
|
211
|
+
if statement.object.is_a? RDF::Node
|
212
|
+
klass = new_object.klass_from_predicate(statement.predicate)
|
213
|
+
next unless klass
|
214
|
+
|
215
|
+
object = klass.new_from_graph(graph, objects)
|
216
|
+
next unless object
|
217
|
+
|
218
|
+
objects[statement.object] = object
|
219
|
+
new_object.send(:<<, statement) { object }
|
220
|
+
else
|
221
|
+
new_object << statement
|
222
|
+
end
|
223
|
+
end # end each_statement
|
224
|
+
|
225
|
+
new_object
|
226
|
+
end
|
227
|
+
end
|
85
228
|
|
86
|
-
module ClassMethods
|
87
|
-
|
88
229
|
##
|
89
|
-
#
|
230
|
+
# Return a persisted instance of a Ladder::Resource from its
|
231
|
+
# RDF subject URI, without knowing the resource class.
|
232
|
+
#
|
233
|
+
# If there is no persisted instance for the URI, but the class
|
234
|
+
# is identifiable, then return a new instance of that class
|
90
235
|
#
|
91
|
-
# @
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
236
|
+
# @param [RDF::URI] uri RDF subject URI for the resource
|
237
|
+
# @return [Ladder::Resource] a resource instance
|
238
|
+
def self.from_uri(uri)
|
239
|
+
klasses = ActiveTriples::Resource.descendants.select(&:name)
|
240
|
+
klass = klasses.find { |k| uri.to_s.include? k.base_uri.to_s }
|
96
241
|
|
97
|
-
|
98
|
-
|
99
|
-
field(name, localize: true) unless fields[name.to_s]
|
100
|
-
end
|
242
|
+
if klass
|
243
|
+
object_id = uri.to_s.match(/[0-9a-fA-F]{24}/).to_s
|
101
244
|
|
102
|
-
|
245
|
+
# Retrieve the object if it's persisted, otherwise return a new one (eg. embedded)
|
246
|
+
return klass.parent.where(id: object_id).exists? ? klass.parent.find(object_id) : klass.parent.new
|
247
|
+
end
|
103
248
|
end
|
104
249
|
end
|
105
|
-
|
106
|
-
end
|
250
|
+
end
|