lib/redlander/model_proxy.rb in redlander-0.3.6 vs lib/redlander/model_proxy.rb in redlander-0.4.0

- old
+ new

@@ -1,104 +1,161 @@ -require 'redlander/stream' -require 'redlander/stream_enumerator' - module Redlander + # Proxy between model and its statements, + # allowing to scope actions on statements + # within a certain model. + # + # @example + # model = Redlander::Model.new + # model.statements + # # => ModelProxy + # model.statements.add(...) + # model.statements.each(...) + # model.statements.find(...) + # # etc... class ModelProxy - include StreamEnumerator + include Enumerable + # @param [Redlander::Model] model def initialize(model) @model = model end # Add a statement to the model. - # It must be a complete statement - all of subject, predicate, object parts must be present. - # Only statements that are legal RDF can be added. - # If the statement already exists in the model, it is not added. # - # Returns true on success or false on failure. + # @note + # All of subject, predicate, object nodes of the statement must be present. + # Only statements that are legal RDF can be added. + # If the statement already exists in the model, it is not added. + # + # @param [Statement] statement + # @return [Boolean] def add(statement) - if statement.valid? - Redland.librdf_model_add_statement(@model.rdf_model, statement.rdf_statement).zero? - end + Redland.librdf_model_add_statement(@model.rdf_model, statement.rdf_statement).zero? end + alias_method :<<, :add - # Delete a statement from the model, - # or delete all statements matching the given criteria. - # Source can be either - # Statement - # or - # Hash (all keys are optional) - # :subject - # :predicate - # :object - def delete(source) - statement = case source - when Statement - source - when Hash - Statement.new(source) - else - # TODO - raise NotImplementedError.new - end + # Delete a statement from the model. + # + # @note + # All of subject, predicate, object nodes of the statement must be present. + # + # @param [Statement] statement + # @return [Boolean] + def delete(statement) Redland.librdf_model_remove_statement(@model.rdf_model, statement.rdf_statement).zero? end + # Delete all statements from the model. + # + # @todo Fix this extremely ineffective (slow) implementation + # @return [Boolean] + def delete_all + each { |st| delete(st) } + end + # Create a statement and add it to the model. # - # Options are: - # :subject, :predicate, :object, - # (see Statement.new for option explanations). - # - # Returns an instance of Statement on success, - # or nil if the statement could not be added. - def create(options = {}) - statement = Statement.new(options) - add(statement) && statement + # @param [Hash] source subject, predicate and object nodes + # of the statement to be created (see Statement#initialize). + # @option source [Node, URI, String, nil] :subject + # @option source [Node, URI, String, nil] :predicate + # @option source [Node, URI, String, nil] :object + # @return [Statement, nil] + def create(source) + statement = Statement.new(source) + add(statement) ? statement : nil end + # Checks whether there are no statements in the model. + # + # @return [Boolean] def empty? size.zero? end + # Size of the model in statements. + # + # @note + # While #count must iterate across all statements in the model, + # {#size} tries to use a more efficient C implementation. + # So {#size} should be preferred to #count in terms of performance. + # However, for non-countable storages, {#size} falls back to + # using #count. Also, {#size} is not available for enumerables + # (e.g. produced from {#each} (without a block) or otherwise) and + # thus cannot be used to count "filtered" results. + # + # @return [Fixnum] def size s = Redland.librdf_model_size(@model.rdf_model) - if s < 0 - raise RedlandError.new("Attempt to get size when using non-countable storage") + s < 0 ? count : s + end + + # Enumerate (and filter) model statements. + # If given no block, returns Enumerator. + # + # @param [Statement, Hash, void] args + # if given Statement or Hash, filter the model statements + # according to the specified pattern (see {#find} options). + # @yieldparam [Statement] + # @return [void] + def each(*args) + if block_given? + rdf_stream = + if args.empty? + Redland.librdf_model_as_stream(@model.rdf_model) + else + pattern = args.first.is_a?(Statement) ? args.first.rdf_statement : Statement.new(args.first) + Redland.librdf_model_find_statements(@model.rdf_model, pattern.rdf_statement) + end + raise RedlandError, "Failed to create a new stream" if rdf_stream.null? + + begin + while Redland.librdf_stream_end(rdf_stream).zero? + statement = Statement.new(Redland.librdf_stream_get_object(rdf_stream)) + yield statement + Redland.librdf_stream_next(rdf_stream) + end + ensure + Redland.librdf_free_stream(rdf_stream) + end else - s + enum_for(:each, *args) end end # Find statements satisfying the given criteria. - # Scope can be: - # :all - # :first - def find(scope, options = {}, &block) - stream = Stream.new(@model, Statement.new(options)) - + # + # @param [:first, :all] scope find just one or all matches + # @param [Hash, Statement] options matching pattern made of: + # - Hash with :subject, :predicate or :object nodes, or + # - "patternized" Statement (nil nodes are matching anything). + # @return [Statement, Array, nil] + def find(scope, options = {}) case scope when :first - stream.current + each(options).first when :all - stream.tail + each(options).to_a else - raise RedlandError.new("Invalid search scope '#{scope}' specified.") + raise RedlandError, "Invalid search scope '#{scope}' specified." end end + # Find a first statement matching the given criteria. + # (Shortcut for {#find}(:first, options)). + # + # @param [Hash] options (see {#find}) + # @return [Statement, nil] def first(options = {}) find(:first, options) end + # Find all statements matching the given criteria. + # (Shortcut for {#find}(:all, options)). + # + # @param [Hash] options (see {#find}) + # @return [Array<Statement>] def all(options = {}) find(:all, options) - end - - - private - - def reset_stream - @stream = Stream.new(@model) end end end