gendered 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
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: