Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
a7e8e4b
WIP on mango queries for couch2
Jan 10, 2017
870e1a9
Merge branch 'couchdb2' into couchdb2-mango-query-interface
Jan 10, 2017
470afe3
more WIP
Jan 10, 2017
9939246
WIP
Jan 10, 2017
fffd313
update test
Jan 10, 2017
c7e5002
change implementation to make scopes chainable
Jan 11, 2017
c17b026
update tests
Jan 11, 2017
2aa8c58
update doc
Jan 11, 2017
a1859be
pass query object amongst scope tree
Jan 12, 2017
9bb1007
update code wip
Jan 12, 2017
cc4701b
try to bind query object as self
Jan 12, 2017
f9e2619
composed query object
Jan 12, 2017
3cff807
passing tests
Jan 12, 2017
893703e
format tests better
Jan 12, 2017
952e422
update tests
Jan 12, 2017
925d3eb
update
Jan 12, 2017
f9152b5
passing tests
Jan 12, 2017
9997a31
clean up code
Jan 12, 2017
a3464a6
fix test for travis
Jan 12, 2017
37cd35e
change args so as not to conflict with method missing args
Jan 13, 2017
5d2b941
add more tests to flesh out the api
Jan 13, 2017
4c8a7f0
more dynamic test
Jan 13, 2017
58db538
respond to comments, add some tests, and refactor
Jan 13, 2017
c9f330e
remove some additional changes
Jan 13, 2017
dfbfb3d
add a autovivifying hash to simplify query building methods
Jan 13, 2017
b6490c4
better defined tests
Jan 14, 2017
09b1746
change macro method to mango_scope from scope
Jan 17, 2017
42fa49c
add more combination selectors
Jan 18, 2017
87aa9bf
fix syntax error
Jan 18, 2017
179469d
reorganize mango query class with a selector module
Jan 18, 2017
d3c8400
add more selectors to the selector table
Jan 18, 2017
2701fd3
add more selectors to the selector table
Jan 18, 2017
4c7ca06
add more selector options
Jan 18, 2017
f91d4a9
add more tests and more selectors
Jan 18, 2017
7a8da57
Merge branch 'couchdb2' into couchdb2-mango-query-interface
Jan 19, 2017
45f7c39
add args to lambda in selector_operator_map
Jan 19, 2017
ccfcb42
inline variable assignment in dolly scope initialize
Jan 19, 2017
c03f4cc
DRY up selector query creation
Jan 20, 2017
73c47f0
add skip method to mango query
Jan 20, 2017
f41e3ee
add more of the selector api by adding more operators and compiste se…
Jan 20, 2017
caabc19
add modulus and regex oeprators
Jan 20, 2017
e173079
add operator value type checking in order to aid developers
Jan 20, 2017
d6a6b8a
make select operator map private
Jan 20, 2017
d1e544e
create an object for operator and value type validation
Jan 20, 2017
e865c8c
allow for one off scopes to be chained to defined scopes, and vice versa
Jan 20, 2017
0b334d0
improve and DRY anonymous scope chaining using method missing
Jan 20, 2017
b0cbe0d
reorganize with module namespacing
Jan 23, 2017
6a3d8b7
allow for anonymous selectors on the class
Jan 23, 2017
04b96e4
more PIE variable names
Jan 23, 2017
fdb9625
clean up namespace
Jan 24, 2017
45ad32c
change lambda style
Jan 25, 2017
59ecaf6
add accepted value tests to query validator test
Jan 30, 2017
177fd28
add accepted value tests to query validator test
Jan 30, 2017
2da6307
move operator validation to proper place
Jan 30, 2017
b302059
get tests to pass
Jan 31, 2017
b8f2e2a
add more tests for unacceptable values
Jan 31, 2017
7bd26d7
update
Feb 15, 2017
f9a64a5
update
Feb 15, 2017
4e3e4d0
update
Feb 15, 2017
efe016c
use variable
Feb 15, 2017
0aeb3f7
inspect collection
Feb 15, 2017
02ec1b6
return a collection with mango scopes
Feb 15, 2017
dbe9e4e
return a collection with mango scopes
Feb 15, 2017
9434871
properly return http response for collection
Feb 15, 2017
4a18549
add a way to parse the response from mango, which does not include rows
Feb 15, 2017
a8c24b9
use parsed response
Feb 16, 2017
a70c133
change build_collection as well
Feb 17, 2017
fcd171b
use parsed for all
javierg Apr 3, 2018
cf41c44
Merge branch 'couchdb2-mango-query-interface' of github.com:amco/doll…
javierg Apr 3, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion lib/dolly/collection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,20 @@ def rows= ary
end
end

