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.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +0 -1
  4. data/Gemfile +1 -1
  5. data/README.md +25 -2
  6. data/Rakefile +1 -1
  7. data/docs/ATDIS-1.0.2 Application Tracking Data Interchange Specification (v1.0.2).doc +0 -0
  8. data/docs/ATDIS-1.0.2 Application Tracking Data Interchange Specification (v1.0.2).pdf +0 -0
  9. data/lib/atdis.rb +2 -7
  10. data/lib/atdis/feed.rb +80 -8
  11. data/lib/atdis/model.rb +74 -128
  12. data/lib/atdis/models/address.rb +17 -0
  13. data/lib/atdis/models/application.rb +31 -0
  14. data/lib/atdis/models/authority.rb +19 -0
  15. data/lib/atdis/models/document.rb +16 -0
  16. data/lib/atdis/models/event.rb +16 -0
  17. data/lib/atdis/models/info.rb +81 -0
  18. data/lib/atdis/models/land_title_ref.rb +26 -0
  19. data/lib/atdis/models/location.rb +23 -0
  20. data/lib/atdis/models/page.rb +56 -0
  21. data/lib/atdis/models/pagination.rb +68 -0
  22. data/lib/atdis/models/person.rb +14 -0
  23. data/lib/atdis/models/reference.rb +13 -0
  24. data/lib/atdis/models/response.rb +16 -0
  25. data/lib/atdis/models/torrens_title.rb +24 -0
  26. data/lib/atdis/validators.rb +26 -10
  27. data/lib/atdis/version.rb +1 -1
  28. data/spec/atdis/feed_spec.rb +78 -22
  29. data/spec/atdis/model_spec.rb +80 -131
  30. data/spec/atdis/models/address_spec.rb +22 -0
  31. data/spec/atdis/models/application_spec.rb +246 -0
  32. data/spec/atdis/models/authority_spec.rb +34 -0
  33. data/spec/atdis/models/document_spec.rb +19 -0
  34. data/spec/atdis/models/event_spec.rb +29 -0
  35. data/spec/atdis/models/info_spec.rb +303 -0
  36. data/spec/atdis/models/land_title_ref_spec.rb +39 -0
  37. data/spec/atdis/models/location_spec.rb +95 -0
  38. data/spec/atdis/models/page_spec.rb +296 -0
  39. data/spec/atdis/models/pagination_spec.rb +153 -0
  40. data/spec/atdis/models/person_spec.rb +19 -0
  41. data/spec/atdis/models/reference_spec.rb +55 -0
  42. data/spec/atdis/models/response_spec.rb +5 -0
  43. data/spec/atdis/models/torrens_title_spec.rb +52 -0
  44. data/spec/atdis/separated_url_spec.rb +4 -4
  45. metadata +141 -135
  46. data/docs/ATDIS-1.0.7 Application Tracking Data Interchange Specification (v1.0).doc +0 -0
  47. data/docs/ATDIS-1.0.7 Application Tracking Data Interchange Specification (v1.0).pdf +0 -0
  48. data/lib/atdis/application.rb +0 -78
  49. data/lib/atdis/document.rb +0 -14
  50. data/lib/atdis/event.rb +0 -17
  51. data/lib/atdis/location.rb +0 -21
  52. data/lib/atdis/page.rb +0 -130
  53. data/lib/atdis/person.rb +0 -12
  54. data/spec/atdis/application_spec.rb +0 -539
  55. data/spec/atdis/document_spec.rb +0 -19
  56. data/spec/atdis/event_spec.rb +0 -29
  57. data/spec/atdis/location_spec.rb +0 -148
  58. data/spec/atdis/page_spec.rb +0 -492
  59. data/spec/atdis/person_spec.rb +0 -19
