rocketjob 3.4.3 → 3.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +27 -0
  3. data/bin/rocketjob +1 -1
  4. data/lib/rocket_job/active_worker.rb +4 -3
  5. data/lib/rocket_job/cli.rb +13 -12
  6. data/lib/rocket_job/config.rb +17 -13
  7. data/lib/rocket_job/dirmon_entry.rb +88 -91
  8. data/lib/rocket_job/extensions/mongo/logging.rb +8 -4
  9. data/lib/rocket_job/extensions/mongoid/factory.rb +8 -6
  10. data/lib/rocket_job/extensions/rocket_job_adapter.rb +2 -4
  11. data/lib/rocket_job/heartbeat.rb +7 -8
  12. data/lib/rocket_job/job_exception.rb +6 -5
  13. data/lib/rocket_job/jobs/dirmon_job.rb +5 -7
  14. data/lib/rocket_job/jobs/housekeeping_job.rb +3 -2
  15. data/lib/rocket_job/jobs/on_demand_job.rb +104 -0
  16. data/lib/rocket_job/jobs/simple_job.rb +0 -2
  17. data/lib/rocket_job/jobs/upload_file_job.rb +100 -0
  18. data/lib/rocket_job/performance.rb +15 -10
  19. data/lib/rocket_job/plugins/cron.rb +7 -124
  20. data/lib/rocket_job/plugins/document.rb +8 -10
  21. data/lib/rocket_job/plugins/job/callbacks.rb +0 -1
  22. data/lib/rocket_job/plugins/job/logger.rb +0 -1
  23. data/lib/rocket_job/plugins/job/model.rb +15 -20
  24. data/lib/rocket_job/plugins/job/persistence.rb +3 -13
  25. data/lib/rocket_job/plugins/job/state_machine.rb +1 -2
  26. data/lib/rocket_job/plugins/job/throttle.rb +16 -12
  27. data/lib/rocket_job/plugins/job/worker.rb +15 -19
  28. data/lib/rocket_job/plugins/processing_window.rb +2 -2
  29. data/lib/rocket_job/plugins/restart.rb +3 -4
  30. data/lib/rocket_job/plugins/retry.rb +2 -3
  31. data/lib/rocket_job/plugins/singleton.rb +2 -3
  32. data/lib/rocket_job/plugins/state_machine.rb +19 -23
  33. data/lib/rocket_job/rocket_job.rb +4 -5
  34. data/lib/rocket_job/server.rb +35 -41
  35. data/lib/rocket_job/version.rb +2 -2
  36. data/lib/rocket_job/worker.rb +22 -21
  37. data/lib/rocketjob.rb +2 -0
  38. data/test/config/mongoid.yml +2 -2
  39. data/test/config_test.rb +0 -2
  40. data/test/dirmon_entry_test.rb +161 -134
  41. data/test/dirmon_job_test.rb +80 -78
  42. data/test/job_test.rb +0 -2
  43. data/test/jobs/housekeeping_job_test.rb +0 -1
  44. data/test/jobs/on_demand_job_test.rb +59 -0
  45. data/test/jobs/upload_file_job_test.rb +99 -0
  46. data/test/plugins/cron_test.rb +1 -3
  47. data/test/plugins/job/callbacks_test.rb +8 -13
  48. data/test/plugins/job/defaults_test.rb +0 -1
  49. data/test/plugins/job/logger_test.rb +2 -4
  50. data/test/plugins/job/model_test.rb +1 -2
  51. data/test/plugins/job/persistence_test.rb +0 -2
  52. data/test/plugins/job/state_machine_test.rb +0 -2
  53. data/test/plugins/job/throttle_test.rb +6 -8
  54. data/test/plugins/job/worker_test.rb +1 -2
  55. data/test/plugins/processing_window_test.rb +0 -2
  56. data/test/plugins/restart_test.rb +0 -1
  57. data/test/plugins/retry_test.rb +1 -2
  58. data/test/plugins/singleton_test.rb +0 -2
  59. data/test/plugins/state_machine_event_callbacks_test.rb +1 -2
  60. data/test/plugins/state_machine_test.rb +0 -2
  61. data/test/plugins/transaction_test.rb +5 -7
  62. data/test/test_db.sqlite3 +0 -0
  63. data/test/test_helper.rb +2 -1
  64. metadata +42 -36
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 72a9239aa38ba9581931c3bd6f1402278b3c9d41
4
- data.tar.gz: aa360338a810a8853a463149084554978c57774f
2
+ SHA256:
3
+ metadata.gz: 9dd8c3c0ab00ae83d73d63aef3887f62d1d2a695399f263814ad7a5881aa980a
4
+ data.tar.gz: 715304e4587a9eda64c3cb5f73a69cd72ecdbf4ee232884c61f36666ef7c1a3a
5
5
  SHA512:
6
- metadata.gz: 1ba33a298630312129bc76f34084e49a348e27bba34e6f452a7d990a0c96d800c664db58b04724503d2cdcdd944a3120ccf09e583661453fb93832d2d38458b3
7
- data.tar.gz: f141cfbd428307638df1ac8ed20d00e345483370e6c4c40523d9409d2afc6f2d23b8c49d8b27c6eedac4aa72b48d6168dbfd1880b43cd20aaf452fdf223e4ce9
6
+ metadata.gz: 3bc50f4e10068d2b193747c24880dee618be8df6ab58e3093f442332f93c591796901132490a856ee955aa19cb78abaad6034eb7280e080c494676c7c36d3899
7
+ data.tar.gz: 86d8dbe971e8e44a8e4854d1691078ac93a25f84fd8e482b9e84a3669457ad8886f84fc47007f199625e70fcecc1f4b1403804e38dc50acebd5c06b13b11d20b
data/README.md CHANGED
@@ -17,6 +17,33 @@ Checkout http://rocketjob.io/
17
17
  * Questions? Join the chat room on Gitter for [rocketjob support](https://gitter.im/rocketjob/support)
18
18
  * [Report bugs](https://github.com/rocketjob/rocketjob/issues)
19
19
 
20
+ ## Contributing to the documentation
21
+
22
+ To contribute to the documentation it is as easy as forking the repository
23
+ and then editing the markdown pages directly via the github web interface.
24
+
25
+ For more complex documentation changes checkout the source code locally.
26
+
27
+ #### Local checkout
28
+
29
+ * Fork the repository in github.
30
+ * Checkout your fork of the source code locally.
31
+ * Install Jekyll
32
+ ~~~
33
+ cd docs
34
+ bundle update
35
+ ~~~
36
+ * Run Jekyll web server:
37
+ ~~~
38
+ jekyll serve
39
+ ~~~
40
+ * Open a web browser to view the local documentation:
41
+ [http://127.0.0.1:4000](http://127.0.0.1:4000)
42
+ * Edit the files in the `/docs` folder.
43
+ * Refresh the page to see the changes.
44
+
45
+ Once the changes are complete, submit a github pull request.
46
+
20
47
  ## Upgrading to V3
21
48
 
22
49
  V3 replaces MongoMapper with Mongoid which supports the latest MongoDB Ruby client driver.
data/bin/rocketjob CHANGED
@@ -6,7 +6,7 @@ require 'rocket_job/cli'
6
6
  # Start a rocketjob server instance from the command line
7
7
  begin
8
8
  RocketJob::CLI.new(ARGV).run
9
- rescue => exc
9
+ rescue Exception => exc
10
10
  # Failsafe logger that writes to STDERR
11
11
  SemanticLogger.add_appender(io: STDERR, level: :error, formatter: :color)
12
12
  SemanticLogger['RocketJob'].error('Rocket Job shutting down due to exception', exc)
@@ -4,7 +4,8 @@ module RocketJob
4
4
  # When this server started working on this job / slice
5
5
  attr_accessor :started_at
6
6
 
7
- attr_accessor :name, :job
7
+ attr_accessor :job
8
+ attr_reader :name
8
9
 
9
10
  # Returns [Hash<String:ActiveWorker>] hash of all servers sorted by name
10
11
  # and what they are currently working on.
@@ -16,7 +17,7 @@ module RocketJob
16
17
  def self.all(server_name = nil)
17
18
  servers = []
18
19
  # Need paused, failed or aborted since servers may still be working on active slices
19
- query = RocketJob::Job.where(:state.in => [:running, :paused, :failed, :aborted])
20
+ query = RocketJob::Job.where(:state.in => %i[running paused failed aborted])
20
21
  query = query.where(worker_name: /\A#{server_name}/) if server_name
21
22
  query.each do |job|
22
23
  servers += job.rocket_job_active_workers
@@ -50,7 +51,7 @@ module RocketJob
50
51
 
51
52
  # Returns [String] the name of the server running this worker
52
53
  def server_name
53
- if match = name.to_s.match(/(.*:.*):.*/)
54
+ if (match = name.to_s.match(/(.*:.*):.*/))
54
55
  match[1]
55
56
  else
56
57
  name
@@ -4,7 +4,7 @@ require 'mongoid'
4
4
  require 'rocketjob'
5
5
  require 'rocket_job/extensions/mongoid/factory'
6
6
  module RocketJob
7
- # Command Line Interface parser for RocketJob
7
+ # Command Line Interface parser for Rocket Job
8
8
  class CLI
9
9
  include SemanticLogger::Loggable
10
10
  attr_accessor :name, :workers, :environment, :pidfile, :directory, :quiet,
@@ -41,7 +41,7 @@ module RocketJob
41
41
  opts = {}
42
42
  opts[:name] = name if name
43
43
  opts[:max_workers] = workers if workers
44
- opts[:filter] = {:_type => filter} if filter
44
+ opts[:filter] = {_type: filter} if filter
45
45
  Server.run(opts)
46
46
  end
47
47
 
@@ -70,12 +70,12 @@ module RocketJob
70
70
  # Override Rails log level if command line option was supplied
71
71
  SemanticLogger.default_level = log_level.to_sym if log_level
72
72
 
73
- if Rails.configuration.eager_load
74
- logger.measure_info('Eager loaded Rails and all Engines') do
75
- Rails.application.eager_load!
76
- Rails::Engine.subclasses.each(&:eager_load!)
77
- self.class.eager_load_jobs(File.expand_path('jobs', File.dirname(__FILE__)))
78
- end
73
+ return unless Rails.configuration.eager_load
74
+
75
+ logger.measure_info('Eager loaded Rails and all Engines') do
76
+ Rails.application.eager_load!
77
+ Rails::Engine.subclasses.each(&:eager_load!)
78
+ self.class.eager_load_jobs(File.expand_path('jobs', File.dirname(__FILE__)))
79
79
  end
80
80
  end
81
81
 
@@ -86,12 +86,14 @@ module RocketJob
86
86
  require 'bundler/setup'
87
87
  Bundler.require(environment)
88
88
  rescue LoadError
89
+ nil
89
90
  end
90
91
 
91
92
  require 'rocketjob'
92
93
  begin
93
94
  require 'rocketjob_pro'
94
95
  rescue LoadError
96
+ nil
95
97
  end
96
98
 
97
99
  # Log to file except when booting rails, when it will add the log file path
@@ -138,10 +140,10 @@ module RocketJob
138
140
  end
139
141
 
140
142
  # Eager load files in jobs folder
141
- def self.eager_load_jobs(path = 'jobs')
142
- Pathname.glob("#{path}/**/*.rb").each do |path|
143
+ def self.eager_load_jobs(job_path = 'jobs')
144
+ Pathname.glob("#{job_path}/**/*.rb").each do |path|
143
145
  next if path.directory?
144
- logger.debug "Loading #{path.to_s}"
146
+ logger.debug "Loading #{path}"
145
147
  require path.expand_path.to_s
146
148
  end
147
149
  end
@@ -198,6 +200,5 @@ module RocketJob
198
200
  end
199
201
  parser.parse! argv
200
202
  end
201
-
202
203
  end
203
204
  end
@@ -51,22 +51,26 @@ module RocketJob
51
51
  # Configure Mongoid
52
52
  def self.load!(environment = 'development', file_name = nil, encryption_file_name = nil)
53
53
  config_file = file_name ? Pathname.new(file_name) : Pathname.pwd.join('config/mongoid.yml')
54
- if config_file.file?
55
- logger.debug "Reading Mongo configuration from: #{config_file}"
56
- Mongoid.load!(config_file, environment)
57
- else
58
- raise(ArgumentError, "Mongo Configuration file: #{config_file.to_s} not found")
59
- end
54
+
55
+ raise(ArgumentError, "Mongo Configuration file: #{config_file} not found") unless config_file.file?
56
+
57
+ logger.debug "Reading Mongo configuration from: #{config_file}"
58
+ Mongoid.load!(config_file, environment)
60
59
 
61
60
  # Load Encryption configuration file if present
62
- if defined?(SymmetricEncryption)
63
- config_file = encryption_file_name ? Pathname.new(encryption_file_name) : Pathname.pwd.join('config/symmetric-encryption.yml')
64
- if config_file.file?
65
- logger.debug "Reading SymmetricEncryption configuration from: #{config_file}"
66
- SymmetricEncryption.load!(config_file.to_s, environment)
61
+ return unless defined?(SymmetricEncryption)
62
+
63
+ config_file =
64
+ if encryption_file_name
65
+ Pathname.new(encryption_file_name)
66
+ else
67
+ Pathname.pwd.join('config/symmetric-encryption.yml')
67
68
  end
68
- end
69
- end
70
69
 
70
+ return unless config_file.file?
71
+
72
+ logger.debug "Reading SymmetricEncryption configuration from: #{config_file}"
73
+ SymmetricEncryption.load!(config_file.to_s, environment)
74
+ end
71
75
  end
72
76
  end
@@ -6,9 +6,14 @@ module RocketJob
6
6
  include Plugins::Document
7
7
  include Plugins::StateMachine
8
8
 
9
+ # The default archive directory that is used when the job being queued does not respond
10
+ # to #upload, and does not have an `archive_directory` specified in this entry
11
+ class_attribute :default_archive_directory
12
+ self.default_archive_directory = 'archive'.freeze
13
+
9
14
  store_in collection: 'rocket_job.dirmon_entries'
10
15
 
11
- # User defined name used to identify this DirmonEntry in Mission Control
16
+ # User defined name used to identify this DirmonEntry in the Web Interface.
12
17
  field :name, type: String
13
18
 
14
19
  # Pattern for finding files
@@ -48,7 +53,7 @@ module RocketJob
48
53
  # If supplied, the file will be moved to this directory before the job is started
49
54
  # If the file was in a sub-directory, the corresponding sub-directory will
50
55
  # be created in the archive directory.
51
- field :archive_directory, type: String
56
+ field :archive_directory, type: String, default: default_archive_directory
52
57
 
53
58
  # If this DirmonEntry is in the failed state, exception contains the cause
54
59
  embeds_one :exception, class_name: 'RocketJob::JobException'
@@ -59,7 +64,7 @@ module RocketJob
59
64
  # Exceeding this number will result in an exception being logged in a failed Dirmon instance.
60
65
  # Dirmon processing will continue with new instances.
61
66
  # TODO: Implement max_hits
62
- #field :max_hits, type: Integer, default: 100
67
+ # field :max_hits, type: Integer, default: 100
63
68
 
64
69
  #
65
70
  # Read-only attributes
@@ -71,6 +76,11 @@ module RocketJob
71
76
  # Unique index on pattern to help prevent two entries from scanning the same files
72
77
  index({pattern: 1}, background: true, unique: true, drop_dups: true)
73
78
 
79
+ before_validation :strip_whitespace
80
+ validates_presence_of :pattern, :job_class_name, :archive_directory
81
+ validate :job_is_a_rocket_job
82
+ validate :job_has_properties
83
+
74
84
  # State Machine events and transitions
75
85
  #
76
86
  # :pending -> :enabled -> :disabled
@@ -108,27 +118,6 @@ module RocketJob
108
118
  end
109
119
  end
110
120
 
111
- # @formatter:on
112
- validates_presence_of :pattern, :job_class_name
113
-
114
- validates_each :job_class_name do |record, attr, value|
115
- exists =
116
- begin
117
- value.nil? ? false : record.job_class.ancestors.include?(RocketJob::Job)
118
- rescue NameError
119
- false
120
- end
121
- record.errors.add(attr, 'job_class_name must be defined and must be derived from RocketJob::Job') unless exists
122
- end
123
-
124
- validates_each :properties do |record, attr, value|
125
- if record.job_class && (methods = record.job_class.instance_methods)
126
- value.each_pair do |k, v|
127
- record.errors.add(attr, "Unknown property: #{k.inspect} with value: #{v}") unless methods.include?("#{k}=".to_sym)
128
- end
129
- end
130
- end
131
-
132
121
  # Security Settings
133
122
  #
134
123
  # A whitelist of paths from which to process files.
@@ -147,7 +136,7 @@ module RocketJob
147
136
  #
148
137
  # Returns [Array<String>] a copy of the whitelisted paths
149
138
  def self.get_whitelist_paths
150
- self.whitelist_paths.dup
139
+ whitelist_paths.dup
151
140
  end
152
141
 
153
142
  # Add a path to the whitelist
@@ -155,8 +144,8 @@ module RocketJob
155
144
  def self.add_whitelist_path(path)
156
145
  # Confirms that path exists
157
146
  path = Pathname.new(path).realpath.to_s
158
- self.whitelist_paths << path
159
- self.whitelist_paths.uniq!
147
+ whitelist_paths << path
148
+ whitelist_paths.uniq!
160
149
  path
161
150
  end
162
151
 
@@ -165,8 +154,8 @@ module RocketJob
165
154
  def self.delete_whitelist_path(path)
166
155
  # Confirms that path exists
167
156
  path = Pathname.new(path).realpath.to_s
168
- self.whitelist_paths.delete(path)
169
- self.whitelist_paths.uniq!
157
+ whitelist_paths.delete(path)
158
+ whitelist_paths.uniq!
170
159
  path
171
160
  end
172
161
 
@@ -193,30 +182,8 @@ module RocketJob
193
182
  counts
194
183
  end
195
184
 
196
- # The default archive directory that is used when the job being queued does not respond
197
- # to #upload, and does not have an `archive_directory` specified in this entry
198
- class_attribute :default_archive_directory
199
-
200
- self.default_archive_directory = '_archive'.freeze
201
-
202
- # Returns [Pathname] the archive_directory if set, otherwise the default_archive_directory
203
- # Creates the archive directory if one is set
204
- def archive_pathname(file_pathname)
205
- if archive_directory
206
- path = Pathname.new(archive_directory)
207
- begin
208
- path.mkpath unless path.exist?
209
- rescue Errno::ENOENT => exc
210
- raise(Errno::ENOENT, "DirmonJob failed to create archive directory: #{path}, #{exc.message}")
211
- end
212
- path.realpath
213
- else
214
- file_pathname.dirname.join(self.class.default_archive_directory).realdirpath
215
- end
216
- end
217
-
218
185
  # Passes each filename [Pathname] found that matches the pattern into the supplied block
219
- def each(&block)
186
+ def each
220
187
  SemanticLogger.named_tagged(dirmon_entry: id.to_s) do
221
188
  # Case insensitive filename matching
222
189
  Pathname.glob(pattern, File::FNM_CASEFOLD).each do |pathname|
@@ -234,7 +201,7 @@ module RocketJob
234
201
  next if file_name.include?(self.class.default_archive_directory)
235
202
 
236
203
  # Security check?
237
- if (whitelist_paths.size > 0) && whitelist_paths.none? { |whitepath| file_name.to_s.start_with?(whitepath) }
204
+ if whitelist_paths.size.positive? && whitelist_paths.none? { |whitepath| file_name.to_s.start_with?(whitepath) }
238
205
  logger.error "Skipping file: #{file_name} since it is not in any of the whitelisted paths: #{whitelist_paths.join(', ')}"
239
206
  next
240
207
  end
@@ -244,7 +211,7 @@ module RocketJob
244
211
  logger.error "Skipping file: #{file_name} since it is not writable by the current user. Must be able to delete/move the file after queueing the job"
245
212
  next
246
213
  end
247
- block.call(pathname)
214
+ yield(pathname)
248
215
  end
249
216
  end
250
217
  end
@@ -264,7 +231,7 @@ module RocketJob
264
231
  end
265
232
  end
266
233
 
267
- # Returns the Job to be queued
234
+ # Returns the Job to be created.
268
235
  def job_class
269
236
  return if job_class_name.nil?
270
237
  job_class_name.constantize
@@ -272,47 +239,44 @@ module RocketJob
272
239
  nil
273
240
  end
274
241
 
275
- # Queues the job for the supplied pathname
242
+ # Archives the file and kicks off a proxy job to upload the file.
276
243
  def later(pathname)
277
- if klass = job_class
278
- logger.measure_info "Enqueued: #{name}, Job class: #{job_class_name}" do
279
- job = klass.new(properties)
280
- upload_file(job, pathname)
281
- job.save!
282
- job
283
- end
284
- else
285
- raise(ArgumentError, "Cannot instantiate a class for: #{job_class_name.inspect}")
286
- end
244
+ job_id = BSON::ObjectId.new
245
+ archived_file_name = archive_file(job_id, pathname)
246
+
247
+ job = RocketJob::Jobs::UploadFileJob.create!(
248
+ job_class_name: job_class_name,
249
+ properties: properties,
250
+ description: "#{name}: #{pathname.basename}",
251
+ upload_file_name: archived_file_name.to_s,
252
+ original_file_name: pathname.to_s,
253
+ job_id: job_id
254
+ )
255
+
256
+ logger.info(
257
+ message: 'Created RocketJob::Jobs::UploadFileJob',
258
+ payload: {
259
+ dirmon_entry_name: name,
260
+ upload_file_name: archived_file_name.to_s,
261
+ original_file_name: pathname.to_s,
262
+ job_class_name: job_class_name,
263
+ job_id: job_id.to_s,
264
+ upload_job_id: job.id.to_s
265
+ }
266
+ )
267
+ job
287
268
  end
288
269
 
289
270
  private
290
271
 
291
- class_attribute :whitelist_paths
292
- self.whitelist_paths = Concurrent::Array.new
293
-
294
- # Upload the file to the job
295
- def upload_file(job, pathname)
296
- if job.respond_to?(:upload)
297
- # With RocketJob Pro the file can be uploaded directly into the Job itself
298
- job.upload(pathname.to_s)
299
- archive_directory ? archive_file(job, pathname) : pathname.unlink
300
- else
301
- upload_default(job, pathname)
302
- end
272
+ # strip whitespaces from all variables that reference paths or patterns
273
+ def strip_whitespace
274
+ self.pattern = pattern.strip unless pattern.nil?
275
+ self.archive_directory = archive_directory.strip unless archive_directory.nil?
303
276
  end
304
277
 
305
- # Archives the file for a job where there was no #upload method
306
- def upload_default(job, pathname)
307
- full_file_name = archive_file(job, pathname)
308
- if job.respond_to?(:upload_file_name=)
309
- job.upload_file_name = full_file_name
310
- elsif job.respond_to?(:full_file_name=)
311
- job.full_file_name = full_file_name
312
- else
313
- raise(ArgumentError, "#{job_class_name} must either have attribute 'upload_file_name' or 'full_file_name'")
314
- end
315
- end
278
+ class_attribute :whitelist_paths
279
+ self.whitelist_paths = Concurrent::Array.new
316
280
 
317
281
  # Move the file to the archive directory
318
282
  #
@@ -322,14 +286,47 @@ module RocketJob
322
286
  #
323
287
  # Note:
324
288
  # - Works across partitions when the file and the archive are on different partitions
325
- def archive_file(job, pathname)
289
+ def archive_file(job_id, pathname)
326
290
  target_path = archive_pathname(pathname)
327
291
  target_path.mkpath
328
- target_file_name = target_path.join("#{job.id}_#{pathname.basename}")
292
+ target_file_name = target_path.join("#{job_id}_#{pathname.basename}")
329
293
  # In case the file is being moved across partitions
330
294
  FileUtils.move(pathname.to_s, target_file_name.to_s)
331
295
  target_file_name.to_s
332
296
  end
333
297
 
298
+ # Returns [Pathname] to the archive directory, and creates it if it does not exist.
299
+ #
300
+ # If `archive_directory` is a relative path, it is appended to the `file_pathname`.
301
+ # If `archive_directory` is an absolute path, it is returned as-is.
302
+ def archive_pathname(file_pathname)
303
+ path = Pathname.new(archive_directory)
304
+ path = file_pathname.dirname.join(archive_directory) if path.relative?
305
+
306
+ begin
307
+ path.mkpath unless path.exist?
308
+ rescue Errno::ENOENT => exc
309
+ raise(Errno::ENOENT, "DirmonJob failed to create archive directory: #{path}, #{exc.message}")
310
+ end
311
+ path.realpath
312
+ end
313
+
314
+ # Validates job_class is a Rocket Job
315
+ def job_is_a_rocket_job
316
+ klass = job_class
317
+ return if job_class_name.nil? || klass&.ancestors&.include?(RocketJob::Job)
318
+ errors.add(:job_class_name, "Job #{job_class_name} must be defined and inherit from RocketJob::Job")
319
+ end
320
+
321
+ # Does the job have all the supplied properties
322
+ def job_has_properties
323
+ klass = job_class
324
+ return unless klass
325
+
326
+ properties.each_pair do |k, _v|
327
+ next if klass.public_method_defined?("#{k}=".to_sym)
328
+ errors.add(:properties, "Unknown Property: Attempted to set a value for #{k.inspect} which is not allowed on the job #{job_class_name}")
329
+ end
330
+ end
334
331
  end
335
332
  end