From b4f38de3ad509963761a6a409c3e19cc729a4fed Mon Sep 17 00:00:00 2001 From: Mike Bethany Date: Sun, 9 Jan 2011 02:28:50 -0500 Subject: [PATCH] Hash searches fixed --- .gitignore | 2 + README.markdown | 107 +++++++++++++++++++++-------- _brainstorm/block_wheres.rb | 80 +++++++++++++++++++++ _brainstorm/regex_captures.rb | 18 +++++ hashmodel.gemspec | 1 - lib/hash_model/hash_model.rb | 22 +++++- spec/hash_model/hash_model_spec.rb | 90 ++++++++++++++++++++++-- 7 files changed, 286 insertions(+), 34 deletions(-) create mode 100644 _brainstorm/block_wheres.rb create mode 100644 _brainstorm/regex_captures.rb diff --git a/.gitignore b/.gitignore index 7a9b48c..2e6f3da 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ pkg .DS_Store *.tmproj tmtags +vendor + diff --git a/README.markdown b/README.markdown index 58cdeda..f1dff66 100644 --- a/README.markdown +++ b/README.markdown @@ -11,17 +11,16 @@ model class take a look at ActiveModel, it's probably more of what you're lookin ## Synopsis The major usefulness of this class is it allows you to filter and search flattened records based on any field. -A field can contain anything, including another hash, a string, and array, or even an Object class like String or Array, not -just an instance of an Object class. +A field can contain anything, including another hash, a string, an array, or even an Object class like String or Array, not just an instance of an Object class. -You can also search using boolean like logic e.g. +Searches are very You can also search using boolean like logic e.g. -@hm = HashModel.new(:raw\_data=>@records) -found = @hm.where {@switch == "-x" && @parameter\_type == String} + @hm = HashModel.new(:raw_data=>@records) + found = @hm.where {:switch == "-x" && :parameter__type == String} ## Usage -Just simple examples for now, waiting till I get a little more stable to do more in depth, for now look at the spec files for usage. +These are just a few of the major methods of the class, to see all the functionality take a look at the RSpec files. ### **Creating with an array of hashes** records = [ @@ -39,14 +38,16 @@ Just simple examples for now, waiting till I get a little more stable to do more >> {:switch=>"-z", :parameter=>{:type=>String}, :description=>"zee svitch zu moost calz", :_id=>4, :_group_id=>2} -### **Adding hashes after creation** +### **Adding hashes after creation : <<, +, add, concat, push** hash_model = HashModel.new - hash_model << records[0] - hash_model << records[1] - hash_model << records[2] + hash_model += records[0] + hash_model.concat records[1] + hash_model.push records[2] ### **Adding another hash model** + # You can also add another HashModel object to the existing one + # and it will add the raw records and reflatten. records = [ {:switch => ["-x", "--xtended"], :parameter => {:type => String, :require => true}, :description => "Xish stuff"}, {:switch => ["-y", "--why"], :description => "lucky what?"} @@ -60,45 +61,97 @@ Just simple examples for now, waiting till I get a little more stable to do more hash_model << hash_model2 # or hash_model += hash_model2 - - -### **Iterating over the HashModel** + + +### **Iterating over the HashModel : each** # the HashModel acts a lot like an array so you can iterate over it hash_model = HashModel.new(:raw_data=>records) hash_model.each do |record| # record is a hash end - -### **Flatten Index** + + +### **Flattening records : flatten_index** # Flatten index is automatically set to the first field ever given # but you can change it hash_model = HashModel.new(:raw_data=>records) puts hash_model.flatten_index + >> :switch + # you can use flattened field names hash_model.flatten_index = :parameter__type + + puts hash_model.flatten_index + >> :parameter__type + puts hash_model - >> {:parameter__type=>String, :switch=>["-x", "--xtended"], :parameter__require=>true, :description=>"Xish stuff", :_id=>0, :_group_id=>0} >> {:parameter__type=>nil, :switch=>["-y", "--why"], :description=>"lucky what?", :_id=>1, :_group_id=>1} >> {:parameter__type=>String, :switch=>"-z", :description=>"zee svitch zu moost calz", :_id=>2, :_group_id=>2} - # Notice that records that don't have the flatten index field have their value set to nil + # Notice that records that don't have the flatten index field have that field added and the value is set to nil -### **Accessing Records** - # You can use the values of the default flatten_index to retrieve the a record - hash_model = HashModel.new(:raw_data=>records) - - puts hash_model.where("-x") +### **Searching Records : where** + # This is where the real power of the library is. You can do complex boolean searches using flattened field names. + + # You can search using just a value and it will search based on the flatten_index + records = [ + {:switch => ["-x", "--xtended"], :parameter => {:type => String, :required => true}, :description => "Xish stuff", :something => 4}, + {:switch => ["-y", "--why"], :description => "lucky what?", :something => 7}, + {:switch => "-z", :parameter => {:type => String, :required => true}, :description => "zee svitch zu moost calz", :something => 4}, + ] + hm = HashModel.new(:raw_data=>records) + where = hm.where("-x") + + puts where + >> {:switch=>"-x", :parameter=>{:type=>String, :required=>true}, :description=>"Xish stuff", :something=>4, :_id=>0, :_group_id=>0} + + # Best of all you can use complex boolean searches using normal and flattend field names. + # Note that flattened field names are seperated with double under lines __ + hm = HashModel.new(:raw_data=>records) + where = hm.where {:something == 7 || (:parameter__type == String && :parameter__required == true)} + + puts where + >> {:switch=>"-x", :parameter=>{:type=>String, :required=>true}, :description=>"Xish stuff", :something=>4, :_id=>0, :_group_id=>0} + >> {:switch=>"--xtended", :parameter=>{:type=>String, :required=>true}, :description=>"Xish stuff", :something=>4, :_id=>1, :_group_id=>0} + >> {:switch=>"-z", :parameter=>{:type=>String, :required=>true}, :description=>"zee svitch zu moost calz", :something=>4, :_id=>4, :_group_id=>2} + + # You can even search using hash values + hm = HashModel.new(:raw_data=>records) + where = hm.where {:parameter == {:type => String, :required => true}} + + puts where + + >> {:switch=>"-x", :parameter=>{:type=>String, :required=>true}, :description=>"Xish stuff", :something=>4, :_id=>0, :_group_id=>0}, + >> {:switch=>"--xtended", :parameter=>{:type=>String, :required=>true}, :description=>"Xish stuff", :something=>4, :_id=>1, :_group_id=>0} + + +### **Finding Sibling Records : group** + # Since the HashModel class flattens records it is sometimes useful to know what records were created from the same raw data record. + # This works exactly like a where search so you can send just a value or send a block + records = [ + {:switch => ["-x", "--xtended"], :parameter => {:type => String, :required => true}, :description => "Xish stuff", :something => 4}, + {:switch => ["-y", "--why"], :description => "lucky what?", :something => 7}, + {:switch => "-z", :parameter => {:type => Integer, :required => true}, :description => "zee svitch zu moost calz", :something => 4}, + ] + hm = HashModel.new(:raw_data=>records) + group = hm.group {(:parameter__type == String && :parameter__required == true && :something == 4) || :something == 7} + + >> {:switch=>"-x", :parameter=>{:type=>String, :required=>true}, :description=>"Xish stuff", :something=>4, :_id=>0, :_group_id=>0} + >> {:switch=>"--xtended", :parameter=>{:type=>String, :required=>true}, :description=>"Xish stuff", :something=>4, :_id=>1, :_group_id=>0} + >> {:switch=>"-y", :description=>"lucky what?", :something=>7, :_id=>2, :_group_id=>1} + >> {:switch=>"--why", :description=>"lucky what?", :something=>7, :_id=>3, :_group_id=>1} + ## Version History -0.3.0 -* Changed where searches to use symbols instead of @variables. e.g. {:x == "x" && :y == "y"} instead of the less natural {@x == "x" && @y == "y"} -* Converted the HashModel filter to a string so it can be viewed and allows the above behavior. -- To do: allow subtractions -* Removed Jeweler and converted to Bundler gem building. +0.3.0 +* Changed where searches to use symbols instead of @variables. e.g. {:x == "x" && :y == "y"} instead of the less natural {@x == "x" && @y == "y"} +* Converted the HashModel filter to a string so it can be viewed and allows the above behavior. +- To do: allow subtractions. +* Removed Jeweler and converted to Bundler gem building. 0.2.0 * Fixed bug if first field name is shorter version of another field name, e.g. :short then :shorter would cause an error. diff --git a/_brainstorm/block_wheres.rb b/_brainstorm/block_wheres.rb new file mode 100644 index 0000000..dfa43ee --- /dev/null +++ b/_brainstorm/block_wheres.rb @@ -0,0 +1,80 @@ +$LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__), "/../lib")) +require 'hash_model' +=begin + + # Creates an object with instance variables for each field at every level + # This allows using a block like {:field1==true && :field2_subfield21="potato"} + def create_object_from_flat_hash(record, hash_record=Class.new.new, parent_key=nil) + + # Iterate through the record creating the object recursively + case record + when Hash + record.each do |key, value| + flat_key = "#{parent_key}#{"__" if !parent_key.nil?}#{key}" + hash_record.instance_variable_set("@#{flat_key}", value) + hash_record = create_object_from_flat_hash(value, hash_record, flat_key) + end + when Array + record.each do |value| + hash_record = create_object_from_flat_hash(value, hash_record, parent_key) + end + else + hash_record.instance_variable_set("@#{parent_key}", record) + end # case + + hash_record + end # create_object_from_flat_hash + + + +records = [ + {:switch => ["-x", "--xtended"], :parameter => {:type => String, :required => true}, :description => "Xish stuff", :something => 4}, + {:switch => ["-y", "--why"], :description => "lucky what?"}, + {:switch => "-z", :parameter => {:type => String, :required => true}, :description => "zee svitch zu moost calz", :something => 4}, +] +hm = HashModel.new(:raw_data=>records) +where = hm.where {:something == 4 && :parameter__type == String && :parameter__required == true} +puts "\nWhere:" +puts where + + +records = [ + {:switch => ["-x", "--xtended"], :parameter => {:type => String, :required => true}, :description => "Xish stuff", :something => 4}, + {:switch => ["-y", "--why"], :description => "lucky what?", :something => 7}, + {:switch => "-z", :parameter => {:type => Integer, :required => true}, :description => "zee svitch zu moost calz", :something => 4}, +] +hm = HashModel.new(:raw_data=>records) +where = hm.where {:parameter == {:type => String, :required => true}} +puts "\nWhere:" +puts where + + +puts "\nflat object" +flat = create_object_from_flat_hash(hm[0]) +puts flat.inspect +=end + +puts "\nproc" +xproc = proc {:parameter == {:type => String, :required => true} && :switch == ["-x", "--xtended"]} +xproc_source = xproc.to_source +puts "xproc: #{xproc_source}" + +matches = xproc_source.scan(/(\:\S+) ==/) + +puts "matches: #{matches}" +puts "\nshow items" +matches.each do |item| + puts "item: #{item}" +end +puts 'done' +#"proc { #{@filter} }.call".gsub(":", "@") + +#x = "{:parameter == {:type => String, :required => true}, " +#x.match + +puts "\nMatch test" +text = "The future Ruby is Ruby" +m1 = text.scan(/(Ruby)/) +puts "m1: #{m1}" + +puts "\n\ndone" diff --git a/_brainstorm/regex_captures.rb b/_brainstorm/regex_captures.rb new file mode 100644 index 0000000..d1d8845 --- /dev/null +++ b/_brainstorm/regex_captures.rb @@ -0,0 +1,18 @@ +string = "My phone number is (123) 555-1234." +phone_re = /\((\d{3})\)\s+(\d{3})-(\d{4})/ +m = phone_re.match(string) +unless m + puts "There was no match..." + exit +end +print "The whole string we started with: " +puts m.string +print "The entire part of the string that matched: " +puts m[0] +puts "The three captures: " +m.captures.each do |cap| + puts "Capture ##{cap}" +end +puts "Here's another way to get at the first capture:" +print "Capture #1: " +puts m[1] \ No newline at end of file diff --git a/hashmodel.gemspec b/hashmodel.gemspec index 6021922..bf4ce0a 100644 --- a/hashmodel.gemspec +++ b/hashmodel.gemspec @@ -11,7 +11,6 @@ Gem::Specification.new do |s| s.homepage = "http://github.com/mikbe/hashmodel" s.summary = %q{Store small amounts of dynamic data and easily search fields (even nested ones)} s.description = %q{A simple MVC type model class for storing records as an array of hashes. You can store deeply nested hashes and still easily flatten and querying the records using flattened field names.} - s.required_ruby_version = '>= 1.8.6' s.add_dependency "sourcify" s.add_dependency "file-tail" diff --git a/lib/hash_model/hash_model.rb b/lib/hash_model/hash_model.rb index 6f059fd..0b8e220 100644 --- a/lib/hash_model/hash_model.rb +++ b/lib/hash_model/hash_model.rb @@ -194,7 +194,13 @@ def where!(value=nil, &search) else # Convert the proc to a string so it can be viewed # and later have :'s turned into @'s - string_search = search.to_source.match(/^proc { (.*) }$/)[1] + + # Sourcify can create single or multi-line procs + source = search.to_source + unless (match = source.match(/^proc do\n(.*)\nend$/)) + match = source.match(/^proc { (.*) }$/) + end + string_search = match[1] end # !value.nil? # Set and process the filter @@ -248,9 +254,21 @@ def flatten new_record.merge!( duplicate_data.merge!( { :_id=>(id+=1), :_group_id=>group_id } ) ) end + # Change the filter so it looks for variables instead of symbols + unless @filter.nil? + proc_filter = @filter.clone + proc_filter.scan(/(:\S+) ==/).each {|match| proc_filter.sub!(match[0], match[0].sub(":","@"))} + proc_filter.sub!(":_group_id", "@_group_id") + proc_filter = "proc { #{proc_filter} }.call" + end + # Add the records to modified data if they pass the filter new_records.each do |new_record| - @modified_data << new_record if @filter.nil? ? true : (create_object_from_flat_hash(new_record).instance_eval( "proc { #{@filter} }.call".gsub(":", "@") ) ) + unless @filter.nil? + @modified_data << new_record if create_object_from_flat_hash(new_record).instance_eval proc_filter + else + @modified_data << new_record + end end end # raw_data.each diff --git a/spec/hash_model/hash_model_spec.rb b/spec/hash_model/hash_model_spec.rb index 7fded40..850c73d 100644 --- a/spec/hash_model/hash_model_spec.rb +++ b/spec/hash_model/hash_model_spec.rb @@ -632,7 +632,61 @@ end # not in place - # Possible addition + context "using blocks" do + + it "should search using a single value boolean block" do + @hm.where {:switch == "-x"}.should == [@flat_records[0]] + end + + it "should search using a complex boolean block" do + records = [ + {:switch => ["-x", "--xtended"], :parameter => {:type => String, :required => true}, :description => "Xish stuff", :something => 4}, + {:switch => ["-y", "--why"], :description => "lucky what?"}, + {:switch => "-z", :parameter => {:type => String, :required => true}, :description => "zee svitch zu moost calz", :something => 4}, + ] + @hm = HashModel.new(:raw_data=>records) + @hm.where {:something == 4 && :parameter__required == true}.should == [ + {:switch=>"-x", :parameter=>{:type=>String, :required=>true}, :description=>"Xish stuff", :something=>4, :_id=>0, :_group_id=>0}, + {:switch=>"--xtended", :parameter=>{:type=>String, :required=>true}, :description=>"Xish stuff", :something=>4, :_id=>1, :_group_id=>0}, + {:switch=>"-z", :parameter=>{:type=>String, :required=>true}, :description=>"zee svitch zu moost calz", :something=>4, :_id=>4, :_group_id=>2} + ] + @hm.where {:parameter__type == String && :parameter__required == true && :something == 4}.should == [ + {:switch=>"-x", :parameter=>{:type=>String, :required=>true}, :description=>"Xish stuff", :something=>4, :_id=>0, :_group_id=>0}, + {:switch=>"--xtended", :parameter=>{:type=>String, :required=>true}, :description=>"Xish stuff", :something=>4, :_id=>1, :_group_id=>0}, + {:switch=>"-z", :parameter=>{:type=>String, :required=>true}, :description=>"zee svitch zu moost calz", :something=>4, :_id=>4, :_group_id=>2} + ] + end + + it "should search using a complex, multi-line boolean block" do + records = [ + {:switch => ["-x", "--xtended"], :parameter => {:type => String, :required => true}, :description => "Xish stuff", :something => 4}, + {:switch => ["-y", "--why"], :description => "lucky what?", :something => 7}, + {:switch => "-z", :parameter => {:type => Integer, :required => true}, :description => "zee svitch zu moost calz", :something => 4}, + ] + @hm = HashModel.new(:raw_data=>records) + @hm.where {(:parameter__type == String && :parameter__required == true && :something == 4) || :something == 7}.should == [ + {:switch=>"-x", :parameter=>{:type=>String, :required=>true}, :description=>"Xish stuff", :something=>4, :_id=>0, :_group_id=>0}, + {:switch=>"--xtended", :parameter=>{:type=>String, :required=>true}, :description=>"Xish stuff", :something=>4, :_id=>1, :_group_id=>0}, + {:switch=>"-y", :description=>"lucky what?", :something=>7, :_id=>2, :_group_id=>1}, + {:switch=>"--why", :description=>"lucky what?", :something=>7, :_id=>3, :_group_id=>1} + ] + end + + it "should search with nested hashes in a block" do + records = [ + {:switch => ["-x", "--xtended"], :parameter => {:type => String, :required => true}, :description => "Xish stuff", :something => 4}, + {:switch => ["-y", "--why"], :description => "lucky what?", :something => 7}, + {:switch => "-z", :parameter => {:type => Integer, :required => true}, :description => "zee svitch zu moost calz", :something => 4}, + ] + @hm = HashModel.new(:raw_data=>records) + @hm.where {:parameter == {:type => String, :required => true}}.should == [ + {:switch=>"-x", :parameter=>{:type=>String, :required=>true}, :description=>"Xish stuff", :something=>4, :_id=>0, :_group_id=>0}, + {:switch=>"--xtended", :parameter=>{:type=>String, :required=>true}, :description=>"Xish stuff", :something=>4, :_id=>1, :_group_id=>0} + ] + end + + end + it "should return false if tested for inclusion of anything other than a hash" do @hm.include?([:switch=>"-x"]).should == false end @@ -692,17 +746,45 @@ end it "should return the records in the same raw data record when using a block" do - @hm.group{@switch == "-y"}.should == [@flat_records[2], @flat_records[3]] + @hm.group{:switch == "-y"}.should == [@flat_records[2], @flat_records[3]] end - it "should work across group_id's if searching for something that returns records from multiple groups" do - @hm.group{@parameter__type == String}.should == [ + it "should group across group_id's if searching for something that returns records from multiple groups" do + @hm.group{:parameter__type == String}.should == [ {:switch=>"-x", :parameter=>{:type=>String, :require=>true}, :description=>"Xish stuff", :_id=>0, :_group_id=>0}, {:switch=>"--xtended", :parameter=>{:type=>String, :require=>true}, :description=>"Xish stuff", :_id=>1, :_group_id=>0}, {:switch => "-z", :parameter => {:type => String}, :description => "zee svitch zu moost calz", :_id=>4, :_group_id=>2} ] end + it "should group with a complex block" do + records = [ + {:switch => ["-x", "--xtended"], :parameter => {:type => String, :required => true}, :description => "Xish stuff", :something => 4}, + {:switch => ["-y", "--why"], :description => "lucky what?", :something => 7}, + {:switch => "-z", :parameter => {:type => Integer, :required => true}, :description => "zee svitch zu moost calz", :something => 4}, + ] + @hm = HashModel.new(:raw_data=>records) + @hm.group {(:parameter__type == String && :parameter__required == true && :something == 4) || :something == 7}.should == [ + {:switch=>"-x", :parameter=>{:type=>String, :required=>true}, :description=>"Xish stuff", :something=>4, :_id=>0, :_group_id=>0}, + {:switch=>"--xtended", :parameter=>{:type=>String, :required=>true}, :description=>"Xish stuff", :something=>4, :_id=>1, :_group_id=>0}, + {:switch=>"-y", :description=>"lucky what?", :something=>7, :_id=>2, :_group_id=>1}, + {:switch=>"--why", :description=>"lucky what?", :something=>7, :_id=>3, :_group_id=>1} + ] + end + + it "should group with nested hashes block" do + records = [ + {:switch => ["-x", "--xtended"], :parameter => {:type => String, :required => true}, :description => "Xish stuff", :something => 4}, + {:switch => ["-y", "--why"], :description => "lucky what?", :something => 7}, + {:switch => "-z", :parameter => {:type => Integer, :required => true}, :description => "zee svitch zu moost calz", :something => 4}, + ] + @hm = HashModel.new(:raw_data=>records) + @hm.group {:parameter == {:type => String, :required => true}}.should == [ + {:switch=>"-x", :parameter=>{:type=>String, :required=>true}, :description=>"Xish stuff", :something=>4, :_id=>0, :_group_id=>0}, + {:switch=>"--xtended", :parameter=>{:type=>String, :required=>true}, :description=>"Xish stuff", :something=>4, :_id=>1, :_group_id=>0} + ] + end + end # not in place context "in place" do