gravatar-ultimate 1.0.0

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.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,24 @@
1
+ .idea
2
+ tmp
3
+
4
+ ## MAC OS
5
+ .DS_Store
6
+
7
+ ## TEXTMATE
8
+ *.tmproj
9
+ tmtags
10
+
11
+ ## EMACS
12
+ *~
13
+ \#*
14
+ .\#*
15
+
16
+ ## VIM
17
+ *.swp
18
+
19
+ ## PROJECT::GENERAL
20
+ coverage
21
+ rdoc
22
+ pkg
23
+
24
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Colin MacKenzie IV
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,100 @@
1
+ = gravatar-ultimate
2
+
3
+ The Ultimate Gravatar Gem!
4
+
5
+ This gem is used to interface with the entire Gravatar API: it's not just for generating image URLs, but for connecting
6
+ to and communicating with the XML-RPC API too! Additionally, it can be used to download the Gravatar image data itself,
7
+ rather than just a URL to that data. This saves you the extra step of having to do so.
8
+
9
+ == Installation
10
+
11
+ gem install gravitar-ultimate
12
+
13
+ == Activate the gem...
14
+
15
+ As with any gem, you have to type a few lines to tell Ruby to actually *use* it. Here's how to do that...
16
+
17
+ ==== ...in Ruby on Rails (v3.x)
18
+
19
+ * This isn't ready yet, but it's in the works.
20
+
21
+ ==== ...in Ruby on Rails (v2.x)
22
+
23
+ * Edit your config/environment.rb file
24
+ * Add this line beneath "Rails::Initializer.run do |config|":
25
+ config.gem 'gravitar-ultimate'
26
+
27
+ ==== ...in vanilla Ruby
28
+
29
+ require 'rubygems'
30
+ gem 'gravitar-ultimate'
31
+ require 'gravitar-ultimate'
32
+
33
+ == Usage
34
+ Using the gem is actually pretty simple. Let's say you want the Gravatar image URL for "generic@example.com":
35
+ url = Gravatar.new("generic@example.com").image_url
36
+
37
+ Cool, huh? Let's take it a step further and grab the actual image *data* so that we can render it on the screen:
38
+ data = Gravatar.new("generic@example.com").image_data
39
+
40
+ Fine, but how about the rest of the API as advertised at http://en.gravatar.com/site/implement/xmlrpc? Well, for
41
+ that you need either the user's Gravatar password, or their API key:
42
+
43
+ api = Gravatar.new("generic@example.com", :password => "helloworld")
44
+ api = Gravatar.new("generic@example.com", :api_key => "AbCdEfG1234")
45
+
46
+ After you have that, things get a lot easier:
47
+
48
+ api.exists? #=> true or false, depending on whether this user has an account.
49
+ api.addresses #=> a list of email addresses and their corresponding images
50
+ api.save_data!(rating, image_data) #=> saves an image to this user's account and returns a handle to it
51
+ api.use_user_image!(handle, an_email_address) #=> uses the specified image handle for the specified email address(es)
52
+ api.exists?("another@example.com") #=> true or false, depending on whether the specified email exists.
53
+
54
+
55
+ == Caching
56
+
57
+ As you can see this is quite powerful. But it gets better. Gravatar Ultimate even manages caching of API responses for
58
+ you! That way, if an error occurs, (such as the Gravatar site being offline), your code won't break. It'll instead
59
+ gracefully fall back to the cached copy! By default, if you are using Rails, it'll use the Rails cache. Otherwise, it'll
60
+ use whatever cache you're using with Gravatar (by default an instance of ActiveSupport::Cache::FileStore).
61
+
62
+ This has obvious benefits when used for the API calls that do not result in changing the user's profile, but what you
63
+ might not have thought of yet is that it also caches #image_data, so you can hook your application up to that method
64
+ without fear of what might happen to all those Gravatar images if the Gravatar server should be unavailable!
65
+
66
+ To customize exactly which cache is used, see the next section...
67
+
68
+ === Configuration
69
+
70
+ To see settings and options you can give for a particular Gravatar instance, check out the Gravatar class documentation.
71
+ There are a few things you can set for Gravatar on a system-wide basis, and that's what we'll go over next.
72
+
73
+ For a non-Rails project, simply set these options before you start using Gravatar. For a Rails project, you should set
74
+ them within an Initializer in config/initializers/any_filename.rb in order to ensure that the settings are applied
75
+ (A) after Gravatar has been included into the project, and (B) before it is actually used by Rails.
76
+
77
+ # You can set the default cache for Gravatar to use:
78
+ Gravatar.cache = ActiveSupport::Cache::SynchronizedMemoryStore.new
79
+
80
+ # You can also set the length of time an item in the Gravatar cache is valid. Default is 24.hours
81
+ Gravatar.duration = 20.minutes
82
+
83
+ # You can also change the logger used by default. It's worth mentioning that, once again, Gravatar will use
84
+ # the Rails logger if it's available. Otherwise, the default is $stdout.
85
+ grav_log = ""
86
+ Gravatar.logger = StringIO.new(grav_log) # logs Gravatar output to a String
87
+
88
+ == Note on Patches/Pull Requests
89
+
90
+ * Fork the project.
91
+ * Make your feature addition or bug fix.
92
+ * Add tests for it. This is important so I don't break it in a
93
+ future version unintentionally.
94
+ * Commit, do not mess with rakefile, version, or history.
95
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
96
+ * Send me a pull request. Bonus points for topic branches.
97
+
98
+ == Copyright
99
+
100
+ Copyright (c) 2010 Colin MacKenzie IV. See LICENSE for details.
@@ -0,0 +1,64 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "gravatar-ultimate"
8
+ gem.summary = %Q{A gem for interfacing with the entire Gravatar API: not just images, but the XML-RPC API too!}
9
+ gem.description = %Q{The Ultimate Gravatar Gem!
10
+
11
+ This gem is used to interface with the entire Gravatar API: it's not just for generating image URLs, but for connecting
12
+ to and communicating with the XML-RPC API too! Additionally, it can be used to download the Gravatar image data itself,
13
+ rather than just a URL to that data. This saves you the extra step of having to do so.}
14
+ gem.email = "sinisterchipmunk@gmail.com"
15
+ gem.homepage = "http://github.com/sinisterchipmunk/gravatar"
16
+ gem.authors = ["Colin MacKenzie IV"]
17
+ gem.add_dependency "sc-core-ext", ">= 1.2.0"
18
+ gem.add_development_dependency "rspec", ">= 1.3.0"
19
+ gem.add_development_dependency "fakeweb", ">= 1.2.8"
20
+ end
21
+ Jeweler::GemcutterTasks.new
22
+ rescue LoadError
23
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
24
+ end
25
+
26
+ require 'spec/rake/spectask'
27
+ Spec::Rake::SpecTask.new(:test) do |test|
28
+ test.libs << 'lib' << 'spec'
29
+ test.pattern = 'spec/**/*_spec.rb'
30
+ test.verbose = true
31
+ end
32
+
33
+ desc "Run all specs"
34
+ task :spec => :test
35
+
36
+ begin
37
+ gem 'rcov'
38
+ require 'rcov/rcovtask'
39
+ Spec::Rake::SpecTask.new(:coverage) do |test|
40
+ test.libs << 'lib' << 'spec'
41
+ test.pattern = "spec/**/*_spec.rb"
42
+ test.verbose = true
43
+ test.rcov = true
44
+ test.rcov_opts = ['--html', '--exclude spec']
45
+ end
46
+ rescue LoadError
47
+ task :coverage do
48
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
49
+ end
50
+ end
51
+
52
+ task :test => :check_dependencies
53
+
54
+ task :default => :test
55
+
56
+ require 'rake/rdoctask'
57
+ Rake::RDocTask.new do |rdoc|
58
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
59
+
60
+ rdoc.rdoc_dir = 'rdoc'
61
+ rdoc.title = "gravatar #{version}"
62
+ rdoc.rdoc_files.include('README*')
63
+ rdoc.rdoc_files.include('lib/**/*.rb')
64
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,77 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{gravatar-ultimate}
8
+ s.version = "1.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Colin MacKenzie IV"]
12
+ s.date = %q{2010-06-16}
13
+ s.description = %q{The Ultimate Gravatar Gem!
14
+
15
+ This gem is used to interface with the entire Gravatar API: it's not just for generating image URLs, but for connecting
16
+ to and communicating with the XML-RPC API too! Additionally, it can be used to download the Gravatar image data itself,
17
+ rather than just a URL to that data. This saves you the extra step of having to do so.}
18
+ s.email = %q{sinisterchipmunk@gmail.com}
19
+ s.extra_rdoc_files = [
20
+ "LICENSE",
21
+ "README.rdoc"
22
+ ]
23
+ s.files = [
24
+ ".document",
25
+ ".gitignore",
26
+ "LICENSE",
27
+ "README.rdoc",
28
+ "Rakefile",
29
+ "VERSION",
30
+ "gravatar-ultimate.gemspec",
31
+ "lib/gravatar-ultimate.rb",
32
+ "lib/gravatar.rb",
33
+ "lib/gravatar/cache.rb",
34
+ "lib/gravatar/dependencies.rb",
35
+ "lib/gravatar_ultimate.rb",
36
+ "spec/credentials.yml.example",
37
+ "spec/fixtures/image.jpg",
38
+ "spec/lib/gravatar/cache_and_logger_spec.rb",
39
+ "spec/lib/gravatar/cache_setup_spec.rb",
40
+ "spec/lib/gravatar/dependencies_spec.rb",
41
+ "spec/lib/gravatar_spec.rb",
42
+ "spec/spec.opts",
43
+ "spec/spec_helper.rb"
44
+ ]
45
+ s.homepage = %q{http://github.com/sinisterchipmunk/gravatar}
46
+ s.rdoc_options = ["--charset=UTF-8"]
47
+ s.require_paths = ["lib"]
48
+ s.rubygems_version = %q{1.3.6}
49
+ s.summary = %q{A gem for interfacing with the entire Gravatar API: not just images, but the XML-RPC API too!}
50
+ s.test_files = [
51
+ "spec/spec_helper.rb",
52
+ "spec/lib/gravatar_spec.rb",
53
+ "spec/lib/gravatar/dependencies_spec.rb",
54
+ "spec/lib/gravatar/cache_and_logger_spec.rb",
55
+ "spec/lib/gravatar/cache_setup_spec.rb"
56
+ ]
57
+
58
+ if s.respond_to? :specification_version then
59
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
60
+ s.specification_version = 3
61
+
62
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
63
+ s.add_runtime_dependency(%q<sc-core-ext>, [">= 1.2.0"])
64
+ s.add_development_dependency(%q<rspec>, [">= 1.3.0"])
65
+ s.add_development_dependency(%q<fakeweb>, [">= 1.2.8"])
66
+ else
67
+ s.add_dependency(%q<sc-core-ext>, [">= 1.2.0"])
68
+ s.add_dependency(%q<rspec>, [">= 1.3.0"])
69
+ s.add_dependency(%q<fakeweb>, [">= 1.2.8"])
70
+ end
71
+ else
72
+ s.add_dependency(%q<sc-core-ext>, [">= 1.2.0"])
73
+ s.add_dependency(%q<rspec>, [">= 1.3.0"])
74
+ s.add_dependency(%q<fakeweb>, [">= 1.2.8"])
75
+ end
76
+ end
77
+
@@ -0,0 +1 @@
1
+ require File.expand_path("../gravatar", __FILE__)
@@ -0,0 +1,282 @@
1
+ require File.expand_path('../gravatar/dependencies', __FILE__)
2
+ require File.expand_path("../gravatar/cache", __FILE__)
3
+
4
+ # ==== Errors ====
5
+ #
6
+ # Errors usually come with a number and human readable text. Generally the text should be followed whenever possible,
7
+ # but a brief description of the numeric error codes are as follows:
8
+ #
9
+ # -7 Use secure.gravatar.com
10
+ # -8 Internal error
11
+ # -9 Authentication error
12
+ # -10 Method parameter missing
13
+ # -11 Method parameter incorrect
14
+ # -100 Misc error (see text)
15
+ #
16
+ class Gravatar
17
+ attr_reader :email
18
+
19
+ # Creates a new instance of Gravatar. Valid options include:
20
+ # :password => the password for this account, to be used instead of :api_key (don't supply both)
21
+ # :api_key or :apikey or :key => the API key for this account, to be used instead of :password (don't supply both)
22
+ # :duration => the cache duration to use for this instance
23
+ # :logger => the logger to use for this instance
24
+ #
25
+ # Note that :password and :api_key are both optional. If omitted, no web services will be available but this
26
+ # user's Gravatar image can still be constructed using #image_uri or #image_data.
27
+ #
28
+ def initialize(email, options = {})
29
+ raise ArgumentError, "Expected :email" unless email
30
+ @options = options || {}
31
+ @email = email
32
+
33
+ pw_or_key = auth.keys.first || :none
34
+ @cache = Gravatar::Cache.new(self.class.cache, options[:duration] || self.class.duration,
35
+ "gravatar-#{email_hash}-#{pw_or_key}", options[:logger] || self.class.logger)
36
+
37
+ if !auth.empty?
38
+ @api = XMLRPC::Client.new("secure.gravatar.com", "/xmlrpc?user=#{email_hash}", 443, nil, nil, nil, nil, true)
39
+ end
40
+ end
41
+
42
+ # The duration of the cache for this instance of Gravatar, independent of any other instance
43
+ def cache_duration
44
+ @cache.duration
45
+ end
46
+
47
+ # Sets the duration of the cache for this instance of Gravatar, independent of any other instance
48
+ def cache_duration=(time)
49
+ @cache.duration = time
50
+ end
51
+
52
+ # Check whether one or more email addresses have corresponding avatars. If no email addresses are
53
+ # specified, the one associated with this object is used.
54
+ #
55
+ # Returns: Boolean for a single email address; a hash of emails => booleans for multiple addresses.
56
+ #
57
+ # This method is cached for up to the value of @duration or Gravatar.duration.
58
+ def exists?(*emails)
59
+ hashed_emails = normalize_email_addresses(emails)
60
+ cache('exists', hashed_emails) do
61
+ hash = call('grav.exists', :hashes => hashed_emails)
62
+ if hash.length == 1
63
+ boolean(hash.values.first)
64
+ else
65
+ dehashify_emails(hash, emails) { |value| boolean(value) }
66
+ end
67
+ end
68
+ end
69
+
70
+ # Gets a list of addresses for this account, returning a hash following this format:
71
+ # {
72
+ # address => {
73
+ # :rating => rating,
74
+ # :userimage => userimage,
75
+ # :userimage_url => userimage_url
76
+ # }
77
+ # }
78
+ #
79
+ # This method is cached for up to the value of @duration or Gravatar.duration.
80
+ def addresses
81
+ cache('addresses') do
82
+ call('grav.addresses').inject({}) do |hash, (address, info)|
83
+ hash[address] = info.merge(:rating => rating(info[:rating]))
84
+ hash
85
+ end
86
+ end
87
+ end
88
+
89
+ # Returns a hash of user images for this account in the following format:
90
+ # { user_img_hash => [rating, url] }
91
+ #
92
+ # This method is cached for up to the value of @duration or Gravatar.duration.
93
+ def user_images
94
+ cache('user_images') do
95
+ call('grav.userimages').inject({}) do |hash, (key, array)|
96
+ hash[key] = [rating(array.first), array.last]
97
+ hash
98
+ end
99
+ end
100
+ end
101
+
102
+ # Saves binary image data as a userimage for this account and returns the ID of the image.
103
+ #
104
+ # This method is not cached.
105
+ def save_data!(rating, data)
106
+ call('grav.saveData', :data => Base64.encode64(data), :rating => _rating(rating))
107
+ end
108
+ alias save_image! save_data!
109
+
110
+ # Read an image via its URL and save that as a userimage for this account, returning true or false
111
+ #
112
+ # This method is not cached.
113
+ def save_url!(rating, url)
114
+ call('grav.saveUrl', :url => url, :rating => _rating(rating))
115
+ end
116
+
117
+ # Use a userimage as a gravatar for one or more addresses on this account. Returns a hash:
118
+ # { email_address => true/false }
119
+ #
120
+ # This method is not cached.
121
+ #
122
+ # This method will clear out the cache, since it may have an effect on what the API methods respond with.
123
+ def use_user_image!(image_hash, *email_addresses)
124
+ hashed_email_addresses = normalize_email_addresses(email_addresses)
125
+ hash = call('grav.useUserimage', :userimage => image_hash, :addresses => hashed_email_addresses)
126
+ returning dehashify_emails(hash, email_addresses) { |value| boolean(value) } do
127
+ expire_cache!
128
+ end
129
+ end
130
+ alias use_image! use_user_image!
131
+
132
+ # Remove the userimage associated with one or more email addresses. Returns a hash of booleans.
133
+ # NOTE: This appears to always return false, even when it is really removing an image. If you
134
+ # know what the deal with that is, drop me a line so I can update this documentation!
135
+ #
136
+ # This method is not cached.
137
+ #
138
+ # This method will clear out the cache, since it may have an effect on what the API methods respond with.
139
+ def remove_image!(*emails)
140
+ hashed_email_addresses = normalize_email_addresses(emails)
141
+ hash = call('grav.removeImage', :addresses => hashed_email_addresses)
142
+ returning dehashify_emails(hash, emails) { |value| boolean(value) } do
143
+ expire_cache!
144
+ end
145
+ end
146
+
147
+ # Remove a userimage from the account and any email addresses with which it is associated. Returns
148
+ # true or false.
149
+ #
150
+ # This method is not cached.
151
+ #
152
+ # This method will clear out the cache, since it may have an effect on what the API methods respond with.
153
+ def delete_user_image!(userimage)
154
+ returning boolean(call('grav.deleteUserimage', :userimage => userimage)) do
155
+ expire_cache!
156
+ end
157
+ end
158
+
159
+ # Runs a simple Gravatar test. Useful for debugging. Gravatar will echo back any arguments you pass.
160
+ # This method is not cached.
161
+ def test(hash)
162
+ call('grav.test', hash)
163
+ end
164
+
165
+ # Returns the MD5 hash for the specified email address, or the one associated with this object.
166
+ def email_hash(email = self.email)
167
+ Digest::MD5.hexdigest(email.downcase.strip)
168
+ end
169
+
170
+ # Returns the URL for this user's gravatar image. Options include:
171
+ #
172
+ # :ssl or :secure if true, HTTPS will be used instead of HTTP. Default is false.
173
+ # :rating or :r a rating threshold for this image. Can be one of [ :g, :pg, :r, :x ]. Default is :g.
174
+ # :size or :s a size for this image. Can be anywhere between 1 and 512. Default is 80.
175
+ # :default or :d a default URL for this image to display if the specified user has no image;
176
+ # or this can be one of [ :identicon, :monsterid, :wavatar, 404 ]. By default a generic
177
+ # Gravatar image URL will be returned.
178
+ # :filetype an extension such as :jpg or :png. Default is omitted.
179
+ #
180
+ # See http://en.gravatar.com/site/implement/url for much more detailed information.
181
+ def image_url(options = {})
182
+ secure = options[:ssl] || options[:secure]
183
+ proto = "http#{secure ? 's' : ''}"
184
+ sub = secure ? "secure" : "www"
185
+
186
+ "#{proto}://#{sub}.gravatar.com/avatar/#{email_hash}#{extension_for_image(options)}#{query_for_image(options)}"
187
+ end
188
+
189
+ # Returns the image data for this user's gravatar image. This is the same as reading the data at #image_url. See
190
+ # that method for more information.
191
+ #
192
+ # This method is cached for up to the value of @duration or Gravatar.duration.
193
+ def image_data(options = {})
194
+ url = image_url(options)
195
+ cache(url) { OpenURI.open_uri(URI.parse(url)).read }
196
+ end
197
+
198
+ def self.version
199
+ @version ||= File.read(File.join(File.dirname(__FILE__), "../VERSION")).chomp
200
+ end
201
+
202
+ private
203
+ def cache(*key, &block)
204
+ @cache.call(*key, &block)
205
+ end
206
+
207
+ def expire_cache!
208
+ @cache.clear!
209
+ end
210
+
211
+ def dehashify_emails(response, emails)
212
+ hashed_emails = emails.collect { |email| email_hash(email) }
213
+ response.inject({}) do |hash, (hashed_email, value)|
214
+ value = yield(value) if value
215
+ email = emails[hashed_emails.index(hashed_email)]
216
+ hash[email] = value
217
+ hash
218
+ end
219
+ end
220
+
221
+ def normalize_email_addresses(addresses)
222
+ addresses.flatten!
223
+ addresses << @email if addresses.empty?
224
+ addresses.map { |email| email_hash(email) }
225
+ end
226
+
227
+ def rating(i)
228
+ case i
229
+ when 0, '0' then :g
230
+ when 1, '1' then :pg
231
+ when 2, '2' then :r
232
+ when 3, '3' then :x
233
+ when :g then 0
234
+ when :pg then 1
235
+ when :r then 2
236
+ when :x then 3
237
+ else raise ArgumentError, "Unexpected rating index: #{i} (expected between 0..3)"
238
+ end
239
+ end
240
+ alias _rating rating
241
+
242
+ def boolean(i)
243
+ i.kind_of?(Numeric) ? i != 0 : i
244
+ end
245
+
246
+ def call(name, args_hash = {})
247
+ r = @api.call(name, auth.merge(args_hash))
248
+ r = r.with_indifferent_access if r.kind_of?(Hash)
249
+ r
250
+ end
251
+
252
+ def auth
253
+ api_key ? {:apikey => api_key} : (password ? {:password => password} : {})
254
+ end
255
+
256
+ def api_key
257
+ options[:apikey] || options[:api_key] || options[:key]
258
+ end
259
+
260
+ def password
261
+ options[:password]
262
+ end
263
+
264
+ def options
265
+ @options
266
+ end
267
+
268
+ def query_for_image(options)
269
+ query = ''
270
+ [:rating, :size, :default, :r, :s, :d].each do |key|
271
+ if options.key?(key)
272
+ query.blank? ? query.concat("?") : query.concat("&")
273
+ query.concat("#{key}=#{CGI::escape options[key].to_s}")
274
+ end
275
+ end
276
+ query
277
+ end
278
+
279
+ def extension_for_image(options)
280
+ options.key?(:filetype) ? "." + (options[:filetype] || "jpg").to_s : ""
281
+ end
282
+ end
@@ -0,0 +1,133 @@
1
+ class Gravatar
2
+ # A wrapper around any given Cache object which provides Gravatar-specific helpers. Used internally.
3
+ class Cache
4
+ attr_reader :real_cache, :namespace
5
+ attr_accessor :duration, :logger
6
+
7
+ def initialize(real_cache, duration, namespace = nil, logger = Gravatar.logger)
8
+ @duration = duration
9
+ @real_cache = real_cache
10
+ @namespace = namespace
11
+ @logger = logger
12
+ end
13
+
14
+ # Provide a series of arguments to be used as a cache key, and a block to be executed when the cache
15
+ # is expired or needs to be populated.
16
+ #
17
+ # Example:
18
+ # cache = Gravatar::Cache.new(Rails.cache, 30.minutes)
19
+ # cache.call(:first_name => "Colin", :last_name => "MacKenzie") { call_webservice(with_some_args) }
20
+ #
21
+ def call(*key, &block)
22
+ cached_copy = read_cache(*key)
23
+ if expired?(*key) && block_given?
24
+ begin
25
+ returning(yield) do |object|
26
+ write_cache(object, *key)
27
+ end
28
+ rescue
29
+ log_error($!)
30
+ cached_copy.nil? ? nil : cached_copy[:object]
31
+ end
32
+ else
33
+ cached_copy.nil? ? nil : cached_copy[:object]
34
+ end
35
+ end
36
+
37
+ # Clears out the entire cache for this object's namespace. This actually removes the objects,
38
+ # instead of simply marking them as expired, so it will be as if the object never existed.
39
+ def clear!
40
+ @real_cache.delete_matched(/^#{Regexp::escape @namespace}/)
41
+ end
42
+
43
+ # forces the specified key to become expired
44
+ def expire!(*key)
45
+ unless expired?(*key)
46
+ @real_cache.write(cache_key(*key), { :expires_at => 1.minute.ago, :object => read_cache(*key)[:object] })
47
+ end
48
+ end
49
+
50
+ # Returns true if the cached copy is nil or expired based on @duration.
51
+ def expired?(*key)
52
+ cached_copy = read_cache(*key)
53
+ cached_copy.nil? || cached_copy[:expires_at] < Time.now
54
+ end
55
+
56
+ # Reads an object from the cache based on the cache key constructed from *key.
57
+ def read_cache(*key)
58
+ @real_cache.read(cache_key(*key))
59
+ end
60
+
61
+ # Writes an object to the cache based on th cache key constructed from *key.
62
+ def write_cache(object, *key)
63
+ @real_cache.write(cache_key(*key), { :expires_at => Time.now + duration, :object => object })
64
+ end
65
+
66
+ # Constructs a cache key from the specified *args and @namespace.
67
+ def cache_key(*args)
68
+ ActiveSupport::Cache.expand_cache_key(args, @namespace)
69
+ end
70
+
71
+ # Logs an error message, as long as self.logger responds to :error or :write.
72
+ # Otherwise, re-raises the error.
73
+ def log_error(error)
74
+ if logger.respond_to?(:error)
75
+ logger.error error.message
76
+ error.backtrace.each { |line| logger.error " #{line}" }
77
+ elsif logger.respond_to?(:write)
78
+ logger.write(([error.message]+error.backtrace).join("\n "))
79
+ logger.write("\n")
80
+ else raise error
81
+ end
82
+ end
83
+ end
84
+
85
+ class << self
86
+ def default_cache_instance
87
+ if defined?(Rails)
88
+ Rails.cache
89
+ else
90
+ ActiveSupport::Cache::FileStore.new("tmp/cache")
91
+ end
92
+ end
93
+
94
+ def default_logger_instance
95
+ if defined?(Rails)
96
+ Rails.logger
97
+ else
98
+ $stdout
99
+ end
100
+ end
101
+
102
+ def cache
103
+ @cache ||= default_cache_instance
104
+ end
105
+
106
+ def cache=(instance)
107
+ @cache = instance
108
+ end
109
+
110
+ def logger
111
+ @logger ||= default_logger_instance
112
+ end
113
+
114
+ def logger=(logger)
115
+ @logger = logger
116
+ end
117
+
118
+ # How long is a cached object good for? Default is 30 minutes.
119
+ def duration
120
+ @duration ||= 24.hours
121
+ end
122
+
123
+ def duration=(duration)
124
+ @duration = duration
125
+ end
126
+
127
+ # Resets any changes to the cache and initializes a new cache. If using Rails, the
128
+ # new cache will be the Rails cache.
129
+ def reset_cache!
130
+ @cache = nil
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,16 @@
1
+ if defined?(Rails)
2
+ Rails.configuration.gem "sc-core-ext", ">= 1.2.0"
3
+ elsif !defined?(Gem)
4
+ require 'rubygems'
5
+ gem 'sc-core-ext', '>= 1.2.0'
6
+ end
7
+
8
+ unless defined?(ScCoreExt) || defined?(Rails) # because Rails will load it later and we don't really need it quite yet.
9
+ require 'sc-core-ext'
10
+ end
11
+
12
+ # The rest of this is core Ruby stuff so it's safe to load immediately, even if Rails is running the show.
13
+ require 'open-uri'
14
+ require "digest/md5"
15
+ require 'xmlrpc/client'
16
+ require 'base64'
@@ -0,0 +1 @@
1
+ require File.expand_path("../gravatar", __FILE__)
@@ -0,0 +1,11 @@
1
+ # To run this test suite, we need to set up some credentials because we're running real HTTP requests.
2
+ # Make sure you have a working Gravatar account, and enter the following options:
3
+ primary_email: email@example.com
4
+ email: email@example.com
5
+ api_key: YOUR_API_KEY
6
+
7
+ # The primary email is the one that's marked 'primary' on your Gravatar account page. Both emails can
8
+ # be the same. The API Key is your Gravatar API key, which you can optionally replace with:
9
+ # password: YOUR_GRAVATAR_PASSWORD
10
+ #
11
+ # Take care not to commit your credentials!
Binary file
@@ -0,0 +1,94 @@
1
+ require 'spec_helper'
2
+
3
+ describe Gravatar::Cache do
4
+ subject { Gravatar::Cache.new(new_cache, 30.minutes, "gravatar-specs") }
5
+
6
+ context "with a nonexistent cache item" do
7
+ it "should be expired" do
8
+ subject.expired?(:nothing).should be_true
9
+ end
10
+
11
+ it "should fire the block" do
12
+ subject.call(:nothing) { @fired = 1 }
13
+ @fired.should == 1
14
+ end
15
+
16
+ it "should return nil if no block given" do
17
+ subject.call(:nothing).should be_nil
18
+ end
19
+
20
+ it "should return the block value" do
21
+ subject.call(:nothing) { 1 }.should == 1
22
+ end
23
+ end
24
+
25
+ context "with a pre-existing cache item" do
26
+ before(:each) { subject.call(:nothing) { 1 } }
27
+
28
+ it "should not be expired" do
29
+ subject.expired?(:nothing).should be_false
30
+ end
31
+
32
+ it "should not fire the block" do
33
+ subject.call(:nothing) { @fired = 1 }
34
+ @fired.should_not == 1
35
+ end
36
+
37
+ it "should return the cached value" do
38
+ subject.call(:nothing) { raise "Block expected not to fire" }.should == 1
39
+ end
40
+
41
+ context "that is expired" do
42
+ before(:each) { subject.expire!(:nothing) }
43
+
44
+ it "should be expired" do
45
+ subject.expired?(:nothing).should be_true
46
+ end
47
+
48
+ it "should fire the block" do
49
+ subject.call(:nothing) { @fired = 1 }
50
+ @fired.should == 1
51
+ end
52
+
53
+ context "in the event of an error while refreshing" do
54
+ before(:each) { subject.logger = StringIO.new("") }
55
+
56
+ it "should recover" do
57
+ proc { subject.call(:nothing) { raise "something bad happened" } }.should_not raise_error(RuntimeError)
58
+ end
59
+
60
+ it "should return the cached copy" do
61
+ subject.call(:nothing) { raise "something bad happened" }.should == 1
62
+ end
63
+
64
+ context "its logger" do
65
+ before(:each) { subject.logger = Object.new }
66
+
67
+ it "should log it if #error is available" do
68
+ subject.logger.stub!(:error => nil)
69
+ subject.logger.should_receive(:error).and_return(nil)
70
+ subject.call(:nothing) { raise "something bad happened" }
71
+ end
72
+
73
+ it "should log it if #write is available" do
74
+ subject.logger.stub!(:write => nil)
75
+ subject.logger.should_receive(:write).and_return(nil)
76
+ subject.call(:nothing) { raise "something bad happened" }
77
+ end
78
+
79
+ it "should re-raise the error if no other methods are available" do
80
+ proc { subject.call(:nothing) { raise "something bad happened" } }.should raise_error(RuntimeError)
81
+ end
82
+ end
83
+ end
84
+
85
+ it "should return the block value" do
86
+ subject.call(:nothing) { 2 }.should == 2
87
+ end
88
+
89
+ it "should return the cached value if there is no block value" do
90
+ subject.call(:nothing).should == 1
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,42 @@
1
+ describe "gravatar cache setup" do
2
+ context "within Rails" do
3
+ before(:each) do
4
+ # We reset it here because we've already set it to MemoryStore for the sake of the majority.
5
+ Gravatar.reset_cache!
6
+ module Rails
7
+ end
8
+ end
9
+
10
+ it "should get the default cache instance from Rails" do
11
+ Rails.should_receive(:cache).and_return("a cache object")
12
+ Gravatar.cache
13
+ end
14
+
15
+ it "should get the current cache from cache assignment, if any" do
16
+ Gravatar.cache = "a cache object"
17
+ Gravatar.cache.should == "a cache object"
18
+ end
19
+ end
20
+
21
+ context "out of Rails" do
22
+ it "should get the default cache from ActiveSupport" do
23
+ Gravatar.cache.should be_kind_of(ActiveSupport::Cache::FileStore)
24
+ end
25
+
26
+ it "should get the current cache from cache assignment, if any" do
27
+ Gravatar.cache = "a cache object"
28
+ Gravatar.cache.should == "a cache object"
29
+ end
30
+ end
31
+
32
+ after(:each) do
33
+ # We reset it here because we've already fubarred it with the Rails tests.
34
+ Gravatar.reset_cache!
35
+
36
+ silence_warnings do
37
+ if defined?(Rails)
38
+ Object.send :remove_const, :Rails
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,29 @@
1
+ require "spec_helper"
2
+
3
+ describe "Dependencies" do
4
+ context "within Rails" do
5
+ before(:each) do
6
+ module ::Rails
7
+ def self.configuration
8
+ return @config if @config
9
+ @config = Object.new
10
+ klass = class << @config; self; end
11
+ klass.instance_eval do
12
+ def gem(*a, &b); end
13
+ public :gem
14
+ end
15
+ @config
16
+ end
17
+ end
18
+ end
19
+
20
+ it "should set a Rails gem dependency" do
21
+ Rails.configuration.should_receive(:gem, :with => ["sc-core-ext", ">= 1.2.0"])
22
+ load File.expand_path("../../../../lib/gravatar/dependencies.rb", __FILE__)
23
+ end
24
+
25
+ after(:each) { silence_warnings { Object.send(:remove_const, :Rails) } }
26
+ end
27
+
28
+ # Don't know how to test the inclusion of sc-core-ext via rubygems, but I suppose there's no reason that should fail.
29
+ end
@@ -0,0 +1,141 @@
1
+ require 'spec_helper'
2
+
3
+ describe Gravatar do
4
+ it "should have a valid version number" do
5
+ Gravatar.version.should =~ /^\d+\.\d+\.\d+$/
6
+ end
7
+
8
+ it "should allow setting cache duration by instance" do
9
+ grav = Gravatar.new($credentials[:primary_email])
10
+ grav.cache_duration = 10.minutes
11
+ grav.cache_duration.should == 10.minutes
12
+ end
13
+
14
+ it "should allow setting cache duration globally" do
15
+ Gravatar.duration = 10.minutes
16
+ Gravatar.new($credentials[:primary_email]).cache_duration.should == 10.minutes
17
+ Gravatar.duration = 30.minutes
18
+ end
19
+
20
+ it "should require :email" do
21
+ proc { subject }.should raise_error(ArgumentError)
22
+ end
23
+
24
+ context "given :email and :key" do
25
+ subject { Gravatar.new($credentials[:primary_email], $credentials)}
26
+
27
+ context "varying image ratings" do
28
+ [:g, :pg, :r, :x].each do |rating|
29
+ it "should save #{rating}-rated URLs and delete them" do
30
+ subject.save_url!(rating, "http://jigsaw.w3.org/css-validator/images/vcss").should ==
31
+ "2df7db511c46303983f0092556a1e47c"
32
+ subject.delete_user_image!("2df7db511c46303983f0092556a1e47c").should == true
33
+ end
34
+ end
35
+
36
+ it "should raise an ArgumentError given an invalid rating" do
37
+ proc { subject.save_url!(:invalid_rating, "http://jigsaw.w3.org/css-validator/images/vcss") }.should \
38
+ raise_error(ArgumentError)
39
+ end
40
+ end
41
+
42
+ it "should return addresses" do
43
+ subject.addresses.should_not be_empty
44
+ end
45
+
46
+ it "should test successfully" do
47
+ subject.test(:greeting => 'hello').should have_key(:response)
48
+ end
49
+
50
+ it "should save URLs and delete them" do
51
+ subject.save_url!(:g, "http://jigsaw.w3.org/css-validator/images/vcss").should == "2df7db511c46303983f0092556a1e47c"
52
+ subject.delete_user_image!("2df7db511c46303983f0092556a1e47c").should == true
53
+ end
54
+
55
+ # Not really the ideal approach but it's a valid test, at least
56
+ it "should save and delete images and associate/unassociate them with accounts" do
57
+ begin
58
+ subject.save_data!(:g, image_data).should == "23f086a793459fa25aab280054fec1b2"
59
+ subject.use_user_image!("23f086a793459fa25aab280054fec1b2", $credentials[:email]).should ==
60
+ { $credentials[:email] => false }
61
+ # See rdoc for #remove_image! for why we're not checking this.
62
+ subject.remove_image!($credentials[:email])#.should == { $credentials[:email] => true }
63
+ subject.delete_user_image!("23f086a793459fa25aab280054fec1b2").should == true
64
+ ensure
65
+ subject.remove_image!($credentials[:email])
66
+ begin
67
+ subject.delete_user_image!("23f086a793459fa25aab280054fec1b2")
68
+ rescue XMLRPC::FaultException
69
+ end
70
+ end
71
+ end
72
+
73
+ it "should return user images" do
74
+ subject.user_images.should == {"fe9dee44a1df19967db30a04083722d5"=>
75
+ [:g, "http://en.gravatar.com/userimage/14612723/fe9dee44a1df19967db30a04083722d5.jpg"]}
76
+ end
77
+
78
+ it "should determine that the user exists" do
79
+ subject.exists?.should be_true
80
+ end
81
+
82
+ it "should determine that a fake user does not exist" do
83
+ subject.exists?("not-even-a-valid-email").should be_false
84
+ end
85
+
86
+ it "should determine that multiple fake users do not exist" do
87
+ subject.exists?("invalid-1", "invalid-2").should == { "invalid-1" => false, "invalid-2" => false }
88
+ end
89
+ end
90
+
91
+ context "given :email" do
92
+ subject { Gravatar.new("sinisterchipmunk@gmail.com") }
93
+
94
+ it "should not raise an error" do
95
+ proc { subject }.should_not raise_error(ArgumentError)
96
+ end
97
+
98
+ it "should return email_hash" do
99
+ subject.email_hash.should == "5d8c7a8d951a28e10bd7407f33df6d63"
100
+ end
101
+
102
+ it "should return gravatar image_url" do
103
+ subject.image_url.should == "http://www.gravatar.com/avatar/5d8c7a8d951a28e10bd7407f33df6d63"
104
+ end
105
+
106
+ it "should return gravitar image data" do
107
+ subject.image_data.should == image_data
108
+ end
109
+
110
+ it "should return gravatar image_url with SSL" do
111
+ subject.image_url(:ssl => true).should == "https://secure.gravatar.com/avatar/5d8c7a8d951a28e10bd7407f33df6d63"
112
+ end
113
+
114
+ it "should return gravatar image_url with size" do
115
+ subject.image_url(:size => 512).should == "http://www.gravatar.com/avatar/5d8c7a8d951a28e10bd7407f33df6d63?size=512"
116
+ end
117
+
118
+ it "should return gravatar image_url with rating" do
119
+ subject.image_url(:rating => 'pg').should == "http://www.gravatar.com/avatar/5d8c7a8d951a28e10bd7407f33df6d63?rating=pg"
120
+ end
121
+
122
+ it "should return gravatar image_url with file type" do
123
+ subject.image_url(:filetype => 'png').should == "http://www.gravatar.com/avatar/5d8c7a8d951a28e10bd7407f33df6d63.png"
124
+ end
125
+
126
+ it "should return gravatar image_url with default image" do
127
+ subject.image_url(:default => "http://example.com/images/example.jpg").should ==
128
+ "http://www.gravatar.com/avatar/5d8c7a8d951a28e10bd7407f33df6d63?default=http%3A%2F%2Fexample.com%2Fimages%2Fexample.jpg"
129
+ end
130
+
131
+ it "should return gravatar image_url with SSL and default and size and rating" do
132
+ combinations = %w(
133
+ https://secure.gravatar.com/avatar/5d8c7a8d951a28e10bd7407f33df6d63?default=identicon&size=80&rating=g
134
+ https://secure.gravatar.com/avatar/5d8c7a8d951a28e10bd7407f33df6d63?size=80&rating=g&default=identicon
135
+ https://secure.gravatar.com/avatar/5d8c7a8d951a28e10bd7407f33df6d63?size=80&default=identicon&rating=g
136
+ https://secure.gravatar.com/avatar/5d8c7a8d951a28e10bd7407f33df6d63?rating=g&size=80&default=identicon
137
+ )
138
+ combinations.should include(subject.image_url(:ssl => true, :default => "identicon", :size => 80, :rating => :g))
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,2 @@
1
+ -c
2
+ -b
@@ -0,0 +1,30 @@
1
+ require File.expand_path("../../lib/gravatar", __FILE__)
2
+ unless defined?(Spec)
3
+ gem 'rspec'
4
+ require 'spec'
5
+ end
6
+
7
+ def image_data
8
+ File.read(File.expand_path("../fixtures/image.jpg", __FILE__))
9
+ end
10
+
11
+ require 'fakeweb'
12
+ FakeWeb.register_uri(:get, "http://www.gravatar.com/avatar/5d8c7a8d951a28e10bd7407f33df6d63", :response =>
13
+ "HTTP/1.1 200 OK\nContent-Type: image/jpg\n\n" +image_data)
14
+
15
+ def new_cache
16
+ ActiveSupport::Cache::MemoryStore.new
17
+ end
18
+
19
+ Gravatar.cache = new_cache
20
+
21
+ class Net::HTTP
22
+ alias_method :original_initialize, :initialize
23
+ def initialize(*args, &block)
24
+ original_initialize(*args, &block)
25
+ @ssl_context = OpenSSL::SSL::SSLContext.new
26
+ @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
27
+ end
28
+ end
29
+
30
+ $credentials = YAML::load(File.read(File.expand_path("../credentials.yml", __FILE__))).with_indifferent_access
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gravatar-ultimate
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 1
7
+ - 0
8
+ - 0
9
+ version: 1.0.0
10
+ platform: ruby
11
+ authors:
12
+ - Colin MacKenzie IV
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-06-16 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: sc-core-ext
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 1
29
+ - 2
30
+ - 0
31
+ version: 1.2.0
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: rspec
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 1
43
+ - 3
44
+ - 0
45
+ version: 1.3.0
46
+ type: :development
47
+ version_requirements: *id002
48
+ - !ruby/object:Gem::Dependency
49
+ name: fakeweb
50
+ prerelease: false
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ segments:
56
+ - 1
57
+ - 2
58
+ - 8
59
+ version: 1.2.8
60
+ type: :development
61
+ version_requirements: *id003
62
+ description: |-
63
+ The Ultimate Gravatar Gem!
64
+
65
+ This gem is used to interface with the entire Gravatar API: it's not just for generating image URLs, but for connecting
66
+ to and communicating with the XML-RPC API too! Additionally, it can be used to download the Gravatar image data itself,
67
+ rather than just a URL to that data. This saves you the extra step of having to do so.
68
+ email: sinisterchipmunk@gmail.com
69
+ executables: []
70
+
71
+ extensions: []
72
+
73
+ extra_rdoc_files:
74
+ - LICENSE
75
+ - README.rdoc
76
+ files:
77
+ - .document
78
+ - .gitignore
79
+ - LICENSE
80
+ - README.rdoc
81
+ - Rakefile
82
+ - VERSION
83
+ - gravatar-ultimate.gemspec
84
+ - lib/gravatar-ultimate.rb
85
+ - lib/gravatar.rb
86
+ - lib/gravatar/cache.rb
87
+ - lib/gravatar/dependencies.rb
88
+ - lib/gravatar_ultimate.rb
89
+ - spec/credentials.yml.example
90
+ - spec/fixtures/image.jpg
91
+ - spec/lib/gravatar/cache_and_logger_spec.rb
92
+ - spec/lib/gravatar/cache_setup_spec.rb
93
+ - spec/lib/gravatar/dependencies_spec.rb
94
+ - spec/lib/gravatar_spec.rb
95
+ - spec/spec.opts
96
+ - spec/spec_helper.rb
97
+ has_rdoc: true
98
+ homepage: http://github.com/sinisterchipmunk/gravatar
99
+ licenses: []
100
+
101
+ post_install_message:
102
+ rdoc_options:
103
+ - --charset=UTF-8
104
+ require_paths:
105
+ - lib
106
+ required_ruby_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ segments:
111
+ - 0
112
+ version: "0"
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ segments:
118
+ - 0
119
+ version: "0"
120
+ requirements: []
121
+
122
+ rubyforge_project:
123
+ rubygems_version: 1.3.6
124
+ signing_key:
125
+ specification_version: 3
126
+ summary: "A gem for interfacing with the entire Gravatar API: not just images, but the XML-RPC API too!"
127
+ test_files:
128
+ - spec/spec_helper.rb
129
+ - spec/lib/gravatar_spec.rb
130
+ - spec/lib/gravatar/dependencies_spec.rb
131
+ - spec/lib/gravatar/cache_and_logger_spec.rb
132
+ - spec/lib/gravatar/cache_setup_spec.rb