libis-workflow-activerecord 0.9.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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +57 -0
  3. data/.travis.yml +50 -0
  4. data/Gemfile +3 -0
  5. data/README.md +13 -0
  6. data/Rakefile +85 -0
  7. data/db/config.yml +71 -0
  8. data/db/migrate/001_create_work_items_table.rb +18 -0
  9. data/db/migrate/002_create_jobs_table.rb +27 -0
  10. data/db/schema.rb +72 -0
  11. data/db/setup_db.rb +14 -0
  12. data/db/travis.config.yml +4 -0
  13. data/lib/libis-workflow-activerecord.rb +1 -0
  14. data/lib/libis/workflow/activerecord.rb +25 -0
  15. data/lib/libis/workflow/activerecord/base.rb +79 -0
  16. data/lib/libis/workflow/activerecord/config.rb +30 -0
  17. data/lib/libis/workflow/activerecord/helpers/hash_serializer.rb +32 -0
  18. data/lib/libis/workflow/activerecord/helpers/property_helper.rb +40 -0
  19. data/lib/libis/workflow/activerecord/helpers/status_serializer.rb +30 -0
  20. data/lib/libis/workflow/activerecord/job.rb +73 -0
  21. data/lib/libis/workflow/activerecord/run.rb +99 -0
  22. data/lib/libis/workflow/activerecord/version.rb +7 -0
  23. data/lib/libis/workflow/activerecord/work_item.rb +90 -0
  24. data/lib/libis/workflow/activerecord/worker.rb +20 -0
  25. data/lib/libis/workflow/activerecord/workflow.rb +40 -0
  26. data/libis-workflow-activerecord.gemspec +42 -0
  27. data/spec/db_env.sh +5 -0
  28. data/spec/db_start.sh +5 -0
  29. data/spec/items.rb +3 -0
  30. data/spec/items/test_dir_item.rb +18 -0
  31. data/spec/items/test_file_item.rb +29 -0
  32. data/spec/items/test_item.rb +6 -0
  33. data/spec/items/test_run.rb +8 -0
  34. data/spec/job_spec.rb +10 -0
  35. data/spec/run_spec.rb +4 -0
  36. data/spec/spec_helper.rb +37 -0
  37. data/spec/tasks/camelize_name.rb +13 -0
  38. data/spec/tasks/checksum_tester.rb +34 -0
  39. data/spec/tasks/collect_files.rb +52 -0
  40. data/spec/test_job.rb +6 -0
  41. data/spec/test_workflow.rb +6 -0
  42. data/spec/test_workitem.rb +7 -0
  43. data/spec/workflow_spec.rb +208 -0
  44. data/spec/workitem_spec.rb +366 -0
  45. metadata +245 -0
