active-triples 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,3 @@
1
+ module ActiveTriples
2
+ VERSION = "0.0.1"
3
+ 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