redlander 0.3.6 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ *~
2
+ .*~
3
+ *.rbc
4
+ ext/*.o
5
+ ext/*.so
6
+ ext/*.bundle
7
+ ext/Makefile
8
+ lib/*.o
9
+ lib/*.so
10
+ lib/*.bundle
11
+ pkg/
12
+ tmtags
13
+ *.gem
14
+ TAGS
15
+ vendor/
16
+ tmp/
17
+ .rbx
18
+ .bundle
19
+ .yardoc/
20
+ doc/
data/ChangeLog ADDED
@@ -0,0 +1,19 @@
1
+ redlander (0.4.0)
2
+
3
+ * deprecated Parser, Serializer, Storage, Stream and related classes and modules
4
+ * moved parsing and serialization methods to Model instance
5
+ * renamed parsing/serialization option :name to :format
6
+ * Redlander::Uri now explicitly belongs to private (internal) API
7
+ * ModelProxy (model.statements) is now Enumerable, with all benefits thereof
8
+ * Statement no longer has "valid?" method and "errors" container
9
+ * blank Nodes can be created with a given ID (:blank_id option)
10
+ * public methods for handling transactions
11
+ * URI is preferred over Redlander::Uri as method arguments or output,
12
+ e.g., Node#datatype, Node#uri and others return an instance of URI
13
+ * updated gem dependencies
14
+ * updated README and YARD documentation
15
+ * added LICENSE (MIT)
16
+
17
+ redlander (0.3.6)
18
+
19
+ ... the dark ages ...
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+ gemspec
3
+
4
+ gem "rake"
data/Gemfile.lock ADDED
@@ -0,0 +1,30 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ redlander (0.4.0)
5
+ ffi (~> 1.1)
6
+ xml_schema (~> 0.1.0)
7
+
8
+ GEM
9
+ remote: http://rubygems.org/
10
+ specs:
11
+ diff-lcs (1.1.3)
12
+ ffi (1.1.0)
13
+ rake (0.9.2.2)
14
+ rspec (2.11.0)
15
+ rspec-core (~> 2.11.0)
16
+ rspec-expectations (~> 2.11.0)
17
+ rspec-mocks (~> 2.11.0)
18
+ rspec-core (2.11.1)
19
+ rspec-expectations (2.11.1)
20
+ diff-lcs (~> 1.1.3)
21
+ rspec-mocks (2.11.1)
22
+ xml_schema (0.1.0)
23
+
24
+ PLATFORMS
25
+ ruby
26
+
27
+ DEPENDENCIES
28
+ rake
29
+ redlander!
30
+ rspec (~> 2)
data/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2012 Slava Kravchenko
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc CHANGED
@@ -1,6 +1,6 @@
1
- = Description
1
+ = Redlander
2
2
 
3
- Redlander is Ruby bindings to Redland library (see http://librdf.org). This is an alternative implementation of the bindings, aiming to be more intuitive, lightweight and less buggy.
3
+ Redlander is Ruby bindings to Redland library (see http://librdf.org) written in C, which is used to manipulate RDF graphs. This is an alternative implementation of Ruby bindings (as opposed to the official bindings), aiming to be more intuitive, lightweight, high-performing and as bug-free as possible.
4
4
 
5
5
  = Installing
6
6
 
@@ -8,17 +8,21 @@ Installing Redlander is simple:
8
8
 
9
9
  $ gem install redlander
10
10
 
11
- Note, that you will have to install Redlander (librdf) for Redlander to work.
11
+ Note, that you will have to install Redland runtime library (librdf) for Redlander to work.
12
12
 
13
13
  = Usage
14
14
 
15
+ This README outlines most obvious use cases. For more details please refer to YARD documentation of Redlander.
16
+
15
17
  To start doing anything useful with Redlander, you need to initialize a model first:
16
18
 
17
19
  $ m = Redlander::Model.new
18
20
 
19
- This creates a model where all RDF statements are stored in the memory. Depending on the selected storage you may need to supply extra parameters like :user or :password. Look-up the options for Storage.initialize_storage method for the list of storage options.
21
+ This creates a model where all RDF statements are stored in the memory. Depending on the selected storage you may need to supply extra parameters like :user or :password. Look-up the options for Model.initialize for the list of available options.
20
22
  Naturally, you don't need to create a model if you just want to play around with independent statements, nodes and the like.
21
23
 
24
+ == RDF Statements
25
+
22
26
  Now that you have created a model, you can access its RDF statements:
23
27
 
24
28
  $ m.statements
@@ -41,21 +45,90 @@ Most of Redlander functionality is accessable via these statements. The API is a
41
45
 
42
46
  Finding statements:
43
47
 
44
- m.statements.find(:first, :object => "subject!")
45
- m.statements.all(:object => "another label")
46
- m.statements.find(:all, :object => "subject!").each { |statement|
47
- puts statement.subject
48
- }
48
+ $ m.statements.find(:first, :object => "subject!")
49
+ $ m.statements.all(:object => "another label")
50
+ $ m.statements.each(:object => "subject!") { |statement|
51
+ puts statement.subject
52
+ }
53
+
54
+ Note that "m.statements.each" is "lazy", while "m.statements.all" (and other finders) is not.
55
+
56
+ You can access the subject, predicate or object of a statement:
57
+
58
+ $ m.statements.first.subject # => (Redlander::Node)
59
+
60
+ Please refer to Redlander::Node API doc for details.
61
+
62
+ == Parsing Input
63
+
64
+ You can fill your model with statements by parsing some external sources like plain or streamed data.
65
+
66
+ $ data = File.read("data.xml")
67
+ $ m.from(data, :format => "rdfxml")
68
+
69
+ If the input is too large, you may prefer streaming it:
70
+
71
+ $ source = URI("http://example.com/data.nt")
72
+ $ m.from(source, :format => "ntriples")
73
+
74
+ If you want to get the data from a local file, you can use "file://" schema for your URI
75
+ (or Redlander::Uri) or use "from_file" method with a local file name (without schema):
76
+
77
+ $ m.from_file("../data.ttl", :format => "turtle")
78
+
79
+ Most frequently used parsing methods are aliased to save you some typing:
80
+ "from_rdfxml", "from_ntriples", "from_turtle", "from_uri/from_file".
81
+
82
+ Finally, you can filter the parsed input to prevent certain statements from getting into your model:
83
+
84
+ $ m.from_turtle(data) do |statement|
85
+ statement.object.value == "good"
86
+ end
87
+
88
+ If the block returns "false", the statement will not be added to the model.
89
+ The above example will add only statements having "literal" objects with a value of "good".
90
+
91
+ == Serializing Model
92
+
93
+ Naturally, you can convert your model into a portable syntax:
94
+
95
+ $ m.to(:format => "rdfxml") # => RDF/XML output
96
+
97
+ There are aliases as well: "to_rdfxml", "to_dot", etc.
98
+
99
+ You can also dump the output directly into a local file:
100
+
101
+ $ m.to_file("data.nt", :format => "ntriples")
102
+
103
+ == Transactions
104
+
105
+ It is possible to wrap all changes you perform on a model in a transaction,
106
+ if transactions are supported by the backend storage. If they are not supported,
107
+ all changes will be instantaneous.
108
+
109
+ $ m.transaction { m.statements.delete_all }
110
+
111
+ There are also dedicated methods to start, commit and rollback a transaction,
112
+ should you not be able to explicitly wrap your changes in a block:
113
+
114
+ $ m.transaction_start
115
+ $ m.delete_all
116
+ $ if lucky?
117
+ m.transaction_commit
118
+ else
119
+ m.transaction_rollback
120
+ end
49
121
 
50
- Note that "m.statements.each" is "lazy", while "m.statements.all" (and other finders) are not.
122
+ All the above methods have their "banged" counterparts ("transaction_start!",
123
+ "transaction_commit!" and "transaction_rollback!") that would raise RedlandError
124
+ in case of an error.
51
125
 
52
- For more details refer to the documentation of Model, Statement, Storage and other classes of Redlander.
53
126
 
54
127
  = Exceptions
55
128
 
56
129
  If anything unexpected happens, Redlander raises RedlandError.
57
130
 
58
- = Authors
131
+ = Authors and Contributors
59
132
 
60
133
  Slava Kravchenko <slava.kravchenko@gmail.com>
61
134
 
data/Rakefile CHANGED
@@ -1,5 +1,6 @@
1
- require 'spec/rake/spectask'
2
- require 'bundler'
1
+ #!/usr/bin/env rake
3
2
 
4
- Bundler::GemHelper.install_tasks
5
- Spec::Rake::SpecTask.new
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new
data/lib/redland.rb CHANGED
@@ -1,7 +1,8 @@
1
+ # @api private
1
2
  # FFI bindings
2
-
3
3
  module Redland
4
- if !defined?(RUBY_ENGINE) || RUBY_ENGINE=='ruby'
4
+ # Handling FFI difference between MRI and Rubinius
5
+ if !defined?(RUBY_ENGINE) || RUBY_ENGINE == 'ruby'
5
6
  require 'ffi'
6
7
  extend FFI::Library
7
8
  ffi_lib "rdf.so.0"
@@ -52,6 +53,7 @@ module Redland
52
53
  attach_function :librdf_node_is_resource, [:pointer], :int
53
54
  attach_function :librdf_node_is_literal, [:pointer], :int
54
55
  attach_function :librdf_node_is_blank, [:pointer], :int
56
+ attach_function :librdf_node_get_literal_value, [:pointer], :string
55
57
  attach_function :librdf_node_get_literal_value_datatype_uri, [:pointer], :pointer
56
58
  attach_function :librdf_node_equals, [:pointer, :pointer], :int
57
59
  attach_function :librdf_node_to_string, [:pointer], :string
@@ -74,6 +76,7 @@ module Redland
74
76
  attach_function :librdf_free_parser, [:pointer], :void
75
77
  attach_function :librdf_parser_parse_into_model, [:pointer, :pointer, :pointer, :pointer], :int
76
78
  attach_function :librdf_parser_parse_string_into_model, [:pointer, :string, :pointer, :pointer], :int
79
+ attach_function :librdf_parser_parse_as_stream, [:pointer, :pointer, :pointer], :pointer
77
80
  attach_function :librdf_parser_parse_string_as_stream, [:pointer, :string, :pointer], :pointer
78
81
 
79
82
  # URI
@@ -81,4 +84,5 @@ module Redland
81
84
  attach_function :librdf_new_uri_from_uri, [:pointer], :pointer
82
85
  attach_function :librdf_free_uri, [:pointer], :void
83
86
  attach_function :librdf_uri_to_string, [:pointer], :string
87
+ attach_function :librdf_uri_equals, [:pointer, :pointer], :int
84
88
  end
data/lib/redlander.rb CHANGED
@@ -9,26 +9,28 @@ require 'redlander/node'
9
9
  require 'redlander/model'
10
10
  require 'redlander/statement'
11
11
 
12
+ # Main Redlander namespace
12
13
  module Redlander
13
-
14
14
  class << self
15
+ # @api private
15
16
  def rdf_world
16
17
  unless @rdf_world
17
18
  @rdf_world = Redland.librdf_new_world
18
- raise RedlandError.new("Could not create a new RDF world") if @rdf_world.null?
19
+ raise RedlandError, "Could not create a new RDF world" if @rdf_world.null?
19
20
  ObjectSpace.define_finalizer(self, proc { Redland.librdf_free_world(@rdf_world) })
20
21
  Redland.librdf_world_open(@rdf_world)
21
22
  end
22
23
  @rdf_world
23
24
  end
24
25
 
26
+ # @api private
25
27
  # Convert options hash into a string for librdf.
26
28
  # What it does:
27
29
  # 1) Convert boolean values into 'yes/no' values
28
30
  # 2) Change underscores in key names into dashes ('dhar_ma' => 'dhar-ma')
29
31
  # 3) Join all options as "key='value'" pairs in a comma-separated string
30
32
  #
31
- # E.g.:
33
+ # @example
32
34
  # to_rdf_options {:key => true, "key_board" => 3}
33
35
  # # => "key='yes',key-board='3'"
34
36
  def to_rdf_options(options = {})
@@ -1,22 +1,58 @@
1
- require 'redlander/storage'
2
- require 'redlander/parser'
3
- require 'redlander/serializer'
1
+ require 'redlander/parsing'
2
+ require 'redlander/serializing'
4
3
  require 'redlander/model_proxy'
5
4
 
6
5
  module Redlander
6
+ # The core object incorporating the repository of RDF statements.
7
7
  class Model
8
- include Redlander::ParsingInstanceMethods
9
- include Redlander::SerializingInstanceMethods
8
+ include Redlander::Parsing
9
+ include Redlander::Serializing
10
10
 
11
+ # @api private
11
12
  attr_reader :rdf_model
12
13
 
13
14
  # Create a new RDF model.
14
- # For explanation of options, read Storage.initialize
15
+ # (For available storage options see http://librdf.org/docs/api/redland-storage-modules.html)
16
+ #
17
+ # @param [Hash] options
18
+ # @option options [String] :storage
19
+ # - "memory" - default, if :storage option is omitted,
20
+ # - "hashes"
21
+ # - "file" - memory model initialized from RDF/XML file,
22
+ # - "uri" - read-only memory model with URI provided in 'name' arg,
23
+ # - "mysql"
24
+ # - "sqlite"
25
+ # - "postgresql"
26
+ # - "tstore"
27
+ # - "virtuoso"
28
+ # - ... anything else that Redland can handle.
29
+ # @option options [String] :name storage identifier (DB file name or database name),
30
+ # @option options [String] :host database host name (for store types: :postgres, :mysql, :tstore),
31
+ # @option options [String] :port database host port (for store types: :postgres, :mysql, :tstore),
32
+ # @option options [String] :database database name (for store types: :postgres, :mysql, :tstore),
33
+ # @option options [String] :user database user name (for store types: :postgres, :mysql, :tstore),
34
+ # @option options [String] :password database user password (for store types: :postgres, :mysql, :tstore),
35
+ # @option options [String] :hash_type hash type (for store types: :bdb), can be either 'memory' or 'bdb',
36
+ # @option options [String] :new force creation of a new store,
37
+ # @option options [String] :dir directory path (for store types: :hashes),
38
+ # @option options [String] :contexts support contexts (for store types: :hashes, :memory),
39
+ # @option options [String] :write allow writing data to the store (for store types: :hashes),
40
+ # @option options [...] ... other storage-specific options.
41
+ # @raise [RedlandError] if it fails to create a storage or a model.
15
42
  def initialize(options = {})
16
- @storage = Storage.new(options)
43
+ options = options.dup
44
+ storage_type = options.delete(:storage) || "memory"
45
+ storage_name = options.delete(:name)
46
+
47
+ @rdf_storage = Redland.librdf_new_storage(Redlander.rdf_world,
48
+ storage_type.to_s,
49
+ storage_name.to_s,
50
+ Redlander.to_rdf_options(options))
51
+ raise RedlandError, "Failed to initialize '#{storage_name}' storage (type: #{storage_type})" if @rdf_storage.null?
52
+ ObjectSpace.define_finalizer(self, proc { Redland.librdf_free_storage(@rdf_storage) })
17
53
 
18
- @rdf_model = Redland.librdf_new_model(Redlander.rdf_world, @storage.rdf_storage, "")
19
- raise RedlandError.new("Failed to create a new model") if @rdf_model.null?
54
+ @rdf_model = Redland.librdf_new_model(Redlander.rdf_world, @rdf_storage, "")
55
+ raise RedlandError, "Failed to create a new model" if @rdf_model.null?
20
56
  ObjectSpace.define_finalizer(self, proc { Redland.librdf_free_model(@rdf_model) })
21
57
  end
22
58
 
@@ -24,27 +60,73 @@ module Redlander
24
60
  #
25
61
  # Similar to Ruby on Rails, a proxy object is actually returned,
26
62
  # which delegates methods to Statement class.
63
+ #
64
+ # @return [ModelProxy]
27
65
  def statements
28
66
  ModelProxy.new(self)
29
67
  end
30
68
 
31
69
  # Wrap changes to the given model in a transaction.
32
70
  # If an exception is raised in the block, the transaction is rolled back.
33
- # (Does not work for all storages, in which case the changes are instanteous).
71
+ #
72
+ # @note Does not work for all storages, in which case the changes are instanteous
73
+ #
74
+ # @yieldparam [void]
75
+ # @return [void]
34
76
  def transaction
35
77
  if block_given?
36
- Redland.librdf_model_transaction_start(@rdf_model).zero? || raise(RedlandError, "Failed to initialize a transaction")
78
+ transaction_start
37
79
  yield
38
- Redland.librdf_model_transaction_commit(@rdf_model).zero? || raise(RedlandError, "Failed to commit the transaction")
80
+ transaction_commit
39
81
  end
40
82
  rescue
41
- rollback
83
+ transaction_rollback
42
84
  raise
43
85
  end
44
86
 
45
- # Rollback the transaction
46
- def rollback
47
- Redland.librdf_model_transaction_rollback(@rdf_model).zero? || raise(RedlandError, "Failed to rollback the latest transaction")
87
+ # Start a transaction, if it is supported by the backend storage.
88
+ #
89
+ # @return [Boolean]
90
+ def transaction_start
91
+ Redland.librdf_model_transaction_start(@rdf_model).zero?
92
+ end
93
+
94
+ # Start a transaction.
95
+ #
96
+ # @raise [RedlandError] if it is not supported by the backend storage
97
+ # @return [true]
98
+ def transaction_start!
99
+ raise RedlandError, "Failed to initialize a transaction" unless transaction_start
100
+ end
101
+
102
+ # Commit a transaction, if it is supported by the backend storage.
103
+ #
104
+ # @return [Boolean]
105
+ def transaction_commit
106
+ Redland.librdf_model_transaction_commit(@rdf_model).zero?
107
+ end
108
+
109
+ # Commit a transaction.
110
+ #
111
+ # @raise [RedlandError] if it is not supported by the backend storage
112
+ # @return [true]
113
+ def transaction_commit!
114
+ raise RedlandError, "Failed to commit the transaction" unless transaction_commit
115
+ end
116
+
117
+ # Rollback a transaction, if it is supported by the backend storage.
118
+ #
119
+ # @return [Boolean]
120
+ def transaction_rollback
121
+ Redland.librdf_model_transaction_rollback(@rdf_model).zero?
122
+ end
123
+
124
+ # Rollback a transaction.
125
+ #
126
+ # @raise [RedlandError] if it is not supported by the backend storage
127
+ # @return [true]
128
+ def transaction_rollback!
129
+ raise RedlandError, "Failed to rollback the latest transaction" unless transaction_rollback
48
130
  end
49
131
  end
50
132
  end
@@ -1,104 +1,161 @@
1
- require 'redlander/stream'
2
- require 'redlander/stream_enumerator'
3
-
4
1
  module Redlander
2
+ # Proxy between model and its statements,
3
+ # allowing to scope actions on statements
4
+ # within a certain model.
5
+ #
6
+ # @example
7
+ # model = Redlander::Model.new
8
+ # model.statements
9
+ # # => ModelProxy
10
+ # model.statements.add(...)
11
+ # model.statements.each(...)
12
+ # model.statements.find(...)
13
+ # # etc...
5
14
  class ModelProxy
6
- include StreamEnumerator
15
+ include Enumerable
7
16
 
17
+ # @param [Redlander::Model] model
8
18
  def initialize(model)
9
19
  @model = model
10
20
  end
11
21
 
12
22
  # Add a statement to the model.
13
- # It must be a complete statement - all of subject, predicate, object parts must be present.
14
- # Only statements that are legal RDF can be added.
15
- # If the statement already exists in the model, it is not added.
16
23
  #
17
- # Returns true on success or false on failure.
24
+ # @note
25
+ # All of subject, predicate, object nodes of the statement must be present.
26
+ # Only statements that are legal RDF can be added.
27
+ # If the statement already exists in the model, it is not added.
28
+ #
29
+ # @param [Statement] statement
30
+ # @return [Boolean]
18
31
  def add(statement)
19
- if statement.valid?
20
- Redland.librdf_model_add_statement(@model.rdf_model, statement.rdf_statement).zero?
21
- end
32
+ Redland.librdf_model_add_statement(@model.rdf_model, statement.rdf_statement).zero?
22
33
  end
34
+ alias_method :<<, :add
23
35
 
24
- # Delete a statement from the model,
25
- # or delete all statements matching the given criteria.
26
- # Source can be either
27
- # Statement
28
- # or
29
- # Hash (all keys are optional)
30
- # :subject
31
- # :predicate
32
- # :object
33
- def delete(source)
34
- statement = case source
35
- when Statement
36
- source
37
- when Hash
38
- Statement.new(source)
39
- else
40
- # TODO
41
- raise NotImplementedError.new
42
- end
36
+ # Delete a statement from the model.
37
+ #
38
+ # @note
39
+ # All of subject, predicate, object nodes of the statement must be present.
40
+ #
41
+ # @param [Statement] statement
42
+ # @return [Boolean]
43
+ def delete(statement)
43
44
  Redland.librdf_model_remove_statement(@model.rdf_model, statement.rdf_statement).zero?
44
45
  end
45
46
 
46
- # Create a statement and add it to the model.
47
+ # Delete all statements from the model.
47
48
  #
48
- # Options are:
49
- # :subject, :predicate, :object,
50
- # (see Statement.new for option explanations).
49
+ # @todo Fix this extremely ineffective (slow) implementation
50
+ # @return [Boolean]
51
+ def delete_all
52
+ each { |st| delete(st) }
53
+ end
54
+
55
+ # Create a statement and add it to the model.
51
56
  #
52
- # Returns an instance of Statement on success,
53
- # or nil if the statement could not be added.
54
- def create(options = {})
55
- statement = Statement.new(options)
56
- add(statement) && statement
57
+ # @param [Hash] source subject, predicate and object nodes
58
+ # of the statement to be created (see Statement#initialize).
59
+ # @option source [Node, URI, String, nil] :subject
60
+ # @option source [Node, URI, String, nil] :predicate
61
+ # @option source [Node, URI, String, nil] :object
62
+ # @return [Statement, nil]
63
+ def create(source)
64
+ statement = Statement.new(source)
65
+ add(statement) ? statement : nil
57
66
  end
58
67
 
68
+ # Checks whether there are no statements in the model.
69
+ #
70
+ # @return [Boolean]
59
71
  def empty?
60
72
  size.zero?
61
73
  end
62
74
 
75
+ # Size of the model in statements.
76
+ #
77
+ # @note
78
+ # While #count must iterate across all statements in the model,
79
+ # {#size} tries to use a more efficient C implementation.
80
+ # So {#size} should be preferred to #count in terms of performance.
81
+ # However, for non-countable storages, {#size} falls back to
82
+ # using #count. Also, {#size} is not available for enumerables
83
+ # (e.g. produced from {#each} (without a block) or otherwise) and
84
+ # thus cannot be used to count "filtered" results.
85
+ #
86
+ # @return [Fixnum]
63
87
  def size
64
88
  s = Redland.librdf_model_size(@model.rdf_model)
65
- if s < 0
66
- raise RedlandError.new("Attempt to get size when using non-countable storage")
89
+ s < 0 ? count : s
90
+ end
91
+
92
+ # Enumerate (and filter) model statements.
93
+ # If given no block, returns Enumerator.
94
+ #
95
+ # @param [Statement, Hash, void] args
96
+ # if given Statement or Hash, filter the model statements
97
+ # according to the specified pattern (see {#find} options).
98
+ # @yieldparam [Statement]
99
+ # @return [void]
100
+ def each(*args)
101
+ if block_given?
102
+ rdf_stream =
103
+ if args.empty?
104
+ Redland.librdf_model_as_stream(@model.rdf_model)
105
+ else
106
+ pattern = args.first.is_a?(Statement) ? args.first.rdf_statement : Statement.new(args.first)
107
+ Redland.librdf_model_find_statements(@model.rdf_model, pattern.rdf_statement)
108
+ end
109
+ raise RedlandError, "Failed to create a new stream" if rdf_stream.null?
110
+
111
+ begin
112
+ while Redland.librdf_stream_end(rdf_stream).zero?
113
+ statement = Statement.new(Redland.librdf_stream_get_object(rdf_stream))
114
+ yield statement
115
+ Redland.librdf_stream_next(rdf_stream)
116
+ end
117
+ ensure
118
+ Redland.librdf_free_stream(rdf_stream)
119
+ end
67
120
  else
68
- s
121
+ enum_for(:each, *args)
69
122
  end
70
123
  end
71
124
 
72
125
  # Find statements satisfying the given criteria.
73
- # Scope can be:
74
- # :all
75
- # :first
76
- def find(scope, options = {}, &block)
77
- stream = Stream.new(@model, Statement.new(options))
78
-
126
+ #
127
+ # @param [:first, :all] scope find just one or all matches
128
+ # @param [Hash, Statement] options matching pattern made of:
129
+ # - Hash with :subject, :predicate or :object nodes, or
130
+ # - "patternized" Statement (nil nodes are matching anything).
131
+ # @return [Statement, Array, nil]
132
+ def find(scope, options = {})
79
133
  case scope
80
134
  when :first
81
- stream.current
135
+ each(options).first
82
136
  when :all
83
- stream.tail
137
+ each(options).to_a
84
138
  else
85
- raise RedlandError.new("Invalid search scope '#{scope}' specified.")
139
+ raise RedlandError, "Invalid search scope '#{scope}' specified."
86
140
  end
87
141
  end
88
142
 
143
+ # Find a first statement matching the given criteria.
144
+ # (Shortcut for {#find}(:first, options)).
145
+ #
146
+ # @param [Hash] options (see {#find})
147
+ # @return [Statement, nil]
89
148
  def first(options = {})
90
149
  find(:first, options)
91
150
  end
92
151
 
152
+ # Find all statements matching the given criteria.
153
+ # (Shortcut for {#find}(:all, options)).
154
+ #
155
+ # @param [Hash] options (see {#find})
156
+ # @return [Array<Statement>]
93
157
  def all(options = {})
94
158
  find(:all, options)
95
159
  end
96
-
97
-
98
- private
99
-
100
- def reset_stream
101
- @stream = Stream.new(@model)
102
- end
103
160
  end
104
161
  end