active-triples 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.travis.yml +12 -0
- data/AUTHORS +2 -0
- data/Gemfile +3 -0
- data/LICENSE +24 -0
- data/README.md +38 -0
- data/active-triples.gemspec +35 -0
- data/lib/active_triples.rb +29 -0
- data/lib/active_triples/configurable.rb +61 -0
- data/lib/active_triples/indexing.rb +76 -0
- data/lib/active_triples/list.rb +155 -0
- data/lib/active_triples/nested_attributes.rb +132 -0
- data/lib/active_triples/node_config.rb +56 -0
- data/lib/active_triples/properties.rb +96 -0
- data/lib/active_triples/repositories.rb +36 -0
- data/lib/active_triples/resource.rb +369 -0
- data/lib/active_triples/term.rb +188 -0
- data/lib/active_triples/version.rb +3 -0
- data/spec/rdf_list_spec.rb +183 -0
- data/spec/rdf_nested_attributes_spec.rb +183 -0
- data/spec/rdf_properties_spec.rb +81 -0
- data/spec/rdf_repositories_spec.rb +28 -0
- data/spec/rdf_resource_spec.rb +447 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/support/active_model_lint.rb +81 -0
- metadata +197 -0
@@ -0,0 +1,188 @@
|
|
1
|
+
module ActiveTriples
|
2
|
+
class Term
|
3
|
+
attr_accessor :parent, :value_arguments, :node_cache
|
4
|
+
delegate *(Array.public_instance_methods - [:__send__, :__id__, :class, :object_id] + [:as_json]), :to => :result
|
5
|
+
def initialize(parent, value_arguments)
|
6
|
+
self.parent = parent
|
7
|
+
self.value_arguments = value_arguments
|
8
|
+
end
|
9
|
+
|
10
|
+
def clear
|
11
|
+
set(nil)
|
12
|
+
end
|
13
|
+
|
14
|
+
def result
|
15
|
+
result = parent.query(:subject => rdf_subject, :predicate => predicate)
|
16
|
+
.map{|x| convert_object(x.object)}
|
17
|
+
.reject(&:nil?)
|
18
|
+
return result if !property_config || property_config[:multivalue]
|
19
|
+
result.first
|
20
|
+
end
|
21
|
+
|
22
|
+
def set(values)
|
23
|
+
values = [values].compact unless values.kind_of?(Array)
|
24
|
+
empty_property
|
25
|
+
values.each do |val|
|
26
|
+
set_value(val)
|
27
|
+
end
|
28
|
+
parent.persist! if parent.class.repository == :parent && parent.send(:repository)
|
29
|
+
end
|
30
|
+
|
31
|
+
def empty_property
|
32
|
+
parent.query([rdf_subject, predicate, nil]).each_statement do |statement|
|
33
|
+
if !uri_class(statement.object) || uri_class(statement.object) == class_for_property
|
34
|
+
parent.delete(statement)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def build(attributes={})
|
40
|
+
new_subject = attributes.key?('id') ? attributes.delete('id') : RDF::Node.new
|
41
|
+
node = make_node(new_subject)
|
42
|
+
node.attributes = attributes
|
43
|
+
if parent.kind_of? List::ListResource
|
44
|
+
parent.list << node
|
45
|
+
return node
|
46
|
+
elsif node.kind_of? RDF::List
|
47
|
+
self.push node.rdf_subject
|
48
|
+
return node
|
49
|
+
end
|
50
|
+
self.push node
|
51
|
+
node
|
52
|
+
end
|
53
|
+
|
54
|
+
def first_or_create(attributes={})
|
55
|
+
result.first || build(attributes)
|
56
|
+
end
|
57
|
+
|
58
|
+
def delete(*values)
|
59
|
+
values.each do |value|
|
60
|
+
parent.delete([rdf_subject, predicate, value])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def << (values)
|
65
|
+
values = Array.wrap(result) | Array.wrap(values)
|
66
|
+
self.set(values)
|
67
|
+
end
|
68
|
+
|
69
|
+
alias_method :push, :<<
|
70
|
+
|
71
|
+
def property_config
|
72
|
+
return type_property if (property == RDF.type || property.to_s == "type") && !parent.send(:properties)[property]
|
73
|
+
parent.send(:properties)[property]
|
74
|
+
end
|
75
|
+
|
76
|
+
def type_property
|
77
|
+
{:multivalue => true, :predicate => RDF.type}
|
78
|
+
end
|
79
|
+
|
80
|
+
def reset!
|
81
|
+
end
|
82
|
+
|
83
|
+
def property
|
84
|
+
value_arguments.last
|
85
|
+
end
|
86
|
+
|
87
|
+
def rdf_subject
|
88
|
+
raise ArgumentError("wrong number of arguments (#{value_arguments.length} for 1-2)") if value_arguments.length < 1 || value_arguments.length > 2
|
89
|
+
if value_arguments.length > 1
|
90
|
+
value_arguments.first
|
91
|
+
else
|
92
|
+
parent.rdf_subject
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
protected
|
97
|
+
|
98
|
+
def node_cache
|
99
|
+
@node_cache ||= {}
|
100
|
+
end
|
101
|
+
|
102
|
+
def set_value(val)
|
103
|
+
object = val
|
104
|
+
val = val.resource if val.respond_to?(:resource)
|
105
|
+
val = value_to_node(val)
|
106
|
+
if val.kind_of? Resource
|
107
|
+
node_cache[val.rdf_subject] = nil
|
108
|
+
add_child_node(val, object)
|
109
|
+
return
|
110
|
+
end
|
111
|
+
val = val.to_uri if val.respond_to? :to_uri
|
112
|
+
raise 'value must be an RDF URI, Node, Literal, or a valid datatype. See RDF::Literal' unless
|
113
|
+
val.kind_of? RDF::Value or val.kind_of? RDF::Literal
|
114
|
+
parent.insert [rdf_subject, predicate, val]
|
115
|
+
end
|
116
|
+
|
117
|
+
def value_to_node(val)
|
118
|
+
valid_datatype?(val) ? RDF::Literal(val) : val
|
119
|
+
end
|
120
|
+
|
121
|
+
def add_child_node(resource,object=nil)
|
122
|
+
parent.insert [rdf_subject, predicate, resource.rdf_subject]
|
123
|
+
resource.parent = parent unless resource.frozen?
|
124
|
+
self.node_cache[resource.rdf_subject] = (object ? object : resource)
|
125
|
+
resource.persist! if resource.class.repository == :parent
|
126
|
+
end
|
127
|
+
|
128
|
+
def predicate
|
129
|
+
return property_config[:predicate] unless property.kind_of? RDF::URI
|
130
|
+
property
|
131
|
+
end
|
132
|
+
|
133
|
+
def valid_datatype?(val)
|
134
|
+
val.is_a? String or val.is_a? Date or val.is_a? Time or val.is_a? Numeric or val.is_a? Symbol or val == !!val
|
135
|
+
end
|
136
|
+
|
137
|
+
# Converts an object to the appropriate class.
|
138
|
+
def convert_object(value)
|
139
|
+
value = value.object if value.kind_of? RDF::Literal
|
140
|
+
value = make_node(value) if value.kind_of? RDF::Resource
|
141
|
+
value
|
142
|
+
end
|
143
|
+
|
144
|
+
##
|
145
|
+
# Build a child resource or return it from this object's cache
|
146
|
+
#
|
147
|
+
# Builds the resource from the class_name specified for the
|
148
|
+
# property.
|
149
|
+
def make_node(value)
|
150
|
+
klass = class_for_value(value)
|
151
|
+
value = RDF::Node.new if value.nil?
|
152
|
+
node = node_cache[value] if node_cache[value]
|
153
|
+
node ||= klass.from_uri(value,parent)
|
154
|
+
return nil if (property_config && property_config[:class_name]) && (class_for_value(value) != class_for_property)
|
155
|
+
self.node_cache[value] ||= node
|
156
|
+
node
|
157
|
+
end
|
158
|
+
|
159
|
+
def final_parent
|
160
|
+
@final_parent ||= begin
|
161
|
+
parent = self.parent
|
162
|
+
while parent != parent.parent && parent.parent
|
163
|
+
parent = parent.parent
|
164
|
+
end
|
165
|
+
return parent.datastream if parent.respond_to?(:datastream) && parent.datastream
|
166
|
+
parent
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def class_for_value(v)
|
171
|
+
uri_class(v) || class_for_property
|
172
|
+
end
|
173
|
+
|
174
|
+
def uri_class(v)
|
175
|
+
v = RDF::URI.new(v) if v.kind_of? String
|
176
|
+
type_uri = parent.query([v, RDF.type, nil]).to_a.first.try(:object)
|
177
|
+
Resource.type_registry[type_uri]
|
178
|
+
end
|
179
|
+
|
180
|
+
def class_for_property
|
181
|
+
klass = property_config[:class_name] if property_config
|
182
|
+
klass ||= Resource
|
183
|
+
klass = ActiveTriples.class_from_string(klass, final_parent.class) if klass.kind_of? String
|
184
|
+
klass
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'nokogiri'
|
3
|
+
require 'linkeddata'
|
4
|
+
|
5
|
+
describe ActiveTriples::List do
|
6
|
+
before :each do
|
7
|
+
class MADS < RDF::Vocabulary("http://www.loc.gov/mads/rdf/v1#")
|
8
|
+
property :complexSubject
|
9
|
+
property :authoritativeLabel
|
10
|
+
property :elementList
|
11
|
+
property :elementValue
|
12
|
+
property :TopicElement
|
13
|
+
property :TemporalElement
|
14
|
+
end
|
15
|
+
class DemoList < ActiveTriples::Resource
|
16
|
+
property :elementList, :predicate => MADS.elementList, :class_name => 'DemoList::List'
|
17
|
+
class List < ActiveTriples::List
|
18
|
+
property :topicElement, :predicate => MADS.TopicElement, :class_name => 'DemoList::List::TopicElement'
|
19
|
+
property :temporalElement, :predicate => MADS.TemporalElement, :class_name => 'DemoList::List::TemporalElement'
|
20
|
+
|
21
|
+
class TopicElement < ActiveTriples::Resource
|
22
|
+
configure :type => MADS.TopicElement
|
23
|
+
property :elementValue, :predicate => MADS.elementValue
|
24
|
+
end
|
25
|
+
class TemporalElement < ActiveTriples::Resource
|
26
|
+
configure :type => MADS.TemporalElement
|
27
|
+
property :elementValue, :predicate => MADS.elementValue
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
after(:each) do
|
33
|
+
Object.send(:remove_const, :DemoList)
|
34
|
+
Object.send(:remove_const, :MADS)
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "a new list" do
|
38
|
+
let (:ds) { DemoList.new('http://example.org/foo') }
|
39
|
+
subject { ds.elementList.build}
|
40
|
+
|
41
|
+
it "should insert at the end" do
|
42
|
+
subject.should be_kind_of DemoList::List
|
43
|
+
subject.size.should == 0
|
44
|
+
subject[1] = DemoList::List::TopicElement.new
|
45
|
+
subject.size.should == 2
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should insert at the head" do
|
49
|
+
subject.should be_kind_of DemoList::List
|
50
|
+
subject.size.should == 0
|
51
|
+
subject[0] = DemoList::List::TopicElement.new
|
52
|
+
subject.size.should == 1
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "that has 4 elements" do
|
56
|
+
before do
|
57
|
+
subject[3] = DemoList::List::TopicElement.new
|
58
|
+
subject.size.should == 4
|
59
|
+
end
|
60
|
+
it "should insert in the middle" do
|
61
|
+
subject[1] = DemoList::List::TopicElement.new
|
62
|
+
subject.size.should == 4
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "return updated xml" do
|
67
|
+
it "should be built" do
|
68
|
+
subject[0] = RDF::URI.new "http://library.ucsd.edu/ark:/20775/bbXXXXXXX6"
|
69
|
+
subject[1] = DemoList::List::TopicElement.new
|
70
|
+
subject[1].elementValue = "Relations with Mexican Americans"
|
71
|
+
subject[2] = RDF::URI.new "http://library.ucsd.edu/ark:/20775/bbXXXXXXX4"
|
72
|
+
subject[3] = DemoList::List::TemporalElement.new
|
73
|
+
subject[3].elementValue = "20th century"
|
74
|
+
ds.elementList = subject
|
75
|
+
doc = Nokogiri::XML(ds.dump(:rdfxml))
|
76
|
+
ns = {rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#", mads: "http://www.loc.gov/mads/rdf/v1#"}
|
77
|
+
expect(doc.xpath('/rdf:RDF/rdf:Description/@rdf:about', ns).map(&:value)).to eq ["http://example.org/foo"]
|
78
|
+
expect(doc.xpath('//rdf:Description/mads:elementList/@rdf:parseType', ns).map(&:value)).to eq ["Collection"]
|
79
|
+
expect(doc.xpath('//rdf:Description/mads:elementList/*[position() = 1]/@rdf:about', ns).map(&:value)).to eq ["http://library.ucsd.edu/ark:/20775/bbXXXXXXX6"]
|
80
|
+
expect(doc.xpath('//rdf:Description/mads:elementList/*[position() = 2]/mads:elementValue', ns).map(&:text)).to eq ["Relations with Mexican Americans"]
|
81
|
+
expect(doc.xpath('//rdf:Description/mads:elementList/*[position() = 3]/@rdf:about', ns).map(&:value)).to eq ["http://library.ucsd.edu/ark:/20775/bbXXXXXXX4"]
|
82
|
+
expect(doc.xpath('//rdf:Description/mads:elementList/*[position() = 4]/mads:elementValue', ns).map(&:text)).to eq ["20th century"]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "an empty list" do
|
88
|
+
subject { DemoList.new.elementList.build }
|
89
|
+
it "should have to_ary" do
|
90
|
+
subject.to_ary.should == []
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "a list that has a constructed element" do
|
95
|
+
let(:ds) { DemoList.new('http://example.org/foo') }
|
96
|
+
let(:list) { ds.elementList.build }
|
97
|
+
let!(:topic) { list.topicElement.build }
|
98
|
+
|
99
|
+
it "should have to_ary" do
|
100
|
+
list.to_ary.size.should == 1
|
101
|
+
list.to_ary.first.class.should == DemoList::List::TopicElement
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should be able to be cleared" do
|
105
|
+
list.topicElement.build
|
106
|
+
list.topicElement.build
|
107
|
+
list.topicElement.build
|
108
|
+
list.size.should == 4
|
109
|
+
list.clear
|
110
|
+
list.size.should == 0
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe "a list with content" do
|
115
|
+
subject do
|
116
|
+
subject = DemoList.new('http://example.org/foo')
|
117
|
+
subject << RDF::RDFXML::Reader.for(:rdfxml).new(<<END
|
118
|
+
<rdf:RDF
|
119
|
+
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:mads="http://www.loc.gov/mads/rdf/v1#">
|
120
|
+
|
121
|
+
<mads:ComplexSubject rdf:about="http://example.org/foo">
|
122
|
+
<mads:elementList rdf:parseType="Collection">
|
123
|
+
<rdf:Description rdf:about="http://library.ucsd.edu/ark:/20775/bbXXXXXXX6"/>
|
124
|
+
<mads:TopicElement>
|
125
|
+
<mads:elementValue>Relations with Mexican Americans</mads:elementValue>
|
126
|
+
</mads:TopicElement>
|
127
|
+
<rdf:Description rdf:about="http://library.ucsd.edu/ark:/20775/bbXXXXXXX4"/>
|
128
|
+
<mads:TemporalElement>
|
129
|
+
<mads:elementValue>20th century</mads:elementValue>
|
130
|
+
</mads:TemporalElement>
|
131
|
+
</mads:elementList>
|
132
|
+
</mads:ComplexSubject>
|
133
|
+
</rdf:RDF>
|
134
|
+
END
|
135
|
+
)
|
136
|
+
|
137
|
+
subject
|
138
|
+
end
|
139
|
+
it "should have a subject" do
|
140
|
+
subject.rdf_subject.to_s.should == "http://example.org/foo"
|
141
|
+
end
|
142
|
+
|
143
|
+
let (:list) { subject.elementList.first }
|
144
|
+
|
145
|
+
it "should have fields" do
|
146
|
+
list.first.rdf_subject.should == "http://library.ucsd.edu/ark:/20775/bbXXXXXXX6"
|
147
|
+
list[1].should be_kind_of DemoList::List::TopicElement
|
148
|
+
list[1].elementValue.should == ["Relations with Mexican Americans"]
|
149
|
+
list[2].rdf_subject.should == "http://library.ucsd.edu/ark:/20775/bbXXXXXXX4"
|
150
|
+
list[3].should be_kind_of DemoList::List::TemporalElement
|
151
|
+
list[3].elementValue.should == ["20th century"]
|
152
|
+
end
|
153
|
+
|
154
|
+
it "should have each" do
|
155
|
+
foo = []
|
156
|
+
list.each { |n| foo << n.class }
|
157
|
+
foo.should == [ActiveTriples::Resource, DemoList::List::TopicElement, ActiveTriples::Resource, DemoList::List::TemporalElement]
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should have to_ary" do
|
161
|
+
ary = list.to_ary
|
162
|
+
ary.size.should == 4
|
163
|
+
ary[1].elementValue.should == ['Relations with Mexican Americans']
|
164
|
+
end
|
165
|
+
|
166
|
+
it "should have size" do
|
167
|
+
list.size.should == 4
|
168
|
+
end
|
169
|
+
|
170
|
+
it "should update fields" do
|
171
|
+
list[3].elementValue = ["1900s"]
|
172
|
+
doc = Nokogiri::XML(subject.dump :rdfxml)
|
173
|
+
ns = {rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#", mads: "http://www.loc.gov/mads/rdf/v1#"}
|
174
|
+
expect(doc.xpath('/rdf:RDF/mads:ComplexSubject/@rdf:about', ns).map(&:value)).to eq ["http://example.org/foo"]
|
175
|
+
expect(doc.xpath('//mads:ComplexSubject/mads:elementList/@rdf:parseType', ns).map(&:value)).to eq ["Collection"]
|
176
|
+
expect(doc.xpath('//mads:ComplexSubject/mads:elementList/*[position() = 1]/@rdf:about', ns).map(&:value)).to eq ["http://library.ucsd.edu/ark:/20775/bbXXXXXXX6"]
|
177
|
+
expect(doc.xpath('//mads:ComplexSubject/mads:elementList/*[position() = 2]/mads:elementValue', ns).map(&:text)).to eq ["Relations with Mexican Americans"]
|
178
|
+
expect(doc.xpath('//mads:ComplexSubject/mads:elementList/*[position() = 3]/@rdf:about', ns).map(&:value)).to eq ["http://library.ucsd.edu/ark:/20775/bbXXXXXXX4"]
|
179
|
+
expect(doc.xpath('//mads:ComplexSubject/mads:elementList/*[position() = 4]/mads:elementValue', ns).map(&:text)).to eq ["1900s"]
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "nesting attribute behavior" do
|
4
|
+
describe ".attributes=" do
|
5
|
+
describe "complex properties" do
|
6
|
+
before do
|
7
|
+
class DummyMADS < RDF::Vocabulary("http://www.loc.gov/mads/rdf/v1#")
|
8
|
+
# componentList and Types of components
|
9
|
+
property :componentList
|
10
|
+
property :Topic
|
11
|
+
property :Temporal
|
12
|
+
property :PersonalName
|
13
|
+
property :CorporateName
|
14
|
+
property :ComplexSubject
|
15
|
+
|
16
|
+
|
17
|
+
# elementList and elementList values
|
18
|
+
property :elementList
|
19
|
+
property :elementValue
|
20
|
+
property :TopicElement
|
21
|
+
property :TemporalElement
|
22
|
+
property :NameElement
|
23
|
+
property :FullNameElement
|
24
|
+
property :DateNameElement
|
25
|
+
end
|
26
|
+
|
27
|
+
class ComplexResource < ActiveTriples::Resource
|
28
|
+
property :topic, predicate: DummyMADS.Topic, class_name: "Topic"
|
29
|
+
property :personalName, predicate: DummyMADS.PersonalName, class_name: "PersonalName"
|
30
|
+
property :title, predicate: RDF::DC.title
|
31
|
+
|
32
|
+
|
33
|
+
accepts_nested_attributes_for :topic, :personalName
|
34
|
+
|
35
|
+
class Topic < ActiveTriples::Resource
|
36
|
+
property :elementList, predicate: DummyMADS.elementList, class_name: "ComplexResource::ElementList"
|
37
|
+
accepts_nested_attributes_for :elementList
|
38
|
+
end
|
39
|
+
class PersonalName < ActiveTriples::Resource
|
40
|
+
property :elementList, predicate: DummyMADS.elementList, class_name: "ComplexResource::ElementList"
|
41
|
+
property :extraProperty, predicate: DummyMADS.elementValue, class_name: "ComplexResource::Topic"
|
42
|
+
accepts_nested_attributes_for :elementList, :extraProperty
|
43
|
+
end
|
44
|
+
class ElementList < ActiveTriples::List
|
45
|
+
configure type: DummyMADS.elementList
|
46
|
+
property :topicElement, predicate: DummyMADS.TopicElement, class_name: "ComplexResource::MadsTopicElement"
|
47
|
+
property :temporalElement, predicate: DummyMADS.TemporalElement
|
48
|
+
property :fullNameElement, predicate: DummyMADS.FullNameElement
|
49
|
+
property :dateNameElement, predicate: DummyMADS.DateNameElement
|
50
|
+
property :nameElement, predicate: DummyMADS.NameElement
|
51
|
+
property :elementValue, predicate: DummyMADS.elementValue
|
52
|
+
accepts_nested_attributes_for :topicElement
|
53
|
+
end
|
54
|
+
class MadsTopicElement < ActiveTriples::Resource
|
55
|
+
configure :type => DummyMADS.TopicElement
|
56
|
+
property :elementValue, predicate: DummyMADS.elementValue
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
after do
|
61
|
+
Object.send(:remove_const, :ComplexResource)
|
62
|
+
Object.send(:remove_const, :DummyMADS)
|
63
|
+
end
|
64
|
+
subject { ComplexResource.new }
|
65
|
+
let(:params) do
|
66
|
+
{ myResource:
|
67
|
+
{
|
68
|
+
topic_attributes: {
|
69
|
+
'0' =>
|
70
|
+
{
|
71
|
+
elementList_attributes: [{
|
72
|
+
topicElement_attributes: [{
|
73
|
+
id: 'http://library.ucsd.edu/ark:/20775/bb3333333x',
|
74
|
+
elementValue:"Cosmology"
|
75
|
+
}]
|
76
|
+
}]
|
77
|
+
},
|
78
|
+
'1' =>
|
79
|
+
{
|
80
|
+
elementList_attributes: [{
|
81
|
+
topicElement_attributes: {'0' => {elementValue:"Quantum Behavior"}}
|
82
|
+
}]
|
83
|
+
}
|
84
|
+
},
|
85
|
+
personalName_attributes: [
|
86
|
+
{
|
87
|
+
id: 'http://library.ucsd.edu/ark:20775/jefferson',
|
88
|
+
elementList_attributes: [{
|
89
|
+
fullNameElement: "Jefferson, Thomas",
|
90
|
+
dateNameElement: "1743-1826"
|
91
|
+
}]
|
92
|
+
}
|
93
|
+
#, "Hemings, Sally"
|
94
|
+
],
|
95
|
+
}
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
describe "on lists" do
|
100
|
+
subject { ComplexResource::PersonalName.new }
|
101
|
+
it "should accept a hash" do
|
102
|
+
subject.elementList_attributes = [{ topicElement_attributes: {'0' => { elementValue:"Quantum Behavior" }, '1' => { elementValue:"Wave Function" }}}]
|
103
|
+
subject.elementList.first[0].elementValue.should == ["Quantum Behavior"]
|
104
|
+
subject.elementList.first[1].elementValue.should == ["Wave Function"]
|
105
|
+
|
106
|
+
end
|
107
|
+
it "should accept an array" do
|
108
|
+
subject.elementList_attributes = [{ topicElement_attributes: [{ elementValue:"Quantum Behavior" }, { elementValue:"Wave Function" }]}]
|
109
|
+
subject.elementList.first[0].elementValue.should == ["Quantum Behavior"]
|
110
|
+
subject.elementList.first[1].elementValue.should == ["Wave Function"]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context "from nested objects" do
|
115
|
+
before do
|
116
|
+
# Replace the graph's contents with the Hash
|
117
|
+
subject.attributes = params[:myResource]
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'should have attributes' do
|
121
|
+
subject.topic[0].elementList.first[0].elementValue.should == ["Cosmology"]
|
122
|
+
subject.topic[1].elementList.first[0].elementValue.should == ["Quantum Behavior"]
|
123
|
+
subject.personalName.first.elementList.first.fullNameElement.should == ["Jefferson, Thomas"]
|
124
|
+
subject.personalName.first.elementList.first.dateNameElement.should == ["1743-1826"]
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'should build nodes with ids' do
|
128
|
+
expect(subject.topic[0].elementList.first[0].rdf_subject).to eq 'http://library.ucsd.edu/ark:/20775/bb3333333x'
|
129
|
+
expect(subject.personalName.first.rdf_subject).to eq 'http://library.ucsd.edu/ark:20775/jefferson'
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'should fail when writing to a non-predicate' do
|
133
|
+
attributes = { topic_attributes: { '0' => { elementList_attributes: [{ topicElement_attributes: [{ fake_predicate:"Cosmology" }] }]}}}
|
134
|
+
expect{ subject.attributes = attributes }.to raise_error ArgumentError
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'should fail when writing to a non-predicate with a setter method' do
|
138
|
+
attributes = { topic_attributes: { '0' => { elementList_attributes: [{ topicElement_attributes: [{ name:"Cosmology" }] }]}}}
|
139
|
+
expect{ subject.attributes = attributes }.to raise_error ArgumentError
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe "with an existing object" do
|
145
|
+
before(:each) do
|
146
|
+
class SpecResource < ActiveTriples::Resource
|
147
|
+
property :parts, predicate: RDF::DC.hasPart, :class_name=>'Component'
|
148
|
+
accepts_nested_attributes_for :parts, allow_destroy: true
|
149
|
+
|
150
|
+
class Component < ActiveTriples::Resource
|
151
|
+
property :label, predicate: RDF::DC.title
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
|
157
|
+
after(:each) do
|
158
|
+
Object.send(:remove_const, :SpecResource)
|
159
|
+
end
|
160
|
+
subject { SpecResource.new }
|
161
|
+
before do
|
162
|
+
subject.attributes = { parts_attributes: [
|
163
|
+
{label: 'Alternator'},
|
164
|
+
{label: 'Distributor'},
|
165
|
+
{label: 'Transmission'},
|
166
|
+
{label: 'Fuel Filter'}]}
|
167
|
+
end
|
168
|
+
let (:replace_object_id) { subject.parts[1].rdf_subject.to_s }
|
169
|
+
let (:remove_object_id) { subject.parts[3].rdf_subject.to_s }
|
170
|
+
|
171
|
+
it "should update nested objects" do
|
172
|
+
subject.parts_attributes= [{id: replace_object_id, label: "Universal Joint"}, {label:"Oil Pump"}, {id: remove_object_id, _destroy: '1', label: "bar1 uno"}]
|
173
|
+
|
174
|
+
subject.parts.map{|p| p.label.first}.should == ['Alternator', 'Universal Joint', 'Transmission', 'Oil Pump']
|
175
|
+
|
176
|
+
end
|
177
|
+
it "create a new object when the id is provided" do
|
178
|
+
subject.parts_attributes= [{id: 'http://example.com/part#1', label: "Universal Joint"}]
|
179
|
+
expect(subject.parts.last.rdf_subject).to eq RDF::URI('http://example.com/part#1')
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|