atdis 0.2 → 0.3
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.
- checksums.yaml +7 -0
- data/.ruby-version +1 -1
- data/.travis.yml +0 -1
- data/Gemfile +1 -1
- data/README.md +25 -2
- data/Rakefile +1 -1
- data/docs/ATDIS-1.0.2 Application Tracking Data Interchange Specification (v1.0.2).doc +0 -0
- data/docs/ATDIS-1.0.2 Application Tracking Data Interchange Specification (v1.0.2).pdf +0 -0
- data/lib/atdis.rb +2 -7
- data/lib/atdis/feed.rb +80 -8
- data/lib/atdis/model.rb +74 -128
- data/lib/atdis/models/address.rb +17 -0
- data/lib/atdis/models/application.rb +31 -0
- data/lib/atdis/models/authority.rb +19 -0
- data/lib/atdis/models/document.rb +16 -0
- data/lib/atdis/models/event.rb +16 -0
- data/lib/atdis/models/info.rb +81 -0
- data/lib/atdis/models/land_title_ref.rb +26 -0
- data/lib/atdis/models/location.rb +23 -0
- data/lib/atdis/models/page.rb +56 -0
- data/lib/atdis/models/pagination.rb +68 -0
- data/lib/atdis/models/person.rb +14 -0
- data/lib/atdis/models/reference.rb +13 -0
- data/lib/atdis/models/response.rb +16 -0
- data/lib/atdis/models/torrens_title.rb +24 -0
- data/lib/atdis/validators.rb +26 -10
- data/lib/atdis/version.rb +1 -1
- data/spec/atdis/feed_spec.rb +78 -22
- data/spec/atdis/model_spec.rb +80 -131
- data/spec/atdis/models/address_spec.rb +22 -0
- data/spec/atdis/models/application_spec.rb +246 -0
- data/spec/atdis/models/authority_spec.rb +34 -0
- data/spec/atdis/models/document_spec.rb +19 -0
- data/spec/atdis/models/event_spec.rb +29 -0
- data/spec/atdis/models/info_spec.rb +303 -0
- data/spec/atdis/models/land_title_ref_spec.rb +39 -0
- data/spec/atdis/models/location_spec.rb +95 -0
- data/spec/atdis/models/page_spec.rb +296 -0
- data/spec/atdis/models/pagination_spec.rb +153 -0
- data/spec/atdis/models/person_spec.rb +19 -0
- data/spec/atdis/models/reference_spec.rb +55 -0
- data/spec/atdis/models/response_spec.rb +5 -0
- data/spec/atdis/models/torrens_title_spec.rb +52 -0
- data/spec/atdis/separated_url_spec.rb +4 -4
- metadata +141 -135
- data/docs/ATDIS-1.0.7 Application Tracking Data Interchange Specification (v1.0).doc +0 -0
- data/docs/ATDIS-1.0.7 Application Tracking Data Interchange Specification (v1.0).pdf +0 -0
- data/lib/atdis/application.rb +0 -78
- data/lib/atdis/document.rb +0 -14
- data/lib/atdis/event.rb +0 -17
- data/lib/atdis/location.rb +0 -21
- data/lib/atdis/page.rb +0 -130
- data/lib/atdis/person.rb +0 -12
- data/spec/atdis/application_spec.rb +0 -539
- data/spec/atdis/document_spec.rb +0 -19
- data/spec/atdis/event_spec.rb +0 -29
- data/spec/atdis/location_spec.rb +0 -148
- data/spec/atdis/page_spec.rb +0 -492
- data/spec/atdis/person_spec.rb +0 -19
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2b0835df894665d409ea7c29c71f70158e4a4d66
|
4
|
+
data.tar.gz: 07d56dc629ee7916e72388bd881b29952a0b629c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 60c9f8efc76ea97d526acd9f8044878b42baefab92c7ea2939e5987e0a38403b04681a384852d067c584343a60a7209b676702f71fed7bcb205ab1275904244f
|
7
|
+
data.tar.gz: 451d11c16ec89abaa8d6660cd83ba9f91001d97b0f11624b333890eb7c8291bf526cab2784c1c3623d38f2d3d3383ddc29071e17cc5957b887c7692f56c27bb3
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ruby-
|
1
|
+
ruby-2.0.0-p353
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
@@ -8,7 +8,7 @@ group :development do
|
|
8
8
|
gem 'rb-fsevent', '~> 0.9'
|
9
9
|
# Probably required on OS X. See https://github.com/guard/guard/wiki/Add-Readline-support-to-Ruby-on-Mac-OS-X
|
10
10
|
gem 'rb-readline'
|
11
|
-
gem 'coveralls', :
|
11
|
+
gem 'coveralls', require: false
|
12
12
|
end
|
13
13
|
|
14
14
|
# Specify your gem's dependencies in atdis.gemspec
|
data/README.md
CHANGED
@@ -6,7 +6,7 @@ A ruby interface to the application tracking data interchange specification (ATD
|
|
6
6
|
|
7
7
|
We're developing this against version ATDIS 1.0.7.
|
8
8
|
|
9
|
-
This is **
|
9
|
+
This is **beta** software and is a work in progress.
|
10
10
|
|
11
11
|
Source code is available on GitHub at https://github.com/openaustralia/atdis
|
12
12
|
|
@@ -26,7 +26,30 @@ Or install it yourself as:
|
|
26
26
|
|
27
27
|
## Usage
|
28
28
|
|
29
|
-
|
29
|
+
### Basic usage
|
30
|
+
|
31
|
+
require 'atdis'
|
32
|
+
f = ATDIS::Feed.new("http://www.planningalerts.org.au/atdis/feed/1/atdis/1.0")
|
33
|
+
|
34
|
+
# Get the first application in the first page of results for all the applications
|
35
|
+
page = f.applications
|
36
|
+
app = page.response.first
|
37
|
+
|
38
|
+
puts "#{app.dat_id}: #{app.description} at #{app.location.address}"
|
39
|
+
|
40
|
+
DA2013-0381: New pool plus deck at 123 Fourfivesix Street Neutral Bay NSW 2089
|
41
|
+
|
42
|
+
### Paging
|
43
|
+
|
44
|
+
page.next_page
|
45
|
+
|
46
|
+
and
|
47
|
+
|
48
|
+
page.previous_page
|
49
|
+
|
50
|
+
### Validation
|
51
|
+
|
52
|
+
page.valid?
|
30
53
|
|
31
54
|
## Contributing
|
32
55
|
|
data/Rakefile
CHANGED
Binary file
|
Binary file
|
data/lib/atdis.rb
CHANGED
@@ -1,12 +1,7 @@
|
|
1
1
|
require "atdis/version"
|
2
2
|
|
3
3
|
require "atdis/validators"
|
4
|
-
require "atdis/model"
|
5
|
-
require "atdis/event"
|
6
|
-
require "atdis/document"
|
7
|
-
require "atdis/location"
|
8
|
-
require "atdis/person"
|
9
|
-
require "atdis/application"
|
10
4
|
require "atdis/feed"
|
11
|
-
require "atdis/page"
|
12
5
|
require "atdis/separated_url"
|
6
|
+
require "atdis/model"
|
7
|
+
require "atdis/models/page"
|
data/lib/atdis/feed.rb
CHANGED
@@ -4,18 +4,90 @@ module ATDIS
|
|
4
4
|
class Feed
|
5
5
|
attr_reader :base_url
|
6
6
|
|
7
|
+
VALID_OPTIONS = [:page, :street, :suburb, :postcode, :lodgement_date_start, :lodgement_date_end, :last_modified_date_start, :last_modified_date_end]
|
8
|
+
|
7
9
|
# base_url - the base url from which the urls for all atdis urls are made
|
8
|
-
# It
|
9
|
-
#
|
10
|
-
# applications is "http://www.council.nsw.gov.au/atdis/1.0/applications.json"
|
10
|
+
# It should be of the form:
|
11
|
+
# http://www.council.nsw.gov.au/atdis/1.0
|
11
12
|
def initialize(base_url)
|
12
|
-
@base_url = base_url
|
13
|
+
@base_url = base_url
|
14
|
+
end
|
15
|
+
|
16
|
+
def applications_url(options = {})
|
17
|
+
invalid_options = options.keys - VALID_OPTIONS
|
18
|
+
if !invalid_options.empty?
|
19
|
+
raise "Unexpected options used: #{invalid_options.join(',')}"
|
20
|
+
end
|
21
|
+
options[:street] = options[:street].join(",") if options[:street].respond_to?(:join)
|
22
|
+
options[:suburb] = options[:suburb].join(",") if options[:suburb].respond_to?(:join)
|
23
|
+
options[:postcode] = options[:postcode].join(",") if options[:postcode].respond_to?(:join)
|
24
|
+
|
25
|
+
q = Feed.options_to_query(options)
|
26
|
+
"#{base_url}/applications.json" + (q ? "?#{q}" : "")
|
27
|
+
end
|
28
|
+
|
29
|
+
def application_url(id)
|
30
|
+
"#{base_url}/#{CGI::escape(id)}.json"
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.base_url_from_url(url)
|
34
|
+
u = URI.parse(url)
|
35
|
+
options = query_to_options(u.query)
|
36
|
+
VALID_OPTIONS.each do |o|
|
37
|
+
options.delete(o)
|
38
|
+
end
|
39
|
+
u.query = options_to_query(options)
|
40
|
+
u.fragment = nil
|
41
|
+
u.path = "/" + u.path.split("/")[1..-2].join("/")
|
42
|
+
u.to_s
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.options_from_url(url)
|
46
|
+
u = URI.parse(url)
|
47
|
+
options = query_to_options(u.query)
|
48
|
+
[:lodgement_date_start, :lodgement_date_end, :last_modified_date_start, :last_modified_date_end].each do |k|
|
49
|
+
options[k] = Date.parse(options[k]) if options[k]
|
50
|
+
end
|
51
|
+
options[:page] = options[:page].to_i if options[:page]
|
52
|
+
# Remove invalid options
|
53
|
+
options.keys.each do |key|
|
54
|
+
if !VALID_OPTIONS.include?(key)
|
55
|
+
options.delete(key)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
options
|
59
|
+
end
|
60
|
+
|
61
|
+
def applications(options = {})
|
62
|
+
Models::Page.read_url(applications_url(options))
|
63
|
+
end
|
64
|
+
|
65
|
+
def application(id)
|
66
|
+
Models::Application.read_url(application_url(id))
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
# Turn a query string of the form "foo=bar&hello=sir" to {foo: "bar", hello: sir"}
|
72
|
+
def self.query_to_options(query)
|
73
|
+
options = {}
|
74
|
+
if query
|
75
|
+
query.split("&").each do |t|
|
76
|
+
key, value = t.split("=")
|
77
|
+
options[key.to_sym] = value
|
78
|
+
end
|
79
|
+
end
|
80
|
+
options
|
13
81
|
end
|
14
82
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
83
|
+
# Turn an options hash of the form {foo: "bar", hello: "sir"} into a query
|
84
|
+
# string of the form "foo=bar&hello=sir"
|
85
|
+
def self.options_to_query(options)
|
86
|
+
if options.empty?
|
87
|
+
nil
|
88
|
+
else
|
89
|
+
options.sort{|a,b| a.first.to_s <=> b.first.to_s}.map{|k,v| "#{k}=#{v}"}.join("&")
|
90
|
+
end
|
19
91
|
end
|
20
92
|
end
|
21
93
|
end
|
data/lib/atdis/model.rb
CHANGED
@@ -7,46 +7,18 @@ module ATDIS
|
|
7
7
|
|
8
8
|
included do
|
9
9
|
class_attribute :attribute_types
|
10
|
-
class_attribute :field_mappings
|
11
10
|
end
|
12
11
|
|
13
12
|
module ClassMethods
|
14
|
-
# of the form {:
|
15
|
-
def casting_attributes(p)
|
16
|
-
define_attribute_methods(p.keys.map{|k| k.to_s})
|
17
|
-
self.attribute_types = p
|
18
|
-
end
|
19
|
-
|
13
|
+
# of the form {section: Fixnum, address: String}
|
20
14
|
def set_field_mappings(p)
|
21
|
-
|
22
|
-
#
|
23
|
-
self.
|
24
|
-
casting_attributes(b)
|
25
|
-
end
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
def leaf_array?(v)
|
30
|
-
if !v.kind_of?(Array)
|
31
|
-
return false
|
32
|
-
end
|
33
|
-
v.all?{|a| !a.kind_of?(Array)}
|
34
|
-
end
|
35
|
-
|
36
|
-
def translate_field_mappings(p)
|
37
|
-
f = ActiveSupport::OrderedHash.new
|
38
|
-
ca = ActiveSupport::OrderedHash.new
|
15
|
+
define_attribute_methods(p.keys.map{|k| k.to_s})
|
16
|
+
# Convert all values to arrays. Doing this for the sake of tidier notation
|
17
|
+
self.attribute_types = {}
|
39
18
|
p.each do |k,v|
|
40
|
-
|
41
|
-
|
42
|
-
ca[v.first] = v[1..-1]
|
43
|
-
else
|
44
|
-
f2, ca2 = translate_field_mappings(v)
|
45
|
-
f[k] = f2
|
46
|
-
ca = ca.merge(ca2)
|
47
|
-
end
|
19
|
+
v = [v] unless v.kind_of?(Array)
|
20
|
+
self.attribute_types[k] = v
|
48
21
|
end
|
49
|
-
[f, ca]
|
50
22
|
end
|
51
23
|
end
|
52
24
|
end
|
@@ -74,105 +46,89 @@ module ATDIS
|
|
74
46
|
# Stores any part of the json that could not be interpreted. Usually
|
75
47
|
# signals an error if it isn't empty.
|
76
48
|
attr_accessor :json_left_overs, :json_load_error
|
77
|
-
|
78
|
-
validate :json_left_overs_is_empty
|
49
|
+
attr_accessor :url
|
79
50
|
|
80
|
-
|
81
|
-
|
82
|
-
end
|
51
|
+
validate :json_loaded_correctly!
|
52
|
+
validate :json_left_overs_is_empty
|
83
53
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
54
|
+
# Partition the data into used and unused by returning [used, unused]
|
55
|
+
def self.partition_by_used(data)
|
56
|
+
used, unused = {}, {}
|
57
|
+
if data.respond_to?(:each)
|
58
|
+
data.each do |key, value|
|
59
|
+
if attribute_keys.include?(key)
|
60
|
+
used[key] = value
|
61
|
+
else
|
62
|
+
unused[key] = value
|
93
63
|
end
|
94
64
|
end
|
65
|
+
else
|
66
|
+
unused = data
|
95
67
|
end
|
96
|
-
|
68
|
+
[used, unused]
|
97
69
|
end
|
98
70
|
|
99
|
-
def self.
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
elsif v.kind_of?(Hash) && data.has_key?(k)
|
104
|
-
r = map_field(key, data[k], mappings[k])
|
105
|
-
if r
|
106
|
-
return r
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
110
|
-
nil
|
71
|
+
def self.read_url(url)
|
72
|
+
r = read_json(RestClient.get(url.to_s).to_str)
|
73
|
+
r.url = url.to_s
|
74
|
+
r
|
111
75
|
end
|
112
76
|
|
113
|
-
def self.
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
else
|
122
|
-
json_left_overs[key] = data[key]
|
123
|
-
end
|
77
|
+
def self.read_json(text)
|
78
|
+
begin
|
79
|
+
data = MultiJson.load(text, symbolize_keys: true)
|
80
|
+
interpret(data)
|
81
|
+
rescue MultiJson::LoadError => e
|
82
|
+
a = interpret({response: []})
|
83
|
+
a.json_load_error = e.to_s
|
84
|
+
a
|
124
85
|
end
|
125
|
-
json_left_overs
|
126
86
|
end
|
127
87
|
|
128
|
-
def self.
|
129
|
-
|
130
|
-
|
131
|
-
if v.kind_of?(Hash)
|
132
|
-
result += attribute_names_from_mappings(v)
|
133
|
-
else
|
134
|
-
result << v
|
135
|
-
end
|
136
|
-
end
|
137
|
-
result
|
88
|
+
def self.interpret(*params)
|
89
|
+
used, unused = partition_by_used(*params)
|
90
|
+
new(used.merge(json_left_overs: unused))
|
138
91
|
end
|
139
92
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
attribute_names_from_mappings(mappings).each do |attribute|
|
144
|
-
values[attribute] = map_field(attribute, data, mappings)
|
93
|
+
def json_loaded_correctly!
|
94
|
+
if json_load_error
|
95
|
+
errors.add(:json, ErrorMessage["Invalid JSON: #{json_load_error}", nil])
|
145
96
|
end
|
146
|
-
values
|
147
97
|
end
|
148
98
|
|
149
|
-
def
|
99
|
+
def json_errors_local
|
150
100
|
r = []
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
r << [
|
101
|
+
# First show special json error
|
102
|
+
if !errors[:json].empty?
|
103
|
+
r << [nil, errors[:json]]
|
104
|
+
end
|
105
|
+
errors.keys.each do |attribute|
|
106
|
+
# The :json attribute is special
|
107
|
+
if attribute != :json
|
108
|
+
e = errors[attribute]
|
109
|
+
r << [{attribute => attributes_before_type_cast[attribute.to_s]}, e.map{|m| ErrorMessage["#{attribute} #{m}", m.spec_section]}] unless e.empty?
|
160
110
|
end
|
161
111
|
end
|
162
112
|
r
|
163
113
|
end
|
164
114
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
115
|
+
def json_errors_in_children
|
116
|
+
r = []
|
117
|
+
attributes.each do |attribute_as_string, value|
|
118
|
+
attribute = attribute_as_string.to_sym
|
119
|
+
e = errors[attribute]
|
120
|
+
if value.respond_to?(:json_errors)
|
121
|
+
r += value.json_errors.map{|a, b| [{attribute => a}, b]}
|
122
|
+
elsif value.kind_of?(Array)
|
123
|
+
f = value.find{|v| v.respond_to?(:json_errors) && !v.json_errors.empty?}
|
124
|
+
r += f.json_errors.map{|a, b| [{attribute => [a]}, b]} if f
|
173
125
|
end
|
174
126
|
end
|
175
|
-
|
127
|
+
r
|
128
|
+
end
|
129
|
+
|
130
|
+
def json_errors
|
131
|
+
json_errors_local + json_errors_in_children
|
176
132
|
end
|
177
133
|
|
178
134
|
# Have we tried to use this attribute?
|
@@ -180,14 +136,6 @@ module ATDIS
|
|
180
136
|
!attributes_before_type_cast[a].nil?
|
181
137
|
end
|
182
138
|
|
183
|
-
def level_used_locally?(level)
|
184
|
-
self.class.level_attribute_names(level).any?{|a| used_attribute?(a)}
|
185
|
-
end
|
186
|
-
|
187
|
-
def level_used?(level)
|
188
|
-
level_used_locally?(level) || level_used_in_children?(level)
|
189
|
-
end
|
190
|
-
|
191
139
|
def json_left_overs_is_empty
|
192
140
|
if json_left_overs && !json_left_overs.empty?
|
193
141
|
# We have extra parameters that shouldn't be there
|
@@ -202,24 +150,22 @@ module ATDIS
|
|
202
150
|
end if params
|
203
151
|
end
|
204
152
|
|
153
|
+
def self.attribute_keys
|
154
|
+
attribute_types.keys
|
155
|
+
end
|
156
|
+
|
205
157
|
# Does what the equivalent on Activerecord does
|
206
158
|
def self.attribute_names
|
207
159
|
attribute_types.keys.map{|k| k.to_s}
|
208
160
|
end
|
209
161
|
|
210
|
-
def self.
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
def self.cast(value, type, options = {})
|
215
|
-
if options[:none_is_nil] && value == "none"
|
216
|
-
nil
|
217
|
-
# If it's already the correct type then we don't need to do anything
|
218
|
-
elsif value.kind_of?(type)
|
162
|
+
def self.cast(value, type)
|
163
|
+
# If it's already the correct type (or nil) then we don't need to do anything
|
164
|
+
if value.nil? || value.kind_of?(type)
|
219
165
|
value
|
220
166
|
# Special handling for arrays. When we typecast arrays we actually typecast each member of the array
|
221
167
|
elsif value.kind_of?(Array)
|
222
|
-
value.map {|v| cast(v, type
|
168
|
+
value.map {|v| cast(v, type)}
|
223
169
|
elsif type == DateTime
|
224
170
|
cast_datetime(value)
|
225
171
|
elsif type == URI
|
@@ -250,7 +196,7 @@ module ATDIS
|
|
250
196
|
|
251
197
|
def attribute=(attr, value)
|
252
198
|
@attributes_before_type_cast[attr] = value
|
253
|
-
@attributes[attr] = Model.cast(value, attribute_types[attr.to_sym][0]
|
199
|
+
@attributes[attr] = Model.cast(value, attribute_types[attr.to_sym][0])
|
254
200
|
end
|
255
201
|
|
256
202
|
def self.cast_datetime(value)
|
@@ -287,7 +233,7 @@ module ATDIS
|
|
287
233
|
RGeo::GeoJSON.decode(hash_symbols_to_string(value))
|
288
234
|
end
|
289
235
|
|
290
|
-
# Converts {:
|
236
|
+
# Converts {foo: {bar: "yes"}} to {"foo" => {"bar" => "yes"}}
|
291
237
|
def self.hash_symbols_to_string(hash)
|
292
238
|
if hash.respond_to?(:each_pair)
|
293
239
|
result = {}
|