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.
@@ -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