@@ -0,0 +1,17 @@
1
+ module ATDIS
2
+ module Models
3
+ class Address < Model
4
+ set_field_mappings ({
5
+ street: String,
6
+ suburb: String,
7
+ postcode: String,
8
+ state: String
9
+ })
10
+
11
+ # Mandatory parameters
12
+ validates :street, :suburb, :postcode, :state, presence_before_type_cast: {spec_section: "4.3.3"}
13
+
14
+ validates :postcode, format: { with: /\A[0-9]{4}\z/, message: ATDIS::ErrorMessage.new("is not a valid postcode", "4.3.3")}
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,31 @@
1
+ require "atdis/models/info"
2
+ require "atdis/models/reference"
3
+ require "atdis/models/location"
4
+ require "atdis/models/event"
5
+ require "atdis/models/document"
6
+ require "atdis/models/person"
7
+
8
+ module ATDIS
9
+ module Models
10
+ class Application < Model
11
+ set_field_mappings ({
12
+ info: Info,
13
+ reference: Reference,
14
+ locations: Location,
15
+ events: Event,
16
+ documents: Document,
17
+ people: Person,
18
+ extended: Object,
19
+ })
20
+
21
+ # Mandatory attributes
22
+ validates :info, :reference, :locations, :events, :documents, presence_before_type_cast: {spec_section: "4.3"}
23
+
24
+ validates :people, array: {spec_section: "4.3"}
25
+ validates :locations, :events, :documents, filled_array: {spec_section: "4.3"}
26
+
27
+ # This model is only valid if the children are valid
28
+ validates :info, :reference, :locations, :events, :documents, :people, valid: true
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,19 @@
1
+ module ATDIS
2
+ module Models
3
+ class Authority < Model
4
+ set_field_mappings ({
5
+ ref: URI,
6
+ name: String
7
+ })
8
+
9
+ # ref is a "Unique Authority Identifier" and should have the form http://www.council.nsw.gov.au/atdis/1.0
10
+ # It should also be consistent for each council.
11
+
12
+ # Mandatory attributes
13
+ validates :ref, :name, presence_before_type_cast: {spec_section: "4.3.1"}
14
+
15
+ validates :ref, http_url: {spec_section: "4.3.1"}
16
+ validates :ref, format: { with: /atdis\/1.0\z/, message: ATDIS::ErrorMessage.new("is not a valid Unique Authority Identifier", "4.3.1")}
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,16 @@
1
+ module ATDIS
2
+ module Models
3
+ class Document < Model
4
+ set_field_mappings ({
5
+ ref: String,
6
+ title: String,
7
+ document_url: URI
8
+ })
9
+
10
+ # Mandatory parameters
11
+ validates :ref, :title, :document_url, presence_before_type_cast: {spec_section: "4.3.5"}
12
+ # Other validations
13
+ validates :document_url, http_url: {spec_section: "4.3.5"}
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module ATDIS
2
+ module Models
3
+ class Event < Model
4
+ set_field_mappings ({
5
+ id: String,
6
+ timestamp: DateTime,
7
+ description: String,
8
+ event_type: String,
9
+ status: String
10
+ })
11
+
12
+ # Mandatory parameters
13
+ validates :id, :timestamp, :description, presence_before_type_cast: {spec_section: "4.3.4"}
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,81 @@
1
+ require "atdis/models/authority"
2
+
3
+ module ATDIS
4
+ module Models
5
+ class Info < Model
6
+ set_field_mappings ({
7
+ dat_id: String,
8
+ development_type: String,
9
+ application_type: String,
10
+ last_modified_date: DateTime,
11
+ description: String,
12
+ authority: Authority,
13
+ lodgement_date: DateTime,
14
+ determination_date: DateTime,
15
+ determination_type: String,
16
+ status: String,
17
+ notification_start_date: DateTime,
18
+ notification_end_date: DateTime,
19
+ officer: String,
20
+ estimated_cost: String,
21
+ related_apps: URI
22
+ })
23
+
24
+ # Mandatory parameters
25
+ # determination_date is not in this list because even though it is mandatory
26
+ # it can be null if there is no value
27
+ validates :dat_id, :development_type, :last_modified_date, :description,
28
+ :authority, :lodgement_date, :status,
29
+ presence_before_type_cast: {spec_section: "4.3.1"}
30
+ # Other validations
31
+ validates :application_type, inclusion: { in: [
32
+ "DA", "CDC", "S96", "Review", "Appeal", "Other"
33
+ ],
34
+ message: ATDIS::ErrorMessage.new("does not have one of the allowed types", "4.3.1")}
35
+ validates :last_modified_date, :lodgement_date, :determination_date,
36
+ :notification_start_date, :notification_end_date,
37
+ date_time: {spec_section: "4.3.1"}
38
+ # We don't need to separately validate presence because this covers it
39
+ validates :determination_type, inclusion: { in: [
40
+ "Pending", "Refused by Council", "Refused under delegation", "Withdrawn",
41
+ "Approved by Council", "Approved under delegation", "Rejected"
42
+ ],
43
+ message: ATDIS::ErrorMessage.new("does not have one of the allowed types", "4.3.1")
44
+ }
45
+ validate :notification_dates_consistent!
46
+ validates :related_apps, array: {spec_section: "4.3.1"}
47
+ validates :related_apps, array_http_url: {spec_section: "4.3.1"}
48
+ validate :related_apps_url_format
49
+ validate :dat_id_is_url_encoded!
50
+
51
+ # This model is only valid if the children are valid
52
+ validates :authority, valid: true
53
+
54
+ # TODO Validate contents of estimated_cost
55
+
56
+ def dat_id_is_url_encoded!
57
+ if dat_id && CGI::escape(dat_id) != dat_id
58
+ errors.add(:dat_id, ErrorMessage.new("should be url encoded", "4.3.1"))
59
+ end
60
+ end
61
+
62
+ def related_apps_url_format
63
+ if related_apps.respond_to?(:all?) && !related_apps.all? {|url| url.to_s =~ /atdis\/1.0\/[^\/]+\.json/}
64
+ errors.add(:related_apps, ErrorMessage.new("contains url(s) not in the expected format", "4.3.1"))
65
+ end
66
+ end
67
+
68
+ def notification_dates_consistent!
69
+ if notification_start_date_before_type_cast && notification_end_date_before_type_cast.blank?
70
+ errors.add(:notification_end_date, ErrorMessage["can not be blank if notification_start_date is set", "4.3.1"])
71
+ end
72
+ if notification_start_date_before_type_cast.blank? && notification_end_date_before_type_cast
73
+ errors.add(:notification_start_date, ErrorMessage["can not be blank if notification_end_date is set", "4.3.1"])
74
+ end
75
+ if notification_start_date && notification_end_date && notification_start_date > notification_end_date
76
+ errors.add(:notification_end_date, ErrorMessage["can not be earlier than notification_start_date", "4.3.1"])
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,26 @@
1
+ require "atdis/models/torrens_title"
2
+
3
+ module ATDIS
4
+ module Models
5
+ class LandTitleRef < Model
6
+ set_field_mappings ({
7
+ torrens: TorrensTitle,
8
+ other: Hash
9
+ })
10
+
11
+ # This model is only valid if the children are valid
12
+ validates :torrens, valid: true
13
+
14
+ validate :check_title_presence
15
+
16
+ def check_title_presence
17
+ if torrens.nil? && other.nil?
18
+ errors.add(:torrens, ATDIS::ErrorMessage.new("or other needs be present", "4.3.3"))
19
+ end
20
+ if torrens && other
21
+ errors.add(:torrens, ATDIS::ErrorMessage.new("and other can't both be present", "4.3.3"))
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,23 @@
1
+ require 'atdis/models/address'
2
+ require "atdis/models/land_title_ref"
3
+ require "rgeo/geo_json"
4
+
5
+ module ATDIS
6
+ module Models
7
+ class Location < Model
8
+ set_field_mappings ({
9
+ address: Address,
10
+ land_title_ref: LandTitleRef,
11
+ geometry: RGeo::GeoJSON
12
+ })
13
+
14
+ # Mandatory parameters
15
+ validates :address, :land_title_ref, presence_before_type_cast: {spec_section: "4.3.3"}
16
+
17
+ validates :geometry, geo_json: {spec_section: "4.3.3"}
18
+
19
+ # This model is only valid if the children are valid
20
+ validates :address, :land_title_ref, valid: true
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,56 @@
1
+ require "atdis/models/response"
2
+ require "atdis/models/pagination"
3
+
4
+ module ATDIS
5
+ module Models
6
+ class Page < Model
7
+ set_field_mappings ({
8
+ response: Response,
9
+ count: Fixnum,
10
+ pagination: Pagination,
11
+ })
12
+
13
+ # Mandatory parameters
14
+ validates :response, presence_before_type_cast: {spec_section: "4.3"}
15
+ # section 6.5 is not explicitly about this but it does contain an example which should be helpful
16
+ validates :response, array: {spec_section: "6.4"}
17
+ validate :count_is_consistent, :all_pagination_is_present
18
+
19
+ # This model is only valid if the children are valid
20
+ validates :response, valid: true
21
+ validates :pagination, valid: true
22
+
23
+ # If some of the pagination fields are present all of the required ones should be present
24
+ def all_pagination_is_present
25
+ if pagination && count.nil?
26
+ errors.add(:count, ErrorMessage["should be present if pagination is being used", "6.4"])
27
+ end
28
+ end
29
+
30
+ def count_is_consistent
31
+ if count
32
+ errors.add(:count, ErrorMessage["is not the same as the number of applications returned", "6.4"]) if count != response.count
33
+ errors.add(:count, ErrorMessage["should not be larger than the number of results per page", "6.4"]) if count > pagination.per_page
34
+ end
35
+ end
36
+
37
+ def previous_url
38
+ raise "Can't use previous_url when loaded with read_json" if url.nil?
39
+ ATDIS::SeparatedURL.merge(url, page: pagination.previous) if pagination && pagination.previous
40
+ end
41
+
42
+ def next_url
43
+ raise "Can't use next_url when loaded with read_json" if url.nil?
44
+ ATDIS::SeparatedURL.merge(url, page: pagination.next) if pagination && pagination.next
45
+ end
46
+
47
+ def previous_page
48
+ Page.read_url(previous_url) if previous_url
49
+ end
50
+
51
+ def next_page
52
+ Page.read_url(next_url) if next_url
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,68 @@
1
+ module ATDIS
2
+ module Models
3
+ class Pagination < Model
4
+ set_field_mappings ({
5
+ previous: Fixnum,
6
+ next: Fixnum,
7
+ current: Fixnum,
8
+ per_page: Fixnum,
9
+ count: Fixnum,
10
+ pages: Fixnum
11
+ })
12
+
13
+ validate :all_pagination_is_present, :previous_is_consistent,
14
+ :next_is_consistent, :current_is_consistent,
15
+ :count_is_consistent
16
+
17
+ # If some of the pagination fields are present all of the required ones should be present
18
+ def all_pagination_is_present
19
+ errors.add(:current, ErrorMessage["should be present if pagination is being used", "6.4"]) if current.nil?
20
+ errors.add(:per_page, ErrorMessage["should be present if pagination is being used", "6.4"]) if per_page.nil?
21
+ errors.add(:count, ErrorMessage["should be present if pagination is being used", "6.4"]) if count.nil?
22
+ errors.add(:pages, ErrorMessage["should be present if pagination is being used", "6.4"]) if pages.nil?
23
+ end
24
+
25
+ def previous_is_consistent
26
+ if previous && current && previous != current - 1
27
+ errors.add(:previous, ErrorMessage["should be one less than current page number or null if first page", "6.4"])
28
+ end
29
+ if previous && current && current == 1
30
+ errors.add(:previous, ErrorMessage["should be null if on the first page", "6.4"])
31
+ end
32
+ if previous.nil? && current && current > 1
33
+ errors.add(:previous, ErrorMessage["can't be null if not on the first page", "6.4"])
34
+ end
35
+ end
36
+
37
+ def next_is_consistent
38
+ if self.next && current && self.next != current + 1
39
+ errors.add(:next, ErrorMessage["should be one greater than current page number or null if last page", "6.4"])
40
+ end
41
+ if self.next.nil? && current != pages
42
+ errors.add(:next, ErrorMessage["can't be null if not on the last page", "6.4"])
43
+ end
44
+ if self.next && current == pages
45
+ errors.add(:next, ErrorMessage["should be null if on the last page", "6.4"])
46
+ end
47
+ end
48
+
49
+ def current_is_consistent
50
+ if current && pages && current > pages
51
+ errors.add(:current, ErrorMessage["is larger than the number of pages", "6.4"])
52
+ end
53
+ if current && current < 1
54
+ errors.add(:current, ErrorMessage["can not be less than 1", "6.4"])
55
+ end
56
+ end
57
+
58
+ def count_is_consistent
59
+ if pages && per_page && count && count > pages * per_page
60
+ errors.add(:count, ErrorMessage["is larger than can be retrieved through paging", "6.4"])
61
+ end
62
+ if pages && per_page && count && count <= (pages - 1) * per_page
63
+ errors.add(:count, ErrorMessage["could fit into a smaller number of pages", "6.4"])
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,14 @@
1
+ module ATDIS
2
+ module Models
3
+ class Person < Model
4
+ set_field_mappings ({
5
+ name: String,
6
+ role: String,
7
+ contact: String
8
+ })
9
+
10
+ # Mandatory parameters
11
+ validates :name, :role, presence_before_type_cast: {spec_section: "4.3.6"}
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ module ATDIS
2
+ module Models
3
+ class Reference < Model
4
+ set_field_mappings ({
5
+ more_info_url: URI,
6
+ comments_url: URI
7
+ })
8
+
9
+ validates :more_info_url, presence_before_type_cast: {spec_section: "4.3.2"}
10
+ validates :more_info_url, :comments_url, http_url: {spec_section: "4.3.2"}
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ require "atdis/models/application"
2
+
3
+ module ATDIS
4
+ module Models
5
+ class Response < Model
6
+ set_field_mappings ({
7
+ application: Application
8
+ })
9
+
10
+ validates :application, presence_before_type_cast: {spec_section: "4.3"}
11
+
12
+ # This model is only valid if the children are valid
13
+ validates :application, valid: true
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,24 @@
1
+ module ATDIS
2
+ module Models
3
+ class TorrensTitle < Model
4
+ set_field_mappings ({
5
+ lot: String,
6
+ section: String,
7
+ dpsp_id: String
8
+ })
9
+
10
+ # Mandatory attributes
11
+ # section is not in this list because it can be null (even though it is mandatory)
12
+ validates :lot, :dpsp_id, presence_before_type_cast: {spec_section: "4.3.3"}
13
+ # TODO: Provide warning if dpsp_id doesn't start with "DP" or "SP"
14
+
15
+ validate :section_can_not_be_empty_string
16
+
17
+ def section_can_not_be_empty_string
18
+ if section == ""
19
+ errors.add(:section, ATDIS::ErrorMessage.new("can't be blank", "4.3.3"))
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -22,35 +22,51 @@ module ATDIS
22
22
  end
