oldbill 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.
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