def doc_rows= ary
ary.each do |doc|
id = doc.delete '_id'
rev = doc.delete '_rev' if doc['_rev']
document = (docs_class || doc_class(id)).new doc
document.doc = doc.merge({'_id' => id, '_rev' => rev})
self << document
end
end

def load
parsed = JSON::parse json
self.rows = parsed['rows']
self.rows = parsed['rows'] if parsed['rows']
self.doc_rows = parsed['docs'] if parsed['docs']
end

def to_json options = {}
Expand Down
24 changes: 23 additions & 1 deletion lib/dolly/document.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require "dolly/query"
require "dolly/property"
require 'dolly/timestamps'
require "dolly/mango"

module Dolly
class Document
Expand All @@ -9,7 +10,7 @@ class Document
extend Dolly::Timestamps

attr_accessor :rows, :doc, :key
class_attribute :properties
class_attribute :properties, :mango_scopes
cattr_accessor :timestamps do
{}
end
Expand Down Expand Up @@ -149,6 +150,27 @@ def self.property *ary
end
end

class << self
def mango_scope scope_name, scope
self.mango_scopes ||= {}
name = scope_name.to_sym
self.mango_scopes[name] = ->(query_object, args=nil) { Mango::Scope.new(query_object, scope, args) }

(class << self; self end).instance_eval do
define_method name do |*args|
self.mango_scopes[name].call(Mango::Query.new(self), *args)
end
end
end

def selector name, *operator, value
anonymous_scope = ->{ selector(name, *operator, value) }
query_object = Mango::Query.new(self)
args = nil
Mango::Scope.new query_object, anonymous_scope, args
end
end

private
#TODO: create a PropertiesSet service object, to do all this
def self.write_methods name
Expand Down
10 changes: 10 additions & 0 deletions lib/dolly/mango.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
require 'dolly/mango/query_validator'
require 'dolly/mango/selector'
require 'dolly/mango/query'
require 'dolly/mango/scope'

module Dolly
module Mango

end
end
38 changes: 38 additions & 0 deletions lib/dolly/mango/query.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
module Dolly
module Mango
class Query
include Dolly::Mango::Selector

FIELDS_KEY = 'fields'.freeze
LIMIT_KEY = 'limit'.freeze
SKIP_KEY = 'skip'.freeze
SORT_KEY = 'sort'.freeze

attr_reader :proxy_class, :query

def initialize proxy_class
@proxy_class = proxy_class
@query = Hash.new{ |h,k| h[k] = Hash.new(&h.default_proc) }
query.compare_by_identity
end

def limit value
query[LIMIT_KEY] = value
end

def sort name, operator
query[SORT_KEY] ||= []
query[SORT_KEY] << {name => operator}
end

def fields *fields
query[FIELDS_KEY] ||= []
query[FIELDS_KEY].push *fields
end

def skip integer
query[SKIP_KEY] = integer.to_i
end
end
end
end
52 changes: 52 additions & 0 deletions lib/dolly/mango/query_validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
module Dolly
module Mango
class QueryValidator

def initialize operator, value
@operator, @value = operator, value
end

def validate!
return if Selector::EQUALITY_OPERATORS.include? operator
raise Dolly::BadQueryArguement.new(operator, 'Boolean') if boolean_op?
raise Dolly::BadQueryArguement.new(operator, Selector::POSSIBLE_TYPE_VALUES.join(', ')) if type_op?
raise Dolly::BadQueryArguement.new(operator, Array) if array_op?
raise Dolly::BadQueryArguement.new(operator, Integer) if int_op?
raise Dolly::BadQueryArguement.new(operator, '[Divisor, Remainder] Array of Integers') if mod_op?
raise Dolly::BadQueryArguement.new(operator, String) if regex_op?
raise Dolly::BadQueryArguement.new(operator, Array) if combination_op?
end

private
attr_reader :operator, :value

def boolean_op?
operator == Selector::EXISTS_OPERATOR && ![true, false].include?(value)
end

def type_op?
operator == Selector::TYPE_OPERATOR && !Selector::POSSIBLE_TYPE_VALUES.include?(value)
end

def array_op?
Selector::ARRAY_OPERATORS.include?(operator) && !value.is_a?(Array)
end

def int_op?
operator == Selector::SIZE_OPERATOR && !value.is_a?(Integer)
end

def mod_op?
operator == Selector::MOD_OPERATOR && (!value.is_a?(Array) || value.count != 2 || value.none? {|el| el.is_a? Integer })
end

