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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 45dd165cc82b4c0d37492943c3e0b404df8a8001
4
- data.tar.gz: 2dfab03e1dcf771e14e25ed8addc0db487ceb746
3
+ metadata.gz: 1af845c196c001b0efd2690158669dba52ada027
4
+ data.tar.gz: 4e014c3a1de5f4a10249682f652cb892a3d20271
5
5
  SHA512:
6
- metadata.gz: ad9be58b9a724ec1d1bdbbc02b6b1ea0d97b433b9fc09bfc8d47afa5fc014cfaea1c473a83065dafe2ce3606e26fd85239d65a4e66f6cedd64d61aaf029abd75
7
- data.tar.gz: afe8c2d93528556df06443068e38b537c2641bfe411fe1250facea51547523294a0415670119daccbd66429ac34a171f17f4384118f285efce1ce351457446f5
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
+ ```
@@ -1,4 +1,4 @@
1
- require 'bigdecimal'
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
@@ -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
- attr_accessor :names, :country_id
12
+ attr_reader :usage
13
+ attr_accessor :names, :options
5
14
 
6
- def initialize(values, country_id = nil)
7
- raise ArgumentError, "cannot be empty" if Array(values).empty?
15
+ def initialize(names, options = {})
16
+ @names = Array(names)
17
+ raise ArgumentError, "names cannot be empty" if @names.empty?
8
18
 
9
- @names = Array(values)
10
- @country_id = country_id
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 = HTTP.get(url)
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
- guesses = JSON.parse(response.body)
19
-
20
- names.collect do |name|
21
- name = Name.new(name) if name.is_a?(String)
38
+ private
22
39
 
23
- guess = case
24
- when guesses.is_a?(Array)
25
- guesses.find { |g| g["name"] == name.value }
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
- if guess["gender"]
31
- name.gender = guess["gender"].to_sym
32
- name.probability = guess["probability"]
33
- name.sample_size = guess["count"]
34
- end
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
- name
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 url
42
- url = "https://api.genderize.io/?"
43
- url += "country_id=#{country_id}&" if country_id
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
- name_queries = names.collect.with_index do |name, index|
46
- "name[#{index}]=#{CGI.escape(name.to_s)}"
47
- end
48
- url + name_queries.join("&")
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
@@ -1,6 +1,6 @@
1
1
  module Gendered
2
2
  class Name
3
- VALID_GENDERS = %i(male female)
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!(country_id = nil)
20
- Guesser.new(self, country_id).guess!
19
+ def guess!(options = {})
20
+ Guesser.new(self, options).guess!
21
21
  gender
22
22
  end
23
23
 
@@ -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!(country_id = nil)
18
+ def guess!
17
19
  names.each_slice(100).each do |slice|
18
- Guesser.new(slice).guess!(country_id)
20
+ Guesser.new(slice, @options).guess!
19
21
  end
20
22
  names.collect(&:gender)
21
23
  end
@@ -1,3 +1,3 @@
1
1
  module Gendered
2
- VERSION = "0.0.7"
2
+ VERSION = "0.0.8"
3
3
  end
@@ -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 "is initialized with country id" do
17
- guesser = Guesser.new(names, 'us')
18
- expect(guesser.country_id).to eq 'us'
19
- end
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 'guesses correctly with country id' do
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!).with('us')
20
- expect(Guesser).to receive(:new).with(subject.names).and_return(guesser)
21
- subject.guess!('us')
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 'returns gender by country id' do
28
- name = Gendered::Name.new('kim')
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!('dk')).to eq :male
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)
@@ -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.7
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-02-23 00:00:00.000000000 Z
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: