atdis 0.3.11 → 0.5.0
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 +4 -4
- data/.rubocop.yml +46 -0
- data/.ruby-version +1 -1
- data/.travis.yml +0 -4
- data/Gemfile +9 -7
- data/Guardfile +4 -3
- data/Rakefile +4 -2
- data/atdis.gemspec +10 -5
- data/lib/atdis.rb +2 -0
- data/lib/atdis/feed.rb +32 -24
- data/lib/atdis/model.rb +108 -95
- data/lib/atdis/models/address.rb +10 -4
- data/lib/atdis/models/application.rb +12 -9
- data/lib/atdis/models/authority.rb +11 -6
- data/lib/atdis/models/document.rb +8 -6
- data/lib/atdis/models/event.rb +10 -8
- data/lib/atdis/models/info.rb +73 -49
- data/lib/atdis/models/land_title_ref.rb +15 -7
- data/lib/atdis/models/location.rb +9 -7
- data/lib/atdis/models/page.rb +36 -21
- data/lib/atdis/models/pagination.rb +91 -32
- data/lib/atdis/models/person.rb +7 -5
- data/lib/atdis/models/reference.rb +7 -5
- data/lib/atdis/models/response.rb +5 -3
- data/lib/atdis/models/torrens_title.rb +9 -7
- data/lib/atdis/separated_url.rb +17 -15
- data/lib/atdis/validators.rb +46 -39
- data/lib/atdis/version.rb +3 -1
- data/spec/atdis/feed_spec.rb +128 -34
- data/spec/atdis/model_spec.rb +124 -51
- data/spec/atdis/models/address_spec.rb +18 -9
- data/spec/atdis/models/application_spec.rb +222 -155
- data/spec/atdis/models/authority_spec.rb +45 -15
- data/spec/atdis/models/document_spec.rb +10 -4
- data/spec/atdis/models/event_spec.rb +23 -11
- data/spec/atdis/models/info_spec.rb +197 -113
- data/spec/atdis/models/land_title_ref_spec.rb +32 -16
- data/spec/atdis/models/location_spec.rb +75 -60
- data/spec/atdis/models/page_spec.rb +244 -135
- data/spec/atdis/models/pagination_spec.rb +177 -77
- data/spec/atdis/models/person_spec.rb +8 -4
- data/spec/atdis/models/reference_spec.rb +29 -16
- data/spec/atdis/models/response_spec.rb +2 -1
- data/spec/atdis/models/torrens_title_spec.rb +24 -18
- data/spec/atdis/separated_url_spec.rb +14 -15
- data/spec/spec_helper.rb +14 -10
- metadata +62 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 569aa3967302c8529a9c74433f54fd105ebe45d7
|
4
|
+
data.tar.gz: 0eb50df65474e2d5bc5787994d58c8a282e87e4a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b6aa7bae02ff0fa4a4a2776ef4bbb16f7438f2332f5b3e419f2281abebe8f25e8894a2beb50b325ca3ca24dce598b58f9c0580b5de408f1e4fe69f712dd4bd57
|
7
|
+
data.tar.gz: 029202949e54506691b3e5c6bb36f2bb73aafd14e707e4ff328b5f739c0a55762a6b35a2f801a5ccbe5d63c8233d23db6c575d40230bf453c2afc4760268f5e7
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# Bumping max line length to something a little more reasonable
|
2
|
+
Metrics/LineLength:
|
3
|
+
Max: 100
|
4
|
+
|
5
|
+
# Not so worried about top-level class documentation. So...
|
6
|
+
Style/Documentation:
|
7
|
+
Enabled: false
|
8
|
+
|
9
|
+
# We prefer double quotes here and it we're making liberal use of multi-line
|
10
|
+
# strings so it makes sense to enforce those to be consistent oo
|
11
|
+
Style/StringLiterals:
|
12
|
+
EnforcedStyle: double_quotes
|
13
|
+
ConsistentQuotesInMultiline: true
|
14
|
+
|
15
|
+
# This one I disagree with. Putting seperators in large numbers makes sense
|
16
|
+
# in some circumstances but in others (an example id in a database table)
|
17
|
+
# it's just nonsensical. Also, I think this one might also be a bit US centric.
|
18
|
+
Style/NumericLiterals:
|
19
|
+
Enabled: false
|
20
|
+
|
21
|
+
# Disable a bunch of metrics to do with code complexity. These as are all
|
22
|
+
# a bit hard-nosed. Maybe after we've done a pass with Code Climate we
|
23
|
+
# can revisit these
|
24
|
+
Metrics/AbcSize:
|
25
|
+
Enabled: false
|
26
|
+
|
27
|
+
Metrics/BlockLength:
|
28
|
+
Enabled: false
|
29
|
+
|
30
|
+
Metrics/ClassLength:
|
31
|
+
Enabled: false
|
32
|
+
|
33
|
+
Metrics/CyclomaticComplexity:
|
34
|
+
Enabled: false
|
35
|
+
|
36
|
+
Metrics/MethodLength:
|
37
|
+
Enabled: false
|
38
|
+
|
39
|
+
Metrics/ModuleLength:
|
40
|
+
Enabled: false
|
41
|
+
|
42
|
+
Metrics/ParameterLists:
|
43
|
+
Enabled: false
|
44
|
+
|
45
|
+
Metrics/PerceivedComplexity:
|
46
|
+
Enabled: false
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ruby-2.
|
1
|
+
ruby-2.3.1
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
@@ -1,14 +1,16 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
2
4
|
|
3
5
|
group :development do
|
6
|
+
gem "coveralls", require: false
|
7
|
+
gem "growl"
|
8
|
+
gem "guard"
|
9
|
+
gem "guard-rspec"
|
10
|
+
gem "rb-fsevent", "~> 0.9"
|
4
11
|
gem "rspec"
|
5
|
-
gem 'guard'
|
6
|
-
gem 'guard-rspec'
|
7
|
-
gem 'growl'
|
8
|
-
gem 'rb-fsevent', '~> 0.9'
|
9
12
|
# Probably required on OS X. See https://github.com/guard/guard/wiki/Add-Readline-support-to-Ruby-on-Mac-OS-X
|
10
|
-
gem
|
11
|
-
gem 'coveralls', require: false
|
13
|
+
gem "rb-readline"
|
12
14
|
end
|
13
15
|
|
14
16
|
# Specify your gem's dependencies in atdis.gemspec
|
data/Guardfile
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# More info at https://github.com/guard/guard#readme
|
2
4
|
|
3
|
-
guard
|
4
|
-
watch(%r{^lib/(.+)\.rb$})
|
5
|
+
guard "rspec" do
|
6
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
5
7
|
watch(%r{^spec/.+_spec\.rb$})
|
6
8
|
end
|
7
|
-
|
data/Rakefile
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "bundler/gem_tasks"
|
2
4
|
|
3
|
-
require
|
5
|
+
require "rspec/core/rake_task"
|
4
6
|
|
5
|
-
RSpec::Core::RakeTask.new(
|
7
|
+
RSpec::Core::RakeTask.new("spec")
|
6
8
|
|
7
9
|
# If you want to make this the default task
|
8
10
|
task default: :spec
|
data/atdis.gemspec
CHANGED
@@ -1,14 +1,16 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path("lib", __dir__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require
|
5
|
+
require "atdis/version"
|
5
6
|
|
6
7
|
Gem::Specification.new do |spec|
|
7
8
|
spec.name = "atdis"
|
8
9
|
spec.version = Atdis::VERSION
|
9
10
|
spec.authors = ["Matthew Landauer"]
|
10
11
|
spec.email = ["matthew@openaustraliafoundation.org.au"]
|
11
|
-
spec.description =
|
12
|
+
spec.description =
|
13
|
+
"A ruby interface to the application tracking data interchange specification (ATDIS) API"
|
12
14
|
spec.summary = spec.description
|
13
15
|
spec.homepage = "http://github.com/openaustralia/atdis"
|
14
16
|
spec.license = "MIT"
|
@@ -17,12 +19,15 @@ Gem::Specification.new do |spec|
|
|
17
19
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
20
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
21
|
spec.require_paths = ["lib"]
|
22
|
+
spec.required_ruby_version = ">= 2.3.1"
|
20
23
|
|
21
24
|
spec.add_development_dependency "bundler", "~> 1.3"
|
22
25
|
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency "rubocop"
|
23
27
|
|
28
|
+
spec.add_dependency "activemodel"
|
29
|
+
spec.add_dependency "activesupport"
|
24
30
|
spec.add_dependency "multi_json", "~> 1.7"
|
25
31
|
spec.add_dependency "rest-client"
|
26
32
|
spec.add_dependency "rgeo-geojson"
|
27
|
-
spec.add_dependency "activemodel", "~> 3"
|
28
33
|
end
|
data/lib/atdis.rb
CHANGED
data/lib/atdis/feed.rb
CHANGED
@@ -1,23 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "rest-client"
|
2
4
|
|
3
5
|
module ATDIS
|
4
6
|
class Feed
|
5
|
-
attr_reader :base_url
|
7
|
+
attr_reader :base_url, :timezone, :ignore_ssl_certificate
|
6
8
|
|
7
|
-
VALID_OPTIONS = [
|
9
|
+
VALID_OPTIONS = %i[page street suburb postcode lodgement_date_start
|
10
|
+
lodgement_date_end last_modified_date_start last_modified_date_end].freeze
|
8
11
|
|
9
12
|
# base_url - the base url from which the urls for all atdis urls are made
|
10
13
|
# It should be of the form:
|
11
14
|
# http://www.council.nsw.gov.au/atdis/1.0
|
12
|
-
|
15
|
+
# timezone - a string (e.g. "Sydney") for the timezone in which times are returned
|
16
|
+
# (Note: times in the feeds that have timezones specified get converted to the
|
17
|
+
# timezone given while times in the feed which don't have a timezone specified
|
18
|
+
# get interpreted in the given timezone)
|
19
|
+
# See https://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html for the
|
20
|
+
# list of possible timezone strings
|
21
|
+
def initialize(base_url, timezone, ignore_ssl_certificate = false)
|
13
22
|
@base_url = base_url
|
23
|
+
@timezone = timezone
|
24
|
+
@ignore_ssl_certificate = ignore_ssl_certificate
|
14
25
|
end
|
15
26
|
|
16
27
|
def applications_url(options = {})
|
17
28
|
invalid_options = options.keys - VALID_OPTIONS
|
18
|
-
|
19
|
-
|
20
|
-
|
29
|
+
|
30
|
+
raise "Unexpected options used: #{invalid_options.join(',')}" unless invalid_options.empty?
|
31
|
+
|
21
32
|
options[:street] = options[:street].join(",") if options[:street].respond_to?(:join)
|
22
33
|
options[:suburb] = options[:suburb].join(",") if options[:suburb].respond_to?(:join)
|
23
34
|
options[:postcode] = options[:postcode].join(",") if options[:postcode].respond_to?(:join)
|
@@ -27,7 +38,7 @@ module ATDIS
|
|
27
38
|
end
|
28
39
|
|
29
40
|
def application_url(id)
|
30
|
-
"#{base_url}/#{CGI
|
41
|
+
"#{base_url}/#{CGI.escape(id)}.json"
|
31
42
|
end
|
32
43
|
|
33
44
|
def self.base_url_from_url(url)
|
@@ -45,44 +56,39 @@ module ATDIS
|
|
45
56
|
def self.options_from_url(url)
|
46
57
|
u = URI.parse(url)
|
47
58
|
options = query_to_options(u.query)
|
48
|
-
[
|
59
|
+
%i[lodgement_date_start lodgement_date_end last_modified_date_start
|
60
|
+
last_modified_date_end].each do |k|
|
49
61
|
options[k] = Date.parse(options[k]) if options[k]
|
50
62
|
end
|
51
63
|
options[:page] = options[:page].to_i if options[:page]
|
52
64
|
# Remove invalid options
|
53
|
-
options.
|
54
|
-
|
55
|
-
options.delete(key)
|
56
|
-
end
|
65
|
+
options.each_key do |key|
|
66
|
+
options.delete(key) unless VALID_OPTIONS.include?(key)
|
57
67
|
end
|
58
68
|
options
|
59
69
|
end
|
60
70
|
|
61
71
|
def applications(options = {})
|
62
|
-
Models::Page.read_url(applications_url(options))
|
72
|
+
Models::Page.read_url(applications_url(options), timezone, ignore_ssl_certificate)
|
63
73
|
end
|
64
74
|
|
65
75
|
def application(id)
|
66
|
-
Models::Application.read_url(application_url(id))
|
76
|
+
Models::Application.read_url(application_url(id), timezone, ignore_ssl_certificate)
|
67
77
|
end
|
68
78
|
|
69
|
-
private
|
70
|
-
|
71
79
|
# Turn a query string of the form "foo=bar&hello=sir" to {foo: "bar", hello: sir"}
|
72
80
|
def self.query_to_options(query)
|
73
81
|
options = {}
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
options[key.to_sym] = (CGI::unescape(value) if value)
|
78
|
-
end
|
82
|
+
(query || "").split("&").each do |t|
|
83
|
+
key, value = t.split("=")
|
84
|
+
options[key.to_sym] = (CGI.unescape(value) if value)
|
79
85
|
end
|
80
86
|
options
|
81
87
|
end
|
82
88
|
|
83
89
|
# Escape but leave commas unchanged (which are valid in query strings)
|
84
|
-
def self.escape(
|
85
|
-
CGI
|
90
|
+
def self.escape(value)
|
91
|
+
CGI.escape(value.to_s).gsub("%2C", ",")
|
86
92
|
end
|
87
93
|
|
88
94
|
# Turn an options hash of the form {foo: "bar", hello: "sir"} into a query
|
@@ -91,7 +97,9 @@ module ATDIS
|
|
91
97
|
if options.empty?
|
92
98
|
nil
|
93
99
|
else
|
94
|
-
options.sort{|a,b| a.first.to_s <=> b.first.to_s
|
100
|
+
options.sort { |a, b| a.first.to_s <=> b.first.to_s }
|
101
|
+
.map { |k, v| "#{k}=#{escape(v)}" }
|
102
|
+
.join("&")
|
95
103
|
end
|
96
104
|
end
|
97
105
|
end
|
data/lib/atdis/model.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "multi_json"
|
4
|
+
require "active_model"
|
5
|
+
require "date"
|
4
6
|
|
5
7
|
module ATDIS
|
6
8
|
module TypeCastAttributes
|
@@ -11,14 +13,14 @@ module ATDIS
|
|
11
13
|
end
|
12
14
|
|
13
15
|
module ClassMethods
|
14
|
-
# of the form {section:
|
15
|
-
def
|
16
|
-
define_attribute_methods(
|
16
|
+
# of the form {section: Integer, address: String}
|
17
|
+
def field_mappings(params)
|
18
|
+
define_attribute_methods(params.keys.map(&:to_s))
|
17
19
|
# Convert all values to arrays. Doing this for the sake of tidier notation
|
18
20
|
self.attribute_types = {}
|
19
|
-
|
20
|
-
v = [v] unless v.
|
21
|
-
|
21
|
+
params.each do |k, v|
|
22
|
+
v = [v] unless v.is_a?(Array)
|
23
|
+
attribute_types[k] = v
|
22
24
|
end
|
23
25
|
end
|
24
26
|
end
|
@@ -40,10 +42,10 @@ module ATDIS
|
|
40
42
|
include Validators
|
41
43
|
include ActiveModel::AttributeMethods
|
42
44
|
include TypeCastAttributes
|
43
|
-
attribute_method_suffix
|
44
|
-
attribute_method_suffix
|
45
|
+
attribute_method_suffix "_before_type_cast"
|
46
|
+
attribute_method_suffix "="
|
45
47
|
|
46
|
-
attr_reader :attributes, :attributes_before_type_cast
|
48
|
+
attr_reader :attributes, :attributes_before_type_cast, :timezone
|
47
49
|
# Stores any part of the json that could not be interpreted. Usually
|
48
50
|
# signals an error if it isn't empty.
|
49
51
|
attr_accessor :json_left_overs, :json_load_error
|
@@ -54,7 +56,8 @@ module ATDIS
|
|
54
56
|
|
55
57
|
# Partition the data into used and unused by returning [used, unused]
|
56
58
|
def self.partition_by_used(data)
|
57
|
-
used
|
59
|
+
used = {}
|
60
|
+
unused = {}
|
58
61
|
if data.respond_to?(:each)
|
59
62
|
data.each do |key, value|
|
60
63
|
if attribute_keys.include?(key)
|
@@ -69,46 +72,54 @@ module ATDIS
|
|
69
72
|
[used, unused]
|
70
73
|
end
|
71
74
|
|
72
|
-
def self.
|
73
|
-
|
75
|
+
def self.read_url_raw(url, ignore_ssl_certificate = false)
|
76
|
+
RestClient::Resource.new(
|
77
|
+
url.to_s,
|
78
|
+
verify_ssl: (OpenSSL::SSL::VERIFY_NONE if ignore_ssl_certificate)
|
79
|
+
).get.to_str
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.read_url(url, timezone, ignore_ssl_certificate = false)
|
83
|
+
r = read_json(read_url_raw(url, ignore_ssl_certificate), timezone)
|
74
84
|
r.url = url.to_s
|
75
85
|
r
|
76
86
|
end
|
77
87
|
|
78
|
-
def self.read_json(text)
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
a
|
86
|
-
end
|
88
|
+
def self.read_json(text, timezone)
|
89
|
+
data = MultiJson.load(text, symbolize_keys: true)
|
90
|
+
interpret(data, timezone)
|
91
|
+
rescue MultiJson::LoadError => e
|
92
|
+
a = interpret({ response: [] }, timezone)
|
93
|
+
a.json_load_error = e.to_s
|
94
|
+
a
|
87
95
|
end
|
88
96
|
|
89
|
-
def self.interpret(
|
90
|
-
used, unused = partition_by_used(
|
91
|
-
new(used.merge(json_left_overs: unused))
|
97
|
+
def self.interpret(data, timezone)
|
98
|
+
used, unused = partition_by_used(data)
|
99
|
+
new(used.merge(json_left_overs: unused), timezone)
|
92
100
|
end
|
93
101
|
|
94
102
|
def json_loaded_correctly!
|
95
|
-
|
96
|
-
|
97
|
-
|
103
|
+
return unless json_load_error
|
104
|
+
|
105
|
+
errors.add(:json, ErrorMessage["Invalid JSON: #{json_load_error}", nil])
|
98
106
|
end
|
99
107
|
|
100
108
|
def json_errors_local
|
101
109
|
r = []
|
102
110
|
# First show special json error
|
103
|
-
if !errors[:json].empty?
|
104
|
-
r << [nil, errors[:json]]
|
105
|
-
end
|
106
111
|
errors.keys.each do |attribute|
|
112
|
+
r << [nil, errors[:json]] unless errors[:json].empty?
|
107
113
|
# The :json attribute is special
|
108
|
-
if attribute
|
109
|
-
|
110
|
-
|
111
|
-
|
114
|
+
next if attribute == :json
|
115
|
+
|
116
|
+
e = errors[attribute]
|
117
|
+
next if e.empty?
|
118
|
+
|
119
|
+
r << [
|
120
|
+
{ attribute => attributes_before_type_cast[attribute.to_s] },
|
121
|
+
e.map { |m| ErrorMessage["#{attribute} #{m}", m.spec_section] }
|
122
|
+
]
|
112
123
|
end
|
113
124
|
r
|
114
125
|
end
|
@@ -117,12 +128,11 @@ module ATDIS
|
|
117
128
|
r = []
|
118
129
|
attributes.each do |attribute_as_string, value|
|
119
130
|
attribute = attribute_as_string.to_sym
|
120
|
-
e = errors[attribute]
|
121
131
|
if value.respond_to?(:json_errors)
|
122
|
-
|
123
|
-
elsif value.
|
124
|
-
f = value.find{|v| v.respond_to?(:json_errors) && !v.json_errors.empty?}
|
125
|
-
r += f.json_errors.map{|a, b| [{attribute => [a]}, b]} if f
|
132
|
+
r += value.json_errors.map { |a, b| [{ attribute => a }, b] }
|
133
|
+
elsif value.is_a?(Array)
|
134
|
+
f = value.find { |v| v.respond_to?(:json_errors) && !v.json_errors.empty? }
|
135
|
+
r += f.json_errors.map { |a, b| [{ attribute => [a] }, b] } if f
|
126
136
|
end
|
127
137
|
end
|
128
138
|
r
|
@@ -133,22 +143,29 @@ module ATDIS
|
|
133
143
|
end
|
134
144
|
|
135
145
|
# Have we tried to use this attribute?
|
136
|
-
def used_attribute?(
|
137
|
-
!attributes_before_type_cast[
|
146
|
+
def used_attribute?(attribute)
|
147
|
+
!attributes_before_type_cast[attribute].nil?
|
138
148
|
end
|
139
149
|
|
140
150
|
def json_left_overs_is_empty
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
151
|
+
return unless json_left_overs && !json_left_overs.empty?
|
152
|
+
|
153
|
+
# We have extra parameters that shouldn't be there
|
154
|
+
errors.add(
|
155
|
+
:json,
|
156
|
+
ErrorMessage["Unexpected parameters in json data: #{MultiJson.dump(json_left_overs)}", "4"]
|
157
|
+
)
|
145
158
|
end
|
146
159
|
|
147
|
-
def initialize(params
|
148
|
-
@
|
160
|
+
def initialize(params, timezone)
|
161
|
+
@timezone = timezone
|
162
|
+
@attributes = {}
|
163
|
+
@attributes_before_type_cast = {}
|
164
|
+
return unless params
|
165
|
+
|
149
166
|
params.each do |attr, value|
|
150
|
-
|
151
|
-
end
|
167
|
+
send("#{attr}=", value)
|
168
|
+
end
|
152
169
|
end
|
153
170
|
|
154
171
|
def self.attribute_keys
|
@@ -157,68 +174,49 @@ module ATDIS
|
|
157
174
|
|
158
175
|
# Does what the equivalent on Activerecord does
|
159
176
|
def self.attribute_names
|
160
|
-
attribute_types.keys.map
|
177
|
+
attribute_types.keys.map(&:to_s)
|
161
178
|
end
|
162
179
|
|
163
|
-
def self.cast(value, type)
|
180
|
+
def self.cast(value, type, timezone)
|
164
181
|
# If it's already the correct type (or nil) then we don't need to do anything
|
165
|
-
if value.nil? || value.
|
182
|
+
if value.nil? || value.is_a?(type)
|
166
183
|
value
|
167
|
-
# Special handling for arrays. When we typecast arrays we actually
|
168
|
-
|
169
|
-
|
184
|
+
# Special handling for arrays. When we typecast arrays we actually
|
185
|
+
# typecast each member of the array
|
186
|
+
elsif value.is_a?(Array)
|
187
|
+
value.map { |v| cast(v, type, timezone) }
|
170
188
|
elsif type == DateTime
|
171
|
-
cast_datetime(value)
|
189
|
+
cast_datetime(value, timezone)
|
172
190
|
elsif type == URI
|
173
191
|
cast_uri(value)
|
174
192
|
elsif type == String
|
175
193
|
cast_string(value)
|
176
|
-
elsif type ==
|
177
|
-
|
194
|
+
elsif type == Integer
|
195
|
+
cast_integer(value)
|
178
196
|
elsif type == RGeo::GeoJSON
|
179
197
|
cast_geojson(value)
|
180
198
|
# Otherwise try to use Type.interpret to do the typecasting
|
181
199
|
elsif type.respond_to?(:interpret)
|
182
|
-
type.interpret(value) if value
|
200
|
+
type.interpret(value, timezone) if value
|
183
201
|
else
|
184
202
|
raise
|
185
203
|
end
|
186
204
|
end
|
187
205
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
end
|
197
|
-
|
198
|
-
def attribute=(attr, value)
|
199
|
-
@attributes_before_type_cast[attr] = value
|
200
|
-
@attributes[attr] = Model.cast(value, attribute_types[attr.to_sym][0])
|
201
|
-
end
|
202
|
-
|
203
|
-
def self.cast_datetime(value)
|
204
|
-
# This would be much easier if we knew we only had to support Ruby 1.9 or greater because it has
|
205
|
-
# an implementation built in. Because for the time being we need to support Ruby 1.8 as well
|
206
|
-
# we'll build an implementation of parsing by hand. Ugh.
|
207
|
-
# Referencing http://www.w3.org/TR/NOTE-datetime
|
208
|
-
# In section 4.3.1 of ATDIS 1.0.4 it shows two variants of iso 8601, either the full date
|
209
|
-
# or the full date with hours, seconds, minutes and timezone. We'll assume that these
|
210
|
-
# are the two variants that are allowed.
|
211
|
-
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))?$/)
|
212
|
-
DateTime.parse(value)
|
213
|
-
end
|
206
|
+
# If timezone is given in the string then the datetime is read in using
|
207
|
+
# the timezone in the string and then converted to the timezone "zone"
|
208
|
+
# If the timezone isn't given in the string then the datetime is read
|
209
|
+
# in using the timezone in "zone"
|
210
|
+
def self.cast_datetime(value, timezone)
|
211
|
+
ActiveSupport::TimeZone.new(timezone).iso8601(value).to_datetime
|
212
|
+
rescue ArgumentError, KeyError
|
213
|
+
nil
|
214
214
|
end
|
215
215
|
|
216
216
|
def self.cast_uri(value)
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
nil
|
221
|
-
end
|
217
|
+
URI.parse(value)
|
218
|
+
rescue URI::InvalidURIError
|
219
|
+
nil
|
222
220
|
end
|
223
221
|
|
224
222
|
def self.cast_string(value)
|
@@ -226,8 +224,8 @@ module ATDIS
|
|
226
224
|
end
|
227
225
|
|
228
226
|
# This casting allows nil values
|
229
|
-
def self.
|
230
|
-
value
|
227
|
+
def self.cast_integer(value)
|
228
|
+
value&.to_i
|
231
229
|
end
|
232
230
|
|
233
231
|
def self.cast_geojson(value)
|
@@ -246,5 +244,20 @@ module ATDIS
|
|
246
244
|
hash
|
247
245
|
end
|
248
246
|
end
|
247
|
+
|
248
|
+
private
|
249
|
+
|
250
|
+
def attribute(attr)
|
251
|
+
@attributes[attr]
|
252
|
+
end
|
253
|
+
|
254
|
+
def attribute_before_type_cast(attr)
|
255
|
+
@attributes_before_type_cast[attr]
|
256
|
+
end
|
257
|
+
|
258
|
+
def attribute=(attr, value)
|
259
|
+
@attributes_before_type_cast[attr] = value
|
260
|
+
@attributes[attr] = Model.cast(value, attribute_types[attr.to_sym][0], timezone)
|
261
|
+
end
|
249
262
|
end
|
250
263
|
end
|