atdis 0.2 → 0.3

Sign up to get free protection for your applications and to get access to all the features.
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