betterific 0.0.1
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.
- 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
|