soulmate 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -13,6 +13,7 @@ group :development do
13
13
  end
14
14
 
15
15
  gem 'redis', "~> 2.1.1"
16
+ gem 'multi_json', "~> 1.0.3"
16
17
  gem 'rack-contrib'
17
18
  gem 'vegas', "~> 0.1.8"
18
19
  gem 'sinatra', "~> 1.2.3"
data/Gemfile.lock CHANGED
@@ -6,6 +6,7 @@ GEM
6
6
  bundler (~> 1.0.0)
7
7
  git (>= 1.2.5)
8
8
  rake
9
+ multi_json (1.0.3)
9
10
  rack (1.2.2)
10
11
  rack-contrib (1.1.0)
11
12
  rack (>= 0.9.1)
@@ -26,6 +27,7 @@ PLATFORMS
26
27
  DEPENDENCIES
27
28
  bundler (~> 1.0.0)
28
29
  jeweler (~> 1.5.2)
30
+ multi_json (~> 1.0.3)
29
31
  rack-contrib
30
32
  rcov
31
33
  redis (~> 2.1.1)
data/README.markdown CHANGED
@@ -56,7 +56,7 @@ Once it's loaded, we can query this data by starting `soulmate-web`:
56
56
 
57
57
  $ soulmate-web --foreground --no-launch --redis=redis://localhost:6379/0
58
58
 
59
- And viewing the service in your browser: <a href="http://localhost:5678/search?types[]=venue&term=stad">http://localhost:5678/search?types[]=venue&term=stad</a>. You should see something like:
59
+ And viewing the service in your browser: http://localhost:5678/search?types[]=venue&term=stad. You should see something like:
60
60
 
