metrojobb 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/feed-example.xml ADDED
@@ -0,0 +1,34 @@
1
+ <?xml version="1.0" encoding="UTF-8" ?>
2
+ <ads>
3
+ <ad orderno="">
4
+ <externalApplication></externalApplication>
5
+ <heading></heading>
6
+ <jobTitle></jobTitle>
7
+ <summary></summary>
8
+ <description></description>
9
+ <location>
10
+ <city></city>
11
+ </location>
12
+ <contact>
13
+ <name></name>
14
+ <phone></phone>
15
+ <email></email>
16
+ </contact>
17
+ <category>
18
+ <name></name>
19
+ </category>
20
+ <employmentType>
21
+ <name></name>
22
+ </employmentType>
23
+ <employer></employer>
24
+ <employerHomePage></employerHomePage>
25
+ <opportunities></opportunities>
26
+ <region>
27
+ <name></name>
28
+ </region>
29
+ <fromdate></fromdate>
30
+ <todate></todate>
31
+ <externalLogoUrl></externalLogoUrl>
32
+ <applicationURL></applicationURL>
33
+ </ad>
34
+ </ads>
@@ -0,0 +1,34 @@
1
+ <?xml version="1.0" encoding="UTF-8" ?>
2
+ <ads>
3
+ <ad orderno=""><!-- ordno Required attribute -->
4
+ <externalApplication>true</externalApplication>
5
+ <heading></heading><!-- Required -->
6
+ <jobTitle></jobTitle><!-- Required -->
7
+ <summary></summary><!-- Required -->
8
+ <description></description><!-- Required -->
9
+ <location>
10
+ <city></city>
11
+ </location><!-- Required locationType -->
12
+ <contact>
13
+ <name></name>
14
+ <phone></phone>
15
+ <email></email>
16
+ </contact><!-- contactType (not required) -->
17
+ <category>
18
+ <name></name>
19
+ </category><!-- Required idOrTextAttributeType -->
20
+ <employmentType>
21
+ <name></name>
22
+ </employmentType>
23
+ <employer></employer>
24
+ <employerHomePage></employerHomePage>
25
+ <opportunities></opportunities>
26
+ <region>
27
+ <name></name>
28
+ </region>
29
+ <fromdate></fromdate>
30
+ <todate></todate>
31
+ <externalLogoUrl></externalLogoUrl>
32
+ <applicationURL></applicationURL><!-- Required -->
33
+ </ad>
34
+ </ads>
data/feed-schema.xsd ADDED
@@ -0,0 +1,57 @@
1
+ <?xml version="1.0" encoding="UTF-8" ?>
2
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
3
+ <!-- Schema used when importing job ads to the Metro Job site. -->
4
+ <xs:element name="ads">
5
+ <xs:complexType>
6
+ <xs:sequence>
7
+ <xs:element ref="ad" maxOccurs="unbounded"/>
8
+ </xs:sequence>
9
+ </xs:complexType>
10
+ </xs:element>
11
+ <xs:element name="ad">
12
+ <xs:complexType>
13
+ <xs:all>
14
+ <xs:element name="externalApplication" type="xs:boolean" minOccurs="0"/>
15
+ <xs:element name="heading" type="xs:string" minOccurs="1"/>
16
+ <xs:element name="jobTitle" type="xs:string" minOccurs="1"/>
17
+ <xs:element name="summary" type="xs:string"/>
18
+ <xs:element name="description" type="xs:string" minOccurs="1"/>
19
+ <xs:element name="location" type="locationType"/>
20
+ <xs:element name="contact" type="contactType" minOccurs="0"/>
21
+ <xs:element name="category" type="idOrTextAttributeType"/>
22
+ <xs:element name="employmentType" type="idOrTextAttributeType"/>
23
+ <xs:element name="employer" type="xs:string" minOccurs="0"/>
24
+ <xs:element name="employerHomePage" type="xs:string"/>
25
+ <xs:element name="opportunities" type="xs:int" minOccurs="0"/>
26
+ <xs:element name="region" type="idOrTextAttributeType"/>
27
+ <xs:element name="fromdate" type="xs:string"/>
28
+ <xs:element name="todate" type="xs:string" minOccurs="0"/>
29
+ <xs:element name="logoFileName" type="xs:string" minOccurs="0"/>
30
+ <xs:element name="externalLogoUrl" type="xs:string" minOccurs="0"/>
31
+ <xs:element name="applicationURL" type="xs:string"/>
32
+ <xs:element name="videoUrl" type="xs:string" minOccurs="0"/>
33
+ </xs:all>
34
+ <xs:attribute name="orderno" type="xs:string" use="required"/>
35
+ </xs:complexType>
36
+ </xs:element>
37
+ <xs:complexType name="idOrTextAttributeType">
38
+ <xs:all>
39
+ <xs:element name="id" type="xs:string" minOccurs="0"/>
40
+ <xs:element name="name" type="xs:string" minOccurs="0"/>
41
+ </xs:all>
42
+ </xs:complexType>
43
+ <xs:complexType name="contactType">
44
+ <xs:all>
45
+ <xs:element name="name" minOccurs="0"/>
46
+ <xs:element name="phone" minOccurs="0"/>
47
+ <xs:element name="email" minOccurs="0"/>
48
+ </xs:all>
49
+ </xs:complexType>
50
+ <xs:complexType name="locationType">
51
+ <xs:all>
52
+ <xs:element name="street" minOccurs="0"/>
53
+ <xs:element name="postalCode" minOccurs="0"/>
54
+ <xs:element name="city" minOccurs="0"/>
55
+ </xs:all>
56
+ </xs:complexType>
57
+ </xs:schema>
data/lib/metrojobb.rb ADDED
@@ -0,0 +1,23 @@
1
+ require 'builder'
2
+
3
+ require 'metrojobb/version'
4
+ # models
5
+ require 'metrojobb/model'
6
+ require 'metrojobb/ad'
7
+ require 'metrojobb/category'
8
+ require 'metrojobb/contact'
9
+ require 'metrojobb/employment_type'
10
+ require 'metrojobb/location'
11
+ require 'metrojobb/region'
12
+
13
+ module Metrojobb
14
+ ORDER_NUMBER = '<<order_number>>'
15
+
16
+ def self.build_ad(data, order_number: ORDER_NUMBER)
17
+ builder = Builder::XmlMarkup.new(indent: 2)
18
+ builder.ads do |ad_node|
19
+ ad_node.ad(ordno: order_number)
20
+ ad_node.ad(ordno: order_number)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,150 @@
1
+ require 'date'
2
+ require 'active_model'
3
+
4
+ module Metrojobb
5
+ class Ad < Model
6
+ YYYY_MM_DD_REGEX = /\d{4}-\d{2}-\d{2}/
7
+
8
+ INVALID_DATE_FORMAT_MSG = "must be in the following format: YYYY-MM-DD"
9
+ TYPE_ERROR_MSG = 'unknown type'
10
+
11
+ attr_accessor *[
12
+ :order_number,
13
+ :external_application,
14
+ :heading,
15
+ :job_title,
16
+ :summary,
17
+ :description,
18
+ :employer,
19
+ :employer_home_page,
20
+ :opportunities,
21
+ :from_date,
22
+ :to_date,
23
+ :external_logo_url,
24
+ :application_url,
25
+ # relations
26
+ :location,
27
+ :contact,
28
+ :employment_type,
29
+ :category,
30
+ :region
31
+ ]
32
+
33
+ validates :order_number, presence: true
34
+ validates :heading, presence: true
35
+ validates :job_title, presence: true
36
+ validates :summary, presence: true
37
+ validates :description, presence: true
38
+ validates :location, presence: true
39
+ validates :category, presence: true
40
+ validates :region, presence: true
41
+
42
+ validates_format_of :from_date, with: YYYY_MM_DD_REGEX, message: INVALID_DATE_FORMAT_MSG
43
+ validates_format_of :to_date, with: YYYY_MM_DD_REGEX, message: INVALID_DATE_FORMAT_MSG
44
+
45
+ validate :validate_location_type
46
+ validate :validate_contact_type
47
+ validate :validate_employment_type_type
48
+ validate :validate_category_type
49
+ validate :validate_region_type
50
+
51
+ validate :validate_location_valid
52
+ validate :validate_contact_valid
53
+ validate :validate_employment_type_valid
54
+ validate :validate_category_valid
55
+ validate :validate_region_valid
56
+
57
+ def to_xml(builder: Builder::XmlMarkup.new(indent: 2))
58
+ builder.ad(orderno: order_number) do |node|
59
+ node.externalApplication(external_application)
60
+ node.heading(heading)
61
+ node.jobTitle(job_title)
62
+ node.summary(summary)
63
+ node.description(description)
64
+ node.employer(employer)
65
+ node.employerHomePage(employer_home_page)
66
+ node.opportunities(opportunities)
67
+ node.fromdate(from_date)
68
+ node.todate(to_date)
69
+ node.externalLogoUrl(external_logo_url)
70
+ node.applicationURL(application_url)
71
+
72
+ location.to_xml(builder: node) if location
73
+ contact.to_xml(builder: node) if contact
74
+ employment_type.to_xml(builder: node) if employment_type
75
+ category.to_xml(builder: node) if category
76
+ region.to_xml(builder: node) if region
77
+ end
78
+ end
79
+
80
+ def validate_location_type
81
+ return unless location
82
+ return if location.is_a?(Location)
83
+
84
+ errors.add(:location, TYPE_ERROR_MSG)
85
+ end
86
+
87
+ def validate_contact_type
88
+ return unless contact
89
+ return if contact.is_a?(Contact)
90
+
91
+ errors.add(:contact, TYPE_ERROR_MSG)
92
+ end
93
+
94
+ def validate_employment_type_type
95
+ return unless employment_type
96
+ return if employment_type.is_a?(EmploymentType)
97
+
98
+ errors.add(:employment_type, TYPE_ERROR_MSG)
99
+ end
100
+
101
+ def validate_category_type
102
+ return unless category
103
+ return if category.is_a?(Category)
104
+
105
+ errors.add(:category, TYPE_ERROR_MSG)
106
+ end
107
+
108
+ def validate_region_type
109
+ return unless region
110
+ return if region.is_a?(Region)
111
+
112
+ errors.add(:region, TYPE_ERROR_MSG)
113
+ end
114
+
115
+ def validate_location_valid
116
+ return unless location.respond_to?(:valid?)
117
+ return if location.valid?
118
+
119
+ errors.add(:location, :invalid)
120
+ end
121
+
122
+ def validate_contact_valid
123
+ return unless contact.respond_to?(:valid?)
124
+ return if contact.valid?
125
+
126
+ errors.add(:contact, :invalid)
127
+ end
128
+
129
+ def validate_employment_type_valid
130
+ return unless employment_type.respond_to?(:valid?)
131
+ return if employment_type.valid?
132
+
133
+ errors.add(:employment_type, :invalid)
134
+ end
135
+
136
+ def validate_category_valid
137
+ return unless category.respond_to?(:valid?)
138
+ return if category.valid?
139
+
140
+ errors.add(:category, :invalid)
141
+ end
142
+
143
+ def validate_region_valid
144
+ return unless region.respond_to?(:valid?)
145
+ return if region.valid?
146
+
147
+ errors.add(:region, :invalid)
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,34 @@
1
+ require 'csv'
2
+ require 'active_model'
3
+
4
+ module Metrojobb
5
+ class Category < Model
6
+ attr_accessor :id, :name
7
+
8
+ NAME_ID_MAP = CSV.read(
9
+ File.expand_path('../../../data/categories.csv', __FILE__)
10
+ ).to_h.invert.freeze
11
+
12
+ ID_NAME_MAP = NAME_ID_MAP.invert.freeze
13
+
14
+ validate :validate_known_category
15
+
16
+ def to_xml(builder: Builder::XmlMarkup.new(indent: 2))
17
+ builder.category do |node|
18
+ node.id(category_id)
19
+ end
20
+ end
21
+
22
+ def category_id
23
+ NAME_ID_MAP[name.presence || id.presence] ||
24
+ id.presence ||
25
+ name.presence
26
+ end
27
+
28
+ def validate_known_category
29
+ return if ID_NAME_MAP[category_id]
30
+
31
+ errors.add(:category_id, :inclusion)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,15 @@
1
+ require 'active_model'
2
+
3
+ module Metrojobb
4
+ class Contact < Model
5
+ attr_accessor :name, :phone, :email
6
+
7
+ def to_xml(builder: Builder::XmlMarkup.new(indent: 2))
8
+ builder.contact do |node|
9
+ node.name(name) if name.present?
10
+ node.phone(phone) if phone.present?
11
+ node.email(email) if email.present?
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,33 @@
1
+ require 'active_model'
2
+
3
+ module Metrojobb
4
+ class EmploymentType < Model
5
+ attr_accessor :id, :name
6
+
7
+ NAME_ID_MAP = CSV.read(
8
+ File.expand_path('../../../data/employmenttypes.csv', __FILE__)
9
+ ).to_h.invert.freeze
10
+
11
+ ID_NAME_MAP = NAME_ID_MAP.invert.freeze
12
+
13
+ validate :validate_known_employment_type
14
+
15
+ def to_xml(builder: Builder::XmlMarkup.new(indent: 2))
16
+ builder.employmentType do |node|
17
+ node.id(metrojobb_id)
18
+ end
19
+ end
20
+
21
+ def metrojobb_id
22
+ NAME_ID_MAP[name.presence || id.presence] ||
23
+ id.presence ||
24
+ name.presence
25
+ end
26
+
27
+ def validate_known_employment_type
28
+ return if ID_NAME_MAP[metrojobb_id]
29
+
30
+ errors.add(:metrojobb_id, :inclusion)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,15 @@
1
+ require 'active_model'
2
+
3
+ module Metrojobb
4
+ class Location < Model
5
+ attr_accessor :street, :postal_code, :city
6
+
7
+ def to_xml(builder: Builder::XmlMarkup.new(indent: 2))
8
+ builder.location do |node|
9
+ node.street(street) if street.present?
10
+ node.postalCode(postal_code) if postal_code.present?
11
+ node.city(city) if city.present?
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ require 'active_model'
2
+
3
+ module Metrojobb
4
+ class Model
5
+ include ActiveModel::Model
6
+
7
+ InvalidError = Class.new(StandardError)
8
+
9
+ def to_xml!
10
+ return to_xml if valid?
11
+
12
+ error_message = "#{model_name} has errors on: #{self.errors.keys.join(', ')}"
13
+ raise(InvalidError, error_message)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,33 @@
1
+ require 'active_model'
2
+
3
+ module Metrojobb
4
+ class Region < Model
5
+ attr_accessor :id, :name
6
+
7
+ NAME_ID_MAP = CSV.read(
8
+ File.expand_path('../../../data/regions.csv', __FILE__)
9
+ ).to_h.invert.freeze
10
+
11
+ ID_NAME_MAP = NAME_ID_MAP.invert.freeze
12
+
13
+ validate :validate_known_region
14
+
15
+ def to_xml(builder: Builder::XmlMarkup.new(indent: 2))
16
+ builder.region do |node|
17
+ node.id(region_id)
18
+ end
19
+ end
20
+
21
+ def region_id
22
+ NAME_ID_MAP[name.presence || id.presence] ||
23
+ id.presence ||
24
+ name.presence
25
+ end
26
+
27
+ def validate_known_region
28
+ return if ID_NAME_MAP[region_id]
29
+
30
+ errors.add(:region_id, :inclusion)
31
+ end
32
+ end
33
+ end