betterific 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +117 -0
- data/Rakefile +5 -0
- data/betterific.gemspec +23 -0
- data/lib/betterific/client.rb +24 -0
- data/lib/betterific/client_constants.rb +22 -0
- data/lib/betterific/client_helpers.rb +45 -0
- data/lib/betterific/json_client.rb +89 -0
- data/lib/betterific/protobuf_client.rb +157 -0
- data/lib/betterific/ruby_extensions.rb +46 -0
- data/lib/betterific/version.rb +3 -0
- data/lib/betterific.rb +21 -0
- data/spec/betterific_spec.rb +7 -0
- data/spec/client_spec.rb +3 -0
- data/spec/json_client_spec.rb +3 -0
- data/spec/protobuf_client_spec.rb +5 -0
- data/spec/spec_helper.rb +147 -0
- metadata +135 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Brad Cater
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
# Betterific
|
2
|
+
|
3
|
+
This gem makes it easy to access the Betterific API to explore betterifs, tags,
|
4
|
+
and users.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
gem 'betterific'
|
11
|
+
|
12
|
+
And then execute:
|
13
|
+
|
14
|
+
$ bundle
|
15
|
+
|
16
|
+
Or install it yourself as:
|
17
|
+
|
18
|
+
$ gem install betterific
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
To get started quickly, you should use Betterific::Client, which uses
|
23
|
+
Betterific::JsonClient in the absence of the
|
24
|
+
[ruby-protocol-buffers](https://github.com/codekitchen/ruby-protocol-buffers)
|
25
|
+
gem. This will use JSON as the data format and requires no extra gems besides
|
26
|
+
the [json](http://flori.github.io/json/) gem, which is likely already installed
|
27
|
+
on your machine.
|
28
|
+
|
29
|
+
Betterific::JsonClient wraps the JSON response using
|
30
|
+
[Hashie](https://github.com/intridea/hashie), so you may access the data using
|
31
|
+
JSON object notation or via method calls. For example,
|
32
|
+
|
33
|
+
Betterific::JsonClient.betterifs(:id => [224])['total_results']
|
34
|
+
|
35
|
+
is equivalent to
|
36
|
+
|
37
|
+
Betterific::JsonClient.betterifs(:id => [224]).total_results
|
38
|
+
|
39
|
+
However, it is recommended that you use the latter notation since it is
|
40
|
+
compatible with Betterific::ProtobufClient, whereas the JSON object notation is
|
41
|
+
not.
|
42
|
+
|
43
|
+
### Betterifs
|
44
|
+
|
45
|
+
You can see the most popular betterifs of the last week using
|
46
|
+
|
47
|
+
Betterific::Client.betterifs(:most_popular)
|
48
|
+
|
49
|
+
A similar call can be used to see the most recent betterifs
|
50
|
+
|
51
|
+
Betterific::Client.betterifs(:most_recent)
|
52
|
+
|
53
|
+
If you already know the id(s) of the betterif(s) that you would like to see,
|
54
|
+
you can use
|
55
|
+
|
56
|
+
Betterific::Client.betterifs(:ids => [id0, id1, ...])
|
57
|
+
|
58
|
+
### Tags and Users
|
59
|
+
|
60
|
+
You can see a list of tags or users by id using
|
61
|
+
|
62
|
+
Betterific::Client.tags(:ids => [id0, id1, ...])
|
63
|
+
|
64
|
+
and
|
65
|
+
|
66
|
+
Betterific::Client.users(:ids => [id0, id1, ...])
|
67
|
+
|
68
|
+
### Search
|
69
|
+
|
70
|
+
You can search for betterifs, tags, users, or all of these using
|
71
|
+
|
72
|
+
Betterific::Client.search(:namespace => :all, :q => 'my query')
|
73
|
+
|
74
|
+
Changing the _:namespace_ parameter will change the type of data returned.
|
75
|
+
|
76
|
+
### Pagination
|
77
|
+
|
78
|
+
All client methods take pagination params _:page_ and *:per_page*. In the case
|
79
|
+
of most popular and most recent betterifs, the filter must be changed to a Hash
|
80
|
+
parameter, like so
|
81
|
+
|
82
|
+
Betterific::Client.betterifs(:filter => :most_popular, :page => 2, :per_page => 10)
|
83
|
+
|
84
|
+
and
|
85
|
+
|
86
|
+
Betterific::Client.betterifs(:filter => :most_recent, :page => 2, :per_page => 10)
|
87
|
+
|
88
|
+
### Using Protocol Buffers
|
89
|
+
|
90
|
+
If you have
|
91
|
+
[ruby-protocol-buffers](https://github.com/codekitchen/ruby-protocol-buffers)
|
92
|
+
installed, Betterific::Client will use Betterific::ProtobufClient in place of
|
93
|
+
Betterific::JsonClient. This will greatly improve performance, as
|
94
|
+
[Protocol Buffers](https://developers.google.com/protocol-buffers/) are highly
|
95
|
+
optimized.
|
96
|
+
|
97
|
+
The Betterific::ProtobufClient responds to the same methods as the
|
98
|
+
Betterific::JsonClient, so it's easy to switch between implementations at will.
|
99
|
+
For example,
|
100
|
+
|
101
|
+
Betterific::JsonClient.users(:id => [2])
|
102
|
+
|
103
|
+
and
|
104
|
+
|
105
|
+
Betterific::ProtobufClient.users(:id => [2])
|
106
|
+
|
107
|
+
return the same data as
|
108
|
+
|
109
|
+
Betterific::Client.users(:id => [2])
|
110
|
+
|
111
|
+
## Contributing
|
112
|
+
|
113
|
+
1. Fork it
|
114
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
115
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
116
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
117
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/betterific.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/betterific/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Brad Cater"]
|
6
|
+
gem.email = ["bradcater@gmail.com"]
|
7
|
+
gem.description = %q{This gem is a Ruby interface to the Betterific API.}
|
8
|
+
gem.summary = %q{This gem is a Ruby interface to the Betterific API. It provides support via Protocol Buffers if the ruby-protocol-buffers gem is installed; otherwise, it uses JSON.}
|
9
|
+
gem.homepage = "https://github.com/bradcater/betterific"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "betterific"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Betterific::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency 'hashie', '~> 2.0.5'
|
19
|
+
gem.add_dependency 'json', '~> 1.8.0'
|
20
|
+
|
21
|
+
gem.add_development_dependency 'ruby-protocol-buffers', '~> 2.4.0'
|
22
|
+
gem.add_development_dependency 'rspec', '~> 2.13.0'
|
23
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Betterific
|
2
|
+
module Client
|
3
|
+
# Delegate all methods to a client implementation if that client
|
4
|
+
# implementation supports the method. Use ProtobufClient if it's
|
5
|
+
# available; otherwise, use JsonClient.
|
6
|
+
#
|
7
|
+
CLIENT = if defined?(Betterific::ProtobufClient)
|
8
|
+
Betterific::ProtobufClient
|
9
|
+
else
|
10
|
+
Betterific::JsonClient
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.respond_to?(method) #:nodoc:
|
14
|
+
return true if CLIENT.respond_to?(method)
|
15
|
+
super
|
16
|
+
end
|
17
|
+
def self.method_missing(method, *args, &block) #:nodoc:
|
18
|
+
if CLIENT.respond_to?(method)
|
19
|
+
return CLIENT.send(method, *args, &block)
|
20
|
+
end
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Betterific
|
2
|
+
module ClientConstants
|
3
|
+
# The base URL for the Betterific API.
|
4
|
+
BASE_URL = 'http://betterific.com/api'.freeze
|
5
|
+
|
6
|
+
# The base URL to GET betterifs.
|
7
|
+
BETTERIFS_BASE_URL = "#{BASE_URL}/betterifs".freeze
|
8
|
+
# The base URL to GET tags.
|
9
|
+
TAGS_BASE_URL = "#{BASE_URL}/tags".freeze
|
10
|
+
# The base URL to GET users.
|
11
|
+
USERS_BASE_URL = "#{BASE_URL}/users".freeze
|
12
|
+
|
13
|
+
# The base URL to GET search results.
|
14
|
+
SEARCH_BASE_URL = "#{BASE_URL}/search".freeze
|
15
|
+
|
16
|
+
# The package in which protocol buffer schemas are defined.
|
17
|
+
PROTO_PACKAGE_NAME = 'BetterIf'.freeze
|
18
|
+
|
19
|
+
# The directory in which to store temporary files.
|
20
|
+
TMP_DIR = File.expand_path(File.join('.', 'tmp'))
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
module Betterific
|
4
|
+
module ClientHelpers
|
5
|
+
def get_http(uri, opts={}) #:nodoc:
|
6
|
+
res = Net::HTTP.get_response(uri)
|
7
|
+
unless res.is_a?(Net::HTTPSuccess)
|
8
|
+
raise "Could not connect to #{uri}"
|
9
|
+
end
|
10
|
+
res
|
11
|
+
end; private :get_http
|
12
|
+
|
13
|
+
def add_page_params(url, opts={}) #:nodoc:
|
14
|
+
to_add = []
|
15
|
+
if opts[:page]
|
16
|
+
unless valid_page_param?(opts[:page])
|
17
|
+
raise "Invalid page: #{opts[:page]}"
|
18
|
+
end
|
19
|
+
to_add << "page=#{opts[:page]}"
|
20
|
+
end
|
21
|
+
if opts[:per_page]
|
22
|
+
unless valid_page_param?(opts[:per_page])
|
23
|
+
raise "Invalid per_page: #{opts[:per_page]}"
|
24
|
+
end
|
25
|
+
to_add << "per_page=#{opts[:per_page]}"
|
26
|
+
end
|
27
|
+
if to_add.size > 0
|
28
|
+
url = "#{url}#{url =~ /\?/ ? '&' : '?'}#{to_add.join('&')}"
|
29
|
+
end
|
30
|
+
url
|
31
|
+
end; private :add_page_params
|
32
|
+
|
33
|
+
def page_params_from_opts(opts) #:nodoc:
|
34
|
+
return {} unless opts.is_a?(Hash)
|
35
|
+
[:page, :per_page].inject({}) do |hsh, k|
|
36
|
+
hsh[k] = opts[k]
|
37
|
+
hsh
|
38
|
+
end
|
39
|
+
end; private :page_params_from_opts
|
40
|
+
|
41
|
+
def valid_page_param?(p) #:nodoc:
|
42
|
+
p.is_a?(Fixnum) && p > 0 && p.to_i == p
|
43
|
+
end; private :valid_page_param?
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'hashie'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Betterific
|
5
|
+
module JsonClient
|
6
|
+
include ::Betterific::ClientConstants
|
7
|
+
class << self
|
8
|
+
include ::Betterific::ClientHelpers
|
9
|
+
def get_json(url, opts={}, url_params={}) #:nodoc:
|
10
|
+
url = add_page_params(url, page_params_from_opts(opts))
|
11
|
+
uri = URI(url)
|
12
|
+
unless url_params.empty?
|
13
|
+
uri.query = URI.encode_www_form(url_params)
|
14
|
+
end
|
15
|
+
Hashie::Mash.new(JSON.parse(get_http(uri).body))
|
16
|
+
end; private :get_json
|
17
|
+
end
|
18
|
+
|
19
|
+
# Get a list of betterifs.
|
20
|
+
#
|
21
|
+
# ==== Parameters
|
22
|
+
#
|
23
|
+
# * +opts+ - If most_popular, gets the most popular betterifs of the last
|
24
|
+
# week.
|
25
|
+
#
|
26
|
+
# If most_recent, gets the most recent betterifs.
|
27
|
+
#
|
28
|
+
# {:ids => [id0, id1, ..., idx]} specifies the ids of the betterif(s) to
|
29
|
+
# return.
|
30
|
+
def self.betterifs(opts={})
|
31
|
+
if [:most_popular, 'most_popular'].include?(opts) || (opts.is_a?(Hash) && [:most_popular, 'most_popular'].include?(opts[:filter]))
|
32
|
+
return get_json("#{BETTERIFS_BASE_URL}/most-popular")
|
33
|
+
elsif [:most_recent, 'most_recent'].include?(opts) || (opts.is_a?(Hash) && [:most_recent, 'most_recent'].include?(opts[:filter]))
|
34
|
+
return get_json("#{BETTERIFS_BASE_URL}/most-recent")
|
35
|
+
elsif opts[:ids]
|
36
|
+
return get_json("#{BETTERIFS_BASE_URL}?betterifs[ids]=#{Array(opts[:ids]).map(&:to_s).join(',')}")
|
37
|
+
else
|
38
|
+
raise "No filter and no ids given."
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Get a list of tags.
|
43
|
+
#
|
44
|
+
# ==== Parameters
|
45
|
+
#
|
46
|
+
# * +opts+ - {:ids => [id0, id1, ..., idx]} specifies the ids of the
|
47
|
+
# tag(s) to return.
|
48
|
+
def self.tags(opts={})
|
49
|
+
if opts[:ids]
|
50
|
+
return get_json("#{TAGS_BASE_URL}?tags[ids]=#{Array(opts[:ids]).map(&:to_s).join(',')}")
|
51
|
+
else
|
52
|
+
raise "No ids given."
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Get a list of users.
|
57
|
+
#
|
58
|
+
# ==== Parameters
|
59
|
+
#
|
60
|
+
# * +opts+ - {:ids => [id0, id1, ..., idx]} specifies the ids of the
|
61
|
+
# user(s) to return.
|
62
|
+
def self.users(opts={})
|
63
|
+
if opts[:ids]
|
64
|
+
return get_json("#{USERS_BASE_URL}?users[ids]=#{Array(opts[:ids]).map(&:to_s).join(',')}")
|
65
|
+
else
|
66
|
+
raise "No ids given."
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Search for betterifs, tags, and users.
|
71
|
+
#
|
72
|
+
# ==== Parameters
|
73
|
+
#
|
74
|
+
# * +opts+ - {:namespace => (all|betterifs|tags|users)} specifies the type
|
75
|
+
# of object(s) to return.
|
76
|
+
#
|
77
|
+
# {:q => <query>} specifies the search query.
|
78
|
+
def self.search(opts={})
|
79
|
+
raise "No namespace given." if opts[:namespace].nil?
|
80
|
+
raise "No q given." if opts[:q].nil?
|
81
|
+
raise "q is blank." if opts[:q].blank?
|
82
|
+
if [:betterifs, 'betterifs', :tags, 'tags', :users, 'users', :all, 'all'].include?(opts[:namespace])
|
83
|
+
return get_json("#{SEARCH_BASE_URL}/#{opts[:namespace]}?q=#{opts[:q]}")
|
84
|
+
else
|
85
|
+
raise "Invalid namespace: #{opts[:namespace]}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'protocol_buffers'
|
2
|
+
require 'protocol_buffers/compiler'
|
3
|
+
|
4
|
+
module Betterific
|
5
|
+
module ProtobufClient
|
6
|
+
include ::Betterific::ClientConstants
|
7
|
+
class << self
|
8
|
+
include ::Betterific::ClientHelpers
|
9
|
+
# Cache of the last time a schema was refreshed by name.
|
10
|
+
LAST_REFRESH = {}
|
11
|
+
# Cache of schemas by URI.
|
12
|
+
PROTO_SCHEMA_CACHE = {}
|
13
|
+
# How many seconds to keep a schema before refreshing.
|
14
|
+
PROTO_TTL_SECONDS = 60
|
15
|
+
def compile_and_load_string(kode, url) #:nodoc
|
16
|
+
unless Dir.exists?(self::TMP_DIR)
|
17
|
+
Dir.mkdir(self::TMP_DIR, 0700)
|
18
|
+
end
|
19
|
+
deps = fetch_and_save_dependencies(kode, url)
|
20
|
+
fname = url.split(/\//)[-1]
|
21
|
+
File.open(File.join(self::TMP_DIR, fname), 'w') do |f|
|
22
|
+
f.write(kode)
|
23
|
+
end
|
24
|
+
deps << fname
|
25
|
+
deps = deps.reject do |d|
|
26
|
+
Kernel.const_defined?(self::PROTO_PACKAGE_NAME.to_sym) && Kernel.const_get(self::PROTO_PACKAGE_NAME.to_sym)
|
27
|
+
.const_defined?(d.gsub(/\.proto$/, '').camelize.to_sym)
|
28
|
+
end
|
29
|
+
unless deps.empty?
|
30
|
+
ProtocolBuffers::Compiler.compile_and_load(deps.map do |d|
|
31
|
+
File.join(self::TMP_DIR, d)
|
32
|
+
end, :include_dirs => [self::TMP_DIR])
|
33
|
+
end
|
34
|
+
end; private :compile_and_load_string
|
35
|
+
|
36
|
+
def fetch_and_save_dependencies(kode, url) #:nodoc
|
37
|
+
kode.scan(/import\s+'([^']+)';/).map do |imp|
|
38
|
+
imp = imp.first
|
39
|
+
imp_url = [url.split(/\//)[0..-2], imp].flatten.join('/')
|
40
|
+
uri = URI(imp_url)
|
41
|
+
if LAST_REFRESH[uri].nil? || (LAST_REFRESH[uri] < Time.now - PROTO_TTL_SECONDS)
|
42
|
+
PROTO_SCHEMA_CACHE[uri] = get_http(URI(imp_url))
|
43
|
+
File.open(File.join(self::TMP_DIR, imp), 'w') do |f|
|
44
|
+
f.write(PROTO_SCHEMA_CACHE[uri].body)
|
45
|
+
end
|
46
|
+
LAST_REFRESH[uri] = Time.now
|
47
|
+
end
|
48
|
+
fetch_and_save_dependencies(PROTO_SCHEMA_CACHE[uri].body, url) + [imp]
|
49
|
+
end.flatten.uniq
|
50
|
+
end; private :fetch_and_save_dependencies
|
51
|
+
|
52
|
+
def get_namespaced_class(klass_string, o=nil) #:nodoc
|
53
|
+
return o if klass_string == nil
|
54
|
+
unless klass_string.is_a?(Array)
|
55
|
+
klass_string = klass_string.split(/::/)
|
56
|
+
end
|
57
|
+
o ||= Kernel
|
58
|
+
o = o.const_get(klass_string.first)
|
59
|
+
return o if klass_string.size == 1
|
60
|
+
get_namespaced_class(klass_string[1..-1], o)
|
61
|
+
end; private :get_namespaced_class
|
62
|
+
|
63
|
+
def get_protobuf(url, opts={}, url_params={}) #:nodoc
|
64
|
+
url = add_page_params(url, page_params_from_opts(opts))
|
65
|
+
proto_url = if url =~ /\?/
|
66
|
+
url.gsub(/\?/, '.protobuf?')
|
67
|
+
else
|
68
|
+
"#{url}.protobuf"
|
69
|
+
end
|
70
|
+
uri = URI(proto_url)
|
71
|
+
unless url_params.empty?
|
72
|
+
uri.query = URI.encode_www_form(url_params)
|
73
|
+
end
|
74
|
+
res = get_http(uri)
|
75
|
+
schema_uri = URI(res.header['X-Protobuf-Schema'])
|
76
|
+
if PROTO_SCHEMA_CACHE[schema_uri].nil? || LAST_REFRESH[schema_uri] < Time.now - PROTO_TTL_SECONDS
|
77
|
+
PROTO_SCHEMA_CACHE[schema_uri] = get_http(schema_uri)
|
78
|
+
LAST_REFRESH[schema_uri] = Time.now
|
79
|
+
end
|
80
|
+
proto_name = schema_uri.to_s.split(/\//).last
|
81
|
+
compile_and_load_string(PROTO_SCHEMA_CACHE[schema_uri].body, schema_uri.to_s)
|
82
|
+
proto_klass = get_namespaced_class("#{self::PROTO_PACKAGE_NAME}::#{proto_name.gsub(/\.proto$/, '').camelize}")
|
83
|
+
proto_klass.parse(res.body)
|
84
|
+
end; private :get_protobuf
|
85
|
+
end
|
86
|
+
|
87
|
+
# Get a list of betterifs.
|
88
|
+
#
|
89
|
+
# ==== Parameters
|
90
|
+
#
|
91
|
+
# * +opts+ - If most_popular, gets the most popular betterifs of the last
|
92
|
+
# week.
|
93
|
+
#
|
94
|
+
# If most_recent, gets the most recent betterifs.
|
95
|
+
#
|
96
|
+
# {:ids => [id0, id1, ..., idx]} specifies the ids of the betterif(s) to
|
97
|
+
# return.
|
98
|
+
def self.betterifs(opts={})
|
99
|
+
if [:most_popular, 'most_popular'].include?(opts) || (opts.is_a?(Hash) && [:most_popular, 'most_popular'].include?(opts[:filter]))
|
100
|
+
return get_protobuf("#{BETTERIFS_BASE_URL}/most-popular")
|
101
|
+
elsif [:most_recent, 'most_recent'].include?(opts) || (opts.is_a?(Hash) && [:most_recent, 'most_recent'].include?(opts[:filter]))
|
102
|
+
return get_protobuf("#{BETTERIFS_BASE_URL}/most-recent")
|
103
|
+
elsif opts[:ids]
|
104
|
+
return get_protobuf("#{BETTERIFS_BASE_URL}?betterifs[ids]=#{Array(opts[:ids]).map(&:to_s).join(',')}")
|
105
|
+
else
|
106
|
+
raise "No filter and no ids given."
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Get a list of tags.
|
111
|
+
#
|
112
|
+
# ==== Parameters
|
113
|
+
#
|
114
|
+
# * +opts+ - {:ids => [id0, id1, ..., idx]} specifies the ids of the
|
115
|
+
# tag(s) to return.
|
116
|
+
def self.tags(opts={})
|
117
|
+
if opts[:ids]
|
118
|
+
return get_protobuf("#{TAGS_BASE_URL}?tags[ids]=#{Array(opts[:ids]).map(&:to_s).join(',')}")
|
119
|
+
else
|
120
|
+
raise "No ids given."
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Get a list of users.
|
125
|
+
#
|
126
|
+
# ==== Parameters
|
127
|
+
#
|
128
|
+
# * +opts+ - {:ids => [id0, id1, ..., idx]} specifies the ids of the
|
129
|
+
# user(s) to return.
|
130
|
+
def self.users(opts={})
|
131
|
+
if opts[:ids]
|
132
|
+
return get_protobuf("#{USERS_BASE_URL}?users[ids]=#{Array(opts[:ids]).map(&:to_s).join(',')}")
|
133
|
+
else
|
134
|
+
raise "No ids given."
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Search for betterifs, tags, and users.
|
139
|
+
#
|
140
|
+
# ==== Parameters
|
141
|
+
#
|
142
|
+
# * +opts+ - {:namespace => (all|betterifs|tags|users)} specifies the type
|
143
|
+
# of object(s) to return.
|
144
|
+
#
|
145
|
+
# {:q => <query>} specifies the search query.
|
146
|
+
def self.search(opts={})
|
147
|
+
raise "No namespace given." if opts[:namespace].nil?
|
148
|
+
raise "No q given." if opts[:q].nil?
|
149
|
+
raise "q is blank." if opts[:q].blank?
|
150
|
+
if [:betterifs, 'betterifs', :tags, 'tags', :users, 'users', :all, 'all'].include?(opts[:namespace])
|
151
|
+
return get_protobuf("#{SEARCH_BASE_URL}/#{opts[:namespace]}?q=#{opts[:q]}")
|
152
|
+
else
|
153
|
+
raise "Invalid namespace: #{opts[:namespace]}"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class Object
|
2
|
+
unless nil.respond_to?(:present?)
|
3
|
+
# Return true if self is not nil, false otherwise.
|
4
|
+
#
|
5
|
+
# This is defined only if it is not yet defined.
|
6
|
+
#
|
7
|
+
def present?
|
8
|
+
!self.nil?
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class String
|
14
|
+
|
15
|
+
unless ''.respond_to?(:blank?)
|
16
|
+
# Return true if self has length 0 or is only whitespace, false otherwise.
|
17
|
+
#
|
18
|
+
# This is defined only if it is not yet defined.
|
19
|
+
#
|
20
|
+
def blank?
|
21
|
+
self.strip.size == 0
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
#Adapted from
|
26
|
+
#https://www.ruby-forum.com/topic/4411006
|
27
|
+
unless ''.respond_to?(:camelize)
|
28
|
+
# Return a camel-case version of self, in contrast to underscore.
|
29
|
+
#
|
30
|
+
# This is defined only if it is not yet defined.
|
31
|
+
#
|
32
|
+
def camelize
|
33
|
+
self.split('_').each(&:capitalize!).join
|
34
|
+
end
|
35
|
+
end
|
36
|
+
unless ''.respond_to?(:underscore)
|
37
|
+
# Return an underscore version of self, in contrast to camel-case.
|
38
|
+
#
|
39
|
+
# This is defined only if it is not yet defined.
|
40
|
+
#
|
41
|
+
def underscore
|
42
|
+
self.scan(/[A-Z][a-z]*/).join('_').downcase
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
data/lib/betterific.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'betterific/client_constants'
|
2
|
+
require 'betterific/client_helpers'
|
3
|
+
require 'betterific/ruby_extensions'
|
4
|
+
require 'betterific/version'
|
5
|
+
|
6
|
+
require 'betterific/json_client'
|
7
|
+
if Gem::Specification.find_all_by_name('ruby-protocol-buffers').any?
|
8
|
+
require 'betterific/protobuf_client'
|
9
|
+
else
|
10
|
+
puts 'Betterific: Install the ruby-protocol-buffers gem to use Betterific::ProtobufClient.'
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'betterific/client'
|
14
|
+
|
15
|
+
module Betterific
|
16
|
+
# See a human-readable form of this gem's current version.
|
17
|
+
#
|
18
|
+
def self.version_string
|
19
|
+
"Betterific version #{Betterific::VERSION}"
|
20
|
+
end
|
21
|
+
end
|
data/spec/client_spec.rb
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'betterific'
|
2
|
+
|
3
|
+
BETTERIFIC_TAG_ID = 400937 #:nodoc
|
4
|
+
BETTERIF_ID = 224 #:nodoc
|
5
|
+
USER_ID = 2 #:nodoc
|
6
|
+
|
7
|
+
SEARCH_KINDS = %w{betterifs tags users}.freeze #:nodoc
|
8
|
+
|
9
|
+
def ensure_valid_api_response(resp, client_modjule, opts={})
|
10
|
+
if opts[:big]
|
11
|
+
resp.total_results.should > 10
|
12
|
+
else
|
13
|
+
resp.total_results.should >= (opts[:allow_empty] ? 0 : 1)
|
14
|
+
end
|
15
|
+
if opts[:big]
|
16
|
+
resp.num_results.should == 10
|
17
|
+
else
|
18
|
+
resp.num_results.should >= (opts[:allow_empty] ? 0 : 1)
|
19
|
+
end
|
20
|
+
if opts[:betterifs]
|
21
|
+
if opts[:big]
|
22
|
+
resp.betterifs.size.should == 10
|
23
|
+
else
|
24
|
+
resp.betterifs.size.should >= (opts[:allow_empty] ? 0 : 1)
|
25
|
+
end
|
26
|
+
unless opts[:allow_empty]
|
27
|
+
if client_modjule == Betterific::JsonClient
|
28
|
+
resp.betterifs.first.tags.is_a?(Array).should == true
|
29
|
+
elsif client_modjule == Betterific::ProtobufClient
|
30
|
+
resp.betterifs.first.tags.is_a?(ProtocolBuffers::RepeatedField).should == true
|
31
|
+
else
|
32
|
+
raise "Invalid client_modjule #{client_modjule}"
|
33
|
+
end
|
34
|
+
if resp.betterifs.first.tags.size > 0
|
35
|
+
if client_modjule == Betterific::ProtobufClient
|
36
|
+
resp.betterifs.first.tags.first.is_a?(BetterIf::Tag).should == true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
if client_modjule == Betterific::ProtobufClient
|
40
|
+
resp.betterifs.first.user.is_a?(BetterIf::User).should == true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
if opts[:tags]
|
45
|
+
if opts[:big]
|
46
|
+
resp.tags.size.should == 10
|
47
|
+
else
|
48
|
+
resp.tags.size.should >= (opts[:allow_empty] ? 0 : 1)
|
49
|
+
end
|
50
|
+
if client_modjule == Betterific::JsonClient
|
51
|
+
resp.tags.is_a?(Array).should == true
|
52
|
+
elsif client_modjule == Betterific::ProtobufClient
|
53
|
+
resp.tags.is_a?(ProtocolBuffers::RepeatedField).should == true
|
54
|
+
else
|
55
|
+
raise "Invalid client_modjule #{client_modjule}"
|
56
|
+
end
|
57
|
+
if client_modjule == Betterific::ProtobufClient && resp.tags.size > 0
|
58
|
+
resp.tags.first.is_a?(BetterIf::Tag).should == true
|
59
|
+
end
|
60
|
+
end
|
61
|
+
if opts[:users]
|
62
|
+
if opts[:big]
|
63
|
+
resp.users.size.should == 10
|
64
|
+
else
|
65
|
+
resp.users.size.should >= (opts[:allow_empty] ? 0 : 1)
|
66
|
+
end
|
67
|
+
if client_modjule == Betterific::JsonClient
|
68
|
+
resp.users.is_a?(Array).should == true
|
69
|
+
elsif client_modjule == Betterific::ProtobufClient
|
70
|
+
resp.users.is_a?(ProtocolBuffers::RepeatedField).should == true
|
71
|
+
else
|
72
|
+
raise "Invalid client_modjule #{client_modjule}"
|
73
|
+
end
|
74
|
+
if client_modjule == Betterific::ProtobufClient && resp.users.size > 0
|
75
|
+
resp.users.first.is_a?(BetterIf::User).should == true
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def client_test(modjule)
|
81
|
+
describe modjule do
|
82
|
+
[['without page params', {}],
|
83
|
+
['with page params', {:page => 2, :per_page => 1}]].each do |(page_params_lbl, page_params)|
|
84
|
+
describe page_params_lbl do
|
85
|
+
[:most_popular, :most_recent].each do |filter|
|
86
|
+
it "should load #{filter} betterifs" do
|
87
|
+
resp = modjule.betterifs(page_params.merge(:filter => filter))
|
88
|
+
ensure_valid_api_response(resp, :betterifs => true, :big => page_params.empty?)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
it "should load betterifs via ids" do
|
92
|
+
resp = modjule.betterifs(page_params.merge(:ids => BETTERIF_ID))
|
93
|
+
ensure_valid_api_response(resp, :betterifs => true, :allow_empty => !page_params.empty?)
|
94
|
+
if page_params.empty?
|
95
|
+
resp.betterifs.first.id.should == BETTERIF_ID
|
96
|
+
end
|
97
|
+
end
|
98
|
+
it "should load tags via ids" do
|
99
|
+
resp = modjule.tags(page_params.merge(:ids => BETTERIFIC_TAG_ID))
|
100
|
+
ensure_valid_api_response(resp, :tags => true, :allow_empty => !page_params.empty?)
|
101
|
+
if page_params.empty?
|
102
|
+
resp.tags.first.id.should == BETTERIFIC_TAG_ID
|
103
|
+
end
|
104
|
+
end
|
105
|
+
it "should load users via ids" do
|
106
|
+
resp = modjule.users(page_params.merge(:ids => USER_ID))
|
107
|
+
ensure_valid_api_response(resp, :users => true, :allow_empty => !page_params.empty?)
|
108
|
+
if page_params.empty?
|
109
|
+
resp.users.first.id.should == USER_ID
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
SEARCH_KINDS.each do |kind|
|
114
|
+
it "should load search for #{kind}" do
|
115
|
+
q = random_query
|
116
|
+
resp = modjule.search(page_params.merge(:namespace => kind, :q => q))
|
117
|
+
resp.q.should == q
|
118
|
+
ensure_valid_api_response(resp.send(kind), kind.to_sym => true, :allow_empty => true)
|
119
|
+
if modjule == Betterific::JsonClient
|
120
|
+
SEARCH_KINDS.each do |other_kind|
|
121
|
+
next if kind == other_kind
|
122
|
+
resp.send(other_kind).present?.should == false
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
it "should load search for all" do
|
128
|
+
q = random_query
|
129
|
+
resp = modjule.search(page_params.merge(:namespace => :all, :q => q))
|
130
|
+
resp.q.should == q
|
131
|
+
SEARCH_KINDS.each do |kind|
|
132
|
+
ensure_valid_api_response(resp.send(kind), kind.to_sym => true, :allow_empty => true)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def random_query
|
141
|
+
'abcdefghijklmnopqrstuvwxyz'.split(//).sample
|
142
|
+
end
|
143
|
+
|
144
|
+
RSpec.configure do |config|
|
145
|
+
config.color_enabled = true
|
146
|
+
config.formatter = 'documentation'
|
147
|
+
end
|
metadata
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: betterific
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Brad Cater
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-07-13 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: hashie
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.0.5
|
22
|
+
prerelease: false
|
23
|
+
type: :runtime
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 2.0.5
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: json
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 1.8.0
|
38
|
+
prerelease: false
|
39
|
+
type: :runtime
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.8.0
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: ruby-protocol-buffers
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 2.4.0
|
54
|
+
prerelease: false
|
55
|
+
type: :development
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.4.0
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rspec
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 2.13.0
|
70
|
+
prerelease: false
|
71
|
+
type: :development
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 2.13.0
|
78
|
+
description: This gem is a Ruby interface to the Betterific API.
|
79
|
+
email:
|
80
|
+
- bradcater@gmail.com
|
81
|
+
executables: []
|
82
|
+
extensions: []
|
83
|
+
extra_rdoc_files: []
|
84
|
+
files:
|
85
|
+
- .gitignore
|
86
|
+
- Gemfile
|
87
|
+
- LICENSE
|
88
|
+
- README.md
|
89
|
+
- Rakefile
|
90
|
+
- betterific.gemspec
|
91
|
+
- lib/betterific.rb
|
92
|
+
- lib/betterific/client.rb
|
93
|
+
- lib/betterific/client_constants.rb
|
94
|
+
- lib/betterific/client_helpers.rb
|
95
|
+
- lib/betterific/json_client.rb
|
96
|
+
- lib/betterific/protobuf_client.rb
|
97
|
+
- lib/betterific/ruby_extensions.rb
|
98
|
+
- lib/betterific/version.rb
|
99
|
+
- spec/betterific_spec.rb
|
100
|
+
- spec/client_spec.rb
|
101
|
+
- spec/json_client_spec.rb
|
102
|
+
- spec/protobuf_client_spec.rb
|
103
|
+
- spec/spec_helper.rb
|
104
|
+
homepage: https://github.com/bradcater/betterific
|
105
|
+
licenses: []
|
106
|
+
post_install_message:
|
107
|
+
rdoc_options: []
|
108
|
+
require_paths:
|
109
|
+
- lib
|
110
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
111
|
+
none: false
|
112
|
+
requirements:
|
113
|
+
- - ! '>='
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
|
+
none: false
|
118
|
+
requirements:
|
119
|
+
- - ! '>='
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
requirements: []
|
123
|
+
rubyforge_project:
|
124
|
+
rubygems_version: 1.8.25
|
125
|
+
signing_key:
|
126
|
+
specification_version: 3
|
127
|
+
summary: This gem is a Ruby interface to the Betterific API. It provides support via
|
128
|
+
Protocol Buffers if the ruby-protocol-buffers gem is installed; otherwise, it uses
|
129
|
+
JSON.
|
130
|
+
test_files:
|
131
|
+
- spec/betterific_spec.rb
|
132
|
+
- spec/client_spec.rb
|
133
|
+
- spec/json_client_spec.rb
|
134
|
+
- spec/protobuf_client_spec.rb
|
135
|
+
- spec/spec_helper.rb
|