metrojobb 0.1.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.
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