gendered 0.0.7 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +17 -0
- data/lib/gendered.rb +28 -1
- data/lib/gendered/guesser.rb +62 -29
- data/lib/gendered/name.rb +3 -3
- data/lib/gendered/name_list.rb +5 -3
- data/lib/gendered/version.rb +1 -1
- data/spec/lib/config_spec.rb +39 -0
- data/spec/lib/gendered/guesser_spec.rb +73 -10
- data/spec/lib/gendered/name_list_spec.rb +13 -7
- data/spec/lib/gendered/name_spec.rb +19 -3
- data/spec/spec_helper.rb +34 -0
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1af845c196c001b0efd2690158669dba52ada027
|
4
|
+
data.tar.gz: 4e014c3a1de5f4a10249682f652cb892a3d20271
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 25bfd3b3bcf1edcbe8d7a5fbaba079282999b0ebdd5cb8defd8ffea517e90d5256c44783e4d095c52faceefc168f80a4fe06fb2fdf48860f366c65f1bb11c3c0
|
7
|
+
data.tar.gz: cd8e3243ea578a02eb7893ef59223ad1c37e1d90407bb5dd9752244bcab341f8c7fde2b19d0c3eefe3bd93ba130aabfbf9e94c2f2ab9de4c85d7cb8a2c6b0861
|
data/README.md
CHANGED
@@ -43,3 +43,20 @@ Or batch up a list of names (which sends only one request per hundred names to t
|
|
43
43
|
> name_list["Sean"].gender
|
44
44
|
=> :male
|
45
45
|
```
|
46
|
+
|
47
|
+
Options can be passed in too...
|
48
|
+
```ruby
|
49
|
+
options = { :apikey => "X123Y456", :country_id => "dk" }
|
50
|
+
name = Gendered::Name.new("Sean")
|
51
|
+
name.guess!(options)
|
52
|
+
name_list = Gendered::NameList.new(["Kim", "Theresa"], options)
|
53
|
+
```
|
54
|
+
|
55
|
+
Or set globally, as defaults...
|
56
|
+
```ruby
|
57
|
+
Gendered.configure do |config|
|
58
|
+
config.apikey = "X123Y456"
|
59
|
+
config.language_id = "pt"
|
60
|
+
# ...
|
61
|
+
end
|
62
|
+
```
|
data/lib/gendered.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "bigdecimal"
|
2
2
|
|
3
3
|
require "http"
|
4
4
|
|
@@ -8,5 +8,32 @@ require "gendered/name_list"
|
|
8
8
|
require "gendered/guesser"
|
9
9
|
|
10
10
|
module Gendered
|
11
|
+
GenderedError = Class.new(StandardError)
|
12
|
+
class RateLimitError < GenderedError
|
13
|
+
attr_reader :limit, :remaining, :reset
|
11
14
|
|
15
|
+
def initialize(message, limit, remaining, reset)
|
16
|
+
super(message)
|
17
|
+
@limit = limit
|
18
|
+
@remaining = remaining
|
19
|
+
@reset = reset
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Config < Struct.new(:apikey, :country_id, :language_id, :connection)
|
24
|
+
def merge(other)
|
25
|
+
hash = respond_to?(:to_h) ? to_h : Hash[each_pair.to_a]
|
26
|
+
hash.merge!(other)
|
27
|
+
hash.reject! { |k,v| v.nil? }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.configure
|
32
|
+
raise ArgumentError, "configuration block required" unless block_given?
|
33
|
+
yield config
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.config
|
37
|
+
@config ||= Config.new
|
38
|
+
end
|
12
39
|
end
|
data/lib/gendered/guesser.rb
CHANGED
@@ -1,51 +1,84 @@
|
|
1
|
+
require "json"
|
2
|
+
|
1
3
|
module Gendered
|
2
4
|
class Guesser
|
5
|
+
ENDPOINT = "https://api.genderize.io".freeze
|
6
|
+
USAGE_HEADERS = {
|
7
|
+
"X-Rate-Limit-Limit" => :limit,
|
8
|
+
"X-Rate-Limit-Remaining" => :remaining,
|
9
|
+
"X-Rate-Reset" => :reset
|
10
|
+
}.freeze
|
3
11
|
|
4
|
-
|
12
|
+
attr_reader :usage
|
13
|
+
attr_accessor :names, :options
|
5
14
|
|
6
|
-
def initialize(
|
7
|
-
|
15
|
+
def initialize(names, options = {})
|
16
|
+
@names = Array(names)
|
17
|
+
raise ArgumentError, "names cannot be empty" if @names.empty?
|
8
18
|
|
9
|
-
@
|
10
|
-
@
|
19
|
+
@options = Gendered.config.merge(options || {})
|
20
|
+
@options[:connection] ||= {}
|
21
|
+
@usage = { :limit => nil, :remaining => nil, :reset => nil }
|
11
22
|
end
|
12
23
|
|
13
24
|
def guess!
|
14
|
-
response =
|
25
|
+
response = request(request_options)
|
26
|
+
update_usage(response)
|
27
|
+
body = parse(response.body)
|
15
28
|
case response.code
|
16
29
|
when 200
|
30
|
+
create_names(body)
|
31
|
+
when 429
|
32
|
+
raise RateLimitError.new(body["error"], *@usage.values_at(:limit, :remaining, :reset))
|
33
|
+
else
|
34
|
+
raise GenderedError.new(body["error"])
|
35
|
+
end
|
36
|
+
end
|
17
37
|
|
18
|
-
|
19
|
-
|
20
|
-
names.collect do |name|
|
21
|
-
name = Name.new(name) if name.is_a?(String)
|
38
|
+
private
|
22
39
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
else
|
27
|
-
guesses
|
28
|
-
end
|
40
|
+
def update_usage(response)
|
41
|
+
USAGE_HEADERS.each { |header, key| @usage[key] = response[header].to_i }
|
42
|
+
end
|
29
43
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
44
|
+
def create_names(guesses)
|
45
|
+
names.collect do |name|
|
46
|
+
name = Name.new(name) if name.is_a?(String)
|
47
|
+
guess = case
|
48
|
+
when guesses.is_a?(Array)
|
49
|
+
guesses.find { |g| g["name"] == name.value }
|
50
|
+
else
|
51
|
+
guesses
|
52
|
+
end
|
35
53
|
|
36
|
-
|
54
|
+
if guess["gender"]
|
55
|
+
name.gender = guess["gender"].to_sym
|
56
|
+
name.probability = guess["probability"]
|
57
|
+
name.sample_size = guess["count"]
|
37
58
|
end
|
59
|
+
|
60
|
+
name
|
38
61
|
end
|
39
62
|
end
|
40
63
|
|
41
|
-
def
|
42
|
-
|
43
|
-
|
64
|
+
def request_options
|
65
|
+
options = {}
|
66
|
+
options[:params] = @options.reject { |k, v| k == :connection || v.nil? }
|
67
|
+
options[:params]["name[]"] = @names
|
68
|
+
options[:connection] = @options[:connection] unless @options[:connection].empty?
|
69
|
+
options
|
70
|
+
end
|
44
71
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
72
|
+
def request(options)
|
73
|
+
HTTP.get(ENDPOINT, options)
|
74
|
+
rescue => e
|
75
|
+
raise GenderedError, "request failed: #{e}"
|
76
|
+
end
|
77
|
+
|
78
|
+
def parse(response)
|
79
|
+
JSON.parse(response)
|
80
|
+
rescue JSON::ParserError => e
|
81
|
+
raise GenderedError, "cannot parse response JSON: #{e}"
|
49
82
|
end
|
50
83
|
end
|
51
84
|
end
|
data/lib/gendered/name.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Gendered
|
2
2
|
class Name
|
3
|
-
VALID_GENDERS =
|
3
|
+
VALID_GENDERS = [:male, :female]
|
4
4
|
|
5
5
|
attr_reader :value
|
6
6
|
|
@@ -16,8 +16,8 @@ module Gendered
|
|
16
16
|
!!@gender
|
17
17
|
end
|
18
18
|
|
19
|
-
def guess!(
|
20
|
-
Guesser.new(self,
|
19
|
+
def guess!(options = {})
|
20
|
+
Guesser.new(self, options).guess!
|
21
21
|
gender
|
22
22
|
end
|
23
23
|
|
data/lib/gendered/name_list.rb
CHANGED
@@ -4,18 +4,20 @@ module Gendered
|
|
4
4
|
|
5
5
|
attr_reader :names
|
6
6
|
|
7
|
-
def initialize(values)
|
7
|
+
def initialize(values, options = {})
|
8
8
|
@names = Array(values).collect do |value|
|
9
9
|
case value
|
10
10
|
when String then Name.new(value)
|
11
11
|
when Name then value
|
12
12
|
end
|
13
13
|
end
|
14
|
+
|
15
|
+
@options = options || {}
|
14
16
|
end
|
15
17
|
|
16
|
-
def guess!
|
18
|
+
def guess!
|
17
19
|
names.each_slice(100).each do |slice|
|
18
|
-
Guesser.new(slice).guess!
|
20
|
+
Guesser.new(slice, @options).guess!
|
19
21
|
end
|
20
22
|
names.collect(&:gender)
|
21
23
|
end
|
data/lib/gendered/version.rb
CHANGED
@@ -0,0 +1,39 @@
|
|
1
|
+
module Gendered
|
2
|
+
describe ".configure" do
|
3
|
+
let :settings do
|
4
|
+
{
|
5
|
+
:apikey => "key",
|
6
|
+
:country_id => "us",
|
7
|
+
:language_id => "en",
|
8
|
+
:connection => { :foo => "bar" }
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
it "allows configuration via a block" do
|
13
|
+
Gendered.configure do |config|
|
14
|
+
settings.each do |name, value|
|
15
|
+
config[name] = value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
settings.each do |name, value|
|
20
|
+
expect(Gendered.config[name]).to eq value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe Config do
|
26
|
+
subject do
|
27
|
+
Gendered.config
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#merge" do
|
31
|
+
it "overrides the default config" do
|
32
|
+
subject[:country_id] = "us"
|
33
|
+
subject[:language_id] = "en"
|
34
|
+
|
35
|
+
expect(subject.merge(:language_id => "pt")).to eq(:country_id => "us", :language_id => "pt")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -1,8 +1,7 @@
|
|
1
1
|
module Gendered
|
2
2
|
describe Guesser do
|
3
|
-
|
4
3
|
let :names do
|
5
|
-
["Sean","Theresa"]
|
4
|
+
["Sean", "Theresa"]
|
6
5
|
end
|
7
6
|
|
8
7
|
subject do
|
@@ -13,19 +12,81 @@ module Gendered
|
|
13
12
|
expect(subject.names).to eq names
|
14
13
|
end
|
15
14
|
|
16
|
-
it "
|
17
|
-
|
18
|
-
expect(
|
19
|
-
|
20
|
-
|
21
|
-
it "creates the correct url" do
|
22
|
-
expect(subject.url).to eq "https://api.genderize.io/?name[0]=Sean&name[1]=Theresa"
|
15
|
+
it "creates the correct request" do
|
16
|
+
params = { "name[]" => names }
|
17
|
+
expect(subject).to receive(:request).with(:params => params).and_return(fake_response)
|
18
|
+
subject.guess!
|
23
19
|
end
|
24
20
|
|
25
21
|
it "cannot be initialized with an empty array" do
|
26
22
|
expect{described_class.new([])}.to raise_error ArgumentError
|
27
23
|
end
|
28
24
|
|
25
|
+
it "raises an error when over the rate limit" do
|
26
|
+
response = fake_response(:code => 429, :usage => { :limit => 1, :remaining => 0, :reset => 2 })
|
27
|
+
expect(subject).to receive(:request).and_return(response)
|
28
|
+
expect { subject.guess! }.to raise_error(RateLimitError) { |error|
|
29
|
+
expect(error.remaining).to eq 0
|
30
|
+
expect(error.limit).to eq 1
|
31
|
+
expect(error.reset).to eq 2
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "options" do
|
36
|
+
[:country_id, :language_id, :apikey].each do |option|
|
37
|
+
context "given the #{option} option" do
|
38
|
+
subject do
|
39
|
+
Guesser.new(names, option => option)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "is initialized correctly" do
|
43
|
+
expect(subject.options[option]).to eq option
|
44
|
+
end
|
45
|
+
|
46
|
+
it "creates the correct request" do
|
47
|
+
params = hash_including(:params => { "name[]" => names, option => option })
|
48
|
+
expect(subject).to receive(:request).with(params).and_return(fake_response)
|
49
|
+
subject.guess!
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "given the :connection option" do
|
55
|
+
subject do
|
56
|
+
options = { :apikey => "key", :connection => { :foo => "bar" } }
|
57
|
+
Guesser.new(names, options)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "is passed to the connection" do
|
61
|
+
params = hash_including(:connection => { :foo => "bar" })
|
62
|
+
expect(subject).to receive(:request).with(params).and_return(fake_response)
|
63
|
+
subject.guess!
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "#usage" do
|
69
|
+
let :usage do
|
70
|
+
{ :limit => nil, :remaining => nil, :reset => nil }
|
71
|
+
end
|
72
|
+
|
73
|
+
it "has no values until a request is made" do
|
74
|
+
expect(subject.usage).to eq usage
|
75
|
+
end
|
76
|
+
|
77
|
+
it "is populated after each request" do
|
78
|
+
usage.keys.each_with_index { |k, i| usage[k] = i }
|
79
|
+
expect(subject).to receive(:request).and_return(fake_response(:usage => usage))
|
80
|
+
subject.guess!
|
81
|
+
expect(subject.usage).to eq usage
|
82
|
+
|
83
|
+
usage.keys.each { |k| usage[k] += 1 }
|
84
|
+
expect(subject).to receive(:request).and_return(fake_response(:usage => usage))
|
85
|
+
subject.guess!
|
86
|
+
expect(subject.usage).to eq usage
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
29
90
|
describe "#guess!" do
|
30
91
|
it "returns a valid guesses hash" do
|
31
92
|
names = subject.guess!
|
@@ -39,8 +100,9 @@ module Gendered
|
|
39
100
|
let :names do
|
40
101
|
["Evat"]
|
41
102
|
end
|
103
|
+
|
42
104
|
it "does not error" do
|
43
|
-
expect{subject.guess!}.to_not raise_error
|
105
|
+
expect{ subject.guess! }.to_not raise_error
|
44
106
|
end
|
45
107
|
end
|
46
108
|
|
@@ -48,6 +110,7 @@ module Gendered
|
|
48
110
|
let :names do
|
49
111
|
["Sean","Sean"]
|
50
112
|
end
|
113
|
+
|
51
114
|
it "guesses them both" do
|
52
115
|
guesses = subject.guess!
|
53
116
|
expect(guesses.collect(&:gender).uniq.size).to eq 1
|
@@ -3,24 +3,28 @@ module Gendered
|
|
3
3
|
subject do
|
4
4
|
described_class.new values
|
5
5
|
end
|
6
|
+
|
6
7
|
let :values do
|
7
|
-
["Sean","Theresa"] * 50
|
8
|
+
["Sean", "Theresa"] * 50
|
8
9
|
end
|
10
|
+
|
9
11
|
describe "#guess!" do
|
10
12
|
it "guesses correctly" do
|
11
13
|
guesser = double(Guesser)
|
12
14
|
expect(guesser).to receive(:guess!)
|
13
|
-
expect(Guesser).to receive(:new).with(subject.names).and_return(guesser)
|
15
|
+
expect(Guesser).to receive(:new).with(subject.names, {}).and_return(guesser)
|
14
16
|
subject.guess!
|
15
17
|
end
|
16
18
|
|
17
|
-
it
|
19
|
+
it "guesses correctly with country_id" do
|
20
|
+
subject = described_class.new(values, :country_id => "us")
|
18
21
|
guesser = double(Guesser)
|
19
|
-
expect(guesser).to receive(:guess!)
|
20
|
-
expect(Guesser).to receive(:new).with(subject.names).and_return(guesser)
|
21
|
-
subject.guess!
|
22
|
+
expect(guesser).to receive(:guess!)
|
23
|
+
expect(Guesser).to receive(:new).with(subject.names, :country_id => "us").and_return(guesser)
|
24
|
+
subject.guess!
|
22
25
|
end
|
23
26
|
end
|
27
|
+
|
24
28
|
context "when the values are strings" do
|
25
29
|
it "sets the names" do
|
26
30
|
subject.names.each.with_index do |name, index|
|
@@ -28,10 +32,12 @@ module Gendered
|
|
28
32
|
end
|
29
33
|
end
|
30
34
|
end
|
35
|
+
|
31
36
|
context "when the values are names" do
|
32
37
|
let :values do
|
33
|
-
[Name.new("Sean"),Name.new("Theresa")]
|
38
|
+
[Name.new("Sean"), Name.new("Theresa")]
|
34
39
|
end
|
40
|
+
|
35
41
|
it "sets the names" do
|
36
42
|
expect(subject.names).to eq values
|
37
43
|
end
|
@@ -20,14 +20,29 @@ module Gendered
|
|
20
20
|
end
|
21
21
|
|
22
22
|
describe "guess!" do
|
23
|
+
it "sets the gender" do
|
24
|
+
subject.guess!
|
25
|
+
expect(subject.gender).to eq :male
|
26
|
+
end
|
27
|
+
|
28
|
+
it "sets the probability" do
|
29
|
+
subject.guess!
|
30
|
+
expect(subject.probability).to be_a BigDecimal
|
31
|
+
end
|
32
|
+
|
33
|
+
it "sets the sample size" do
|
34
|
+
subject.guess!
|
35
|
+
expect(subject.sample_size).to be_a Integer
|
36
|
+
end
|
37
|
+
|
23
38
|
it "returns the gender" do
|
24
39
|
expect(subject.guess!).to eq :male
|
25
40
|
end
|
26
41
|
|
27
|
-
it
|
28
|
-
name = Gendered::Name.new(
|
42
|
+
it "returns gender by country id" do
|
43
|
+
name = Gendered::Name.new("kim")
|
29
44
|
expect(name.guess!).to eq :female
|
30
|
-
expect(name.guess!(
|
45
|
+
expect(name.guess!(:country_id => "dk")).to eq :male
|
31
46
|
end
|
32
47
|
end
|
33
48
|
|
@@ -38,6 +53,7 @@ module Gendered
|
|
38
53
|
expect(subject.gender).to eq value
|
39
54
|
end
|
40
55
|
end
|
56
|
+
|
41
57
|
it "raises an argument error if the gender is set to something invalid" do
|
42
58
|
%w(eunich).each do |value|
|
43
59
|
expect{subject.gender = value}.to raise_error(ArgumentError)
|
data/spec/spec_helper.rb
CHANGED
@@ -1,5 +1,39 @@
|
|
1
1
|
require "gendered"
|
2
2
|
|
3
3
|
RSpec.configure do |config|
|
4
|
+
config.after :each do
|
5
|
+
Gendered.config.members.each do |name|
|
6
|
+
Gendered.config[name] = nil
|
7
|
+
end
|
8
|
+
end
|
4
9
|
|
10
|
+
config.include Module.new {
|
11
|
+
SUCCESS_RESPONSE = {
|
12
|
+
:count => 100,
|
13
|
+
:gender => "male",
|
14
|
+
:name => "Sean",
|
15
|
+
:probability => 1.0
|
16
|
+
}
|
17
|
+
|
18
|
+
FAILURE_RESPONSE = {
|
19
|
+
:error => "Some thang went wrong"
|
20
|
+
}
|
21
|
+
|
22
|
+
def fake_response(options = {})
|
23
|
+
code = options[:code] || 200
|
24
|
+
body = (code == 200 ? SUCCESS_RESPONSE : FAILURE_RESPONSE).merge(options[:body] || {})
|
25
|
+
|
26
|
+
headers = {}
|
27
|
+
usage = options[:usage] || {}
|
28
|
+
|
29
|
+
Gendered::Guesser::USAGE_HEADERS.each do |header, key|
|
30
|
+
headers[header] = usage.include?(key) ? usage[key] : header.object_id
|
31
|
+
end
|
32
|
+
|
33
|
+
response = double(:code => code, :body => JSON.dump(body))
|
34
|
+
allow(response).to receive(:[]) { |name| headers[name] }
|
35
|
+
|
36
|
+
response
|
37
|
+
end
|
38
|
+
}
|
5
39
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gendered
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sean Devine
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-11-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: http
|
@@ -91,6 +91,7 @@ files:
|
|
91
91
|
- lib/gendered/name.rb
|
92
92
|
- lib/gendered/name_list.rb
|
93
93
|
- lib/gendered/version.rb
|
94
|
+
- spec/lib/config_spec.rb
|
94
95
|
- spec/lib/gendered/guesser_spec.rb
|
95
96
|
- spec/lib/gendered/name_list_spec.rb
|
96
97
|
- spec/lib/gendered/name_spec.rb
|
@@ -115,13 +116,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
115
116
|
version: '0'
|
116
117
|
requirements: []
|
117
118
|
rubyforge_project:
|
118
|
-
rubygems_version: 2.4.5
|
119
|
+
rubygems_version: 2.4.5.1
|
119
120
|
signing_key:
|
120
121
|
specification_version: 4
|
121
122
|
summary: Guess the gender of a name.
|
122
123
|
test_files:
|
124
|
+
- spec/lib/config_spec.rb
|
123
125
|
- spec/lib/gendered/guesser_spec.rb
|
124
126
|
- spec/lib/gendered/name_list_spec.rb
|
125
127
|
- spec/lib/gendered/name_spec.rb
|
126
128
|
- spec/spec_helper.rb
|
127
|
-
has_rdoc:
|