redlander 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,153 @@
1
+ module Redlander
2
+
3
+ class Statement
4
+
5
+ include ErrorContainer
6
+
7
+ attr_reader :rdf_statement
8
+
9
+ # Create an RDF statement.
10
+ # Options are:
11
+ # :subject
12
+ # :predicate
13
+ # :object
14
+ def initialize(options = {})
15
+ @rdf_statement = if options.is_a?(SWIG::TYPE_p_librdf_statement_s)
16
+ # A special case, where you can pass an instance of SWIG::TYPE_p_librdf_statement_s
17
+ # in order to create a Statement from an internal RDF statement representation.
18
+ options
19
+ else
20
+ s = options[:subject] && Node.new(options[:subject]).rdf_node
21
+ p = options[:predicate] && Node.new(options[:predicate]).rdf_node
22
+ o = options[:object] && Node.new(options[:object]).rdf_node
23
+ Redland.librdf_new_statement_from_nodes(Redlander.rdf_world, s, p, o)
24
+ end
25
+
26
+ raise RedlandError.new("Failed to create a new statement") unless @rdf_statement
27
+ ObjectSpace.define_finalizer(@rdf_statement, proc { Redland.librdf_free_statement(@rdf_statement) })
28
+ end
29
+
30
+ def subject
31
+ rdf_node = Redland.librdf_statement_get_subject(@rdf_statement)
32
+ rdf_node && Node.new(rdf_node)
33
+ end
34
+
35
+ def predicate
36
+ rdf_node = Redland.librdf_statement_get_predicate(@rdf_statement)
37
+ rdf_node && Node.new(rdf_node)
38
+ end
39
+
40
+ def object
41
+ rdf_node = Redland.librdf_statement_get_object(@rdf_statement)
42
+ rdf_node && Node.new(rdf_node)
43
+ end
44
+
45
+ # set the subject of the statement
46
+ def subject=(node)
47
+ Redland.librdf_statement_set_subject(@rdf_statement, rdf_node_from(node))
48
+ end
49
+
50
+ # set the predicate of the statement
51
+ def predicate=(node)
52
+ Redland.librdf_statement_set_predicate(@rdf_statement, rdf_node_from(node))
53
+ end
54
+
55
+ # set the object of the statement
56
+ def object=(node)
57
+ Redland.librdf_statement_set_object(@rdf_statement, rdf_node_from(node))
58
+ end
59
+
60
+ def model
61
+ @model
62
+ end
63
+
64
+ # Add the statement to the given model.
65
+ #
66
+ # Returns the model on success, or nil.
67
+ # NOTE: Duplicate statements are not added to the model.
68
+ # However, this doesn't result in an error here.
69
+ def model=(model)
70
+ if model.nil?
71
+ @model = nil
72
+ else
73
+ if self.valid?
74
+ if Redland.librdf_model_add_statement(model.rdf_model, @rdf_statement).zero?
75
+ @model = model
76
+ else
77
+ nil
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ # Destroy the statement (remove it from the model, if possible).
84
+ #
85
+ # Returns true if successfully removed from the model, or false.
86
+ # If the statement is not bound to a model, false is returned.
87
+ def destroy
88
+ if @model
89
+ if Redland.librdf_model_remove_statement(@model.rdf_model, @rdf_statement).zero?
90
+ self.model = nil
91
+ true
92
+ else
93
+ false
94
+ end
95
+ else
96
+ false
97
+ end
98
+ end
99
+
100
+ def eql?(other_statement)
101
+ model == other_statement.model &&
102
+ subject == other_statement.subject &&
103
+ predicate == other_statement.predicate &&
104
+ object == other_statement.object
105
+ end
106
+ alias_method :==, :eql?
107
+
108
+ def hash
109
+ self.class.hash + to_s.hash
110
+ end
111
+
112
+ def to_s
113
+ Redland.librdf_statement_to_string(@rdf_statement)
114
+ end
115
+
116
+ # A valid statement satisfies the following:
117
+ # URI or blank subject, URI predicate and URI or blank or literal object (i.e. anything).
118
+ def valid?
119
+ if is_valid = attributes_satisfy?
120
+ errors.clear
121
+ else
122
+ errors.add("is invalid")
123
+ end
124
+ is_valid
125
+ end
126
+
127
+
128
+ private
129
+
130
+ def rdf_node_from(node)
131
+ if node.nil?
132
+ nil
133
+ else
134
+ # According to Redland docs,
135
+ # the node here becomes a part of the statement
136
+ # and must not be used by the caller!
137
+ if node.frozen?
138
+ raise RedlandError.new("The node is already bound to a statement and cannot be added.")
139
+ else
140
+ node.freeze.rdf_node
141
+ end
142
+ end
143
+ end
144
+
145
+ def attributes_satisfy?
146
+ !subject.nil? && (subject.resource? || subject.blank?) &&
147
+ !predicate.nil? && predicate.resource? &&
148
+ !object.nil?
149
+ end
150
+
151
+ end
152
+
153
+ end
@@ -0,0 +1,37 @@
1
+ module Redlander
2
+
3
+ # Wrapper for a librdf_stream object.
4
+ module StatementIterator
5
+
6
+ include Enumerable
7
+
8
+ # Iterate over statements in the stream.
9
+ def each
10
+ # TODO: The options specify matching criteria: subj, pred, obj;
11
+ # if an option is not specified, it matches any value,
12
+ # so with no options given, all statements will be returned.
13
+ if block_given?
14
+ while Redland.librdf_stream_end(@rdf_stream).zero?
15
+ yield current
16
+ Redland.librdf_stream_next(@rdf_stream).zero?
17
+ end
18
+ else
19
+ raise ::LocalJumpError.new("no block given")
20
+ end
21
+ end
22
+
23
+
24
+ private
25
+
26
+ # Get the current Statement in the stream.
27
+ def current
28
+ rdf_statement = Redland.librdf_stream_get_object(@rdf_stream)
29
+ statement = Statement.new(rdf_statement)
30
+ # not using Statement#model= in order to avoid re-adding the statement to the model
31
+ statement.instance_variable_set(:@model, @model)
32
+ statement
33
+ end
34
+
35
+ end
36
+
37
+ end
@@ -0,0 +1,79 @@
1
+ module Redlander
2
+
3
+ module Storage
4
+
5
+ VALID_STORAGE_TYPES = [:memory, :hashes, :file, :uri, :tstore, :mysql, :sqlite, :postgresql]
6
+
7
+ # Creates a store of the given type
8
+ #
9
+ # Store types (:storage option) are:
10
+ # :memory
11
+ # :hashes
12
+ # :file - memory model initialized from RDF/XML file
13
+ # :uri - read-only memory model with URI provided in 'name' arg
14
+ # :mysql
15
+ # :sqlite
16
+ # :postgresql
17
+ # :tstore
18
+ # Options are:
19
+ # :name - ?
20
+ # :host - database host name (for store types: :postgres, :mysql, :tstore)
21
+ # :port - database host port (for store types: :postgres, :mysql, :tstore)
22
+ # :database - database name (for store types: :postgres, :mysql, :tstore)
23
+ # :user - database user name (for store types: :postgres, :mysql, :tstore)
24
+ # :password - database user password (for store types: :postgres, :mysql, :tstore)
25
+ # :hash_type - hash type (for store types: :bdb)
26
+ # can be either 'memory' or 'bdb'
27
+ # :new - force creation of a new store
28
+ # :dir - directory path (for store types: :hashes)
29
+ # :contexts - support contexts (for store types: :hashes, :memory)
30
+ # :write - allow writing data to the store (for store types: :hashes)
31
+ #
32
+ # NOTE: When dealing with databases,
33
+ # Redland (1.0.7) just crashes when the required tables aren't available!
34
+ def self.initialize_storage(options = {})
35
+ storage_type, storage_options = split_options(options)
36
+ storage_type ||= :memory
37
+
38
+ unless VALID_STORAGE_TYPES.include?(storage_type)
39
+ raise RedlandError.new("Unknown storage type: #{storage_type}")
40
+ end
41
+
42
+ rdf_storage = Redland.librdf_new_storage(Redlander.rdf_world,
43
+ storage_type.to_s,
44
+ storage_options.delete(:name).to_s,
45
+ Redlander.to_rdf_options(storage_options))
46
+ raise RedlandError.new("Failed to initialize storage") unless rdf_storage
47
+ ObjectSpace.define_finalizer(rdf_storage, proc { Redland.librdf_free_storage(rdf_storage) })
48
+
49
+ rdf_storage
50
+ end
51
+
52
+ # Wrap changes to the given model in a transaction.
53
+ # If an exception is raised in the block, the transaction is rolled back.
54
+ # (Does not work for all storages, in which case the changes are instanteous).
55
+ def self.transaction(model, &block)
56
+ Redland.librdf_model_transaction_start(model.rdf_model).zero? || RedlandError.new("Failed to initialize a transaction")
57
+ block.call
58
+ Redland.librdf_model_transaction_commit(model.rdf_model).zero? || RedlandError.new("Failed to commit the transaction")
59
+ rescue
60
+ rollback(model)
61
+ end
62
+
63
+ # Rollback a latest transaction for the given model.
64
+ def self.rollback(model)
65
+ Redland.librdf_model_transaction_rollback(model.rdf_model).zero? || RedlandError.new("Failed to rollback the latest transaction")
66
+ end
67
+
68
+
69
+ private
70
+
71
+ def self.split_options(options = {})
72
+ storage_options = options.dup
73
+ storage_type = storage_options.delete(:storage)
74
+ [storage_type, storage_options]
75
+ end
76
+
77
+ end
78
+
79
+ end
@@ -0,0 +1,3 @@
1
+ module Redlander
2
+ VERSION = "0.2.1"
3
+ end
@@ -0,0 +1,239 @@
1
+ require "spec_helper"
2
+
3
+ describe Model do
4
+
5
+ it "should be created with default options" do
6
+ lambda { Model.new }.should_not raise_exception
7
+ end
8
+
9
+ describe "statements" do
10
+
11
+ before :each do
12
+ @model = Model.new
13
+ end
14
+
15
+ it do
16
+ @model.statements.should be_an_instance_of(ModelProxy)
17
+ end
18
+
19
+ it "should be created in the model" do
20
+ lambda {
21
+ @model.statements.create(statement_attributes)
22
+ }.should change(@model.statements, :size).by(1)
23
+ end
24
+
25
+ it "should be bound to the model" do
26
+ statement = @model.statements.create(statement_attributes)
27
+ statement.model.should be(@model)
28
+ end
29
+
30
+ it "should be iterated over" do
31
+ statement = @model.statements.create(statement_attributes)
32
+ statements = []
33
+ lambda {
34
+ @model.statements.each do |s|
35
+ statements << s
36
+ end
37
+ }.should change(statements, :size).by(1)
38
+ statements.first.should eql(statement)
39
+ end
40
+
41
+ it "should be added to the model" do
42
+ statement = Statement.new(statement_attributes)
43
+ lambda {
44
+ lambda {
45
+ @model.statements.add(statement)
46
+ }.should change(@model.statements, :size).by(1)
47
+ }.should change(statement, :model).from(nil).to(@model)
48
+ @model.statements.should include(statement)
49
+ end
50
+
51
+ it "should be found without a block" do
52
+ statement = @model.statements.create(statement_attributes)
53
+ @model.statements.find(:first).should eql(statement)
54
+ @model.statements.first.should eql(statement)
55
+ @model.statements.find(:all).should eql([statement])
56
+ @model.statements.all.should eql([statement])
57
+ end
58
+
59
+ it "should be found with a block" do
60
+ statements = []
61
+ statement = @model.statements.create(statement_attributes)
62
+ lambda {
63
+ @model.statements.find(:all) do |st|
64
+ statements << st
65
+ end
66
+ }.should change(statements, :size).by(1)
67
+ statements.first.should eql(statement)
68
+ end
69
+
70
+ it "should be found by an attribute" do
71
+ statements = []
72
+ statement = @model.statements.create(statement_attributes)
73
+ lambda {
74
+ @model.statements.find(:all, :object => statement.object) do |st|
75
+ statements << st
76
+ end
77
+ }.should change(statements, :size).by(1)
78
+ statements.first.should eql(statement)
79
+ end
80
+
81
+ it "should not be found with a block" do
82
+ statements = []
83
+ statement = @model.statements.create(statement_attributes)
84
+ lambda {
85
+ @model.statements.find(:all, :object => "another object") do |st|
86
+ statements << st
87
+ end
88
+ }.should_not change(statements, :size)
89
+ end
90
+
91
+ it "should be removed from the model" do
92
+ statement = @model.statements.create(statement_attributes)
93
+ lambda {
94
+ statement.destroy
95
+ }.should change(@model.statements, :size).by(-1)
96
+ @model.statements.should_not include(statement)
97
+ statement.model.should be_nil
98
+ end
99
+
100
+
101
+ private
102
+
103
+ def statement_attributes
104
+ s = URI.parse('http://example.com/concepts#subject')
105
+ p = URI.parse('http://example.com/concepts#label')
106
+ o = "subject!"
107
+ {
108
+ :subject => s,
109
+ :predicate => p,
110
+ :object => o
111
+ }
112
+ end
113
+
114
+ end
115
+
116
+ describe "serialization" do
117
+
118
+ before :each do
119
+ @model = Model.new
120
+ s = URI.parse("http://example.com/concepts#two-dimensional_seismic_imaging")
121
+ p = URI.parse("http://www.w3.org/2000/01/rdf-schema#label")
122
+ o = "2-D seismic imaging@en"
123
+ @model.statements.create(:subject => s, :predicate => p, :object => o)
124
+ end
125
+
126
+ it "should produce RDF/XML content" do
127
+ content = @model.to_rdfxml
128
+ content.should be_an_instance_of(String)
129
+ content.should include('2-D seismic imaging')
130
+ end
131
+
132
+ it "should produce N-Triples content" do
133
+ content = @model.to_ntriples
134
+ content.should be_an_instance_of(String)
135
+ content.should include('2-D seismic imaging@en')
136
+ end
137
+
138
+ it "should produce Turtle content" do
139
+ content = @model.to_turtle
140
+ content.should be_an_instance_of(String)
141
+ content.should include('2-D seismic imaging@en')
142
+ end
143
+
144
+ it "should produce JSON content" do
145
+ content = @model.to_json
146
+ content.should be_an_instance_of(String)
147
+ content.should include('2-D seismic imaging@en')
148
+ end
149
+
150
+ it "should produce DOT content" do
151
+ content = @model.to_dot
152
+ content.should be_an_instance_of(String)
153
+ content.should include('2-D seismic imaging@en')
154
+ end
155
+
156
+ describe "file source" do
157
+
158
+ before :each do
159
+ content = '<?xml version="1.0" encoding="utf-8"?><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="http://example.com/concepts#two-dimensional_seismic_imaging"><ns0:label xmlns:ns0="http://www.w3.org/2000/01/rdf-schema#" xml:lang="en">2-D seismic imaging</ns0:label></rdf:Description></rdf:RDF>'
160
+ reference_model = Model.new
161
+ reference_model.from_rdfxml(content, :base_uri => 'http://example.com/concepts')
162
+ reference_model.to_file(filename)
163
+ end
164
+
165
+ after :each do
166
+ cleanup
167
+ end
168
+
169
+ it "should be loaded from a file" do
170
+ lambda {
171
+ @model.from_file(filename, :base_uri => 'http://example.com/concepts')
172
+ }.should change(@model.statements, :size).by(1)
173
+ end
174
+
175
+ end
176
+
177
+ describe "file destination" do
178
+
179
+ before :each do
180
+ cleanup
181
+ end
182
+
183
+ after :each do
184
+ cleanup
185
+ end
186
+
187
+ it "should produce a file" do
188
+ @model.to_file(filename)
189
+ File.should be_exists(filename)
190
+ File.size(filename).should_not be_zero
191
+ end
192
+
193
+ end
194
+
195
+ private
196
+
197
+ def cleanup
198
+ File.delete(filename) if File.exists?(filename)
199
+ end
200
+
201
+ def filename
202
+ "test_model.rdf"
203
+ end
204
+
205
+ end
206
+
207
+ describe "deserialization" do
208
+
209
+ before :each do
210
+ @model = Model.new
211
+ end
212
+
213
+ it "should be successful for RDF/XML data" do
214
+ content = '<?xml version="1.0" encoding="utf-8"?><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="http://example.com/concepts#two-dimensional_seismic_imaging"><ns0:label xmlns:ns0="http://www.w3.org/2000/01/rdf-schema#" xml:lang="en">2-D seismic imaging</ns0:label></rdf:Description></rdf:RDF>'
215
+ lambda {
216
+ @model.from_rdfxml(content, :base_uri => 'http://example.com/concepts')
217
+ }.should change(@model.statements, :size).by(1)
218
+ end
219
+
220
+ it "should be successful for N-Triples data" do
221
+ content = '<http://example.org/ns/a2> <http://example.org/ns/b2> <http://example.org/ns/c2> .'
222
+ lambda {
223
+ @model.from_ntriples(content)
224
+ }.should change(@model.statements, :size).by(1)
225
+ end
226
+
227
+ it "should be successful for Turtle data" do
228
+ content = '# this is a complete turtle document
229
+ @prefix foo: <http://example.org/ns#> .
230
+ @prefix : <http://other.example.org/ns#> .
231
+ foo:bar foo: : .'
232
+ lambda {
233
+ @model.from_turtle(content, :base_uri => 'http://example.com/concepts')
234
+ }.should change(@model.statements, :size).by(1)
235
+ end
236
+
237
+ end
238
+
239
+ end