@@ -0,0 +1,79 @@
1
+ require 'yaml'
2
+ require 'libis/tools/extend/hash'
3
+
4
+ module Libis
5
+ module Workflow
6
+ module ActiveRecord
7
+
8
+ module Base
9
+
10
+ def self.included(klass)
11
+ klass.extend(ClassMethods)
12
+
13
+ # def [](key)
14
+ # self.send(key.to_sym)
15
+ # end
16
+ #
17
+ # def []=(key,value)
18
+ # self.send(key.to_s + '=', value)
19
+ # end
20
+ end
21
+
22
+ module ClassMethods
23
+ def from_hash(hash)
24
+ self.create_from_hash(hash.cleanup, [:name])
25
+ end
26
+
27
+ def create_from_hash(hash, id_tags, &block)
28
+ hash = hash.key_symbols_to_strings(recursive: true)
29
+ id_tags = id_tags.map(&:to_s)
30
+ return nil unless id_tags.empty? || id_tags.any? { |k| hash.include?(k) }
31
+ tags = id_tags.inject({}) do |h, k|
32
+ v = hash.delete(k)
33
+ h[k] = v if v
34
+ h
35
+ end
36
+ item = tags.empty? ? self.new : self.find_or_initialize_by(tags)
37
+ block.call(item, hash) if block unless hash.empty?
38
+ item.assign_attributes(hash)
39
+ item.save!
40
+ item
41
+ end
42
+
43
+ end
44
+
45
+ def to_hash
46
+ result = self.attributes.reject { |k, v| v.blank? || volatile_attributes.include?(k) }
47
+ result = result.to_yaml
48
+ # noinspection RubyResolve
49
+ YAML.load(result)
50
+ end
51
+
52
+ def to_s
53
+ self.name || "#{self.class.name}_#{self.id}"
54
+ end
55
+
56
+ protected
57
+
58
+ def volatile_attributes
59
+ %w'id created_at updated_at'
60
+ end
61
+
62
+ private
63
+
64
+ def copy_attributes(other)
65
+ self.set(
66
+ other.attributes.reject do |k, _|
67
+ volatile_attributes.include? k.to_s
68
+ end.each_with_object({}) do |(k, v), h|
69
+ h[k] = v.duplicable? ? v.dup : v
70
+ end
71
+ )
72
+ self
73
+ end
74
+
75
+ end
76
+
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,30 @@
1
+ # noinspection RubyResolve
2
+ require 'singleton'
3
+ require 'active_record'
4
+
5
+ require 'libis/workflow/base/logging'
6
+
7
+ require 'libis/workflow/config'
8
+ module Libis
9
+ module Workflow
10
+ module ActiveRecord
11
+
12
+ # noinspection RubyConstantNamingConvention
13
+ Config = ::Libis::Workflow::Config
14
+
15
+ Config.define_singleton_method(:database_connect) do |config_file = 'db/config.yml', environment = :production|
16
+ # noinspection RubyResolve
17
+ instance.database_connect(config_file, environment)
18
+ end
19
+
20
+ Config.send(:define_method, :database_connect) do |config_file = 'db/config.yml', environment = :production|
21
+ db_config = YAML.load_file(config_file)
22
+ # ::ActiveRecord::Base.logger = Libis::Workflow::ActiveRecord::Config.logger
23
+ ::ActiveRecord::Base.establish_connection(db_config[environment.to_s])
24
+ end
25
+
26
+ Config[:log_dir] = '.'
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,32 @@
1
+ require 'active_support/core_ext/hash/indifferent_access'
2
+ require 'libis/tools/extend/hash'
3
+
4
+ module Libis
5
+ module Workflow
6
+ module ActiveRecord
7
+ module Helpers
8
+
9
+ class HashSerializer
10
+
11
+ def self.dump(hash)
12
+ delete_proc = Proc.new do |_, v|
13
+ # Cleanup the hash recursively and remove entries with value == nil
14
+ # noinspection RubyScope
15
+ v.delete_if(&delete_proc) if v.kind_of?(Hash)
16
+ v.nil?
17
+ end
18
+ hash.delete_if &delete_proc if hash.is_a?(Hash)
19
+ # Store a nil value if the hash is empty
20
+ (hash.is_a?(Hash) && !hash.empty?) ? hash : nil
21
+ end
22
+
23
+ def self.load(hash)
24
+ (hash || {}).with_indifferent_access
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,40 @@
1
+ require 'yaml'
2
+
3
+ module Libis
4
+ module Workflow
5
+ module ActiveRecord
6
+ module Helpers
7
+
8
+ module PropertyHelper
9
+ def self.included(klass)
10
+ klass.extend(ClassMethods)
11
+ end
12
+
13
+ module ClassMethods
14
+
15
+ def property_field(name, options = {})
16
+ default = options[:default]
17
+ default = default.call if default.is_a? Proc
18
+ if options[:type]
19
+ # noinspection RubyResolve
20
+ options.reverse_merge! reader: lambda {|value| value.blank? ? nil : YAML.load(value)},
21
+ writer: lambda {|value| value ? YAML.dump(value) : nil}
22
+ end
23
+ self.send(:define_method, name, lambda {
24
+ value = properties[name]
25
+ (options[:reader] ? options[:reader].call(value) : value) || default
26
+ })
27
+ self.send(:define_method, "#{name}=", lambda {|value|
28
+ value ||= default
29
+ properties[name] = options[:writer] ? options[:writer].call(value) : value
30
+ })
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,30 @@
1
+ require 'active_support/core_ext/hash/indifferent_access'
2
+
3
+ module Libis
4
+ module Workflow
5
+ module ActiveRecord
6
+ module Helpers
7
+
8
+ class StatusSerializer
9
+
10
+ def self.dump(array)
11
+ return nil unless array.is_a?(Array) && !array.empty?
12
+ array || []
13
+ end
14
+
15
+ def self.load(array)
16
+ (array || []).map do |status|
17
+ status = status.with_indifferent_access
18
+ status[:status] = status[:status].to_sym if status.has_key? :status
19
+ status[:created] = DateTime.parse(status[:created]) if status.has_key? :created
20
+ status[:updated] = DateTime.parse(status[:updated]) if status.has_key? :updated
21
+ status
22
+ end
23
+ end
24
+
25
+ end
26
+
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,73 @@
1
+ require 'libis/workflow/base/job'
2
+ require 'libis/workflow/activerecord/base'
3
+
4
+ module Libis
5
+ module Workflow
6
+ module ActiveRecord
7
+
8
+ class Job < ::ActiveRecord::Base
9
+ include ::Libis::Workflow::Base::Job
10
+ include ::Libis::Workflow::ActiveRecord::Base
11
+
12
+ # noinspection RailsParamDefResolve
13
+ has_many :runs, -> {order 'created_at asc'},
14
+ class_name: Libis::Workflow::ActiveRecord::Run.to_s,
15
+ foreign_key: :job_id,
16
+ autosave: true
17
+ # noinspection RailsParamDefResolve
18
+ belongs_to :workflow, class_name: Libis::Workflow::ActiveRecord::Workflow.to_s,
19
+ autosave: true
20
+
21
+ # index({workflow_id: 1, workflow_type: 1, name: 1}, {name: 'by_workflow'})
22
+
23
+ def self.from_hash(hash)
24
+ self.create_from_hash(hash, [:name]) do |item, cfg|
25
+ item.workflow = Libis::Workflow::ActiveRecord::Workflow.from_hash(name: cfg.delete('workflow'))
26
+ end
27
+ end
28
+
29
+ def logger
30
+ # noinspection RubyResolve
31
+ return ::Libis::Workflow::ActiveRecord::Config.logger unless self.log_to_file
32
+ logger = ::Logging::Repository.instance[self.name]
33
+ return logger if logger
34
+ unless ::Logging::Appenders[self.name]
35
+ ::Logging::Appenders::RollingFile.new(
36
+ self.name,
37
+ filename: File.join(::Libis::Workflow::ActiveRecord::Config[:log_dir], "#{self.name}.{{%Y%m%d}}.log"),
38
+ layout: ::Libis::Workflow::ActiveRecord::Config.get_log_formatter,
39
+ truncate: true,
40
+ age: self.log_age,
41
+ keep: self.log_keep,
42
+ roll_by: 'date',
43
+ level: self.log_level
44
+ )
45
+ end
46
+ logger = ::Libis::Workflow::ActiveRecord::Config.logger(self.name, self.name)
47
+ logger.additive = false
48
+ logger
49
+ end
50
+
51
+ # noinspection RubyStringKeysInHashInspection
52
+ def execute(opts = {})
53
+ opts['run_config'] ||= {}
54
+ if self.log_each_run
55
+ opts['run_config']['log_to_file'] = true
56
+ opts['run_config']['log_level'] = self.log_level
57
+ end
58
+ if (run_name = opts.delete('run_name'))
59
+ opts['run_config']['run_name'] = run_name
60
+ end
61
+ super opts
62
+ end
63
+
64
+ # def create_run_object
65
+ # # noinspection RubyResolve
66
+ # self.runs.build
67
+ # end
68
+
69
+ end
70
+
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,99 @@
1
+ require 'fileutils'
2
+ require 'libis/workflow/base/run'
3
+
4
+ module Libis
5
+ module Workflow
6
+ module ActiveRecord
7
+
8
+ class Run < Libis::Workflow::ActiveRecord::WorkItem
9
+
10
+ include ::Libis::Workflow::Base::Run
11
+
12
+ property_field :start_date, type: Time, default: lambda {Time.now}
13
+ property_field :log_to_file, type: TrueClass, default: false
14
+ property_field :log_level, default: 'INFO'
15
+ property_field :log_filename
16
+ property_field :run_name
17
+
18
+ # noinspection RailsParamDefResolve
19
+ belongs_to :job, class_name: Libis::Workflow::ActiveRecord::Job.to_s,
20
+ autosave: true
21
+
22
+ set_callback(:destroy, :before) do |run|
23
+ run.rm_workdir
24
+ run.rm_log
25
+ end
26
+
27
+ def rm_log
28
+ # noinspection RubyResolve
29
+ log_file = self.log_filename
30
+ FileUtils.rm(log_file) if log_file && !log_file.blank? && File.exist?(log_file)
31
+ end
32
+
33
+ def rm_workdir
34
+ workdir = self.work_dir
35
+ FileUtils.rmtree workdir if workdir && !workdir.blank? && Dir.exist?(workdir)
36
+ end
37
+
38
+ def work_dir
39
+ # noinspection RubyResolve
40
+ dir = File.join(Libis::Workflow::Config.workdir, self.id.to_s)
41
+ FileUtils.mkpath dir unless Dir.exist?(dir)
42
+ dir
43
+ end
44
+
45
+ def run(action = :run)
46
+ self.start_date = Time.now
47
+ self.tasks = []
48
+ super action
49
+ close_logger
50
+ end
51
+
52
+ def logger
53
+ # noinspection RubyResolve
54
+ unless self.log_to_file
55
+ return self.job.logger
56
+ end
57
+ logger = ::Logging::Repository.instance[self.name]
58
+ return logger if logger
59
+ unless ::Logging::Appenders[self.name]
60
+ # noinspection RubyResolve
61
+ self.log_filename ||= File.join(::Libis::Workflow::ActiveRecord::Config[:log_dir], "#{self.name}-#{self.id}.log")
62
+ # noinspection RubyResolve
63
+ ::Logging::Appenders::File.new(
64
+ self.name,
65
+ filename: self.log_filename,
66
+ layout: ::Libis::Workflow::ActiveRecord::Config.get_log_formatter,
67
+ level: self.log_level
68
+ )
69
+ end
70
+ logger = ::Libis::Workflow::ActiveRecord::Config.logger(self.name, self.name)
71
+ logger.additive = false
72
+ # noinspection RubyResolve
73
+ logger.level = self.log_level
74
+ logger
75
+ end
76
+
77
+ def close_logger
78
+ # noinspection RubyResolve
79
+ return unless self.log_to_file
80
+ ::Logging::Appenders[self.name].close
81
+ ::Logging::Appenders.remove(self.name)
82
+ ::Logging::Repository.instance.delete(self.name)
83
+ end
84
+
85
+ def name
86
+ parts = [self.job.name]
87
+ parts << self.run_name unless self.run_name.blank?
88
+ parts << self.created_at.strftime('%Y%m%d-%H%M%S')
89
+ parts << self.id.to_s if self.run_name.blank?
90
+ parts.join('-')
91
+ rescue
92
+ self.id.to_s
93
+ end
94
+
95
+ end
96
+
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,7 @@
1
+ module Libis
2
+ module Workflow
3
+ module ActiveRecord
4
+ VERSION = '0.9.0' unless const_defined? :VERSION # the guard is against a redefinition warning that happens on Travis
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,90 @@
1
+ require 'active_support/core_ext/hash/indifferent_access'
2
+ require 'libis-workflow-activerecord'
3
+
4
+ require_relative 'helpers/hash_serializer'
5
+ require_relative 'helpers/status_serializer'
6
+ require_relative 'helpers/property_helper'
7
+
8
+ module Libis
9
+ module Workflow
10
+ module ActiveRecord
11
+
12
+ class WorkItem < ::ActiveRecord::Base
13
+ include Libis::Workflow::Base::WorkItem
14
+ include Libis::Workflow::ActiveRecord::Base
15
+ include Libis::Workflow::ActiveRecord::Helpers::PropertyHelper
16
+
17
+ self.table_name = 'work_items'
18
+ # noinspection RubyArgCount
19
+ serialize :options, Libis::Workflow::ActiveRecord::Helpers::HashSerializer
20
+ # noinspection RubyArgCount
21
+ serialize :properties, Libis::Workflow::ActiveRecord::Helpers::HashSerializer
22
+ # noinspection RubyArgCount
23
+ serialize :status_log, Libis::Workflow::ActiveRecord::Helpers::StatusSerializer
24
+
25
+ # noinspection RailsParamDefResolve
26
+ has_many :items,
27
+ -> {order('id')},
28
+ class_name: Libis::Workflow::ActiveRecord::WorkItem.to_s,
29
+ foreign_key: :parent_id,
30
+ autosave: true
31
+
32
+ # noinspection RailsParamDefResolve
33
+ belongs_to :parent, class_name: Libis::Workflow::ActiveRecord::WorkItem.to_s,
34
+ inverse_of: :items,
35
+ autosave: true
36
+
37
+ def add_item(item)
38
+ raise Libis::WorkflowError, 'Trying to add item already linked to another item' unless item.parent.nil?
39
+ super(item)
40
+ end
41
+
42
+ def duplicate
43
+ new_item = self.class.new
44
+ new_item.properties = {}.with_indifferent_access
45
+ self.properties.each {|k, v| new_item.properties[k.to_sym] = v.dup}
46
+ new_item.options = {}.with_indifferent_access
47
+ self.options.each {|k, v| new_item.options[k.to_sym] = v.dup}
48
+ new_item.status_log = []
49
+ yield new_item if block_given?
50
+ new_item
51
+ end
52
+
53
+ def copy_item(item, &block)
54
+ new_item = item.duplicate &block
55
+ new_item.parent = nil
56
+ add_item new_item
57
+ item.items.each {|i| new_item.copy_item(i)}
58
+ new_item
59
+ end
60
+
61
+ def move_item(item)
62
+ old_parent = item.parent
63
+ item.parent = self
64
+ item.save
65
+ old_parent.items.reset
66
+ self.items.reset
67
+ item
68
+ end
69
+
70
+ def get_items
71
+ self.items
72
+ end
73
+
74
+ def get_item_list
75
+ self.items.to_a
76
+ end
77
+
78
+ protected
79
+
80
+ def add_status_log(info)
81
+ # noinspection RubyResolve
82
+ self.status_log << info.with_indifferent_access
83
+ self.status_log.last
84
+ end
85
+
86
+ end
87
+
88
+ end
89
+ end
90
+ end