enigma_io 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.
Files changed (43) hide show
  1. data/.gitignore +17 -0
  2. data/.rubocop.yml +14 -0
  3. data/.travis.yml +14 -0
  4. data/Gemfile +11 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +64 -0
  7. data/Rakefile +28 -0
  8. data/enigma_io.gemspec +30 -0
  9. data/examples/data.md +94 -0
  10. data/examples/export.md +35 -0
  11. data/lib/enigma/client.rb +26 -0
  12. data/lib/enigma/download.rb +81 -0
  13. data/lib/enigma/endpoint.rb +108 -0
  14. data/lib/enigma/endpoints/data.rb +5 -0
  15. data/lib/enigma/endpoints/export.rb +22 -0
  16. data/lib/enigma/endpoints/meta.rb +5 -0
  17. data/lib/enigma/endpoints/stats.rb +5 -0
  18. data/lib/enigma/response.rb +17 -0
  19. data/lib/enigma/version.rb +6 -0
  20. data/lib/enigma.rb +49 -0
  21. data/test/client_test.rb +32 -0
  22. data/test/data_test.rb +81 -0
  23. data/test/endpoint_test.rb +55 -0
  24. data/test/export_test.rb +67 -0
  25. data/test/fixtures/download.csv +4 -0
  26. data/test/fixtures/download.zip +0 -0
  27. data/test/fixtures/vcr_cassettes/compound_average.yml +121 -0
  28. data/test/fixtures/vcr_cassettes/data_with_error.yml +38 -0
  29. data/test/fixtures/vcr_cassettes/export.yml +37 -0
  30. data/test/fixtures/vcr_cassettes/filtered_data.yml +6727 -0
  31. data/test/fixtures/vcr_cassettes/limit_data.yml +54 -0
  32. data/test/fixtures/vcr_cassettes/page_data.yml +53 -0
  33. data/test/fixtures/vcr_cassettes/selected_data.yml +541 -0
  34. data/test/fixtures/vcr_cassettes/simple_data.yml +6762 -0
  35. data/test/fixtures/vcr_cassettes/simple_metadata.yml +75 -0
  36. data/test/fixtures/vcr_cassettes/simple_stats.yml +48 -0
  37. data/test/fixtures/vcr_cassettes/sorted_data.yml +6690 -0
  38. data/test/fixtures/vcr_cassettes/sorted_data_descending.yml +6690 -0
  39. data/test/meta_test.rb +26 -0
  40. data/test/response_test.rb +13 -0
  41. data/test/stats_test.rb +27 -0
  42. data/test/test_helper.rb +37 -0
  43. metadata +223 -0
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rubocop.yml ADDED
@@ -0,0 +1,14 @@
1
+ # This configuration was generated by `rubocop --auto-gen-config`.
2
+ # The point is for the user to remove these configuration records
3
+ # one by one as the offences are removed from the code base.
4
+
5
+ MethodLength:
6
+ Max: 20
7
+
8
+ TrivialAccessors:
9
+ Enabled: false
10
+
11
+ AllCops:
12
+ Excludes:
13
+ - test/**
14
+ - examples/**
data/.travis.yml ADDED
@@ -0,0 +1,14 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.3"
4
+ - "2.0.0"
5
+ - "2.1.0"
6
+ - jruby-19mode # JRuby in 1.9 mode
7
+
8
+ # uncomment this line if your project needs to run something other than `rake`:
9
+ # script: bundle exec rspec spec
10
+ before_install: gem update --remote bundler
11
+
12
+ script:
13
+ - bundle exec rake test
14
+ - bundle exec rubocop
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
4
+ group :test do
5
+ gem 'rake', '>= 0.8.7'
6
+ gem 'simplecov'
7
+ gem 'webmock'
8
+ gem 'excon', '>= 0.27.4'
9
+ gem 'vcr'
10
+ gem 'mocha'
11
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Stephen Pike
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,64 @@
1
+ # Ruby client for the enigma api
2
+
3
+ Ruby client for the enigma api located at https://app.enigma.io/api. Supports ruby >= 1.9.3
4
+
5
+ Note that you need api key to use their api.
6
+
7
+ [![Build Status](https://travis-ci.org/scpike/enigma.png?branch=master)](https://travis-ci.org/scpike/enigma)
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'enigma'
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install enigma
22
+
23
+ ## Usage
24
+
25
+ Basic usage is straightforward. There are also more detailed [data api examples](examples/data.md) and [export api examples](examples/export.md) available.
26
+
27
+ # Defaults to looking for key in ENV['ENIGMA_KEY']
28
+
29
+ client = Enigma::Client.new(key: :secret_key)
30
+
31
+ client.meta('us.gov.whitehouse.visitor-list')
32
+
33
+ res = client.data('us.gov.whitehouse.visitor-list')
34
+
35
+ # get some data
36
+
37
+ res.result.map { |r| ... }
38
+
39
+ client.stats('us.gov.whitehouse.visitor-list', select: 'type_of_access')
40
+
41
+ client.export('us.gov.whitehouse.visitor-list').parse.each do |row|
42
+ puts row.inspect
43
+ end
44
+
45
+ ## Contributing
46
+
47
+ 1. Fork it
48
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
49
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
50
+ 4. Push to the branch (`git push origin my-new-feature`)
51
+ 5. Create new Pull Request
52
+
53
+ ### Notes
54
+
55
+ You'll need to have rubocop installed.
56
+
57
+ gem install rubocop
58
+
59
+ Tests are run with `rake`. Check the test coverage (printed when you
60
+ run rake) as well as the output of rubocop (also run with rake).
61
+
62
+ ## License
63
+
64
+ MIT license
data/Rakefile ADDED
@@ -0,0 +1,28 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ task :gendoc do
5
+ system "yardoc"
6
+ system "yard stats --list-undoc"
7
+ end
8
+
9
+ Rake::TestTask.new do |t|
10
+ t.libs << 'test'
11
+ t.test_files = FileList['test/**_test.rb']
12
+ t.verbose = false
13
+ t.warning = true
14
+ end
15
+
16
+ task :rubocop do |t|
17
+ sh 'rubocop'
18
+ end
19
+
20
+ task :build => :gendoc do
21
+ system "gem build enigma_io.gemspec"
22
+ end
23
+
24
+ task :release => :build do
25
+ system "gem push enigma_io-#{Enigma::VERSION}.gem"
26
+ end
27
+
28
+ task default: [:test, :rubocop]
data/enigma_io.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'enigma/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'enigma_io'
8
+ spec.version = Enigma::VERSION
9
+ spec.authors = ['Stephen Pike']
10
+ spec.email = ['steve@scpike.net']
11
+ spec.description = 'Ruby client for the Enigma API'
12
+ spec.summary = 'Ruby client for the Enigma API'
13
+ spec.homepage = 'https://github.com/scpike/enigma'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
17
+ spec.test_files = Dir.glob("{test/**/*}")
18
+ spec.require_paths = ['lib']
19
+
20
+ spec.add_development_dependency 'bundler', '~> 1.3'
21
+ spec.add_development_dependency 'rake'
22
+ spec.add_development_dependency 'rubocop'
23
+ spec.add_development_dependency 'yard'
24
+
25
+ spec.add_runtime_dependency 'typhoeus'
26
+ spec.add_runtime_dependency 'hashie'
27
+ spec.add_runtime_dependency 'rubyzip', '>= 1.0.0'
28
+
29
+ spec.required_ruby_version = '>= 1.9.3'
30
+ end
data/examples/data.md ADDED
@@ -0,0 +1,94 @@
1
+ # Accessing the Enigma metadata and data apis
2
+
3
+ #### Create a client, key should be set in the environment variable 'ENIGMA_KEY'
4
+
5
+ client = Enigma::Client.new
6
+
7
+ #### Let's look at internet use in the United States
8
+
9
+ res = client.meta('us.gov.census.internet.usage11')
10
+
11
+ #### The result is a Hashie::Mash object, so you can access it like a hash or like an object
12
+
13
+ puts res.keys
14
+
15
+ => ["datapath", "success", "info", "result", "raw"]
16
+
17
+ `raw` is the raw repsonse from the Enigma api. Everything else is
18
+ the contents of the api response
19
+
20
+ #### Verify this is a table (something we can get data about):
21
+
22
+ res.info.result_type
23
+
24
+ => "table"
25
+
26
+ #### Let's see the columns
27
+
28
+ res.result.columns.each { |c| puts "#{c.id}: #{c.description}" }
29
+
30
+ > region: State Region
31
+ >
32
+ > total_over3: Total (thousands)
33
+ >
34
+ > number_somewhere: Number of individuals accessing the Internet from some location (thousands). "Some location" means Internet access that occurs either inside or outside the householder's home.
35
+ >
36
+ > percent_somewhere: Percent of individuals accessing the Internet from some location. "Some location" means Internet access that occurs either inside or outside the householder's home.
37
+ >
38
+ > number_inhome: Individual lives in household with Internet (thousands). At least one member of the individual's household reported using the Internet from home.
39
+ >
40
+ > percent_inhome: Percent of individual living in household with Internet. At least one member of the individual's household reported using the Internet from home.
41
+ >
42
+ > serialid: Serialid
43
+
44
+ #### Now let's get the data
45
+
46
+ data = client.data('us.gov.census.internet.usage11')
47
+
48
+ data.result.each { |r| puts "#{r.region} #{r.percent_inhome}" }
49
+
50
+ > United States 76.50
51
+ >
52
+ > Alaska 80.00
53
+ >
54
+ > Alabama 69.50
55
+ >
56
+ > Arizona 76.00
57
+ >
58
+ > Arkansas 68.50
59
+ >
60
+ > ...
61
+
62
+ #### Let's find the state withthe highest percentage. We also only care about the region and percent_inhome columns
63
+
64
+ res = client.data('us.gov.census.internet.usage11',
65
+ sort: 'percent_inhome',
66
+ select: [ 'region', 'percent_inhome' ])
67
+
68
+ puts res.result.first.to_hash
69
+
70
+ => {"region" =>"New Hampshire", "percent_inhome" =>"87.10"}
71
+
72
+ ##### What about the lowest?
73
+
74
+ puts res.result.last.to_hash
75
+
76
+ => {"region" =>"Mississippi", "percent_inhome" =>"61.40"}
77
+
78
+ ##### Let's search for Pennsylvania
79
+
80
+ res = client.data('us.gov.census.internet.usage11',
81
+ select: [ 'region', 'percent_inhome' ],
82
+ search: { region: 'Pennsylvania })
83
+
84
+ #### Verify only one result found
85
+
86
+ > res.info.total_results
87
+
88
+ => 1
89
+
90
+ #### Check it out
91
+
92
+ > puts res.first.to_hash
93
+
94
+ => {"region" =>"Pennsylvania", "percent_inhome" =>"75.40"}
@@ -0,0 +1,35 @@
1
+ # Export API
2
+
3
+ #### Start the connection
4
+
5
+ client = Enigma::Client.new
6
+
7
+ dl = client.export('us.gov.census.internet.usage11')
8
+
9
+ #### This did *not* download anything. The api replied with a link to where the file will eventually be
10
+
11
+ puts dl.download_url
12
+
13
+ > Secret one time url
14
+
15
+ #### If you want the actual contents, you can ask the client to poll until the file is available
16
+
17
+ dl.get # puts the zipped contents in dl.raw_download
18
+
19
+ #### Write the zipped contents to a file
20
+
21
+ zipfile = File.open('out.zip', 'wb')
22
+
23
+ dl.write zipfile
24
+
25
+ #### Write the unzipped contents to a file
26
+
27
+ csvfile = File.open('out.csv', 'wb')
28
+
29
+ dl.write_csv csvfile
30
+
31
+ #### Read the contents of the csv file
32
+
33
+ dl.parse.each do |row|
34
+ puts row.inspect
35
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+
3
+ module Enigma
4
+ # Connects to the Enigma api at https://app.enigma.io/api.
5
+ #
6
+ class Client
7
+ # Creates a new client connection
8
+ #
9
+ # @option opts [String] key - Enigma API key.
10
+ #
11
+ # The api key Defaults to the ENIGMA_KEY environment variable
12
+ #
13
+ def initialize(opts = {})
14
+ Enigma.key = ENV['ENIGMA_KEY'] || opts[:key]
15
+ fail ArgumentError, 'API key is required' unless Enigma.key
16
+ end
17
+
18
+ # Each endpoint becomes a method like `client.meta`
19
+ #
20
+ Endpoint.descendants.each do |klass|
21
+ define_method klass.url_chunk do |*args|
22
+ klass.new(*args).request
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,81 @@
1
+ # coding: utf-8
2
+
3
+ module Enigma
4
+ # Handle polling the url returned by the Export api until we're able
5
+ # to download it. Will load the zipped contents of the file into
6
+ # memory, and can then either write it to disk (`write`), write the
7
+ # unzipped version to disk (`write_csv`) or parse the unzipped
8
+ # contents using the CSV library as an array of hashes
9
+ #
10
+ class Download
11
+ DELAY = 1 # How long to wait between polling attempts, in seconds
12
+ attr_accessor :response, :raw_download, :download_contents
13
+
14
+ def initialize(res)
15
+ self.response = res
16
+ end
17
+
18
+ def download_url
19
+ response.export_url
20
+ end
21
+
22
+ def datapath
23
+ response.datapath
24
+ end
25
+
26
+ def get
27
+ @raw_download ||= do_download
28
+ end
29
+
30
+ def write(io)
31
+ get
32
+ io.write raw_download
33
+ end
34
+
35
+ def write_tmp
36
+ tmp = Tempfile.new(datapath)
37
+ write(tmp)
38
+ tmp.rewind
39
+ tmp
40
+ end
41
+
42
+ def unzip
43
+ @download_contents ||=
44
+ begin
45
+ tmp = write_tmp
46
+ contents = nil
47
+ Zip::File.open(tmp.path) do |zipfile|
48
+ contents = zipfile.first.get_input_stream.read
49
+ end
50
+ contents
51
+ end
52
+ end
53
+
54
+ def write_csv(io)
55
+ unzip
56
+ io.write download_contents
57
+ end
58
+
59
+ def parse(opts = {})
60
+ opts = { headers: true, header_converters: :symbol }
61
+ CSV.parse(unzip, opts.merge(opts || {}))
62
+ end
63
+
64
+ def do_download
65
+ Enigma.logger.info "Trying to download #{download_url}"
66
+ success = false
67
+ until success
68
+ req = Typhoeus::Request.new(download_url)
69
+ req.on_complete do |response|
70
+ if response.response_code == 404
71
+ sleep DELAY
72
+ else
73
+ success = true
74
+ return response.body
75
+ end
76
+ end
77
+ req.run
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,108 @@
1
+ # coding: utf-8
2
+ module Enigma
3
+ # Generic Enigma endpoint. Knows how to construct a URL, request it,
4
+ # and wrap the response in an `Enigma::Response`
5
+ #
6
+ # Assumes the api endpoints for its descendants are a lowercase
7
+ # version of their class names
8
+ #
9
+ # Handles some nice conversion of select, search, and where clauses
10
+ # from ruby hashes to string parameters
11
+ #
12
+ class Endpoint
13
+ attr_accessor :params, :datapath, :url
14
+
15
+ def self.descendants
16
+ ObjectSpace.each_object(Class).select { |klass| klass < self }
17
+ end
18
+
19
+ def initialize(datapath, opts = {})
20
+ self.datapath = datapath
21
+ self.params = opts
22
+ end
23
+
24
+ def self.url_chunk
25
+ to_s.gsub(/.*::/, '').downcase
26
+ end
27
+
28
+ def params
29
+ @params[:where] = serialize_where(@params[:where]) if @params[:where]
30
+ @params[:search] = serialize_search(@params[:search]) if @params[:search]
31
+ @params[:select] = serialize_select(@params[:select]) if @params[:select]
32
+ @params
33
+ end
34
+
35
+ # Serialize a where clause. Allows you to pass in a hash and have
36
+ # it converted to an equality where
37
+ #
38
+ # > Filter results with a SQL-style "where" clause. Only applies to
39
+ # > numerical columns - use the "search" parameter for strings. Valid
40
+ # > operators are >, < and =. Only one "where" clause per request is
41
+ # > currently supported.
42
+ #
43
+ # @param [String|Hash] where clause to convert
44
+ # @return [String] parameter ready for the request
45
+ #
46
+ def serialize_where(where)
47
+ if where.is_a? Hash
48
+ column, value = where.first
49
+ "#{column}=#{value}"
50
+ else
51
+ where
52
+ end
53
+ end
54
+
55
+ # Serialize a search clause. Allows you to pass in a hash of
56
+ # one or more fieldName: value pairs
57
+ #
58
+ # @param [String|Hash] search clause to convert
59
+ # @return [String] parameter ready for the request
60
+ #
61
+ def serialize_search(search)
62
+ if search.is_a? Hash
63
+ search.map do |field, value|
64
+ value = [value].flatten.join('|')
65
+ "@#{field} (#{value})"
66
+ end.join ' '
67
+ else
68
+ search
69
+ end
70
+ end
71
+
72
+ # Serialize a search clause. Allows you to pass in an array of
73
+ # column names
74
+ #
75
+ # @param [String|Array] select clause to convert
76
+ # @return [String] parameter ready for the request
77
+ #
78
+ def serialize_select(select)
79
+ if select.is_a? Enumerable
80
+ select.join(',')
81
+ else
82
+ select
83
+ end
84
+ end
85
+
86
+ # Endpoints show up in urls as a lowercase version of their class
87
+ # names
88
+ def url_chunk
89
+ self.class.url_chunk
90
+ end
91
+
92
+ def path
93
+ [Enigma.api_version, url_chunk, Enigma.key, datapath].join('/')
94
+ end
95
+
96
+ def url
97
+ URI.join(Enigma.root_url, path).to_s
98
+ end
99
+
100
+ def request
101
+ Enigma.logger.info "Making request to #{url}"
102
+ req = Typhoeus::Request.new(url, method: :get, params: params).run
103
+ Response.parse(req)
104
+ end
105
+ end
106
+ end
107
+
108
+ Dir[File.dirname(__FILE__) + '/endpoints/*.rb'].each { |file| require(file) }
@@ -0,0 +1,5 @@
1
+ # coding: utf-8
2
+ module Enigma
3
+ class Data < Endpoint
4
+ end
5
+ end
@@ -0,0 +1,22 @@
1
+ # coding: utf-8
2
+ module Enigma
3
+ # The export endpoint has no filtering, but it returns a URL where
4
+ # the download will eventually be available. This client will
5
+ # default to waiting for the file to be available before continuing
6
+ #
7
+ class Export < Endpoint
8
+ attr_accessor :download, :unzip
9
+
10
+ # The API request responds with a URL to poll until it's
11
+ # ready. Create a new download object with that URL and return it
12
+ #
13
+ # @return [Download]
14
+ #
15
+ def request
16
+ req = Typhoeus::Request.new(url, method: :get, params: params).run
17
+ Enigma.logger.info req.body
18
+ res = Response.parse(req)
19
+ Download.new(res)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,5 @@
1
+ # coding: utf-8
2
+ module Enigma
3
+ class Meta < Endpoint
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ # coding: utf-8
2
+ module Enigma
3
+ class Stats < Endpoint
4
+ end
5
+ end
@@ -0,0 +1,17 @@
1
+ # coding: utf-8
2
+
3
+ module Enigma
4
+ # We'll wrap the response in a Hashie::Mash subclass so that you can
5
+ # access the attributes as function calls
6
+ #
7
+ # Raw response is kept in the `raw` attribute
8
+ #
9
+ class Response
10
+ def self.parse(res)
11
+ mash = Hashie::Mash.new(JSON.parse(res.body))
12
+ mash.raw = res
13
+ fail mash.message.to_s if mash.info && mash.info.error
14
+ mash
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,6 @@
1
+ # coding: utf-8
2
+
3
+ # Enigma version
4
+ module Enigma
5
+ VERSION = '0.0.1'
6
+ end
data/lib/enigma.rb ADDED
@@ -0,0 +1,49 @@
1
+ # coding: utf-8
2
+ require 'rubygems'
3
+
4
+ # Required gems
5
+ require 'hashie'
6
+ require 'typhoeus'
7
+
8
+ # Core dependencies
9
+ require 'net/https'
10
+ require 'uri'
11
+ require 'json'
12
+ require 'zip'
13
+ require 'csv'
14
+
15
+ # Library
16
+ require 'enigma/version'
17
+ require 'enigma/download'
18
+ require 'enigma/endpoint'
19
+ require 'enigma/response'
20
+ require 'enigma/client'
21
+
22
+ # Access to the engima API
23
+ module Enigma
24
+ attr_accessor :key, :root_url, :api_version
25
+
26
+ def self.root_url
27
+ 'https://api.enigma.io/'
28
+ end
29
+
30
+ def self.api_version
31
+ 'v2'
32
+ end
33
+
34
+ def self.key
35
+ @key
36
+ end
37
+ def self.key=(k)
38
+ @key = k
39
+ end
40
+
41
+ def self.logger
42
+ @logger ||=
43
+ begin
44
+ logger = Logger.new(STDOUT)
45
+ logger.level = Logger::INFO
46
+ logger
47
+ end
48
+ end
49
+ end