atdis 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 +17 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/Gemfile +15 -0
- data/Guardfile +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +37 -0
- data/Rakefile +8 -0
- data/atdis.gemspec +28 -0
- data/docs/ATDIS-1.0.4 Application Tracking Data Interchange Specification.docx +0 -0
- data/docs/ATDIS-1.0.4 Application Tracking Data Interchange Specification.pdf +0 -0
- data/lib/atdis/application.rb +47 -0
- data/lib/atdis/document.rb +12 -0
- data/lib/atdis/event.rb +12 -0
- data/lib/atdis/feed.rb +21 -0
- data/lib/atdis/location.rb +18 -0
- data/lib/atdis/model.rb +190 -0
- data/lib/atdis/page.rb +114 -0
- data/lib/atdis/person.rb +10 -0
- data/lib/atdis/separated_url.rb +37 -0
- data/lib/atdis/validators.rb +51 -0
- data/lib/atdis/version.rb +3 -0
- data/lib/atdis.rb +12 -0
- data/spec/atdis/application_spec.rb +406 -0
- data/spec/atdis/document_spec.rb +15 -0
- data/spec/atdis/event_spec.rb +25 -0
- data/spec/atdis/feed_spec.rb +40 -0
- data/spec/atdis/location_spec.rb +97 -0
- data/spec/atdis/model_spec.rb +39 -0
- data/spec/atdis/page_spec.rb +390 -0
- data/spec/atdis/person_spec.rb +15 -0
- data/spec/atdis/separated_url_spec.rb +40 -0
- data/spec/spec_helper.rb +18 -0
- metadata +191 -0
data/.gitignore
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-1.8.7-p370
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
group :development do
|
4
|
+
gem "rspec"
|
5
|
+
gem 'guard'
|
6
|
+
gem 'guard-rspec'
|
7
|
+
gem 'growl'
|
8
|
+
gem 'rb-fsevent', '~> 0.9'
|
9
|
+
# Probably required on OS X. See https://github.com/guard/guard/wiki/Add-Readline-support-to-Ruby-on-Mac-OS-X
|
10
|
+
gem 'rb-readline'
|
11
|
+
gem 'coveralls', :require => false
|
12
|
+
end
|
13
|
+
|
14
|
+
# Specify your gem's dependencies in atdis.gemspec
|
15
|
+
gemspec
|
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 OpenAustralia Foundation Limited
|
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,37 @@
|
|
1
|
+
# Atdis
|
2
|
+
|
3
|
+
[](https://travis-ci.org/openaustralia/atdis) [](https://coveralls.io/r/openaustralia/atdis?branch=master) [](https://codeclimate.com/github/openaustralia/atdis)
|
4
|
+
|
5
|
+
A ruby interface to the application tracking data interchange specification (ATDIS) API
|
6
|
+
|
7
|
+
We're developing this against version ATDIS 1.0.4.
|
8
|
+
|
9
|
+
This is **highly alpha** software that probably doesn't yet do what it says on the tin. It is very much a work in progress.
|
10
|
+
|
11
|
+
Source code is available on GitHub at https://github.com/openaustralia/atdis
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
Add this line to your application's Gemfile:
|
16
|
+
|
17
|
+
gem 'atdis'
|
18
|
+
|
19
|
+
And then execute:
|
20
|
+
|
21
|
+
$ bundle
|
22
|
+
|
23
|
+
Or install it yourself as:
|
24
|
+
|
25
|
+
$ gem install atdis
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
TODO: Write usage instructions here
|
30
|
+
|
31
|
+
## Contributing
|
32
|
+
|
33
|
+
1. Fork it
|
34
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
35
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
36
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
37
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/atdis.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'atdis/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "atdis"
|
8
|
+
spec.version = Atdis::VERSION
|
9
|
+
spec.authors = ["Matthew Landauer"]
|
10
|
+
spec.email = ["matthew@openaustraliafoundation.org.au"]
|
11
|
+
spec.description = %q{A ruby interface to the application tracking data interchange specification (ATDIS) API}
|
12
|
+
spec.summary = spec.description
|
13
|
+
spec.homepage = "http://github.com/openaustralia/atdis"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
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_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
|
24
|
+
spec.add_dependency "multi_json", "~> 1.7"
|
25
|
+
spec.add_dependency "rest-client"
|
26
|
+
spec.add_dependency "rgeo-geojson"
|
27
|
+
spec.add_dependency "activemodel", "~> 3"
|
28
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
|
3
|
+
module ATDIS
|
4
|
+
class Application < Model
|
5
|
+
field_mappings :application => {
|
6
|
+
:info => {
|
7
|
+
:dat_id => [:dat_id, String],
|
8
|
+
:last_modified_date => [:last_modified_date, DateTime],
|
9
|
+
:description => [:description, String],
|
10
|
+
:authority => [:authority, String],
|
11
|
+
:lodgement_date => [:lodgement_date, DateTime],
|
12
|
+
:determination_date => [:determination_date, DateTime],
|
13
|
+
:status => [:status, String],
|
14
|
+
:notification_start_date => [:notification_start_date, DateTime],
|
15
|
+
:notification_end_date => [:notification_end_date, DateTime],
|
16
|
+
:officer => [:officer, String],
|
17
|
+
:estimated_cost => [:estimated_cost, String]
|
18
|
+
},
|
19
|
+
:reference => {
|
20
|
+
:more_info_url => [:more_info_url, URI],
|
21
|
+
:comments_url => [:comments_url, URI]
|
22
|
+
},
|
23
|
+
:location => [:location, Location],
|
24
|
+
:events => [:events, Event],
|
25
|
+
:documents => [:documents, Document],
|
26
|
+
:people => [:people, Person],
|
27
|
+
:extended => [:extended, Object]
|
28
|
+
}
|
29
|
+
|
30
|
+
# Mandatory parameters
|
31
|
+
validates :dat_id, :last_modified_date, :description, :authority, :lodgement_date, :determination_date, :status,
|
32
|
+
:more_info_url, :location, :presence_before_type_cast => true
|
33
|
+
|
34
|
+
# Other validations
|
35
|
+
validates :notification_start_date, :notification_end_date, :last_modified_date, :lodgement_date, :determination_date,
|
36
|
+
:date_time => true
|
37
|
+
validates :more_info_url, :http_url => true
|
38
|
+
validates :location, :valid => true
|
39
|
+
|
40
|
+
# TODO Validate associated like locations, events, documents, people
|
41
|
+
# TODO Do we need to do extra checking to ensure that events, documents and people are arrays?
|
42
|
+
# TODO Separate validation for L2 and L3 compliance?
|
43
|
+
# TODO Validate date orders. i.e. determination_date >= lodgement_date and notification_end_date >= notification_start_date
|
44
|
+
# TODO also last_modified_date >= lodgement_date and all the other dates. In other words we can't put a future date in. That
|
45
|
+
# doesn't make sense in this context. Also should check dates in things like Events (to see that they're not in the future)
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module ATDIS
|
2
|
+
class Document < Model
|
3
|
+
field_mappings :ref => [:ref, String],
|
4
|
+
:title => [:title, String],
|
5
|
+
:document_url => [:document_url, URI]
|
6
|
+
|
7
|
+
# Mandatory parameters
|
8
|
+
validates :ref, :title, :document_url, :presence_before_type_cast => true
|
9
|
+
# Other validations
|
10
|
+
validates :document_url, :http_url => true
|
11
|
+
end
|
12
|
+
end
|
data/lib/atdis/event.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
module ATDIS
|
2
|
+
class Event < Model
|
3
|
+
field_mappings :id => [:id, String],
|
4
|
+
:date => [:date, DateTime],
|
5
|
+
:description => [:description, String],
|
6
|
+
:event_type => [:event_type, String],
|
7
|
+
:status => [:status, String]
|
8
|
+
|
9
|
+
# Mandatory parameters
|
10
|
+
validates :id, :date, :description, :presence_before_type_cast => true
|
11
|
+
end
|
12
|
+
end
|
data/lib/atdis/feed.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require "rest-client"
|
2
|
+
|
3
|
+
module ATDIS
|
4
|
+
class Feed
|
5
|
+
attr_reader :base_url
|
6
|
+
|
7
|
+
# base_url - the base url from which the urls for all atdis urls are made
|
8
|
+
# It is the concatenation of the protocol and web address as defined in section 4.2 of specification
|
9
|
+
# For example if the base_url is "http://www.council.nsw.gov.au" then the url for listing all the
|
10
|
+
# applications is "http://www.council.nsw.gov.au/atdis/1.0/applications.json"
|
11
|
+
def initialize(base_url)
|
12
|
+
@base_url = base_url.kind_of?(URI) ? base_url : URI.parse(base_url)
|
13
|
+
end
|
14
|
+
|
15
|
+
def applications(page = 1)
|
16
|
+
url = base_url + "atdis/1.0/applications.json"
|
17
|
+
url += "?page=#{page}" if page > 1
|
18
|
+
Page.read_url(url)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require "rgeo/geo_json"
|
2
|
+
|
3
|
+
module ATDIS
|
4
|
+
class Location < Model
|
5
|
+
field_mappings :address => [:address, String],
|
6
|
+
:land_title_ref => {
|
7
|
+
:lot => [:lot, String],
|
8
|
+
:section => [:section, String],
|
9
|
+
:dpsp_id => [:dpsp_id, String]
|
10
|
+
},
|
11
|
+
:geometry => [:geometry, RGeo::GeoJSON]
|
12
|
+
|
13
|
+
# Mandatory parameters
|
14
|
+
validates :address, :lot, :section, :dpsp_id, :presence_before_type_cast => true
|
15
|
+
|
16
|
+
validates :geometry, :geo_json => true
|
17
|
+
end
|
18
|
+
end
|
data/lib/atdis/model.rb
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
module ATDIS
|
5
|
+
module TypeCastAttributes
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
class_attribute :attribute_types
|
10
|
+
class_attribute :valid_fields
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def casting_attributes(p)
|
15
|
+
define_attribute_methods(p.keys.map{|k| k.to_s})
|
16
|
+
self.attribute_types = p
|
17
|
+
end
|
18
|
+
|
19
|
+
def field_mappings(p)
|
20
|
+
a, b = translate_field_mappings(p)
|
21
|
+
self.valid_fields = a
|
22
|
+
casting_attributes(b)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def translate_field_mappings(p)
|
28
|
+
f = {}
|
29
|
+
ca = {}
|
30
|
+
p.each do |k,v|
|
31
|
+
if v.kind_of?(Array)
|
32
|
+
f[k] = v[0]
|
33
|
+
ca[v.first] = v[1]
|
34
|
+
else
|
35
|
+
f2, ca2 = translate_field_mappings(v)
|
36
|
+
f[k] = f2
|
37
|
+
ca = ca.merge(ca2)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
[f, ca]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class Model
|
46
|
+
include ActiveModel::Validations
|
47
|
+
include Validators
|
48
|
+
include ActiveModel::AttributeMethods
|
49
|
+
include TypeCastAttributes
|
50
|
+
attribute_method_suffix '_before_type_cast'
|
51
|
+
attribute_method_suffix '='
|
52
|
+
|
53
|
+
attr_reader :attributes
|
54
|
+
# Stores any part of the json that could not be interpreted. Usually
|
55
|
+
# signals an error if it isn't empty.
|
56
|
+
attr_accessor :json_left_overs
|
57
|
+
|
58
|
+
validate :json_left_overs_is_empty
|
59
|
+
|
60
|
+
def json_left_overs_is_empty
|
61
|
+
if json_left_overs && !json_left_overs.empty?
|
62
|
+
# We have extra parameters that shouldn't be there
|
63
|
+
errors.add(:json, "Unexpected parameters in json data: #{MultiJson.dump(json_left_overs)}")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def initialize(params={})
|
68
|
+
@attributes, @attributes_before_type_cast = {}, {}
|
69
|
+
params.each do |attr, value|
|
70
|
+
self.send("#{attr}=", value)
|
71
|
+
end if params
|
72
|
+
end
|
73
|
+
|
74
|
+
# Does what the equivalent on Activerecord does
|
75
|
+
def self.attribute_names
|
76
|
+
attribute_types.keys.map{|k| k.to_s}
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.interpret(*params)
|
80
|
+
new(map_fields(valid_fields, *params))
|
81
|
+
end
|
82
|
+
|
83
|
+
# Map json structure to our values
|
84
|
+
def self.map_fields(valid_fields, data)
|
85
|
+
values = {:json_left_overs => {}}
|
86
|
+
data.each_key do |key|
|
87
|
+
if valid_fields[key]
|
88
|
+
if valid_fields[key].kind_of?(Hash)
|
89
|
+
v2 = map_fields(valid_fields[key], data[key])
|
90
|
+
l2 = v2.delete(:json_left_overs)
|
91
|
+
values = values.merge(v2)
|
92
|
+
values[:json_left_overs][key] = l2 unless l2.empty?
|
93
|
+
else
|
94
|
+
values[valid_fields[key]] = data[key]
|
95
|
+
end
|
96
|
+
else
|
97
|
+
values[:json_left_overs][key] = data[key]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
values
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.cast(value, type)
|
104
|
+
# If it's already the correct type then we don't need to do anything
|
105
|
+
if value.kind_of?(type)
|
106
|
+
value
|
107
|
+
# Special handling for arrays. When we typecast arrays we actually typecast each member of the array
|
108
|
+
elsif value.kind_of?(Array)
|
109
|
+
value.map {|v| cast(v, type)}
|
110
|
+
elsif type == DateTime
|
111
|
+
cast_datetime(value)
|
112
|
+
elsif type == URI
|
113
|
+
cast_uri(value)
|
114
|
+
elsif type == String
|
115
|
+
cast_string(value)
|
116
|
+
elsif type == Fixnum
|
117
|
+
cast_fixnum(value)
|
118
|
+
elsif type == RGeo::GeoJSON
|
119
|
+
cast_geojson(value)
|
120
|
+
# Otherwise try to use Type.interpret to do the typecasting
|
121
|
+
elsif type.respond_to?(:interpret)
|
122
|
+
type.interpret(value) if value
|
123
|
+
else
|
124
|
+
raise
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
def attribute(attr)
|
131
|
+
@attributes[attr]
|
132
|
+
end
|
133
|
+
|
134
|
+
def attribute_before_type_cast(attr)
|
135
|
+
@attributes_before_type_cast[attr]
|
136
|
+
end
|
137
|
+
|
138
|
+
def attribute=(attr, value)
|
139
|
+
@attributes_before_type_cast[attr] = value
|
140
|
+
@attributes[attr] = Model.cast(value, attribute_types[attr.to_sym])
|
141
|
+
end
|
142
|
+
|
143
|
+
def self.cast_datetime(value)
|
144
|
+
# This would be much easier if we knew we only had to support Ruby 1.9 or greater because it has
|
145
|
+
# an implementation built in. Because for the time being we need to support Ruby 1.8 as well
|
146
|
+
# we'll build an implementation of parsing by hand. Ugh.
|
147
|
+
# Referencing http://www.w3.org/TR/NOTE-datetime
|
148
|
+
# In section 4.3.1 of ATDIS 1.0.4 it shows two variants of iso 8601, either the full date
|
149
|
+
# or the full date with hours, seconds, minutes and timezone. We'll assume that these
|
150
|
+
# are the two variants that are allowed.
|
151
|
+
if value.respond_to?(:match) && value.match(/^\d\d\d\d-\d\d-\d\d(T\d\d:\d\d:\d\d(Z|(\+|-)\d\d:\d\d))?$/)
|
152
|
+
DateTime.parse(value)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def self.cast_uri(value)
|
157
|
+
begin
|
158
|
+
URI.parse(value)
|
159
|
+
rescue URI::InvalidURIError
|
160
|
+
nil
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def self.cast_string(value)
|
165
|
+
value.to_s
|
166
|
+
end
|
167
|
+
|
168
|
+
# This casting allows nil values
|
169
|
+
def self.cast_fixnum(value)
|
170
|
+
value.to_i if value
|
171
|
+
end
|
172
|
+
|
173
|
+
def self.cast_geojson(value)
|
174
|
+
RGeo::GeoJSON.decode(hash_symbols_to_string(value))
|
175
|
+
end
|
176
|
+
|
177
|
+
# Converts {:foo => {:bar => "yes"}} to {"foo" => {"bar" => "yes"}}
|
178
|
+
def self.hash_symbols_to_string(hash)
|
179
|
+
if hash.respond_to?(:each_pair)
|
180
|
+
result = {}
|
181
|
+
hash.each_pair do |key, value|
|
182
|
+
result[key.to_s] = hash_symbols_to_string(value)
|
183
|
+
end
|
184
|
+
result
|
185
|
+
else
|
186
|
+
hash
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
data/lib/atdis/page.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
module ATDIS
|
2
|
+
class Page < Model
|
3
|
+
attr_accessor :url
|
4
|
+
|
5
|
+
field_mappings :response => [:results, Application],
|
6
|
+
:count => [:count, Fixnum],
|
7
|
+
:pagination => {
|
8
|
+
:previous => [:previous_page_no, Fixnum],
|
9
|
+
:next => [:next_page_no, Fixnum],
|
10
|
+
:current => [:current_page_no, Fixnum],
|
11
|
+
:per_page => [:no_results_per_page, Fixnum],
|
12
|
+
:count => [:total_no_results, Fixnum],
|
13
|
+
:pages => [:total_no_pages, Fixnum]
|
14
|
+
}
|
15
|
+
|
16
|
+
# Mandatory parameters
|
17
|
+
validates :results, :presence_before_type_cast => true
|
18
|
+
validates :results, :valid => true
|
19
|
+
validate :count_is_consistent, :all_pagination_is_present, :previous_page_no_is_consistent, :next_page_no_is_consistent
|
20
|
+
validate :current_page_no_is_consistent, :total_no_results_is_consistent
|
21
|
+
|
22
|
+
# If some of the pagination fields are present all of the required ones should be present
|
23
|
+
def all_pagination_is_present
|
24
|
+
if count || previous_page_no || next_page_no || current_page_no || no_results_per_page ||
|
25
|
+
total_no_results || total_no_pages
|
26
|
+
errors.add(:count, "should be present if pagination is being used") if count.nil?
|
27
|
+
errors.add(:current_page_no, "should be present if pagination is being used") if current_page_no.nil?
|
28
|
+
errors.add(:no_results_per_page, "should be present if pagination is being used") if no_results_per_page.nil?
|
29
|
+
errors.add(:total_no_results, "should be present if pagination is being used") if total_no_results.nil?
|
30
|
+
errors.add(:total_no_pages, "should be present if pagination is being used") if total_no_pages.nil?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def count_is_consistent
|
35
|
+
if count
|
36
|
+
errors.add(:count, "is not the same as the number of applications returned") if count != results.count
|
37
|
+
errors.add(:count, "should not be larger than the number of results per page") if count > no_results_per_page
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def previous_page_no_is_consistent
|
42
|
+
if current_page_no
|
43
|
+
if previous_page_no
|
44
|
+
if previous_page_no != current_page_no - 1
|
45
|
+
errors.add(:previous_page_no, "should be one less than current page number or null if first page")
|
46
|
+
end
|
47
|
+
if current_page_no == 1
|
48
|
+
errors.add(:previous_page_no, "should be null if on the first page")
|
49
|
+
end
|
50
|
+
else
|
51
|
+
if current_page_no > 1
|
52
|
+
errors.add(:previous_page_no, "can't be null if not on the first page")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def next_page_no_is_consistent
|
59
|
+
if next_page_no && next_page_no != current_page_no + 1
|
60
|
+
errors.add(:next_page_no, "should be one greater than current page number or null if last page")
|
61
|
+
end
|
62
|
+
if next_page_no.nil? && current_page_no != total_no_pages
|
63
|
+
errors.add(:next_page_no, "can't be null if not on the last page")
|
64
|
+
end
|
65
|
+
if next_page_no && current_page_no == total_no_pages
|
66
|
+
errors.add(:next_page_no, "should be null if on the last page")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def current_page_no_is_consistent
|
71
|
+
if current_page_no
|
72
|
+
errors.add(:current_page_no, "is larger than the number of pages") if current_page_no > total_no_pages
|
73
|
+
errors.add(:current_page_no, "can not be less than 1") if current_page_no < 1
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def total_no_results_is_consistent
|
78
|
+
if total_no_pages && total_no_results > total_no_pages * no_results_per_page
|
79
|
+
errors.add(:total_no_results, "is larger than can be retrieved through paging")
|
80
|
+
end
|
81
|
+
if total_no_pages && total_no_results <= (total_no_pages - 1) * no_results_per_page
|
82
|
+
errors.add(:total_no_results, "could fit into a smaller number of pages")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.read_url(url)
|
87
|
+
r = read_json(RestClient.get(url.to_s).to_str)
|
88
|
+
r.url = url.to_s
|
89
|
+
r
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.read_json(text)
|
93
|
+
interpret(MultiJson.load(text, :symbolize_keys => true))
|
94
|
+
end
|
95
|
+
|
96
|
+
def previous_url
|
97
|
+
raise "Can't use previous_url when loaded with read_json" if url.nil?
|
98
|
+
ATDIS::SeparatedURL.merge(url, :page => previous_page_no) if previous_page_no
|
99
|
+
end
|
100
|
+
|
101
|
+
def next_url
|
102
|
+
raise "Can't use next_url when loaded with read_json" if url.nil?
|
103
|
+
ATDIS::SeparatedURL.merge(url, :page => next_page_no) if next_page_no
|
104
|
+
end
|
105
|
+
|
106
|
+
def previous
|
107
|
+
Page.read_url(previous_url) if previous_url
|
108
|
+
end
|
109
|
+
|
110
|
+
def next
|
111
|
+
Page.read_url(next_url) if next_url
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
data/lib/atdis/person.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module ATDIS
|
2
|
+
class SeparatedURL
|
3
|
+
|
4
|
+
def self.merge(full_url, params)
|
5
|
+
url, url_params = split(full_url)
|
6
|
+
combine(url, url_params.merge(params))
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def self.combine(url, url_params)
|
12
|
+
# Doing this jiggery pokery to ensure the params are sorted alphabetically (even on Ruby 1.8)
|
13
|
+
query = url_params.map{|k,v| [k.to_s, v]}.sort.map{|k,v| "#{CGI.escape(k)}=#{CGI.escape(v.to_s)}"}.join("&")
|
14
|
+
if url_params.empty?
|
15
|
+
url
|
16
|
+
else
|
17
|
+
url + "?" + query
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.split(full_url)
|
22
|
+
uri = URI.parse(full_url)
|
23
|
+
if (uri.scheme == "http" && uri.port == 80) || (uri.scheme == "https" && uri.port == 443)
|
24
|
+
url = "#{uri.scheme}://#{uri.host}#{uri.path}"
|
25
|
+
else
|
26
|
+
url = "#{uri.scheme}://#{uri.host}:#{uri.port}#{uri.path}"
|
27
|
+
end
|
28
|
+
if uri.query
|
29
|
+
url_params = Hash[*CGI::parse(uri.query).map{|k,v| [k.to_sym,v.first]}.flatten]
|
30
|
+
else
|
31
|
+
url_params = {}
|
32
|
+
end
|
33
|
+
[url, url_params]
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
|
3
|
+
module ATDIS
|
4
|
+
module Validators
|
5
|
+
class GeoJsonValidator < ActiveModel::EachValidator
|
6
|
+
def validate_each(record, attribute, value)
|
7
|
+
raw_value = record.send("#{attribute}_before_type_cast")
|
8
|
+
if raw_value.present? && value.nil?
|
9
|
+
record.errors.add(attribute, "is not valid GeoJSON")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class DateTimeValidator < ActiveModel::EachValidator
|
15
|
+
def validate_each(record, attribute, value)
|
16
|
+
raw_value = record.send("#{attribute}_before_type_cast")
|
17
|
+
if raw_value.present? && !value.kind_of?(DateTime)
|
18
|
+
record.errors.add(attribute, "is not a valid date")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class HttpUrlValidator < ActiveModel::EachValidator
|
24
|
+
def validate_each(record, attribute, value)
|
25
|
+
raw_value = record.send("#{attribute}_before_type_cast")
|
26
|
+
if raw_value.present? && !value.kind_of?(URI::HTTP) && !value.kind_of?(URI::HTTPS)
|
27
|
+
record.errors.add(attribute, "is not a valid URL")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Take into account the value before type casting
|
33
|
+
class PresenceBeforeTypeCastValidator < ActiveModel::EachValidator
|
34
|
+
def validate_each(record, attribute, value)
|
35
|
+
raw_value = record.send("#{attribute}_before_type_cast")
|
36
|
+
unless raw_value.present?
|
37
|
+
record.errors.add(attribute, "can't be blank")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# This attribute itself needs to be valid
|
43
|
+
class ValidValidator < ActiveModel::EachValidator
|
44
|
+
def validate_each(record, attribute, value)
|
45
|
+
if (value.respond_to?(:valid?) && !value.valid?) || (value && !value.respond_to?(:valid?) && !value.all?{|v| v.valid?})
|
46
|
+
record.errors.add(attribute, "is not valid")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|