food_info 0.0.6 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/HISTORY.markdown +4 -0
- data/README.markdown +10 -0
- data/Rakefile +6 -0
- data/TODO.txt +4 -1
- data/food_info.gemspec +3 -0
- data/lib/food_info.rb +33 -5
- data/lib/food_info/adapters/fat_secret.rb +2 -2
- data/lib/food_info/cache_adapters.rb +9 -0
- data/lib/food_info/cache_adapters/default.rb +15 -0
- data/lib/food_info/cache_adapters/mem_cache_compatible.rb +34 -0
- data/lib/food_info/version.rb +1 -1
- data/test/fat_secret_test.rb +14 -0
- data/test/test_helper.rb +3 -0
- metadata +64 -23
- data/lib/food_info/cache_adapters/mem_cache.rb +0 -7
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f8b2e3a8689708f445b743d999bb38af47fe07a5
|
4
|
+
data.tar.gz: 2e705705c0d40d234460f3efd16edb076bda8bf9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 786ff75020061bb4c9e5737bf8b9dc8fc946cf11c060e00ffe340777c72fbb906244fdd0559f64d2a6b07382d8e070d35c5ed8ba7c61a75f321e15d8f234cd39
|
7
|
+
data.tar.gz: a50c3920280484191fcb2f8ab56b5ad4dc3e4d295f33cc9169e6ae47aeeb49a2fe3483bc16efe361df36a64dd69f992c9f13b469d56a504b9de50e42affe9f68
|
data/HISTORY.markdown
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 0.0.7 December 5, 2013
|
4
|
+
* Pulling in misc bugfixes from eclipxe
|
5
|
+
* Adding caching: can pass in memcache-compatible object to use caching
|
6
|
+
|
3
7
|
## 0.0.6 October 21, 2011
|
4
8
|
* FatSecret: Retry once if timestamp invalid (API only supports single request/second)
|
5
9
|
|
data/README.markdown
CHANGED
@@ -29,6 +29,16 @@ Once that's done, the first step is to tell FoodInfo which adapter you want to u
|
|
29
29
|
FoodInfo.establish_connection(:fat_secret, :key => 'YOUR-KEY', :secret => 'YOUR-KEY')
|
30
30
|
|
31
31
|
|
32
|
+
### Caching
|
33
|
+
|
34
|
+
To cache results, FoodInfo supports passing in an instance of a memcache-API-compatible (i.e. responds to <code>get</code> and <code>set</code>) caching object. I recommend using the [Dalli gem](https://github.com/mperham/dalli).
|
35
|
+
|
36
|
+
require 'dalli'
|
37
|
+
client = Dalli::Client.new('localhost:11211')
|
38
|
+
FoodInfo.establish_connection(:fat_secret, :key => 'YOUR-KEY', :secret => 'YOUR-KEY', :cache => client)
|
39
|
+
|
40
|
+
With that in place repeated <code>search</code> or <code>details</code> requests will pull from the cache, and not the API endpoint.
|
41
|
+
|
32
42
|
### Searching
|
33
43
|
|
34
44
|
Now we can search for foods.
|
data/Rakefile
CHANGED
data/TODO.txt
CHANGED
@@ -1,2 +1,5 @@
|
|
1
|
-
* Add DB caching (or is that better left to the client, who can control the caching layer completely?)
|
2
1
|
* It'd be great to have a connection pool, so we could process multiple requests concurrently. I've added a skeleton for it, but actually using it would require a non-blocking HTTP library in HTTParty.
|
2
|
+
|
3
|
+
FatSecret ratelimits requests to one/second.
|
4
|
+
- Could add queue and only process one per second (then separate event loop running while pool full, can't necessarily handle additional requests in meantime)
|
5
|
+
- Could roundrobin between multiple API keys, but presumably violates TOS
|
data/food_info.gemspec
CHANGED
@@ -11,6 +11,9 @@ Gem::Specification.new do |gem|
|
|
11
11
|
gem.add_dependency('httparty', '>= 0.7.7')
|
12
12
|
gem.add_dependency('hashie', '>= 1.1.0')
|
13
13
|
gem.add_dependency('ruby-hmac')
|
14
|
+
|
15
|
+
gem.add_development_dependency "rake"
|
16
|
+
gem.add_development_dependency "minitest"
|
14
17
|
|
15
18
|
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
16
19
|
gem.files = `git ls-files`.split("\n")
|
data/lib/food_info.rb
CHANGED
@@ -3,6 +3,7 @@ require 'hashie'
|
|
3
3
|
require "food_info/utils"
|
4
4
|
require "food_info/errors"
|
5
5
|
require "food_info/adapters"
|
6
|
+
require "food_info/cache_adapters"
|
6
7
|
require "food_info/version"
|
7
8
|
|
8
9
|
|
@@ -15,18 +16,35 @@ module FoodInfo
|
|
15
16
|
class << self
|
16
17
|
|
17
18
|
# Sets the adapter we'll be pulling data from.
|
19
|
+
#
|
20
|
+
# Example usage:
|
21
|
+
# FoodInfo.establish_connection(:fat_secret, :key => '...', :secret => '...')
|
22
|
+
#
|
23
|
+
# Example with cache:
|
24
|
+
# require 'dalli' # Use dalli gem to interface with memcache
|
25
|
+
# client = Dalli::Client.new('localhost:11211')
|
26
|
+
# FoodInfo.establish_connection(:fat_secret, :key => '...', :secret => '...', :cache => client)
|
18
27
|
def establish_connection(adapter_name, opts = {})
|
19
28
|
klass = ADAPTERS[adapter_name.to_sym]
|
20
29
|
raise UnsupportedAdapter.new("Requested adapter ('#{adapter_name}') is unknown") unless klass
|
30
|
+
|
31
|
+
# Set up the pool of workers (net yet implemented, so defaulting to "pool" of one)
|
21
32
|
@@pool = []
|
22
33
|
@@cursor = 0
|
23
34
|
(opts.delete(:pool) || 1).to_i.times do
|
24
35
|
@@pool << klass.new(opts)
|
25
36
|
end
|
26
37
|
|
38
|
+
# Set up the cache, if any provided
|
39
|
+
obj = opts.delete(:cache)
|
40
|
+
@cache = obj ? FoodInfo::CacheAdapters::MemCacheCompatible.new(obj) : FoodInfo::CacheAdapters::Default.new
|
41
|
+
|
27
42
|
true
|
28
43
|
end
|
29
44
|
|
45
|
+
# ======================
|
46
|
+
# = Public API Methods =
|
47
|
+
# ======================
|
30
48
|
def search(q, opts = {})
|
31
49
|
cached(:search, q, opts)
|
32
50
|
end
|
@@ -36,11 +54,13 @@ module FoodInfo
|
|
36
54
|
end
|
37
55
|
|
38
56
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
57
|
+
# ==========================
|
58
|
+
# = Implementation details =
|
59
|
+
# ==========================
|
60
|
+
def cached(method, param, opts = {})
|
61
|
+
key = request_key(method, param, opts)
|
62
|
+
|
63
|
+
@cache.get(key) || @cache.set(key, next_adapter.send(method, param, opts))
|
44
64
|
end
|
45
65
|
|
46
66
|
# FUTURE: This connection pool code won't do much good until HTTParty is non-blocking
|
@@ -50,6 +70,14 @@ module FoodInfo
|
|
50
70
|
@@pool[@@cursor]
|
51
71
|
end
|
52
72
|
|
73
|
+
# Convert method + args into a string for use as the cache key
|
74
|
+
def request_key(method, param, opts = {})
|
75
|
+
# {:param => 123, :other => "something"} # => "other=something:param=123"
|
76
|
+
str_opts = opts.sort{|a,b| a.to_s <=> b.to_s}.map{|pair| pair.join('=')}.join(':')
|
77
|
+
|
78
|
+
"FoodInfo:#{method}:#{param}:#{str_opts}"
|
79
|
+
end
|
80
|
+
|
53
81
|
end
|
54
82
|
end
|
55
83
|
|
@@ -19,7 +19,7 @@ module FoodInfo
|
|
19
19
|
|
20
20
|
def search(q, opts = {})
|
21
21
|
params = {
|
22
|
-
:search_expression => q,
|
22
|
+
:search_expression => URI.escape(q),
|
23
23
|
:page_number => opts[:page] || 1,
|
24
24
|
:max_results => opts[:per_page] || 20
|
25
25
|
}
|
@@ -42,7 +42,7 @@ module FoodInfo
|
|
42
42
|
data = self.class.get( query_url )
|
43
43
|
|
44
44
|
if data['error'] # Invalid timestamp can happen if more than one request/second. Allow retrying once.
|
45
|
-
if data['error']['message'] =~
|
45
|
+
if data['error']['message'] =~ /Invalid\/expired timestamp/ && retried < RETRY_INVALID_TIMESTAMP
|
46
46
|
return query(method, opts, retried + 1)
|
47
47
|
else
|
48
48
|
raise DataSourceException.new(data['error']['message'])
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require "food_info/cache_adapters/default"
|
2
|
+
require "food_info/cache_adapters/mem_cache_compatible"
|
3
|
+
|
4
|
+
module FoodInfo
|
5
|
+
# All FoodInfo CacheAdapters must expose two public methods, +set+ and +get+, and will need to
|
6
|
+
# behave compatibly with the memcache API.
|
7
|
+
module CacheAdapters
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module FoodInfo
|
2
|
+
module CacheAdapters
|
3
|
+
class MemCacheCompatible
|
4
|
+
|
5
|
+
def initialize(obj = nil)
|
6
|
+
if obj && obj.respond_to?(:get) && obj.respond_to?(:set)
|
7
|
+
@cache = obj
|
8
|
+
else
|
9
|
+
raise "FoodInfo::CacheAdapters::MemCacheCompatible must be initialized with an object that responds to get and set (look into the Dalli gem)"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def set(key, val)
|
14
|
+
begin
|
15
|
+
@cache.set(key, val)
|
16
|
+
rescue Exception => e
|
17
|
+
STDERR.puts "FoodInfo Cache Error (set): #{e}"
|
18
|
+
end
|
19
|
+
|
20
|
+
val
|
21
|
+
end
|
22
|
+
|
23
|
+
def get(key)
|
24
|
+
begin
|
25
|
+
@cache.get(key)
|
26
|
+
rescue Exception => e
|
27
|
+
STDERR.puts "FoodInfo Cache Error (get): #{e}"
|
28
|
+
return nil
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/food_info/version.rb
CHANGED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class FatSecretTest < Minitest::Test
|
4
|
+
|
5
|
+
def setup
|
6
|
+
FoodInfo.establish_connection(:fat_secret, :key => ENV['FS_KEY'], :secret => ENV['FS_SECRET'])
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_retreives_details
|
10
|
+
f = FoodInfo.details("33689")
|
11
|
+
assert_equal "Cheddar Cheese", f.name
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
CHANGED
@@ -1,49 +1,85 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: food_info
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.0.7
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Kali Donovan
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2013-12-05 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: httparty
|
16
|
-
requirement:
|
17
|
-
none: false
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
18
16
|
requirements:
|
19
|
-
- -
|
17
|
+
- - '>='
|
20
18
|
- !ruby/object:Gem::Version
|
21
19
|
version: 0.7.7
|
22
20
|
type: :runtime
|
23
21
|
prerelease: false
|
24
|
-
version_requirements:
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.7.7
|
25
27
|
- !ruby/object:Gem::Dependency
|
26
28
|
name: hashie
|
27
|
-
requirement:
|
28
|
-
none: false
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
29
30
|
requirements:
|
30
|
-
- -
|
31
|
+
- - '>='
|
31
32
|
- !ruby/object:Gem::Version
|
32
33
|
version: 1.1.0
|
33
34
|
type: :runtime
|
34
35
|
prerelease: false
|
35
|
-
version_requirements:
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.1.0
|
36
41
|
- !ruby/object:Gem::Dependency
|
37
42
|
name: ruby-hmac
|
38
|
-
requirement:
|
39
|
-
none: false
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
40
44
|
requirements:
|
41
|
-
- -
|
45
|
+
- - '>='
|
42
46
|
- !ruby/object:Gem::Version
|
43
47
|
version: '0'
|
44
48
|
type: :runtime
|
45
49
|
prerelease: false
|
46
|
-
version_requirements:
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: minitest
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
47
83
|
description: Generic Ruby interface to look up nutritional information on food. Design
|
48
84
|
is modular so other adapters can be plugged in, but only data source currently implemented
|
49
85
|
is FatSecret.
|
@@ -69,32 +105,37 @@ files:
|
|
69
105
|
- lib/food_info/adapters/fat_secret/data/search_result.rb
|
70
106
|
- lib/food_info/adapters/fat_secret/data/search_results.rb
|
71
107
|
- lib/food_info/adapters/fat_secret/request.rb
|
72
|
-
- lib/food_info/cache_adapters
|
108
|
+
- lib/food_info/cache_adapters.rb
|
109
|
+
- lib/food_info/cache_adapters/default.rb
|
110
|
+
- lib/food_info/cache_adapters/mem_cache_compatible.rb
|
73
111
|
- lib/food_info/errors.rb
|
74
112
|
- lib/food_info/utils.rb
|
75
113
|
- lib/food_info/version.rb
|
114
|
+
- test/fat_secret_test.rb
|
115
|
+
- test/test_helper.rb
|
76
116
|
homepage: https://github.com/deviantech/food_info
|
77
117
|
licenses: []
|
118
|
+
metadata: {}
|
78
119
|
post_install_message:
|
79
120
|
rdoc_options: []
|
80
121
|
require_paths:
|
81
122
|
- lib
|
82
123
|
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
-
none: false
|
84
124
|
requirements:
|
85
|
-
- -
|
125
|
+
- - '>='
|
86
126
|
- !ruby/object:Gem::Version
|
87
127
|
version: '0'
|
88
128
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
-
none: false
|
90
129
|
requirements:
|
91
|
-
- -
|
130
|
+
- - '>='
|
92
131
|
- !ruby/object:Gem::Version
|
93
132
|
version: '0'
|
94
133
|
requirements: []
|
95
134
|
rubyforge_project:
|
96
|
-
rubygems_version: 1.
|
135
|
+
rubygems_version: 2.1.11
|
97
136
|
signing_key:
|
98
|
-
specification_version:
|
137
|
+
specification_version: 4
|
99
138
|
summary: API for researching nutritional information of various foods
|
100
|
-
test_files:
|
139
|
+
test_files:
|
140
|
+
- test/fat_secret_test.rb
|
141
|
+
- test/test_helper.rb
|