redlander 0.2.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,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