citibike 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.
@@ -0,0 +1,104 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'citibike/api'
4
+ require 'citibike/response'
5
+
6
+ module Citibike
7
+
8
+ # Client for interacting with the Citibike NYC
9
+ # unofficial API
10
+ class Client
11
+
12
+ attr_reader :options, :connection
13
+
14
+ VALID_KEYS = [:unwrapped]
15
+
16
+ def initialize(opts = {})
17
+ @options = {}
18
+ # Parse out the valid keys for this class
19
+ VALID_KEYS.each do |vk|
20
+ @options[vk] = opts[vk]
21
+ end
22
+
23
+ # Raw forces unwrapped to be true
24
+ if opts[:raw]
25
+ @options[:unwrapped] = true
26
+ end
27
+
28
+ @connection = Citibike::Connection.new(opts)
29
+ end
30
+
31
+ #
32
+ # Wrapper around a call to list all stations
33
+ #
34
+ # @return [Response] [A response object unless]
35
+ def stations
36
+ resp = self.connection.request(
37
+ :get,
38
+ Citibike::Station.path
39
+ )
40
+
41
+ return resp if @options[:unwrapped]
42
+
43
+ Citibike::Responses::Station.new(resp)
44
+ end
45
+
46
+ def helmets
47
+ resp = self.connection.request(
48
+ :get,
49
+ Citibike::Station.path
50
+ )
51
+
52
+ return resp if @options[:unwrapped]
53
+
54
+ Citibike::Responses::Helmet.new(resp)
55
+ end
56
+
57
+ def branches
58
+ resp = self.connection.request(
59
+ :get,
60
+ Citibike::Station.path
61
+ )
62
+
63
+ return resp if @options[:unwrapped]
64
+
65
+ Citibike::Responses::Branch.new(resp)
66
+ end
67
+
68
+ def self.stations
69
+ Citibike::Responses::Station.new(
70
+ # create a new connection in case
71
+ self.connection.request(
72
+ :get,
73
+ Citibike::Station.path
74
+ )
75
+ )
76
+ end
77
+
78
+ def self.helmets
79
+ Citibike::Responses::Helmet.new(
80
+ self.connection.request(
81
+ :get,
82
+ Citibike::Helmet.path
83
+ )
84
+ )
85
+ end
86
+
87
+ def self.branches
88
+ Citibike::Responses::Branch.new(
89
+ self.connection.request(
90
+ :get,
91
+ Citibike::Branch.path
92
+ )
93
+ )
94
+ end
95
+
96
+ private
97
+
98
+ def self.connection
99
+ @connection ||= Citibike::Connection.new
100
+ end
101
+
102
+ end
103
+
104
+ end
@@ -0,0 +1,85 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'faraday'
4
+ require 'faraday_middleware'
5
+ require 'yajl'
6
+
7
+ module Citibike
8
+ # Class representing a Faraday instance used to connect to the
9
+ # actual Citibike API, takes a variety of options
10
+ class Connection
11
+
12
+ def initialize(opts = {})
13
+ @options = {
14
+ adapter: Faraday.default_adapter,
15
+ headers: {
16
+ 'Accept' => 'application/json; charset=utf-8',
17
+ 'UserAgent' => 'Citibike Gem'
18
+ },
19
+ proxy: nil,
20
+ ssl: {
21
+ verify: false
22
+ },
23
+ debug: false,
24
+ test: false,
25
+ stubs: nil,
26
+ raw: false,
27
+ format_path: true,
28
+ format: :json,
29
+ url: 'http://appservices.citibikenyc.com/'
30
+ }
31
+ # Reject any keys that aren't already in @options
32
+ @options.keys.each do |k|
33
+ @options[k] = opts[k] if opts.key?(k)
34
+ end
35
+ end
36
+
37
+ def http
38
+ @http ||= Faraday.new(@options) do |connection|
39
+ connection.use Faraday::Request::UrlEncoded
40
+ connection.use Faraday::Response::ParseJson unless @options[:raw]
41
+ connection.use Faraday::Response::Logger if @options[:debug]
42
+ if @options[:test]
43
+ connection.adapter(@options[:adapter], @options[:stubs])
44
+ else
45
+ connection.adapter(@options[:adapter])
46
+ end
47
+ end
48
+ end
49
+
50
+ #
51
+ # Makes a request to the API through the set up interface
52
+ # @param method [Symbol] [:get, :post, :put, :delete]
53
+ # @param path [String] [The path to request from the server]
54
+ # @param options = {} [Optional Hash] [Args to include in the request]
55
+ #
56
+ # @return [String or Hash] [The result of the request, generally a hash]
57
+ def request(method, path, options = {})
58
+ if @options[:format_path]
59
+ path = format_path(path, @options[:format])
60
+ end
61
+ response = self.http.send(method) do |request|
62
+ case method
63
+ when :get, :delete
64
+ request.url(path, options)
65
+ when :post, :put
66
+ request.path = path
67
+ request.body = options unless options.empty?
68
+ end
69
+ end
70
+ response.body
71
+ end
72
+
73
+ private
74
+
75
+ def format_path(path, format)
76
+ if path =~ /\./
77
+ path
78
+ else
79
+ [path, format].map(&:to_s).compact.join('.')
80
+ end
81
+ end
82
+
83
+ end
84
+
85
+ end
@@ -0,0 +1,119 @@
1
+ # encoding: UTF-8
2
+
3
+ module Citibike
4
+
5
+ # Base class that defines the shared behavior for
6
+ # all the different response types
7
+ class Response
8
+
9
+ include Enumerable
10
+
11
+ Dir[File.expand_path('../responses/*.rb', __FILE__)].each { |f| require f }
12
+
13
+ # allow direct access to the data stored in this object
14
+ attr_reader :data, :success, :last_update
15
+
16
+ # undefine methods from this class so they get proxied to data
17
+ undef_method :to_s, :inspect
18
+
19
+ # Initializes a Response object
20
+ # @param data [Hash] [Response data from an api request]
21
+ #
22
+ # @return [Nil] [This return value is ignored]
23
+ def initialize(data)
24
+ @data = data['results']
25
+ @success = data['ok']
26
+ @last_update = Time.at(data['last_updated'].to_i)
27
+
28
+ # build the id_hash
29
+ @id_hash ||= {}
30
+ @data.each do |d|
31
+ @id_hash[d.id] = d
32
+ end
33
+ end
34
+
35
+ #
36
+ # Returns true if this holds a successful response
37
+ #
38
+ # @return [Boolean] [Whether the request succeeded]
39
+ def success?
40
+ @success == true
41
+ end
42
+
43
+ # each provided to include enumberable
44
+ # @param &block [Block] [For enumerable]
45
+ #
46
+ # @return [Enumerable] [Another enumerable]
47
+ def each(&block)
48
+ self.data.each(&block)
49
+ end
50
+
51
+ #
52
+ # Clones this object and populates
53
+ # @param data [Array] [Array of API objects]
54
+ #
55
+ # @return [Response] [A clone of self wrapping data]
56
+ def clone_with(data)
57
+ clone = self.clone
58
+ clone.instance_variable_set(:@data, data)
59
+ clone
60
+ end
61
+
62
+ #
63
+ # Finds the given object by id using a hash for a shortcut
64
+ # @param id [Integer] [The id to lookup]
65
+ #
66
+ # @return [Citibike::Api] [Some type of API object e.g Helmet or Station]
67
+ def find_by_id(id)
68
+ id = id.to_i
69
+
70
+ @id_hash[id]
71
+ end
72
+
73
+ def find_by_ids(*ids)
74
+ ids.map { |i| self.find_by_id(i) }.compact
75
+ end
76
+
77
+ #
78
+ # Returns every object within dist (miles)
79
+ # of lat/long
80
+ #
81
+ # @param lat [Float] [A latitude position]
82
+ # @param long [Float] [A longitude position]
83
+ # @param dist [Float] [A radius in miles]
84
+ #
85
+ # @return [Array] [Array of objects within dist of lat/long]
86
+ def all_within(lat, long, dist)
87
+ @data.select { |d| d.distance_from(lat, long) < dist }
88
+ end
89
+
90
+ #
91
+ # Returns every object within dist miles
92
+ # of obj
93
+ #
94
+ # @param obj [Api] [Api Object]
95
+ # @param dist [Float] [Distance to consider]
96
+ #
97
+ # @return [type] [description]
98
+ def all_near(obj, dist)
99
+ @data.select do |d|
100
+ if d.id == obj.id
101
+ false
102
+ else
103
+ d.distance_from(obj.latitude, obj.longitude) < dist
104
+ end
105
+ end
106
+ end
107
+
108
+ # Delegates any undefined methods to the underlying data
109
+ def method_missing(sym, *args, &block)
110
+ if self.data.respond_to?(sym)
111
+ return self.data.send(sym, *args, &block)
112
+ end
113
+
114
+ super
115
+ end
116
+
117
+ end
118
+
119
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: UTF-8
2
+
3
+ module Citibike
4
+ # Namespace for different response objects
5
+ # representing a list of api objects
6
+ module Responses
7
+ # Represents a list of Branch API objects
8
+ class Branch < Citibike::Response
9
+
10
+ #
11
+ # Transforms part of the input hash to the proper
12
+ # type of object. Expects the keys:
13
+ # {
14
+ # :data => [{...}, {...}],
15
+ # :ok => true/false,
16
+ # :last_updated => Time
17
+ # }
18
+ #
19
+ # @param data [Hash] [A hash from the Citibike API]
20
+ #
21
+ # @return [nil] [Not used]
22
+ def initialize(data = {})
23
+ data['results'].map! { |r| Citibike::Branch.new(r) }
24
+ super
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+
31
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: UTF-8
2
+
3
+ module Citibike
4
+
5
+ module Responses
6
+ # Represents a list of Helmet API objects
7
+ class Helmet < Citibike::Response
8
+
9
+ def initialize(data)
10
+ data['results'].map! { |r| Citibike::Helmet.new(r) }
11
+ super
12
+ end
13
+
14
+ end
15
+
16
+ end
17
+
18
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: UTF-8
2
+
3
+ module Citibike
4
+
5
+ module Responses
6
+ # Represents a list of Station api objects
7
+ class Station < Citibike::Response
8
+
9
+ def initialize(data)
10
+ data['results'].map! { |r| Citibike::Station.new(r) }
11
+ super
12
+ end
13
+
14
+ end
15
+
16
+ end
17
+
18
+ end
@@ -0,0 +1,5 @@
1
+ # encoding: UTF-8
2
+
3
+ module Citibike
4
+ VERSION = '0.0.1'
5
+ end
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+
3
+ describe Citibike::Station do
4
+
5
+ let(:stations) do
6
+ Citibike.stations
7
+ end
8
+
9
+ let(:stat) do
10
+ stations.first
11
+ end
12
+
13
+ around(:each) do |example|
14
+ VCR.use_cassette(:station, record: :new_episodes) do
15
+ example.run
16
+ end
17
+ end
18
+
19
+ it "should provide consistent accessor methods" do
20
+ stat.nearby_stations.should be_a(Array)
21
+ # this field is nil for some reason
22
+ stat.should respond_to(:station_address)
23
+ stat.available_docks.should be_a(Integer)
24
+ stat.available_bikes.should be_a(Integer)
25
+
26
+ stat.should be_active
27
+ end
28
+
29
+ it "should provide a helper for nearby station ids" do
30
+ stat.nearby_station_ids.should be_a(Array)
31
+ stat.nearby_station_ids.first.should be_a(Integer)
32
+ end
33
+
34
+ context ".distance_to_nearby" do
35
+ it "should provide a helper to get the distance to a nearby station" do
36
+ stat.distance_to_nearby(
37
+ stat.nearby_station_ids.first
38
+ ).should be_a(Float)
39
+ end
40
+
41
+ it "should raise an error if the provided id is not nearby" do
42
+ lambda{
43
+ stat.distance_to_nearby(stat.nearby_station_ids.sum)
44
+ }.should raise_error
45
+ end
46
+ end
47
+
48
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe Citibike do
4
+
5
+ it "should proxy methods to Citibike::Client" do
6
+ Citibike::Client.expects(:stations)
7
+ Citibike::Client.expects(:helmets)
8
+ Citibike::Client.expects(:branches)
9
+
10
+ Citibike.stations
11
+ Citibike.helmets
12
+ Citibike.branches
13
+ end
14
+
15
+ end