23
23
  end
24
24
 
25
- class DateTimeOrNoneValidator < ActiveModel::EachValidator
25
+ class HttpUrlValidator < ActiveModel::EachValidator
26
26
  def validate_each(record, attribute, value)
27
27
  raw_value = record.send("#{attribute}_before_type_cast")
28
- if raw_value.present? && raw_value != "none" && !value.kind_of?(DateTime)
29
- message = "is not a valid date or none"
28
+ if raw_value.present? && !value.kind_of?(URI::HTTP) && !value.kind_of?(URI::HTTPS)
29
+ message = "is not a valid URL"
30
30
  message = ErrorMessage[message, options[:spec_section]] if options[:spec_section]
31
31
  record.errors.add(attribute, message)
32
32
  end
33
33
  end
34
34
  end
35
35
 
36
- class HttpUrlValidator < ActiveModel::EachValidator
36
+ class ArrayHttpUrlValidator < ActiveModel::EachValidator
37
37
  def validate_each(record, attribute, value)
38
- raw_value = record.send("#{attribute}_before_type_cast")
39
- if raw_value.present? && !value.kind_of?(URI::HTTP) && !value.kind_of?(URI::HTTPS)
40
- message = "is not a valid URL"
38
+ if value.present? && value.kind_of?(Array) &&
39
+ value.any?{|v| !v.kind_of?(URI::HTTP) && !v.kind_of?(URI::HTTPS)}
40
+ message = "contains an invalid URL"
41
+ message = ErrorMessage[message, options[:spec_section]] if options[:spec_section]
42
+ record.errors.add(attribute, message)
43
+ end
44
+ end
45
+ end
46
+
47
+ class ArrayValidator < ActiveModel::EachValidator
48
+ def validate_each(record, attribute, value)
49
+ if value && !value.kind_of?(Array)
50
+ message = "should be an array"
41
51
  message = ErrorMessage[message, options[:spec_section]] if options[:spec_section]
42
52
  record.errors.add(attribute, message)
43
53
  end
44
54
  end
45
55
  end
46
56
 
47
- class ArrayValidator < ActiveModel::EachValidator
57
+ # Can't be an empty array
58
+ class FilledArrayValidator < ActiveModel::EachValidator
48
59
  def validate_each(record, attribute, value)
49
60
  if value && !value.kind_of?(Array)
50
61
  message = "should be an array"
51
62
  message = ErrorMessage[message, options[:spec_section]] if options[:spec_section]
52
63
  record.errors.add(attribute, message)
53
64
  end
65
+ if value && value.kind_of?(Array) && value.empty?
66
+ message = "should not be an empty array"
67
+ message = ErrorMessage[message, options[:spec_section]] if options[:spec_section]
68
+ record.errors.add(attribute, message)
69
+ end
54
70
  end
55
71
  end
56
72
 
@@ -59,7 +75,7 @@ module ATDIS
59
75
  def validate_each(record, attribute, value)
60
76
  raw_value = record.send("#{attribute}_before_type_cast")
61
77
  if !raw_value.kind_of?(Array) && !raw_value.present?
62
- message = "can't be blank"
78
+ message = "can't be blank"
63
79
  message = ErrorMessage[message, options[:spec_section]] if options[:spec_section]
64
80
  record.errors.add(attribute, message)
65
81
  end
@@ -70,7 +86,7 @@ module ATDIS
70
86
  class ValidValidator < ActiveModel::EachValidator
71
87
  def validate_each(record, attribute, value)
72
88
  if (value.respond_to?(:valid?) && !value.valid?) || (value && !value.respond_to?(:valid?) && !value.all?{|v| v.valid?})
73
- record.errors.add(attribute, ErrorMessage["is not valid", nil])
89
+ record.errors.add(attribute, ErrorMessage["is not valid (see further errors for details)", nil])
74
90
  end
75
91
  end
76
92
  end