oldbill 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --format Fuubar
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ # Will automatically pull in this gem and all its
6
+ # dependencies specified in the gemspec
7
+ gem "oldbill", :path => File.expand_path("..", __FILE__)
8
+ gem "yard"
9
+
10
+ # These are development dependencies
11
+ group :test do
12
+ gem "rake"
13
+ gem "rspec", "2.5.0"
14
+ gem 'vcr', '~> 1.5', :require => false
15
+ gem 'webmock', '~> 1.6', :require => false
16
+ gem "autotest"
17
+ end
18
+
data/README.rdoc ADDED
@@ -0,0 +1,82 @@
1
+ = OldBill
2
+
3
+ OldBill gives you a nice ruby wrapper for the {police.uk API}[http://police.uk]
4
+
5
+ == Idiomatic Ruby
6
+ == Concrete classes and methods modelling Police data
7
+
8
+ == Quickstart
9
+
10
+ @session = OldBill.session.create(:username => "username", :password => "password")
11
+
12
+ @session.locate(52.6397278, -1.1322921).crimes_by_month
13
+
14
+ === Defaults:
15
+
16
+ caching # => true (boolean)
17
+ expires_in # => 60*60*24 (seconds)
18
+ cache # => Moneta::Memory.new "(Moneta Supported Cache) http://github.com/wycats/moneta
19
+ server # => "api-stage.carboncalculated.com" (string)
20
+ api_version #=> "v1" (string)
21
+ logging # => true (boolean)
22
+
23
+ == This is overriding the defaults
24
+
25
+ @session = OldBill::Session.create(
26
+ :username => "username",
27
+ :password => "password"
28
+ :cache => Moneta::S3.new(:access_key_id => ENV['AWS_ACCESS_KEY_ID'], :secret_access_key => ENV['AWS_SECRET_ACCESS_KEY'], :bucket => 'carbon')
29
+ :expires_in => 60*60*24*14
30
+ )
31
+
32
+ This has created a session with S3 Moneta cache that expires in 14 days
33
+
34
+ == Resources
35
+
36
+ <b>Police API</b> for code: http://policeapi2.rkh.co.uk/api/docs/
37
+
38
+ <b>Gemcutter</b> for the gem: http://gemcutter.org/gems/oldbill
39
+
40
+ == Using OldBill
41
+
42
+ The API is separated into 2 area neighbourhoods and crime
43
+
44
+ === Forces
45
+
46
+ @session = OldBill.session.create(:username => "username", :password => "password")
47
+ @session.forces
48
+
49
+ === Crimes
50
+
51
+ @session = OldBill.session.create(:username => "username", :password => "password")
52
+
53
+ @session.crimes_by_month("leicestershire", "C01")
54
+
55
+ @session.street_level_crimes(52.6397278, -1.1322921)
56
+
57
+ @session.crime_categories
58
+
59
+ === Neighbourhoods
60
+
61
+ @session = OldBill.session.create(:username => "username", :password => "password")
62
+
63
+ @session.neighbourhoods("leicestershire")
64
+
65
+ @session.neighbourhood("leicestershire", "C01")
66
+
67
+ You can then call methods on the neighbourhood
68
+
69
+ @session.neighbourhood("leicestershire", "C01").street_level_crimes
70
+ @session.neighbourhood("leicestershire", "C01").crimes_by_month
71
+ @session.neighbourhood("leicestershire", "C01").events
72
+ @session.neighbourhood("leicestershire", "C01").police_officers
73
+
74
+ == Locate A Neighbourhood
75
+
76
+ @session = OldBill.session.create(:username => "username", :password => "password")
77
+ @session.locate(52.6397278, -1.1322921)
78
+
79
+ @session.locate(52.6397278, -1.1322921).events
80
+ @session.locate(52.6397278, -1.1322921).crimes_by_month
81
+ @session.locate(52.6397278, -1.1322921).police_officers
82
+ @session.locate(52.6397278, -1.1322921).full_neighbourhood.street_level_crimes
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1 @@
1
+ Autotest.add_discovery { ["rspec2"] }
@@ -0,0 +1,46 @@
1
+ module OldBill
2
+ module Api
3
+ module Crime
4
+
5
+ # @return [Array<Hashie::Dash>] #id #name
6
+ def forces
7
+ api_call(:get, "/forces") do |response|
8
+ response.map{|category| Hashie::Mash.new(merge_session!(category))}
9
+ end
10
+ end
11
+
12
+ # @return [OldBill::Force>] #id #name
13
+ def force(force, params = {})
14
+ api_call(:get, "/forces/#{force}", params) do |response|
15
+ OldBill::V2::Force.new(merge_session!(response))
16
+ end
17
+ end
18
+
19
+ # @param [String] force
20
+ # @param [String] neighbourhood
21
+ # @return [Array<OldBill::CrimeByMonth>]
22
+ def crimes_by_month(force, neighbourhood, params = {})
23
+ api_call(:get, "/#{force}/#{neighbourhood}/crime", params) do |response|
24
+ (response.empty? ? [] : response["crimes"].map{|crime_by_month| OldBill::V2::CrimeByMonth.new(merge_session!({:month => crime_by_month[0]}.merge(crime_by_month[1])))})
25
+ end
26
+ end
27
+
28
+ # @param [Float] Latitude of the requested crime area
29
+ # @param [Float] Longitude of the requested crime area
30
+ # @return [Array<OldBill::Crimes::StreetLevel>]
31
+ def street_level_crimes(lat, lng, params = {})
32
+ api_call(:get, "/crimes-street/all-crime", params.merge!({:lat => lat, :lng => lng})) do |response|
33
+ response.map{|street_crime| OldBill::V2::Crimes::StreetLevel.new(merge_session!(street_crime))}
34
+ end
35
+ end
36
+
37
+ # @return [Array<Hashie::Dash>] #url #name
38
+ def crime_categories
39
+ api_call(:get, "/crime-categories") do |response|
40
+ response.map{|category| Hashie::Mash.new(merge_session!(category))}
41
+ end
42
+ end
43
+
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,56 @@
1
+ module OldBill
2
+ module Api
3
+ module Neighbourhood
4
+
5
+
6
+ # @param [String] force
7
+ # @return [Array<OldBill::Neighbourhood>] only partially loaded
8
+ def neighbourhoods(force, params = {})
9
+ api_call(:get, "/#{force}/neighbourhoods", params) do |response|
10
+ response.map{|neighbourhood| OldBill::V2::Neighbourhood.new(merge_session!(neighbourhood.merge({:force => force, :fully_loaded => false}))) }
11
+ end
12
+ end
13
+
14
+ # @param [String] force
15
+ # @param [String] neighbourhood
16
+ # @return [Array<OldBill::Neighbourhood>]
17
+ def neighbourhood(force, neighbourhood, params = {})
18
+ api_call(:get, "/#{force}/#{neighbourhood}", params) do |response|
19
+ OldBill::V2::Neighbourhood.new(merge_session!(response.merge({:force => force, :fully_loaded => true})))
20
+ end
21
+ end
22
+
23
+ # @param [String] force
24
+ # @param [String] neighbourhood
25
+ # @return [Array<OldBill::V2::Neighbourhoods::Event>]
26
+ def events(force, neighbourhood, params = {})
27
+ api_call(:get, "/#{force}/#{neighbourhood}/events", params) do |response|
28
+ response.map{|event| OldBill::V2::Neighbourhoods::Event.new(merge_session!(event))}
29
+ end
30
+ end
31
+
32
+
33
+ # @param [String] force
34
+ # @param [String] neighbourhood
35
+ # @return [Array<OldBill::V2::Neighbourhoods::PoliceOfficer>]
36
+ def police_officers(force, neighbourhood, params = {})
37
+ api_call(:get, "/#{force}/#{neighbourhood}/people", params) do |response|
38
+ response.map{|event| OldBill::V2::Neighbourhoods::PoliceOfficer.new(merge_session!(event))}
39
+ end
40
+ end
41
+
42
+
43
+ # @param [Float] lat
44
+ # @param [Float] lng
45
+ # @return [OldBill::V2::Neighbourhoods::Locator]
46
+ def locate(lat, lng, params = {})
47
+ api_call(:get, "/locate-neighbourhood", params.merge!({:q => "#{lat},#{lng}"})) do |response|
48
+ OldBill::V2::Neighbourhoods::Locator.new(merge_session!(response))
49
+ end
50
+ rescue NotFoundError
51
+ nil
52
+ end
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,24 @@
1
+ module OldBill
2
+
3
+ # Raised when OldBill server cannot be found
4
+ class ServerNotFound < StandardError; end
5
+
6
+ # Raised when OldBill returns the HTTP status code 400
7
+ class BadRequestError < StandardError; end
8
+
9
+ # Raised when OldBill returns the HTTP status code 401
10
+ class UnauthorizedRequestError < StandardError; end
11
+
12
+ # Raised when OldBill returns the HTTP status code 403
13
+ class ForbiddenError < StandardError; end
14
+
15
+ # Raised when OldBill returns the HTTP status code 404
16
+ class NotFoundError < StandardError; end
17
+
18
+ # Raised when OldBill returns the HTTP status code 405
19
+ class MethodNotAllowedError < StandardError; end
20
+
21
+ # Raised when OldBill returns the HTTP status code 500
22
+ class InternalServerError < StandardError; end
23
+
24
+ end
@@ -0,0 +1,43 @@
1
+ module OldBill
2
+ class Service
3
+ include HTTParty
4
+ format :json
5
+ headers = {'Accept' => 'application/json'}
6
+
7
+ # api not used is in server
8
+ def initialize(server, api_version, username, password, logging = false)
9
+ self.class.base_uri server
10
+ self.class.send(:debug_output) if logging
11
+ self.class.basic_auth username, password
12
+ end
13
+
14
+ def get(path, params = {})
15
+ perform_request(:get, path, params)
16
+ end
17
+
18
+ private
19
+ def perform_request(type, path, params)
20
+ response = self.class.send(type, path, :query => params)
21
+ check_response(response)
22
+ response
23
+ rescue Errno::ECONNREFUSED, SocketError
24
+ raise OldBill::ServerNotFound, "Police API Server could not be found"
25
+ end
26
+
27
+ # checking the status code of the response; if we are not authenticated
28
+ # then authenticate the session
29
+ # @raise [OldBill:BadRequestError] if the status code is 400
30
+ # @raise [OldBill:UnauthorizedRequestError] if a we get a 401
31
+ def check_response(response)
32
+ case response.code
33
+ when 400 then raise OldBill::BadRequestError.new "Not enough parameters to produce a valid response."
34
+ when 401 then raise OldBill::UnauthorizedRequestError.new "The username password could not be recognised and the request is not authorized."
35
+ when 403 then raise OldBill::ForbiddenError.new "The requested method is not available you do not have access"
36
+ when 404 then raise OldBill::NotFoundError.new "A method was requested that is not available in the API version specified"
37
+ when 405 then raise OldBill::MethodNotAllowedError.new "The HTTP request that was made requested an API method that can not process the HTTP method used."
38
+ when 500 then raise OldBill::InternalServerError.new "Internal Server Error"
39
+ end
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,85 @@
1
+ require 'active_support/core_ext/object/to_query'
2
+ module OldBill
3
+ class Session
4
+
5
+ # accessors
6
+ attr_accessor :cache, :caching, :logging, :server, :expires_in, :api_version
7
+
8
+ include OldBill::Api::Crime
9
+ include OldBill::Api::Neighbourhood
10
+
11
+ # creating a session one must supply the api_key as this is always required basically
12
+ # @param [Hash] options
13
+ # @return [OldBill::Session]
14
+ def self.create(options = {})
15
+ username = options.delete(:username)
16
+ password = options.delete(:password)
17
+ raise ArgumentError.new("You need a username password") unless username && password
18
+ new(username, password, options)
19
+ end
20
+
21
+ # @param [String] api_key
22
+ # @param [Hash] options
23
+ # default options
24
+ # caching => true
25
+ # expires_in => 60*60*24
26
+ # cache => Moneta::Memory.new
27
+ # server => "policeapi2.rkh.co.uk/api/" server has api version hmm should be param!
28
+ # api_version => 2
29
+ # logging => true
30
+ def initialize(username, password, options)
31
+ @username = username
32
+ @password = password
33
+ if @caching = options[:caching].nil? ? true : options.delete(:caching)
34
+ @expires_in = options.delete(:expires_in) || 60*60*24
35
+ @cache = options.delete(:cache) || Moneta::Memory.new
36
+ end
37
+ @server = options.delete(:server) || "policeapi2.rkh.co.uk/api"
38
+ @api_version = options.delete(:api_version) || 2
39
+ @logging = options[:logging].nil? ? false : options.delete(:logging)
40
+ end
41
+
42
+ def api_call(method, path, params ={}, &proc)
43
+ if cache = caching? && (@cache[cache_key(path, params)])
44
+ return cache
45
+ else
46
+ result = service.send(method, path, params)
47
+ result = yield result if block_given?
48
+ store_call(result, path, params || {}) if caching?
49
+ result
50
+ end
51
+ end
52
+
53
+ def service
54
+ @service ||= OldBill::Service.new(@server, @api_version, @username, @password, @logging)
55
+ end
56
+
57
+ def caching?
58
+ @caching
59
+ end
60
+
61
+ protected
62
+ # @param [String] path
63
+ # @param [Hash] params
64
+ # @return [String] the cache key from the path and the params
65
+ def cache_key(path, params = {})
66
+ parts = []
67
+ parts << path
68
+ parts << params.to_query
69
+ parts.join("/")
70
+ end
71
+
72
+ # @param [Object] result
73
+ # @param [String] path
74
+ # @param [Hash] params
75
+ def store_call(result, path, params)
76
+ key = cache_key(path, params ||{})
77
+ @cache.store(key, result, :expires_in => @expires_in)
78
+ end
79
+
80
+ def merge_session!(hash)
81
+ hash.merge({:session => self})
82
+ end
83
+
84
+ end
85
+ end
@@ -0,0 +1,30 @@
1
+ module OldBill
2
+ module V2
3
+ class CrimeByMonth < Hashie::Trash
4
+
5
+ def self.property(property_name, options = {})
6
+ if options[:from] && options[:from] =~ /-/
7
+ translations << options[:from].to_sym
8
+ define_method "#{options[:from]}=" do |val|
9
+ self[:"#{property_name}"] = val
10
+ end
11
+ options.delete(:from)
12
+ super(property_name, options)
13
+ else
14
+ super
15
+ end
16
+ end
17
+
18
+ property :month
19
+ property :robbery
20
+ property :burglary
21
+ property :anti_social_behaviour, :from => "anti-social-behaviour"
22
+ property :other_crime, :from => "other-crime"
23
+ property :all_crime, :from => "all-crime"
24
+ property :vehicle_crime, :from => "vehicle-crime"
25
+ property :violent_crime, :from => "violent-crime"
26
+ property :session
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,34 @@
1
+ module OldBill
2
+ module V2
3
+ module Crimes
4
+ class Location < Hashie::Dash
5
+
6
+
7
+ property :name #Name if available
8
+ property :longitude #Location longitude
9
+ property :latitude #Location latitude
10
+ property :street #Location latitude
11
+
12
+ # id Unique identifier for the street
13
+ # name Name of the location.
14
+ # This is only an approximation of where the crime happened
15
+ def street_parsed(value)
16
+ unless value.nil?
17
+ Hashie::Mash.new(value)
18
+ end
19
+ end
20
+
21
+
22
+ def []=(property, value)
23
+ case property
24
+ when "street"
25
+ super(property.to_s, street_parsed(value))
26
+ else
27
+ super
28
+ end
29
+ end
30
+
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,32 @@
1
+ module OldBill
2
+ module V2
3
+ module Crimes
4
+ class StreetLevel < Hashie::Dash
5
+
6
+ property :category #Category of the crime
7
+ property :id #ID of the crime.
8
+ property :context #Extra information about the crime (if applicable)
9
+ property :month #Month of the crime
10
+ property :location #Month of the crime
11
+ property :session #Month of the crime
12
+
13
+ def location_parsed(value)
14
+ unless value.nil?
15
+ Location.new(value)
16
+ end
17
+ end
18
+
19
+ # == yak!!!!! refactor me please
20
+ def []=(property, value)
21
+ case property
22
+ when "location"
23
+ super(property.to_s, location_parsed(value))
24
+ else
25
+ super
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,37 @@
1
+ module OldBill
2
+ module V2
3
+ class Force < Hashie::Dash
4
+
5
+ property :id
6
+ property :name
7
+ property :description
8
+ property :url #Force website URL
9
+ property :title
10
+ property :telephone #Force telephone number
11
+ property :id # Unique force identifier
12
+ property :session
13
+ property :engagement_methods
14
+
15
+
16
+ # url Method website URL
17
+ # description Method description
18
+ # title Method title
19
+ def engagement_methods_parsed(value)
20
+ unless value.nil?
21
+ value.map{|engagement_method| Hashie::Mash.new(engagement_method)}
22
+ end
23
+ end
24
+
25
+
26
+ def []=(property, value)
27
+ case property
28
+ when "engagement_methods"
29
+ super(property.to_s, engagement_methods_parsed(value))
30
+ else
31
+ super
32
+ end
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,98 @@
1
+ module OldBill
2
+ module V2
3
+ class Neighbourhood < Hashie::Dash
4
+
5
+ property :id #Police force specific team identifier.
6
+ property :url_force #URL for the neighbourhood on the Force's website
7
+ property :name #Name of the neighbourhood
8
+ property :url #URL
9
+ property :population #Population of the neighbourhood
10
+ property :description #Description
11
+ property :fully_loaded, :default => false
12
+ property :force
13
+ property :links
14
+ property :contact_details
15
+ property :centre
16
+ property :locations
17
+ property :session
18
+
19
+ # url
20
+ # description
21
+ # title
22
+ def links_parsed(value)
23
+ unless value.nil?
24
+ value.map{|link| Hashie::Mash.new(link)}
25
+ end
26
+ end
27
+
28
+ # email
29
+ # telephone
30
+ def contact_details_parsed(value)
31
+ unless value.nil?
32
+ Hashie::Mash.new(value)
33
+ end
34
+ end
35
+
36
+ # @note This may not be exactly in the centre of the neighbourhood
37
+ # latitude
38
+ # longitude
39
+ def centre_parsed(value)
40
+ unless value.nil?
41
+ Hashie::Mash.new(value)
42
+ end
43
+ end
44
+
45
+ def locations_parsed(value)
46
+ unless value.nil?
47
+ value.map{|location| OldBill::V2::Neighbourhoods::Location.new(location)}
48
+ end
49
+ end
50
+
51
+
52
+ # == yak!!!!! refactor me please
53
+ def []=(property, value)
54
+ case property
55
+ when "locations"
56
+ super(property.to_s, locations_parsed(value))
57
+ when "centre"
58
+ super(property.to_s, centre_parsed(value))
59
+ when "contact_details"
60
+ super(property.to_s, contact_details_parsed(value))
61
+ when "links"
62
+ super(property.to_s, links_parsed(value))
63
+ else
64
+ super
65
+ end
66
+ end
67
+
68
+ # @return [Array<OldBill::Crimes::StreetLevel>]
69
+ def street_level_crimes
70
+ fully_loaded!
71
+ @street_level_crimes ||= session.street_level_crimes(self.centre.latitude, self.centre.longitude)
72
+ end
73
+
74
+ def crimes_by_month
75
+ @crimes_by_month ||= session.crimes_by_month(self.force, self.neighbourhood)
76
+ end
77
+
78
+ def events
79
+ @events ||= session.events(force,id)
80
+ end
81
+
82
+ def police_officers
83
+ @police_officers ||= session.police_officers(self.force, self.neighbourhood)
84
+ end
85
+
86
+ def fully_loaded?
87
+ fully_loaded
88
+ end
89
+
90
+ # if not fully_loaded update its attributes
91
+ def fully_loaded!
92
+ return if fully_loaded
93
+ self.send(:initialize, session.neighbourhood(force, id).to_hash) # hmm calling private method I am MAD
94
+ end
95
+
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,43 @@
1
+ module OldBill
2
+ module V2
3
+ module Neighbourhoods
4
+ class Event < Hashie::Dash
5
+
6
+ property :description #Description of the event
7
+ property :title #Title of the event
8
+ property :address #Address of the event
9
+ property :type #Type of event
10
+ property :contact_details
11
+ property :start_date
12
+ property :session
13
+
14
+ # email
15
+ # telephone
16
+ def contact_details_parsed(value)
17
+ unless value.nil?
18
+ Hashie::Mash.new(value)
19
+ end
20
+ end
21
+
22
+ def start_date_parsed(value)
23
+ unless value.nil?
24
+ Date.parse(value)
25
+ end
26
+ end
27
+
28
+ # == yak!!!!! refactor me please
29
+ def []=(property, value)
30
+ case property
31
+ when "contact_details"
32
+ super(property.to_s, contact_details_parsed(value))
33
+ when "start_date"
34
+ super(property.to_s, start_date_parsed(value))
35
+ else
36
+ super
37
+ end
38
+ end
39
+
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,19 @@
1
+ module OldBill
2
+ module V2
3
+ module Neighbourhoods
4
+ class Location < Hashie::Dash
5
+
6
+ property :name #Name if available
7
+ property :longitude #Location longitude
8
+ property :latitude #Location longitude
9
+ property :postcode #Postcode
10
+ property :address #Location address
11
+ property :telephone #Location latitude
12
+ property :type #Type of location, e.g. "station" (police station)
13
+ property :description #Description
14
+ property :session
15
+
16
+ end
17
+ end
18
+ end
19
+ end