geo_loc 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 85f8da0f505236bceaffc0b362d4343c3027795b
4
+ data.tar.gz: be4a7ab27768cc9889882a34953a10b5ef385576
5
+ SHA512:
6
+ metadata.gz: b247a7760ffc4f16c4160c29726f7a5578b685c2be34b48e94e536a0ffd6d36f904c657d6c58dab0b90b0406ee131f7b1695d263abefcd7db742ab287ae0cd4c
7
+ data.tar.gz: 7a6fc5fba322c615039c618bf91d35927f7cd7eea58bb9432a4e134587b67c85ce350716bf332177b57e56a8b1011195bb4314cf1b809eb2245061d5ff236387
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --warnings
3
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in geo_loc.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,14 @@
1
+ Copyright (c) 2014 Justin Wiley
2
+
3
+ This program is free software: you can redistribute it and/or modify
4
+ it under the terms of the GNU General Public License as published by
5
+ the Free Software Foundation, either version 3 of the License, or
6
+ (at your option) any later version.
7
+
8
+ This program is distributed in the hope that it will be useful,
9
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ GNU General Public License for more details.
12
+
13
+ You should have received a copy of the GNU General Public License
14
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
data/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # GeoLoc
2
+
3
+ A quick-and-dirty wrapper for the GeoIP gem that handles downloading and unzipping geodata.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'geo_loc'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install geo_loc
20
+
21
+ ## Usage
22
+
23
+ Caveats:
24
+
25
+ - this gem is the minimum viable product for my purposes, your mileage may vary, feel free to fork and contribute if you run into issues
26
+ - it's designed for city level lookup resolution using Maxminds free city IP data, other lookup methods are not currently supported
27
+ - it assumes data files are .gzipped
28
+
29
+ The [GeoIP gem](https://github.com/cjheath/geoip) does a great job of accepting an IP address and digging through a MaxMind geolocation file to find a corresponding address.
30
+
31
+ One thing it doesn't do, however, is pull and unzip the data file for you. This gem attempts to add this functionality, by automatically downloading the file if necessary and storing it in a sensible directory (or directory of your choice).
32
+
33
+ ### Typical usage
34
+
35
+ require 'geo_loc'
36
+
37
+ if gdata = GeoLoc.new.ip('10.0.0.1')
38
+ self.country = gdata[:country_code2]
39
+ self.state = gdata[:region_name]
40
+ self.city = gdata[:city_name]
41
+ self.zip = gdata[:postal_code]
42
+ self.lat = gdata[:latitude]
43
+ self.long = gdata[:longitude]
44
+ else
45
+ # ...sadness
46
+ end
47
+
48
+ This will download the latest release of Maxmind city data (see GeoIP gem for locations) if it doesn't already exist, and gzip it.
49
+
50
+ The #ip method rescues StandardError, so connectivity issues, file format issues will be logged and nil returned.
51
+
52
+ The hash returned by #ip comes directly from geoip, see geoip for details.
53
+
54
+ ### Frequency of update
55
+
56
+ City data is **not** automatically refreshed. If you're deploying your application once a week or more frequently, and the data file is stored in a location that is overwritten after every deploy, this isn't an issue since the next time GeoLoc.new.ip executes it will pull the file. If it is an issue, you can manually force the sync data via:
57
+
58
+ GeoLoc.new.sync_data!
59
+
60
+ Since syncing will pull down and decompress an 11mb+ file, users may experience a delay the first time GeoLoc.new.ip executes. It's probably a good idea to do this as part of your deploy process.
61
+
62
+ Note that #sync_data! does not rescue exceptions.
63
+
64
+ ### Overriding default locations
65
+
66
+ Without any initialization options, GeoLoc will try to pick default directory locations for you. If Rails is defined, it will use Rails.root + '/tmp'. If Rails isn't defined, it will use '/tmp'. An example of customized options:
67
+
68
+ GeoLoc.new(filename: 'my-file',
69
+ geodata_dir: 'my-dir',
70
+ geodata_file: 'geodata',
71
+ compressed_geodata_file: 'geodata.gz',
72
+ geodata_url: 'http://thedata.gz',
73
+ logger: MyLogger.new('/tmp/mylogger.log'))
74
+
75
+ In general, the options will work if only one is passed, for example if you want to customize the data where geodata is stored.
76
+
77
+ ### Rspec warnings
78
+
79
+ warning: instance variable @data_exists not initialized
80
+
81
+ Is being triggered on running unit specs, if you know the cause and resolution to this issue, please let me know.
82
+
83
+ ## Contributing
84
+
85
+ 1. Fork it ( https://github.com/[my-github-username]/geo_loc/fork )
86
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
87
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
88
+ 4. Push to the branch (`git push origin my-new-feature`)
89
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/geo_loc.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'geo_loc/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "geo_loc"
8
+ spec.version = GeoLoc::VERSION
9
+ spec.authors = ["Justin Wiley"]
10
+ spec.email = ["justin.wiley@gmail.com"]
11
+ spec.summary = %q{A quick-and-dirty wrapper for the GeoIP gem that handles downloading and unzipping geodata}
12
+ spec.description = %q{A quick-and-dirty wrapper for the GeoIP gem that handles downloading and unzipping geodata}
13
+ spec.homepage = ""
14
+ spec.license = "GPL V3"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "geoip", "~> 1.4.0"
22
+
23
+ spec.add_development_dependency "pry"
24
+ spec.add_development_dependency "bundler", "~> 1.6"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "rspec", "~> 3.0"
27
+ end
data/lib/geo_loc.rb ADDED
@@ -0,0 +1,68 @@
1
+ require 'geo_loc/version'
2
+ require 'geoip'
3
+ require 'open-uri'
4
+ require 'zlib'
5
+ require 'logger'
6
+
7
+ # Geolocates given IP using maxminds geo ip database
8
+ # Pulls database down if it doesnt exists
9
+ # Note that in test environment (outside of mocked unit tests), will pull the ~11mb file, leading to lengthened initial test run
10
+
11
+ class GeoLoc
12
+ # FILENAME = 'GeoLiteCity.dat'
13
+ # GEODATA_FILE = File.join(Rails.root, '/tmp/', FILENAME)
14
+ # COMPRESSED_GEODATA_FILE = File.join(Rails.root, FILENAME + '.gz')
15
+ # GEODATA_URL = 'http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz'
16
+
17
+ attr_accessor :filename, :geodata_file, :geodata_dir, :compressed_geodata_file, :geodata_url, :logger
18
+
19
+ def initialize(filename: nil, geodata_dir: nil, geodata_file: nil, compressed_geodata_file: nil, geodata_url: nil, logger: nil)
20
+ self.filename = filename || 'GeoLiteCity.dat'
21
+ self.geodata_dir = geodata_dir || root_dir
22
+ self.geodata_file = geodata_file || File.join(self.geodata_dir, self.filename)
23
+ self.compressed_geodata_file = compressed_geodata_file || self.geodata_file + '.gz'
24
+ self.geodata_url = geodata_url || 'http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz'
25
+ self.logger = logger || logger_method
26
+ end
27
+
28
+ def logger_method; defined?(Rails) && Rails.respond_to?(:logger) ? Rails.logger : Logger.new('/tmp/geoloc.log'); end
29
+ def root_dir; defined?(Rails) && Rails.respond_to?(:root) ? File.join(Rails.root, '/tmp') : '/tmp'; end
30
+
31
+ def download_compressed_geodata
32
+ logger.info "Downloading geodata #{geodata_url}"
33
+ open(compressed_geodata_file, 'wb') {|f| f << open(geodata_url).read }
34
+ end
35
+
36
+ def decompress_geodata
37
+ logger.info "Decompressing #{compressed_geodata_file}"
38
+ File.open(compressed_geodata_file) do |cf|
39
+ begin
40
+ gz = Zlib::GzipReader.new(cf)
41
+ File.open(geodata_file, 'w'){|f| f << gz.read}
42
+ ensure
43
+ gz.close
44
+ end
45
+ end
46
+ end
47
+
48
+ def sync_data!
49
+ download_compressed_geodata
50
+ decompress_geodata
51
+ end
52
+
53
+ def ip ip_address
54
+ begin
55
+ if @data_exists || File.exist?(geodata_file)
56
+ @data_exists = true
57
+ else
58
+ sync_data!
59
+ end
60
+
61
+ @geoip ||= GeoIP.new(geodata_file)
62
+ @geoip.city(ip_address).try(:to_hash)
63
+ rescue StandardError => e
64
+ logger.error "Failed to geo ip address #{ip_address}\n#{e}\n#{e.backtrace.join("\n")}"
65
+ nil
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,3 @@
1
+ class GeoLoc
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,100 @@
1
+ # require 'pry'
2
+ require_relative '../../lib/geo_loc.rb'
3
+ require_relative '../spec_helper'
4
+
5
+ describe GeoLoc do
6
+ let(:gl) { GeoLoc.new }
7
+ let(:gddir) { gl.geodata_dir }
8
+ let(:gdfile) { gl.geodata_file }
9
+ let(:gdzfile) { gl.compressed_geodata_file }
10
+ let(:gdurl) { gl.geodata_url }
11
+ let(:double_io) { double('IO', read: 'data')}
12
+
13
+ before do
14
+ GeoLoc.send(:remove_const, :Rails) if defined? GeoLoc::Rails
15
+ end
16
+
17
+ describe 'intialize' do
18
+ it 'provides default locations for storing geodata, url of where to download data from' do
19
+ gdfile.should be == "/tmp/GeoLiteCity.dat"
20
+ gddir.should be == '/tmp'
21
+ gdzfile.should be == "/tmp/GeoLiteCity.dat.gz"
22
+ gdurl.should be == "http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz"
23
+ end
24
+
25
+ it 'allows these values to be overridden as options' do
26
+ gl = GeoLoc.new geodata_dir: '/mydir'
27
+ gl.geodata_dir.should be == '/mydir'
28
+ end
29
+
30
+ it 'uses Rails envrionment root dir if Rails is defined' do
31
+ class GeoLoc::Rails
32
+ def self.root; 'rails-root'; end
33
+ def self.logger; Logger.new('/dev/null'); end
34
+ end
35
+ gddir.should be == 'rails-root/tmp'
36
+ end
37
+ end
38
+
39
+ describe 'refreshing geo ip data' do
40
+ before do
41
+ [gdzfile, gdfile].map{|f| FileUtils.rm(f) if File.exist?(f) }
42
+ end
43
+
44
+ it '#download_compressed_geodata pulls down compressed geoip data' do
45
+ gl.should_receive(:open).with(gdurl).and_return(double_io)
46
+ gl.should_receive(:open).with(gdzfile, 'wb').and_yield([])
47
+
48
+ gl.download_compressed_geodata
49
+ end
50
+
51
+ it '#decompress_geodata decompresses geodata using zlib' do
52
+ contents = 'test data'
53
+ Zlib::GzipWriter.open(gdzfile) do |gz|
54
+ gz.write contents
55
+ end
56
+
57
+ File.exist?(gdfile).should be_falsey
58
+ gl.decompress_geodata
59
+ res = File.read(gdfile)
60
+ res.should be == contents
61
+ end
62
+
63
+ it '#sync_data! does both, downloads and decompresses' do
64
+ gl.should_receive(:download_compressed_geodata)
65
+ gl.should_receive(:decompress_geodata)
66
+ gl.sync_data!
67
+ end
68
+ end
69
+
70
+ describe '#ip' do
71
+ let(:double_geoip) { double(GeoIP, city: 'city data') }
72
+ let(:ip) { '127.0.0.1' }
73
+
74
+ it 'returns the geo data for a given ip address' do
75
+ gl.stub(:sync_data!)
76
+ double_geoip.should_receive(:city).with(ip)
77
+ GeoIP.should_receive(:new).with(gdfile).and_return(double_geoip)
78
+ gl.ip(ip)
79
+ end
80
+
81
+ it 'should sync the data file if it doesnt exist' do
82
+ File.should_receive(:exist?).and_return(false)
83
+ gl.should_receive(:sync_data!)
84
+ GeoIP.should_receive(:new).with(gdfile).and_return(double_geoip)
85
+ gl.ip(ip)
86
+ end
87
+
88
+ it 'should not sync if it does' do
89
+ File.should_receive(:exist?).and_return(true)
90
+ gl.should_not_receive(:sync_data!)
91
+ GeoIP.should_receive(:new).with(gdfile).and_return(double_geoip)
92
+ gl.ip(ip)
93
+ end
94
+
95
+ it 'rescues exceptions, returning nil' do
96
+ File.should_receive(:exist?).and_raise(StandardError)
97
+ gl.ip(ip).should be_nil
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,84 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # The generated `.rspec` file contains `--require spec_helper` which will cause this
4
+ # file to always be loaded, without a need to explicitly require it in any files.
5
+ #
6
+ # Given that it is always loaded, you are encouraged to keep this file as
7
+ # light-weight as possible. Requiring heavyweight dependencies from this file
8
+ # will add to the boot time of your test suite on EVERY test run, even for an
9
+ # individual file that may not need all of that loaded. Instead, make a
10
+ # separate helper file that requires this one and then use it only in the specs
11
+ # that actually need it.
12
+ #
13
+ # The `.rspec` file also contains a few flags that are not defaults but that
14
+ # users commonly want.
15
+ #
16
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
17
+ RSpec.configure do |config|
18
+ # The settings below are suggested to provide a good initial experience
19
+ # with RSpec, but feel free to customize to your heart's content.
20
+ config.expect_with :rspec do |c|
21
+ c.syntax = :should
22
+ end
23
+ config.mock_with :rspec do |c|
24
+ c.syntax = :should
25
+ end
26
+ =begin
27
+ # These two settings work together to allow you to limit a spec run
28
+ # to individual examples or groups you care about by tagging them with
29
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
30
+ # get run.
31
+ config.filter_run :focus
32
+ config.run_all_when_everything_filtered = true
33
+
34
+ # Many RSpec users commonly either run the entire suite or an individual
35
+ # file, and it's useful to allow more verbose output when running an
36
+ # individual spec file.
37
+ if config.files_to_run.one?
38
+ # Use the documentation formatter for detailed output,
39
+ # unless a formatter has already been configured
40
+ # (e.g. via a command-line flag).
41
+ config.default_formatter = 'doc'
42
+ end
43
+
44
+ # Print the 10 slowest examples and example groups at the
45
+ # end of the spec run, to help surface which specs are running
46
+ # particularly slow.
47
+ config.profile_examples = 10
48
+
49
+ # Run specs in random order to surface order dependencies. If you find an
50
+ # order dependency and want to debug it, you can fix the order by providing
51
+ # the seed, which is printed after each run.
52
+ # --seed 1234
53
+ config.order = :random
54
+
55
+ # Seed global randomization in this process using the `--seed` CLI option.
56
+ # Setting this allows you to use `--seed` to deterministically reproduce
57
+ # test failures related to randomization by passing the same `--seed` value
58
+ # as the one that triggered the failure.
59
+ Kernel.srand config.seed
60
+
61
+ # rspec-expectations config goes here. You can use an alternate
62
+ # assertion/expectation library such as wrong or the stdlib/minitest
63
+ # assertions if you prefer.
64
+ config.expect_with :rspec do |expectations|
65
+ # Enable only the newer, non-monkey-patching expect syntax.
66
+ # For more details, see:
67
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
68
+ expectations.syntax = :expect
69
+ end
70
+
71
+ # rspec-mocks config goes here. You can use an alternate test double
72
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
73
+ config.mock_with :rspec do |mocks|
74
+ # Enable only the newer, non-monkey-patching expect syntax.
75
+ # For more details, see:
76
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
77
+ mocks.syntax = :expect
78
+
79
+ # Prevents you from mocking or stubbing a method that does not exist on
80
+ # a real object. This is generally recommended.
81
+ mocks.verify_partial_doubles = true
82
+ end
83
+ =end
84
+ end
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: geo_loc
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Justin Wiley
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: geoip
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.4.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.4.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.6'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.6'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ description: A quick-and-dirty wrapper for the GeoIP gem that handles downloading
84
+ and unzipping geodata
85
+ email:
86
+ - justin.wiley@gmail.com
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".gitignore"
92
+ - ".rspec"
93
+ - Gemfile
94
+ - LICENSE.txt
95
+ - README.md
96
+ - Rakefile
97
+ - geo_loc.gemspec
98
+ - lib/geo_loc.rb
99
+ - lib/geo_loc/version.rb
100
+ - spec/lib/geo_loc_spec.rb
101
+ - spec/spec_helper.rb
102
+ homepage: ''
103
+ licenses:
104
+ - GPL V3
105
+ metadata: {}
106
+ post_install_message:
107
+ rdoc_options: []
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ requirements: []
121
+ rubyforge_project:
122
+ rubygems_version: 2.2.2
123
+ signing_key:
124
+ specification_version: 4
125
+ summary: A quick-and-dirty wrapper for the GeoIP gem that handles downloading and
126
+ unzipping geodata
127
+ test_files:
128
+ - spec/lib/geo_loc_spec.rb
129
+ - spec/spec_helper.rb