Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Hash searches fixed
  • Loading branch information
mikbe committed Jan 9, 2011
1 parent 4770192 commit b4f38de
Show file tree
Hide file tree
Showing 7 changed files with 286 additions and 34 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Expand Up @@ -7,3 +7,5 @@ pkg
.DS_Store
*.tmproj
tmtags
vendor

107 changes: 80 additions & 27 deletions README.markdown
Expand Up @@ -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 = [
Expand All @@ -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?"}
Expand All @@ -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.
Expand Down
80 changes: 80 additions & 0 deletions _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"
18 changes: 18 additions & 0 deletions _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]
1 change: 0 additions & 1 deletion hashmodel.gemspec
Expand Up @@ -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"
Expand Down
22 changes: 20 additions & 2 deletions lib/hash_model/hash_model.rb
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit b4f38de

Please sign in to comment.