Permalink
Browse files

enhanced sub searching

Mongo like. [] indicates elemMatch on mongo and for the memory table
store it ends up working similarly
  • Loading branch information...
Dan Peterson & Edward Muller freeformz
Dan Peterson & Edward Muller authored and freeformz committed Dec 28, 2010
1 parent 8903a4f commit 98a1e4116f69c1c7038d00211bea507918186311
Showing with 80 additions and 20 deletions.
  1. +9 −1 Gemfile.lock
  2. +1 −0 cloudkit.gemspec
  3. +38 −17 lib/cloudkit/store/memory_table.rb
  4. +22 −1 lib/cloudkit/store/mongo_store.rb
  5. +10 −1 spec/spec_helper.rb
View
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
- cloudkit (0.12.0.pre3)
+ cloudkit (0.12.0.pre4)
bson (~> 1.1.2)
bson_ext (~> 1.1.2)
formatador (~> 0.0.10)
@@ -17,9 +17,11 @@ GEM
specs:
bson (1.1.2)
bson_ext (1.1.2)
+ columnize (0.3.2)
diff-lcs (1.1.2)
formatador (0.0.10)
json (1.4.6)
+ linecache (0.43)
macaddr (1.0.0)
mongo (1.1.2)
bson (>= 1.1.1)
@@ -36,6 +38,11 @@ GEM
rspec-expectations (2.2.0)
diff-lcs (~> 1.1.2)
rspec-mocks (2.2.0)
+ ruby-debug (0.10.4)
+ columnize (>= 0.1)
+ ruby-debug-base (~> 0.10.4.0)
+ ruby-debug-base (0.10.4)
+ linecache (>= 0.3)
ruby-openid (2.1.8)
uuid (2.0.1)
macaddr (~> 1.0)
@@ -55,5 +62,6 @@ DEPENDENCIES
rack-test
rake (~> 0.8.7)
rspec (~> 2.2.0)
+ ruby-debug
ruby-openid (~> 2.1)
uuid (= 2.0.1)
View
@@ -38,5 +38,6 @@ EOD
s.add_development_dependency 'rake', '~>0.8.7'
s.add_development_dependency 'rspec', '~> 2.2.0'
s.add_development_dependency 'rack-test'
+ s.add_development_dependency 'ruby-debug'
end
@@ -60,7 +60,7 @@ def query(&block)
q.run(self)
end
- protected
+ protected
def valid?(record)
return false unless record.is_a?(Hash)
@@ -78,29 +78,50 @@ def initialize
@conditions = []
end
+ def element_matcher(search, conditions, document)
+ search_path = search.split('.')
+ key = search_path.shift
+ doc_key = (key && key[-2..-1] == '[]') ? key[0..-3] : key
+
+ if key
+ new_document = case document
+ when Hash
+ document[doc_key]
+ when Array
+ document.map {|t| t[doc_key] if t.is_a?(Hash) }.compact.flatten
+ end
+
+ element_matcher(search_path.join('.'), conditions, new_document)
+ elsif search_path.empty?
+ case document
+ when Array
+ document.any? do |item|
+ element_matcher(search, conditions, item)
+ end
+ when Hash
+ case conditions
+ when Hash
+ conditions.all? {|c_k, c_v| document[c_k] == c_v }
+ else
+ document[key] == conditions
+ end
+ else
+ document == conditions
+ end
+ end
+ end
+
# Run a query against the provided table using the conditions stored in this
# MemoryQuery instance. Returns all records that match all conditions.
# Conditions are added using #add_condition.
def run(table)
table.keys.inject([]) do |result, key|
- if @conditions.all? do |condition|
+ if @conditions.all? do |condition|
if condition[0] == 'search'
JSON.parse(condition[2]).all? do |search_key, search_value|
target = JSON.parse(table[key]['json'])
- search_key.split('.').each do |sub|
- case target
- when Hash
- target = target[sub]
- when Array
- target = target.map { |item| item[sub] if item.is_a?(Hash) }.compact.flatten
- end
- end
- case target
- when Array
- target.any? { |item| item == search_value }
- else
- target == search_value
- end
+
+ element_matcher(search_key, search_value, target)
end
else
table[key][condition[0]] == condition[2]
@@ -118,7 +139,7 @@ def locate_targets(term,targets)
when Hash
targets = [targets[term]]
when Array
- targets = targets.select { |item| item.has_key?(term) }.map
+ targets = targets.select { |item| item.has_key?(term) }.map
end
end
@@ -388,6 +388,24 @@ def initialize
@conditions = []
end
+ def elem_matcherize(search, conditions)
+ search_path = search.split('.')
+ bracketed_element = search_path.detect {|p| p[-2..-1] == '[]' }
+
+ if bracketed_element
+ key = search_path.slice!(0..search_path.index(bracketed_element)).join('.')
+ else
+ key = search_path.slice!(0..-1).join('.')
+ end
+ doc_key = (key && key[-2..-1] == '[]') ? key[0..-3] : key
+
+ if key != doc_key
+ { doc_key => { '$elemMatch' => search_path.empty? ? conditions : elem_matcherize(search_path.join('.'), conditions) } }
+ elsif key == doc_key
+ { doc_key => search_path.empty? ? conditions : elem_matcherize(search_path.join('.'), conditions) }
+ end
+ end
+
# Runs the query
# Usually called only by Cloudkit::MongoStore.query
#
@@ -398,7 +416,10 @@ def run(table)
if condition[0] == 'search'
search_conditions = JSON(condition[2])
search_conditions.each do |key, value|
- fquery.update("json.#{key}" => value)
+ debugger
+ mongo_query = elem_matcherize(key, value)
+ mongo_query["json.#{mongo_query.keys.first}"] = mongo_query.delete(mongo_query.keys.first)
+ fquery.update(mongo_query)
end
fquery
else
View
@@ -105,7 +105,7 @@ def json_body
it "which should reject non-hash records" do
expect {
@table['a'] = 1
- }.to
+ }.to
end
it "which should reject non-string record keys" do
@@ -211,6 +211,15 @@ def json_body
q.add_condition('search', :eql, {'services.nodes.name' => 'three'}.to_json)
}.should == [{:pk=>"two", "json"=>"{\"services\":[{\"nodes\":[{\"name\":\"three\"},{\"name\":\"four\"}]}]}"}]
end
+
+ it "should query for a full sub item match" do
+ @table["one"] = {'json' => { 'services' => [ {'nodes' => [ {'name' => 'one', 'kind' => 'good'},{'name' => 'two','kind' => 'bad'} ] } ] }.to_json }
+ @table["two"] = {'json' => { 'services' => [ {'nodes' => [ {'name' => 'one', 'kind' => 'bad'},{'name' => 'four'} ] } ] }.to_json }
+ @table.query { |q|
+ q.add_condition("search", :eql, {"services[].nodes[]" => {"name" => "one", "kind" => "bad"}}.to_json)
+ }.map {|r| r.update("json" => JSON.parse(r["json"])) }.
+ should == [{:pk=>"two", "json"=>JSON.parse("{\"services\":[{\"nodes\":[{\"kind\":\"bad\",\"name\":\"one\"},{\"name\":\"four\"}]}]}")}]
+ end
end
end

0 comments on commit 98a1e41

Please sign in to comment.