61
61
  {
62
62
  "term": "stad",
data/bin/soulmate CHANGED
@@ -27,14 +27,47 @@ parser = OptionParser.new do |opts|
27
27
 
28
28
  opts.separator ""
29
29
  opts.separator "Commands:"
30
- opts.separator " load TYPE Loads items of specified type read from stdin in the JSON lines format"
30
+ opts.separator " load TYPE Replaces collection specified by TYPE with items read from stdin in the JSON lines format."
31
+ opts.separator " add TYPE Adds items to collection specified by TYPE read from stdin in the JSON lines format."
32
+ opts.separator " remove TYPE Removes items from collection specified by TYPE read from stdin in the JSON lines format. Items only require an 'id', all other fields are ignored."
33
+ opts.separator " query TYPE QUERY Queries for items from collection specified by TYPE."
31
34
  end
32
35
 
33
36
  def load(type)
34
37
  puts "Loading items of type #{type}..."
35
- items = $stdin.read.split("\n").map { |l| JSON.parse(l) }
36
- total = Soulmate::Loader.new(type).load(items)
37
- puts "Loaded a total of #{total} items"
38
+ items = $stdin.read.split("\n").map { |l| MultiJson.decode(l) }
39
+ loaded = Soulmate::Loader.new(type).load(items)
40
+ puts "Loaded a total of #{loaded.size} items"
41
+ end
42
+
43
+ def add(type)
44
+ puts "Adding items of type #{type}..."
45
+ loader = Soulmate::Loader.new(type)
46
+ items = $stdin.read.split("\n").map { |l| MultiJson.decode(l) }
47
+ items.each do |item|
48
+ loader.add(item)
49
+ end
50
+ puts "Loaded a total of #{items.size} items"
51
+ end
52
+
53
+ def remove(type)
54
+ puts "Removing items of type #{type}..."
55
+ loader = Soulmate::Loader.new(type)
56
+ items = $stdin.read.split("\n").map { |l| MultiJson.decode(l) }
57
+ items.each do |item|
58
+ loader.remove(item)
59
+ end
60
+ puts "Removed a total of #{items.size} items"
61
+ end
62
+
63
+ def query(type, query)
64
+ puts "> Querying '#{type}' for '#{query}'"
65
+ matcher = Soulmate::Matcher.new(type)
66
+ results = matcher.matches_for_term(query, :limit => 0)
67
+ results.each do |item|
68
+ puts MultiJson.encode(item)
69
+ end
70
+ puts "> Found #{results.size} matches"
38
71
  end
39
72
 
40
73
  parser.parse!
@@ -42,6 +75,12 @@ parser.parse!
42
75
  case ARGV[0]
43
76
  when 'load'
44
77
  load ARGV[1]
78
+ when 'add'
79
+ add ARGV[1]
80
+ when 'remove'
81
+ remove ARGV[1]
82
+ when 'query'
83
+ query ARGV[1], ARGV[2]
45
84
  else
46
85
  puts parser.help
47
86
  end
data/lib/soulmate.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  require 'uri'
2
- require 'json'
2
+ require 'multi_json'
3
3
  require 'redis'
4
4
 
5
5
  require 'soulmate/version'
@@ -5,7 +5,7 @@ module Soulmate
5
5
  words = normalize(phrase).split(' ')
6
6
  words.map do |w|
7
7
  (MIN_COMPLETE-1..(w.length-1)).map{ |l| w[0..l] }
8
- end.flatten
8
+ end.flatten.uniq
9
9
  end
10
10
 
11
11
  def normalize(str)
@@ -19,27 +19,41 @@ module Soulmate
19
19
  # delete the data stored for this type
20
20
  Soulmate.redis.del(database)
21
21
 
22
- items_loaded = 0
23
22
  items.each_with_index do |item, i|
24
- id = item["id"]
25
- term = item["term"]
26
- score = item["score"]
23
+ add(item, :skip_duplicate_check => true)
24
+ end
25
+ end
27
26
 
28
- if id and term
29
- # store the raw data in a separate key to reduce memory usage
30
- Soulmate.redis.hset(database, id, JSON.dump(item))
27
+ # "id", "term", "score", "aliases", "data"
28
+ def add(item, opts = {})
29
+ opts = { :skip_duplicate_check => false }.merge(opts)
30
+ raise ArgumentError unless item["id"] && item["term"]
31
+
32
+ # kill any old items with this id
33
+ remove("id" => item["id"]) unless opts[:skip_duplicate_check]
34
+
35
+ # store the raw data in a separate key to reduce memory usage
36
+ Soulmate.redis.hset(database, item["id"], MultiJson.encode(item))
37
+ phrase = ([item["term"]] + (item["aliases"] || [])).join(' ')
38
+ prefixes_for_phrase(phrase).each do |p|
39
+ Soulmate.redis.sadd(base, p) # remember this prefix in a master set
40
+ Soulmate.redis.zadd("#{base}:#{p}", item["score"], item["id"]) # store the id of this term in the index
41
+ end
42
+ end
31
43
 
32
- phrase = ([term] + (item["aliases"] || [])).join(' ')
33
- prefixes_for_phrase(phrase).uniq.each do |p|
34
- Soulmate.redis.sadd(base, p) # remember this prefix in a master set
35
- Soulmate.redis.zadd("#{base}:#{p}", score, id) # store the id of this term in the index
36
- end
37
- items_loaded += 1
44
+ # remove only cares about an item's id, but for consistency takes an object
45
+ def remove(item)
46
+ prev_item = Soulmate.redis.hget(database, item["id"])
47
+ if prev_item
48
+ prev_item = MultiJson.decode(prev_item)
49
+ # undo the operations done in add
50
+ Soulmate.redis.hdel(database, prev_item["id"])
51
+ phrase = ([prev_item["term"]] + (prev_item["aliases"] || [])).join(' ')
52
+ prefixes_for_phrase(phrase).each do |p|
53
+ Soulmate.redis.srem(base, p)
54
+ Soulmate.redis.zrem("#{base}:#{p}", prev_item["id"])
38
55
  end
39
- puts "added #{i} entries" if i % 100 == 0 and i != 0
40
56
  end
41
-
42
- items_loaded
43
57
  end
44
58
  end
45
59
  end
@@ -3,22 +3,30 @@ module Soulmate
3
3
  class Matcher < Base
4
4
 
5
5
  def matches_for_term(term, options = {})
6
+ options = { :limit => 5, :cache => true }.merge(options)
7
+
6
8
  words = normalize(term).split(' ').reject do |w|
7
9
  w.size < MIN_COMPLETE or STOP_WORDS.include?(w)
8
10
  end.sort
9
11
 
10
- options[:limit] ||= 5
12
+ return [] if words.empty?
11
13
 
12
14
  cachekey = "#{cachebase}:" + words.join('|')
13
15
 
14
- if !Soulmate.redis.exists(cachekey)
16
+ if !options[:cache] || !Soulmate.redis.exists(cachekey)
15
17
  interkeys = words.map { |w| "#{base}:#{w}" }
16
18
  Soulmate.redis.zinterstore(cachekey, interkeys)
17
19
  Soulmate.redis.expire(cachekey, 10 * 60) # expire after 10 minutes
18
20
  end
19
21
 
20
22
  ids = Soulmate.redis.zrevrange(cachekey, 0, options[:limit] - 1)
21
- ids.size > 0 ? Soulmate.redis.hmget(database, *ids).map { |r| JSON.parse(r) } : []
23
+ if ids.size > 0
24
+ Soulmate.redis.hmget(database, *ids)
25
+ .reject{ |r| r.nil? } # handle cached results for ids which have since been deleted
26
+ .map { |r| MultiJson.decode(r) }
27
+ else
28
+ []
29
+ end
22
30
  end
23
31
  end
24
32
  end
@@ -14,7 +14,7 @@ module Soulmate
14
14
  end
15
15
 
16
16
  get '/' do
17
- JSON.pretty_generate({ :soulmate => Soulmate::Version::STRING, :status => "ok" })
17
+ MultiJson.encode({ :soulmate => Soulmate::Version::STRING, :status => "ok" })
18
18
  end
19
19
 
20
20
  get '/search' do
@@ -30,7 +30,7 @@ module Soulmate
30
30
  results[type] = matcher.matches_for_term(term, :limit => limit)
31
31
  end
32
32
 
33
- JSON.pretty_generate({
33
+ MultiJson.encode({
34
34
  :term => params[:term],
35
35
  :results => results
36
36
  })
@@ -38,7 +38,7 @@ module Soulmate
38
38
 
39
39
  not_found do
40
40
  content_type 'application/json', :charset => 'utf-8'
41
- JSON.pretty_generate({ :error => "not found" })
41
+ MultiJson.encode({ :error => "not found" })
42
42
  end
43
43
 
44
44
  end
@@ -2,7 +2,7 @@ module Soulmate
2
2
  module Version
3
3
  MAJOR = 0
4
4
  MINOR = 0
5
- PATCH = 4
5
+ PATCH = 5
6
6
 
7
7
  STRING = [MAJOR, MINOR, PATCH].compact.join('.')
8
8
  end
data/soulmate.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{soulmate}
8
- s.version = "0.0.4"
8
+ s.version = "0.0.5"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Eric Waller"]
12
- s.date = %q{2011-07-07}
12
+ s.date = %q{2011-10-12}
13
13
  s.description = %q{Soulmate is a tool to help solve the common problem of developing a fast autocomplete feature. It uses Redis's sorted sets to build an index of partial words and corresponding top matches, and provides a simple sinatra app to query them. Soulmate finishes your sentences.}
14
14
  s.email = %q{eric@seatgeek.com}
15
15
  s.executables = ["soulmate", "soulmate-web"]
@@ -55,6 +55,7 @@ Gem::Specification.new do |s|
55
55
 
56
56
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
57
57
  s.add_runtime_dependency(%q<redis>, ["~> 2.1.1"])
58
+ s.add_runtime_dependency(%q<multi_json>, ["~> 1.0.3"])
58
59
  s.add_runtime_dependency(%q<rack-contrib>, [">= 0"])
59
60
  s.add_runtime_dependency(%q<vegas>, ["~> 0.1.8"])
60
61
  s.add_runtime_dependency(%q<sinatra>, ["~> 1.2.3"])
@@ -68,6 +69,7 @@ Gem::Specification.new do |s|
68
69
  s.add_runtime_dependency(%q<json>, ["~> 1.4.6"])
69
70
  else
70
71
  s.add_dependency(%q<redis>, ["~> 2.1.1"])
72
+ s.add_dependency(%q<multi_json>, ["~> 1.0.3"])
71
73
  s.add_dependency(%q<rack-contrib>, [">= 0"])
72
74
  s.add_dependency(%q<vegas>, ["~> 0.1.8"])
73
75
  s.add_dependency(%q<sinatra>, ["~> 1.2.3"])
@@ -82,6 +84,7 @@ Gem::Specification.new do |s|
82
84
  end
83
85
  else
84
86
  s.add_dependency(%q<redis>, ["~> 2.1.1"])
87
+ s.add_dependency(%q<multi_json>, ["~> 1.0.3"])
85
88
  s.add_dependency(%q<rack-contrib>, [">= 0"])
86
89
  s.add_dependency(%q<vegas>, ["~> 0.1.8"])
87
90
  s.add_dependency(%q<sinatra>, ["~> 1.2.3"])
@@ -5,12 +5,12 @@ class TestSoulmate < Test::Unit::TestCase
5
5
  items = []
6
6
  venues = File.open(File.expand_path(File.dirname(__FILE__)) + '/samples/venues.json', "r")
7
7
  venues.each_line do |venue|
8
- items << JSON.parse(venue)
8
+ items << MultiJson.decode(venue)
9
9
  end
10
10
 
11
11
  items_loaded = Soulmate::Loader.new('venues').load(items)
12
12
 
13
- assert_equal 6, items_loaded
13
+ assert_equal 6, items_loaded.size
14
14
 
15
15
  matcher = Soulmate::Matcher.new('venues')
16
16
  results = matcher.matches_for_term('stad', :limit => 5)
@@ -23,12 +23,12 @@ class TestSoulmate < Test::Unit::TestCase
23
23
  items = []
24
24
  venues = File.open(File.expand_path(File.dirname(__FILE__)) + '/samples/venues.json', "r")
25
25
  venues.each_line do |venue|
26
- items << JSON.parse(venue)
26
+ items << MultiJson.decode(venue)
27
27
  end
28
28
 
29
29
  items_loaded = Soulmate::Loader.new('venues').load(items)
30
30
 
31
- assert_equal 6, items_loaded
31
+ assert_equal 6, items_loaded.size
32
32
 
33
33
  matcher = Soulmate::Matcher.new('venues')
34
34
  results = matcher.matches_for_term('land shark stadium', :limit => 5)
@@ -42,4 +42,60 @@ class TestSoulmate < Test::Unit::TestCase
42
42
  results = matcher.matches_for_term('stadium', :limit => 5)
43
43
  assert_equal 5, results.size
44
44
  end
45
+
46
+ def test_can_remove_items
47
+
48
+ loader = Soulmate::Loader.new('venues')
49
+ matcher = Soulmate::Matcher.new('venues')
50
+
51
+ # empty the collection
52
+ loader.load([])
53
+ results = matcher.matches_for_term("te", :cache => false)
54
+ assert_equal 0, results.size
55
+
56
+ loader.add("id" => 1, "term" => "Testing this", "score" => 10)
57
+ results = matcher.matches_for_term("te", :cache => false)
58
+ assert_equal 1, results.size
59
+
60
+ loader.remove("id" => 1)
61
+ results = matcher.matches_for_term("te", :cache => false)
62
+ assert_equal 0, results.size
63
+
64
+ end
65
+
66
+ def test_can_update_items
67
+
68
+ loader = Soulmate::Loader.new('venues')
69
+ matcher = Soulmate::Matcher.new('venues')
70
+
71
+ # empty the collection
72
+ loader.load([])
73
+
74
+ # initial data
75
+ loader.add("id" => 1, "term" => "Testing this", "score" => 10)
76
+ loader.add("id" => 2, "term" => "Another Term", "score" => 9)
77
+ loader.add("id" => 3, "term" => "Something different", "score" => 5)
78
+
79
+ results = matcher.matches_for_term("te", :cache => false)
80
+ assert_equal 2, results.size
81
+ assert_equal "Testing this", results.first["term"]
82
+ assert_equal 10, results.first["score"]
83
+
84
+ # update id:1
85
+ loader.add("id" => 1, "term" => "Updated", "score" => 5)
86
+
87
+ results = matcher.matches_for_term("te", :cache => false)
88
+ assert_equal 1, results.size
89
+ assert_equal "Another Term", results.first["term"]
90
+ assert_equal 9, results.first["score"]
91
+
92
+ end
93
+
94
+ def test_prefixes_for_phrase
95
+ loader = Soulmate::Loader.new('venues')
96
+
97
+ assert_equal ["te", "tes", "test", "testi", "testin", "th", "thi", "this"], loader.prefixes_for_phrase("testin' this")
98
+ assert_equal ["te", "tes", "test"], loader.prefixes_for_phrase("test test")
99
+ assert_equal ["so", "sou", "soul", "soulm", "soulma", "soulmat", "soulmate"], loader.prefixes_for_phrase("SoUlmATE")
100
+ end
45
101
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: soulmate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,12 +9,12 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-07-07 00:00:00.000000000 -04:00
12
+ date: 2011-10-12 00:00:00.000000000 -04:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: redis
17
- requirement: &2164813260 !ruby/object:Gem::Requirement
17
+ requirement: &2151919160 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ~>
@@ -22,10 +22,21 @@ dependencies:
22
22
  version: 2.1.1
23
23
  type: :runtime
24
24
  prerelease: false
25
- version_requirements: *2164813260
25
+ version_requirements: *2151919160
26
+ - !ruby/object:Gem::Dependency
27
+ name: multi_json
28
+ requirement: &2151917560 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 1.0.3
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: *2151917560
26
37
  - !ruby/object:Gem::Dependency
27
38
  name: rack-contrib
28
- requirement: &2164811620 !ruby/object:Gem::Requirement
39
+ requirement: &2151916600 !ruby/object:Gem::Requirement
29
40
  none: false
30
41
  requirements:
31
42
  - - ! '>='
@@ -33,10 +44,10 @@ dependencies:
33
44
  version: '0'
34
45
  type: :runtime
35
46
  prerelease: false
36
- version_requirements: *2164811620
47
+ version_requirements: *2151916600
37
48
  - !ruby/object:Gem::Dependency
38
49
  name: vegas
39
- requirement: &2164810860 !ruby/object:Gem::Requirement
50
+ requirement: &2151915480 !ruby/object:Gem::Requirement
40
51
  none: false
41
52
  requirements:
42
53
  - - ~>
@@ -44,10 +55,10 @@ dependencies:
44
55
  version: 0.1.8
45
56
  type: :runtime
46
57
  prerelease: false
47
- version_requirements: *2164810860
58
+ version_requirements: *2151915480
48
59
  - !ruby/object:Gem::Dependency
49
60
  name: sinatra
50
- requirement: &2164809840 !ruby/object:Gem::Requirement
61
+ requirement: &2151914480 !ruby/object:Gem::Requirement
51
62
  none: false
52
63
  requirements:
53
64
  - - ~>
@@ -55,10 +66,10 @@ dependencies:
55
66
  version: 1.2.3
56
67
  type: :runtime
57
68
  prerelease: false
58
- version_requirements: *2164809840
69
+ version_requirements: *2151914480
59
70
  - !ruby/object:Gem::Dependency
60
71
  name: shoulda
61
- requirement: &2164808840 !ruby/object:Gem::Requirement
72
+ requirement: &2151913720 !ruby/object:Gem::Requirement
62
73
  none: false
63
74
  requirements:
64
75
  - - ! '>='
@@ -66,10 +77,10 @@ dependencies:
66
77
  version: '0'
67
78
  type: :development
68
79
  prerelease: false
69
- version_requirements: *2164808840
80
+ version_requirements: *2151913720
70
81
  - !ruby/object:Gem::Dependency
71
82
  name: bundler
72
- requirement: &2164807780 !ruby/object:Gem::Requirement
83
+ requirement: &2151912740 !ruby/object:Gem::Requirement
73
84
  none: false
74
85
  requirements:
75
86
  - - ~>
@@ -77,10 +88,10 @@ dependencies:
77
88
  version: 1.0.0
78
89
  type: :development
79
90
  prerelease: false
80
- version_requirements: *2164807780
91
+ version_requirements: *2151912740
81
92
  - !ruby/object:Gem::Dependency
82
93
  name: jeweler
83
- requirement: &2164807160 !ruby/object:Gem::Requirement
94
+ requirement: &2151911840 !ruby/object:Gem::Requirement
84
95
  none: false
85
96
  requirements:
86
97
  - - ~>
@@ -88,10 +99,10 @@ dependencies:
88
99
  version: 1.5.2
89
100
  type: :development
90
101
  prerelease: false
91
- version_requirements: *2164807160
102
+ version_requirements: *2151911840
92
103
  - !ruby/object:Gem::Dependency
93
104
  name: rcov
94
- requirement: &2164806540 !ruby/object:Gem::Requirement
105
+ requirement: &2151910900 !ruby/object:Gem::Requirement
95
106
  none: false
96
107
  requirements:
97
108
  - - ! '>='
@@ -99,10 +110,10 @@ dependencies:
99
110
  version: '0'
100
111
  type: :development
101
112
  prerelease: false
102
- version_requirements: *2164806540
113
+ version_requirements: *2151910900
103
114
  - !ruby/object:Gem::Dependency
104
115
  name: redis
105
- requirement: &2164805780 !ruby/object:Gem::Requirement
116
+ requirement: &2151909380 !ruby/object:Gem::Requirement
106
117
  none: false
107
118
  requirements:
108
119
  - - ! '>='
@@ -110,10 +121,10 @@ dependencies:
110
121
  version: '2.0'
111
122
  type: :runtime
112
123
  prerelease: false
113
- version_requirements: *2164805780
124
+ version_requirements: *2151909380
114
125
  - !ruby/object:Gem::Dependency
115
126
  name: vegas
116
- requirement: &2164805020 !ruby/object:Gem::Requirement
127
+ requirement: &2151907400 !ruby/object:Gem::Requirement
117
128
  none: false
118
129
  requirements:
119
130
  - - ! '>='
@@ -121,10 +132,10 @@ dependencies:
121
132
  version: 0.1.0
122
133
  type: :runtime
123
134
  prerelease: false
124
- version_requirements: *2164805020
135
+ version_requirements: *2151907400
125
136
  - !ruby/object:Gem::Dependency
126
137
  name: sinatra
127
- requirement: &2164804440 !ruby/object:Gem::Requirement
138
+ requirement: &2151904740 !ruby/object:Gem::Requirement
128
139
  none: false
129
140
  requirements:
130
141
  - - ! '>='
@@ -132,10 +143,10 @@ dependencies:
132
143
  version: '1.0'
133
144
  type: :runtime
134
145
  prerelease: false
135
- version_requirements: *2164804440
146
+ version_requirements: *2151904740
136
147
  - !ruby/object:Gem::Dependency
137
148
  name: json
138
- requirement: &2164803800 !ruby/object:Gem::Requirement
149
+ requirement: &2151901600 !ruby/object:Gem::Requirement
139
150
  none: false
140
151
  requirements:
141
152
  - - ~>
@@ -143,7 +154,7 @@ dependencies:
143
154
  version: 1.4.6
144
155
  type: :runtime
145
156
  prerelease: false
146
- version_requirements: *2164803800
157
+ version_requirements: *2151901600
147
158
  description: Soulmate is a tool to help solve the common problem of developing a fast
148
159
  autocomplete feature. It uses Redis's sorted sets to build an index of partial words
149
160
  and corresponding top matches, and provides a simple sinatra app to query them.
@@ -194,7 +205,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
194
205
  version: '0'
195
206
  segments:
196
207
  - 0
197
- hash: 1504170323034549202
208
+ hash: -3865399172879930147
198
209
  required_rubygems_version: !ruby/object:Gem::Requirement
199
210
  none: false
200
211
  requirements: