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.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +53 -0
- data/LICENSE.txt +21 -0
- data/README.md +101 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/data/categories.csv +362 -0
- data/data/employmenttypes.csv +4 -0
- data/data/regions.csv +586 -0
- data/feed-example.xml +34 -0
- data/feed-gem-default.xml +34 -0
- data/feed-schema.xsd +57 -0
- data/lib/metrojobb.rb +23 -0
- data/lib/metrojobb/ad.rb +150 -0
- data/lib/metrojobb/category.rb +34 -0
- data/lib/metrojobb/contact.rb +15 -0
- data/lib/metrojobb/employment_type.rb +33 -0
- data/lib/metrojobb/location.rb +15 -0
- data/lib/metrojobb/model.rb +16 -0
- data/lib/metrojobb/region.rb +33 -0
- data/lib/metrojobb/version.rb +3 -0
- data/metrojobb.gemspec +31 -0
- metadata +154 -0
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
|
data/lib/metrojobb/ad.rb
ADDED
@@ -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
|