def regex_op?
operator == Selector::REGEX_OPERATOR && !value.is_a?(String)
end

def combination_op?
Selector::COMBINATION_OPERATORS.include?(operator) && !value.is_a?(Array)
end
end
end
end
33 changes: 33 additions & 0 deletions lib/dolly/mango/scope.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
module Dolly
module Mango
class Scope
attr_reader :query_object, :scope, :scope_args

delegate :proxy_class, :query, to: :query_object

def initialize query_object, scope, scope_args
@query_object, @scope, @scope_args = query_object, scope, scope_args
evaluate_scope
end

def method_missing(method, *args, &block)
if proxy_class.mango_scopes.include?(method)
proxy_class.mango_scopes[method].call(query_object, args)
elsif query_object.respond_to? method
query_object.send(method, *args, &block)
return self
else
resp = proxy_class.database.mango query.to_json
collection = Dolly::Collection.new(resp.response.body, proxy_class)
collection.send(method, *args, &block)
end
end

private

def evaluate_scope
query_object.instance_exec(*scope_args, &scope)
end
end
end
end
125 changes: 125 additions & 0 deletions lib/dolly/mango/selector.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
module Dolly
module Mango
module Selector

SELECTOR = 'selector'.freeze

# Equality Operators
EQ_OPERATOR = '$eq'.freeze
NE_OPERATOR = '$ne'.freeze
GT_OPERATOR = '$gt'.freeze
LT_OPERATOR = '$lt'.freeze
GTE_OPERATOR = '$gte'.freeze
LTE_OPERATOR = '$lte'.freeze

EQUALITY_OPERATORS = [ EQ_OPERATOR, NE_OPERATOR, GT_OPERATOR, LT_OPERATOR, GTE_OPERATOR, LTE_OPERATOR ].freeze

# Object Operators
EXISTS_OPERATOR = '$exists'.freeze
TYPE_OPERATOR = '$type'.freeze

OBJECT_OPERATORS = [EXISTS_OPERATOR, TYPE_OPERATOR].freeze

# Array Operators
IN_OPERATOR = '$in'.freeze
NIN_OPERATOR = '$nin'.freeze
SIZE_OPERATOR = '$size'.freeze

ARRAY_OPERATORS = [IN_OPERATOR, NIN_OPERATOR].freeze

# Miscellaneous Operators
MOD_OPERATOR = '$mod'.freeze
REGEX_OPERATOR = '$regex'.freeze

MISC_OPERATORS = [MOD_OPERATOR, REGEX_OPERATOR].freeze

# Combination Operators
OR_OPERATOR = '$or'.freeze
NOT_SELECTOR = '$not'.freeze
NOR_SELECTOR = '$nor'.freeze
AND_OPERATOR = '$and'.freeze
ALL_OPERATOR = '$all'.freeze
EM_OPERATOR = '$elemMatch'.freeze

COMBINATION_OPERATORS = [ OR_OPERATOR, NOT_SELECTOR, NOR_SELECTOR, AND_OPERATOR, ALL_OPERATOR, EM_OPERATOR ].freeze

POSSIBLE_TYPE_VALUES = %w/null boolean number string array object/.freeze

ALL_OPERATORS = [ EQUALITY_OPERATORS, OBJECT_OPERATORS, ARRAY_OPERATORS, SIZE_OPERATOR, MISC_OPERATORS, COMBINATION_OPERATORS ].flatten.freeze

def selector name, *operator, value
proxy_operator = operator.last
operator_check! proxy_operator
operator = operator.count > 1 ? operator : operator.first
select_operator_map[operator].call(name, value)
end

private

