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