lib/lotus/model/adapters/memory/query.rb in lotus-model-0.3.0 vs lib/lotus/model/adapters/memory/query.rb in lotus-model-0.3.1

- old
+ new

@@ -1,6 +1,7 @@ require 'forwardable' +require 'ostruct' require 'lotus/utils/kernel' module Lotus module Model module Adapters @@ -15,11 +16,11 @@ # # @example # # query.where(language: 'ruby') # .and(framework: 'lotus') - # .desc(:users_count).all + # .reverse_order(:users_count).all # # # the records are fetched only when we invoke #all # # It implements Ruby's `Enumerable` and borrows some methods from `Array`. # Expect a query to act like them. @@ -92,17 +93,46 @@ # # @example Range # # query.where(year: 1900..1982) # + # @example Using block + # + # query.where { age > 31 } + # # @example Multiple conditions # # query.where(language: 'ruby') # .where(framework: 'lotus') - def where(condition) - column, value = _expand_condition(condition) - conditions.push([:where, Proc.new{ find_all{|r| r.fetch(column, nil) == value} }]) + # + # @example Multiple conditions with blocks + # + # query.where { language == 'ruby' } + # .where { framework == 'lotus' } + # + # @example Mixed hash and block conditions + # + # query.where(language: 'ruby') + # .where { framework == 'lotus' } + def where(condition = nil, &blk) + if blk + _push_evaluated_block_condition(:where, blk, :find_all) + elsif condition + _push_to_expanded_condition(:where, condition) do |column, value| + Proc.new { + find_all { |r| + case value + when Array,Set,Range + value.include?(r.fetch(column, nil)) + else + r.fetch(column, nil) == value + end + } + } + end + end + self end alias_method :and, :where @@ -129,13 +159,28 @@ # query.where(id: 1).or(author_id: [15, 23]) # # @example Range # # query.where(country: 'italy').or(year: 1900..1982) - def or(condition=nil, &blk) - column, value = _expand_condition(condition) - conditions.push([:or, Proc.new{ find_all{|r| r.fetch(column) == value} }]) + # + # @example Using block + # + # query.where { age == 31 }.or { age == 32 } + # + # @example Mixed hash and block conditions + # + # query.where(language: 'ruby') + # .or { framework == 'lotus' } + def or(condition = nil, &blk) + if blk + _push_evaluated_block_condition(:or, blk, :find_all) + elsif condition + _push_to_expanded_condition(:or, condition) do |column, value| + Proc.new { find_all { |r| r.fetch(column) == value} } + end + end + self end # Logical negation of a #where condition. # @@ -163,13 +208,33 @@ # # @example Multiple conditions # # query.exclude(language: 'java') # .exclude(company: 'enterprise') - def exclude(condition) - column, value = _expand_condition(condition) - conditions.push([:where, Proc.new{ reject {|r| r.fetch(column) == value} }]) + # + # @example Using block + # + # query.exclude { age > 31 } + # + # @example Multiple conditions with blocks + # + # query.exclude { language == 'java' } + # .exclude { framework == 'spring' } + # + # @example Mixed hash and block conditions + # + # query.exclude(language: 'java') + # .exclude { framework == 'spring' } + def exclude(condition = nil, &blk) + if blk + _push_evaluated_block_condition(:where, blk, :reject) + elsif condition + _push_to_expanded_condition(:where, condition) do |column, value| + Proc.new { reject { |r| r.fetch(column) == value} } + end + end + self end alias_method :not, :exclude @@ -202,11 +267,11 @@ # # @return self # # @since 0.1.0 # - # @see Lotus::Model::Adapters::Sql::Query#desc + # @see Lotus::Model::Adapters::Memory::Query#reverse_order # # @example Single column # # query.order(:name) # @@ -223,42 +288,78 @@ end self end + # Alias for order + # + # @since 0.1.0 + # + # @see Lotus::Model::Adapters::Memory::Query#order + # + # @example Single column + # + # query.asc(:name) + # + # @example Multiple columns + # + # query.asc(:name, :year) + # + # @example Multiple invokations + # + # query.asc(:name).asc(:year) alias_method :asc, :order # Specify the descending order of the records, sorted by the given # columns. # # @param columns [Array<Symbol>] the column names # # @return self # - # @since 0.1.0 + # @since 0.3.1 # - # @see Lotus::Model::Adapters::Sql::Query#order + # @see Lotus::Model::Adapters::Memory::Query#order # # @example Single column # - # query.desc(:name) + # query.reverse_order(:name) # # @example Multiple columns # - # query.desc(:name, :year) + # query.reverse_order(:name, :year) # # @example Multiple invokations # - # query.desc(:name).desc(:year) - def desc(*columns) + # query.reverse_order(:name).reverse_order(:year) + def reverse_order(*columns) Lotus::Utils::Kernel.Array(columns).each do |column| modifiers.push(Proc.new{ sort_by!{|r| r.fetch(column)}.reverse! }) end self end + # Alias for reverse_order + # + # @since 0.1.0 + # + # @see Lotus::Model::Adapters::Memory::Query#reverse_order + # + # @example Single column + # + # query.desc(:name) + # + # @example Multiple columns + # + # query.desc(:name, :year) + # + # @example Multiple invokations + # + # query.desc(:name).desc(:year) + alias_method :desc, :reverse_order + # Limit the number of records to return. # # @param number [Fixnum] # # @return self @@ -484,11 +585,54 @@ def _all_with_present_column(column) all.map {|record| record.public_send(column) }.compact end - def _expand_condition(condition) - Array(condition).flatten + # Expands and yields keys and values of a query hash condition and + # stores the result and condition type in the conditions array. + # + # It yields condition's keys and values to allow the caller to create a proc + # object to be stored and executed later performing the actual query. + # + # @param condition_type [Symbol] the condition type. (eg. `:where`, `:or`) + # @param condition [Hash] the query condition to be expanded. + # + # @return [Array<Array>] the conditions array itself. + # + # @api private + # @since 0.3.1 + def _push_to_expanded_condition(condition_type, condition) + proc = yield Array(condition).flatten(1) + conditions.push([condition_type, proc]) + end + + # Evaluates a block condition of a specified type and stores it in the + # conditions array. + # + # @param condition_type [Symbol] the condition type. (eg. `:where`, `:or`) + # @param condition [Proc] the query condition to be evaluated and stored. + # @param strategy [Symbol] the iterator method to be executed. + # (eg. `:find_all`, `:reject`) + # + # @return [Array<Array>] the conditions array itself. + # + # @raise [Lotus::Model::InvalidQueryError] if block raises error when + # evaluated. + # + # @api private + # @since 0.3.1 + def _push_evaluated_block_condition(condition_type, condition, strategy) + conditions.push([condition_type, Proc.new { + send(strategy) { |r| + begin + OpenStruct.new(r).instance_eval(&condition) + rescue NoMethodError + # TODO improve the error message, informing which + # attributes are invalid + raise Lotus::Model::InvalidQueryError.new + end + } + }]) end end end end end