def select_operator_map
{
eq: ->(name, value) { build_equality_selector name, value, EQ_OPERATOR },
ne: ->(name, value) { build_equality_selector name, value, NE_OPERATOR },
gt: ->(name, value) { build_equality_selector name, value, GT_OPERATOR },
gte: ->(name, value) { build_equality_selector name, value, GTE_OPERATOR },
lt: ->(name, value) { build_equality_selector name, value, LT_OPERATOR },
lte: ->(name, value) { build_equality_selector name, value, LTE_OPERATOR },

exists: ->(name, value=true) { build_equality_selector name, value, EXISTS_OPERATOR },
type: ->(name, value) { build_equality_selector name, value, TYPE_OPERATOR },

in: ->(name, value) { build_equality_selector name, value, IN_OPERATOR },
nin: ->(name, value) { build_equality_selector name, value, NIN_OPERATOR },
size: ->(name, value) { build_equality_selector name, value, SIZE_OPERATOR },

mod: ->(name, value) { build_equality_selector name, value, MOD_OPERATOR },
regex: ->(name, value) { build_equality_selector name, value, REGEX_OPERATOR },

nor: ->(name, value) { build_exclusive_selector name, value, NOR_SELECTOR} ,
all: ->(name, value) { build_exclusive_selector name, value, ALL_OPERATOR },
and: ->(name, value) { build_exclusive_selector name, value, AND_OPERATOR },
or: ->(name, value) { build_exclusive_selector name, value, OR_OPERATOR },

[:em, :gt] => ->(name, value) { build_composite_selector name, value, EM_OPERATOR, GT_OPERATOR },
[:em, :gte] => ->(name, value) { build_composite_selector name, value, EM_OPERATOR, GTE_OPERATOR },
[:em, :lt] => ->(name, value) { build_composite_selector name, value, EM_OPERATOR, LT_OPERATOR },
[:em, :lte] => ->(name, value) { build_composite_selector name, value, EM_OPERATOR, LTE_OPERATOR },
[:em, :or] => ->(name, value) { build_composite_selector name, value, EM_OPERATOR, OR_OPERATOR },
[:em, :and] => ->(name, value) { build_composite_selector name, value, EM_OPERATOR, AND_OPERATOR },

[:not, :gt] => ->(name, value) { build_composite_selector name, value, NOT_OPERATOR, GT_OPERATOR },
[:not, :gte] => ->(name, value) { build_composite_selector name, value, NOT_OPERATOR, GTE_OPERATOR },
[:not, :lt] => ->(name, value) { build_composite_selector name, value, NOT_OPERATOR, LT_OPERATOR },
[:not, :lte] => ->(name, value) { build_composite_selector name, value, NOT_OPERATOR, LTE_OPERATOR },
[:not, :or] => ->(name, value) { build_composite_selector name, value, NOT_OPERATOR, OR_OPERATOR },
[:not, :and] => ->(name, value) { build_composite_selector name, value, NOT_OPERATOR, AND_OPERATOR }
}.freeze
end

def build_equality_selector name, value, operator
operator_value_type_check!(operator, value)
query[SELECTOR][name][operator] = value
end

def build_exclusive_selector name, value, operator
operator_value_type_check!(operator, value)
query[SELECTOR][operator][name] = value
end

def build_composite_selector name, value, *operators
first, second = operators
operator_value_type_check!(second, value)

query[SELECTOR][name][first][second] = value
end

def operator_check! operator
raise Dolly::UnrecognizedOperator.new(operator) unless select_operator_map.keys.include? operator
end

def operator_value_type_check! operator, value
QueryValidator.new(operator, value).validate!
end
end
end
end
2 changes: 1 addition & 1 deletion lib/dolly/query.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def last limit = 1

def build_collection q
res = database.all_docs(q)
Collection.new res.response.body, name_for_class
Collection.new res.parsed_response.to_json, name_for_class
end

def find_with doc, view_name, opts = {}
Expand Down
7 changes: 6 additions & 1 deletion lib/dolly/request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class Request
include HTTParty
DEFAULT_HOST = 'localhost'
DEFAULT_PORT = '5984'
MANGO_QUERY = '_find'.freeze

attr_accessor :database_name, :host, :port, :bulk_document

Expand Down Expand Up @@ -44,6 +45,10 @@ def delete resource
request :delete, full_path(resource), {}
end

def mango data
request :post, full_path(MANGO_QUERY), {body: data}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you want to return the parsed response?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont see why I would. none of the other request do so

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👌 just wondering

end

def attach resource, attachment_name, data, headers = {}
data = StringIO.new(data) if data.is_a?(String)
request :put, attachment_path(resource, attachment_name), {body: data, headers: headers}
Expand All @@ -59,7 +64,7 @@ def uuids opts = {}

def all_docs data = {}
data = values_to_json data.merge( include_docs: true )
request :get, full_path('_all_docs'), {query: data}
request(:get, full_path('_all_docs'), { query: data })
end

def request method, resource, data = nil
Expand Down
18 changes: 18 additions & 0 deletions lib/exceptions/dolly.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,22 @@ def to_s
end
class DocumentInvalidError < RuntimeError; end
class MissingPropertyError < RuntimeError; end
class BadQueryArguement < RuntimeError
def initialize operator, expected_type
@operator, @expected_type = operator, expected_type
end

def to_s
"The operator #{@operator} only accepts a(n) #{@expected_type}"
end
end
class UnrecognizedOperator < RuntimeError
def initialize operator
@operator = operator
end

def to_s
"The operator #{@operator} is unrecognized"
end
end
end
Loading