joblin 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.
Files changed (111) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +1 -0
  3. data/app/models/joblin/background_task/api_access.rb +148 -0
  4. data/app/models/joblin/background_task/attachments.rb +47 -0
  5. data/app/models/joblin/background_task/executor.rb +63 -0
  6. data/app/models/joblin/background_task/options.rb +75 -0
  7. data/app/models/joblin/background_task/retention_policy.rb +28 -0
  8. data/app/models/joblin/background_task.rb +72 -0
  9. data/app/models/joblin/concerns/job_working_dirs.rb +21 -0
  10. data/db/migrate/20250903184852_create_background_tasks.rb +12 -0
  11. data/joblin.gemspec +35 -0
  12. data/lib/joblin/batching/batch.rb +537 -0
  13. data/lib/joblin/batching/callback.rb +135 -0
  14. data/lib/joblin/batching/chain_builder.rb +247 -0
  15. data/lib/joblin/batching/compat/active_job.rb +108 -0
  16. data/lib/joblin/batching/compat/sidekiq/web/batches_assets/css/styles.less +182 -0
  17. data/lib/joblin/batching/compat/sidekiq/web/batches_assets/js/batch_tree.js +108 -0
  18. data/lib/joblin/batching/compat/sidekiq/web/batches_assets/js/util.js +2 -0
  19. data/lib/joblin/batching/compat/sidekiq/web/helpers.rb +41 -0
  20. data/lib/joblin/batching/compat/sidekiq/web/views/_batch_tree.erb +6 -0
  21. data/lib/joblin/batching/compat/sidekiq/web/views/_batches_table.erb +44 -0
  22. data/lib/joblin/batching/compat/sidekiq/web/views/_common.erb +13 -0
  23. data/lib/joblin/batching/compat/sidekiq/web/views/_jobs_table.erb +21 -0
  24. data/lib/joblin/batching/compat/sidekiq/web/views/_pagination.erb +26 -0
  25. data/lib/joblin/batching/compat/sidekiq/web/views/batch.erb +81 -0
  26. data/lib/joblin/batching/compat/sidekiq/web/views/batches.erb +23 -0
  27. data/lib/joblin/batching/compat/sidekiq/web/views/pool.erb +137 -0
  28. data/lib/joblin/batching/compat/sidekiq/web/views/pools.erb +47 -0
  29. data/lib/joblin/batching/compat/sidekiq/web.rb +218 -0
  30. data/lib/joblin/batching/compat/sidekiq.rb +149 -0
  31. data/lib/joblin/batching/compat.rb +20 -0
  32. data/lib/joblin/batching/context_hash.rb +157 -0
  33. data/lib/joblin/batching/hier_batch_ids.lua +25 -0
  34. data/lib/joblin/batching/jobs/base_job.rb +7 -0
  35. data/lib/joblin/batching/jobs/concurrent_batch_job.rb +20 -0
  36. data/lib/joblin/batching/jobs/managed_batch_job.rb +175 -0
  37. data/lib/joblin/batching/jobs/serial_batch_job.rb +20 -0
  38. data/lib/joblin/batching/pool.rb +254 -0
  39. data/lib/joblin/batching/pool_refill.lua +47 -0
  40. data/lib/joblin/batching/schedule_callback.lua +14 -0
  41. data/lib/joblin/batching/status.rb +89 -0
  42. data/lib/joblin/engine.rb +15 -0
  43. data/lib/joblin/lazy_access.rb +72 -0
  44. data/lib/joblin/uniqueness/compat/active_job.rb +75 -0
  45. data/lib/joblin/uniqueness/compat/sidekiq.rb +135 -0
  46. data/lib/joblin/uniqueness/compat.rb +20 -0
  47. data/lib/joblin/uniqueness/configuration.rb +25 -0
  48. data/lib/joblin/uniqueness/job_uniqueness.rb +49 -0
  49. data/lib/joblin/uniqueness/lock_context.rb +199 -0
  50. data/lib/joblin/uniqueness/locksmith.rb +92 -0
  51. data/lib/joblin/uniqueness/on_conflict/base.rb +32 -0
  52. data/lib/joblin/uniqueness/on_conflict/log.rb +13 -0
  53. data/lib/joblin/uniqueness/on_conflict/null_strategy.rb +9 -0
  54. data/lib/joblin/uniqueness/on_conflict/raise.rb +11 -0
  55. data/lib/joblin/uniqueness/on_conflict/reject.rb +21 -0
  56. data/lib/joblin/uniqueness/on_conflict/reschedule.rb +20 -0
  57. data/lib/joblin/uniqueness/on_conflict.rb +62 -0
  58. data/lib/joblin/uniqueness/strategy/base.rb +107 -0
  59. data/lib/joblin/uniqueness/strategy/until_and_while_executing.rb +35 -0
  60. data/lib/joblin/uniqueness/strategy/until_executed.rb +20 -0
  61. data/lib/joblin/uniqueness/strategy/until_executing.rb +20 -0
  62. data/lib/joblin/uniqueness/strategy/until_expired.rb +16 -0
  63. data/lib/joblin/uniqueness/strategy/while_executing.rb +26 -0
  64. data/lib/joblin/uniqueness/strategy.rb +27 -0
  65. data/lib/joblin/uniqueness/unique_job_common.rb +79 -0
  66. data/lib/joblin/version.rb +3 -0
  67. data/lib/joblin.rb +37 -0
  68. data/spec/batching/batch_spec.rb +493 -0
  69. data/spec/batching/callback_spec.rb +38 -0
  70. data/spec/batching/compat/active_job_spec.rb +107 -0
  71. data/spec/batching/compat/sidekiq_spec.rb +127 -0
  72. data/spec/batching/context_hash_spec.rb +54 -0
  73. data/spec/batching/flow_spec.rb +82 -0
  74. data/spec/batching/integration/fail_then_succeed.rb +42 -0
  75. data/spec/batching/integration/integration.rb +57 -0
  76. data/spec/batching/integration/nested.rb +88 -0
  77. data/spec/batching/integration/simple.rb +47 -0
  78. data/spec/batching/integration/workflow.rb +134 -0
  79. data/spec/batching/integration_helper.rb +50 -0
  80. data/spec/batching/pool_spec.rb +161 -0
  81. data/spec/batching/status_spec.rb +76 -0
  82. data/spec/batching/support/base_job.rb +19 -0
  83. data/spec/batching/support/sample_callback.rb +2 -0
  84. data/spec/internal/config/database.yml +5 -0
  85. data/spec/internal/config/routes.rb +5 -0
  86. data/spec/internal/config/storage.yml +3 -0
  87. data/spec/internal/db/combustion_test.sqlite +0 -0
  88. data/spec/internal/db/schema.rb +6 -0
  89. data/spec/internal/log/test.log +48200 -0
  90. data/spec/internal/public/favicon.ico +0 -0
  91. data/spec/models/background_task_spec.rb +41 -0
  92. data/spec/spec_helper.rb +29 -0
  93. data/spec/uniqueness/compat/active_job_spec.rb +49 -0
  94. data/spec/uniqueness/compat/sidekiq_spec.rb +68 -0
  95. data/spec/uniqueness/lock_context_spec.rb +106 -0
  96. data/spec/uniqueness/on_conflict/log_spec.rb +11 -0
  97. data/spec/uniqueness/on_conflict/raise_spec.rb +10 -0
  98. data/spec/uniqueness/on_conflict/reschedule_spec.rb +63 -0
  99. data/spec/uniqueness/on_conflict_spec.rb +16 -0
  100. data/spec/uniqueness/spec_helper.rb +19 -0
  101. data/spec/uniqueness/strategy/base_spec.rb +100 -0
  102. data/spec/uniqueness/strategy/until_and_while_executing_spec.rb +48 -0
  103. data/spec/uniqueness/strategy/until_executed_spec.rb +23 -0
  104. data/spec/uniqueness/strategy/until_executing_spec.rb +23 -0
  105. data/spec/uniqueness/strategy/until_expired_spec.rb +23 -0
  106. data/spec/uniqueness/strategy/while_executing_spec.rb +33 -0
  107. data/spec/uniqueness/support/lock_strategy.rb +28 -0
  108. data/spec/uniqueness/support/on_conflict.rb +24 -0
  109. data/spec/uniqueness/support/test_worker.rb +19 -0
  110. data/spec/uniqueness/unique_job_common_spec.rb +45 -0
  111. metadata +308 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 35d34b0f27040ddb94b3cfcd15d01c6fcdc12b18895a6b525032ec1d2d0aa41f
4
+ data.tar.gz: 6cbc121c07c836a85e77c294c92baf82d3338fac4625f38d697973fdf5a97577
5
+ SHA512:
6
+ metadata.gz: aa4cad19edc21301edd5ff875bbfbff1fec1eed88a10814112380b14ce3f59f7efa3bd7d82741ed86674df1f48dc765d4df62495bb9c14d0aeb444375893755a
7
+ data.tar.gz: a59dbee1edea55c4f68ff30607aa20c70862462db1c123a0585ba8f757bfd41440a827b6226b7613d197ab224db71e1a05080cfa43f7d18c7fe15eb6b700cf1c
data/README.md ADDED
@@ -0,0 +1 @@
1
+ # Joblin
@@ -0,0 +1,148 @@
1
+ module Joblin
2
+ module BackgroundTask::ApiAccess
3
+ extend ActiveSupport::Concern
4
+
5
+ class_methods do
6
+ def api_access_rules(&blk)
7
+ @api_access_rules ||= []
8
+ if blk
9
+ @api_access_rules << ApiAccessRules.new.tap do |aar|
10
+ aar.instance_exec(&blk)
11
+ end
12
+ nil
13
+ else
14
+ [*superclass.try(:api_access_rules), *@api_access_rules].compact.freeze
15
+ end
16
+ end
17
+
18
+ def allow_api_access!(&blk)
19
+ include ApiAccess::Mixin unless self < ApiAccess::Mixin
20
+ @api_access_allowed = true
21
+ api_access_rules(&blk) if blk
22
+ end
23
+
24
+ def api_access_allowed?
25
+ # This is intentionally not inherited by subclasses
26
+ @api_access_allowed
27
+ end
28
+
29
+ def find_for_api(id)
30
+ task = find(id)
31
+ raise ActiveRecord::RecordNotFound unless task.class.api_access_allowed?
32
+ task
33
+ end
34
+
35
+ def build_from_api(controller_or_params, options = {}, params: nil, exec_context: nil)
36
+ if controller_or_params.respond_to?(:params)
37
+ exec_context ||= controller_or_params
38
+ params ||= controller_or_params.params.require(:background_task)
39
+ else
40
+ params ||= controller_or_params
41
+ end
42
+
43
+ task_type = self == BackgroundTask ? params[:type].safe_constantize : self
44
+ raise ActiveRecord::RecordNotFound unless task_type && task_type <= BackgroundTask
45
+ raise ActiveRecord::RecordNotFound unless task_type.api_access_allowed?
46
+
47
+ task = task_type.new
48
+ rule_sets = task_type.api_access_rules
49
+
50
+ # Apply permitted options
51
+ if params[:options].present?
52
+ permitted_options = rule_sets.map(&:permitted_options).flatten
53
+ task.options.merge!(params[:options].permit(permitted_options).to_h)
54
+ end
55
+
56
+ # Apply default options
57
+ rule_sets.reverse.each do |aar|
58
+ aar.default_options.each do |k, v|
59
+ next if task.options.key?(k)
60
+ task.options[k] = v.is_a?(Proc) ? exec_context.instance_exec(&v) : v
61
+ end
62
+ end
63
+
64
+ # Apply any additional/hard-coded options
65
+ task.options.merge!(options)
66
+
67
+ val_errors = task.api_validate_options
68
+ raise ActiveRecord::ValidationError.new(val_errors) unless val_errors.empty?
69
+
70
+ task
71
+ end
72
+ end
73
+
74
+ included do
75
+ api_access_rules do
76
+ default_option(:creator) { try(:current_user) }
77
+ end
78
+ end
79
+
80
+ module Mixin
81
+ extend ActiveSupport::Concern
82
+
83
+ included do
84
+ if defined?(BackgroundTaskChannel)
85
+ after_commit do
86
+ BackgroundTaskChannel.broadcast_to(self, api_serialize)
87
+ end
88
+ end
89
+ end
90
+
91
+ def api_serialize
92
+ builder = Jbuilder.new do |json|
93
+ json.id id
94
+ json.type type
95
+ json.workflow_state workflow_state
96
+
97
+ rule_sets = self.class.api_access_rules
98
+ rule_sets.each do |aar|
99
+ instance_exec(json, &aar.serializer) if aar.serializer
100
+ end
101
+ end
102
+
103
+ builder.attributes!
104
+ end
105
+
106
+ def api_validate_options
107
+ errors = []
108
+ rule_sets = self.class.api_access_rules
109
+ rule_sets.each do |aar|
110
+ aar.validators.each do |validator|
111
+ errors << instance_exec(&validator)
112
+ end
113
+ end
114
+ errors.flatten.compact.uniq
115
+ end
116
+ end
117
+
118
+ class ApiAccessRules
119
+ attr_accessor :serializer
120
+ attr_reader :validators
121
+ attr_reader :default_options
122
+ attr_reader :permitted_options
123
+
124
+ def initialize
125
+ @serializer = nil
126
+ @validators = []
127
+ @permitted_options = []
128
+ @default_options = {}
129
+ end
130
+
131
+ def default_option(key, value = nil, &blk)
132
+ @default_options[key] = blk || value
133
+ end
134
+
135
+ def permit_options(*args)
136
+ @permitted_options.concat(args)
137
+ end
138
+
139
+ def validate(&blk)
140
+ @validators << blk
141
+ end
142
+
143
+ def serialize(&blk)
144
+ @serializer = blk
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,47 @@
1
+ module Joblin
2
+ module BackgroundTask::Attachments
3
+ extend ActiveSupport::Concern
4
+
5
+ def attachment_path(key, expires_in: true)
6
+ if !expires_in || Rails.env.development? || Rails.env.test?
7
+ return Rails.application.routes.url_helpers.rails_blob_path(
8
+ send(key),
9
+ only_path: true,
10
+ disposition: 'attachment',
11
+ # organization_id: current_organization&.id,
12
+ )
13
+ end
14
+
15
+ if expires_in == true
16
+ send(key).url
17
+ else expires_in
18
+ send(key).url(expires_in:)
19
+ end
20
+ end
21
+
22
+ def attach_file(key, path, as: nil)
23
+ path = File.join(working_dir, path) unless Pathname.new(path).absolute?
24
+
25
+ self.send(key).attach(
26
+ io: File.open(path),
27
+ filename: as || File.basename(path)
28
+ )
29
+ end
30
+
31
+ def load_attachment(key, save_as: nil)
32
+ @loaded_attachments ||= {}
33
+
34
+ @loaded_attachments[key] ||= begin
35
+ save_as = save_as || send(key).filename.to_s || key.to_s
36
+ save_as = File.join(working_dir, save_as) unless Pathname.new(save_as).absolute?
37
+
38
+ File.open(save_as, 'w') do |file|
39
+ send(key).download do |chunk|
40
+ file.write(chunk.force_encoding("UTF-8"))
41
+ end
42
+ end
43
+ save_as
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,63 @@
1
+ module Joblin
2
+ module BackgroundTask::Executor
3
+ extend ActiveSupport::Concern
4
+
5
+ class_methods do
6
+ def inherited(subclass)
7
+ subclass.const_set(:ExecutorJob, Class.new(self::ExecutorJob))
8
+ super
9
+ end
10
+
11
+ def job_executor_class
12
+ self::ExecutorJob
13
+ end
14
+
15
+ def executor_eval(&blk)
16
+ job_executor_class.class_eval(&blk)
17
+ end
18
+
19
+ delegate :queue_as, :sidekiq_options, to: :job_executor_class
20
+
21
+ %i[before around after].each do |hook_type|
22
+ define_method(:"#{hook_type}_perform") do |*args, &blk|
23
+ blk ||= args[0].to_proc
24
+ job_executor_class.send(:"#{hook_type}_perform") do |*args|
25
+ the_task.instance_exec(&blk)
26
+ end
27
+ end
28
+ end
29
+
30
+ # delegate :set, to: :job_executor_class
31
+
32
+ # delegate :before_perform, :around_perform, :after_perform, to: :job_executor_class
33
+ # delegate :before_enqueue, :around_enqueue, :after_enqueue, to: :job_executor_class
34
+ end
35
+
36
+ included do
37
+ delegate_missing_to :_current_executor
38
+ end
39
+
40
+ protected
41
+
42
+ def _current_executor
43
+ @executor
44
+ end
45
+
46
+ class ExecutorJob < ActiveJob::Base
47
+ # include JobWorkingDirs
48
+
49
+ def perform
50
+ the_task.update(workflow_state: "started") if the_task.workflow_state == "scheduled"
51
+
52
+ the_task.perform
53
+ end
54
+
55
+ def the_task
56
+ @the_task ||= BackgroundTask.find(batch_context[:background_task_id]).tap do |task|
57
+ task.instance_variable_set(:@executor, self)
58
+ end
59
+ end
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,75 @@
1
+ module Joblin
2
+ module BackgroundTask::Options
3
+ extend ActiveSupport::Concern
4
+
5
+ class_methods do
6
+ def bt_passthrough_values
7
+ @bt_passthrough_values ||= (instance_methods - ActiveRecord::Base.instance_methods - %i[options= batch_id= id_value= type=]).map(&:to_s).select { |m| m.end_with?('=') }.map { |m| m[0..-2] }
8
+ end
9
+ end
10
+
11
+ included do
12
+ serialize :extra_options, coder: LazyAccess
13
+
14
+ after_initialize do
15
+ self.extra_options ||= HashWithIndifferentAccess.new
16
+ end
17
+ end
18
+
19
+ def options
20
+ @task_options_view ||= TaskOptionsView.new(self)
21
+ end
22
+
23
+ class TaskOptionsView
24
+ def initialize(context)
25
+ @context = context
26
+ end
27
+
28
+ def [](key)
29
+ if @context.class.bt_passthrough_values.include?(key.to_s)
30
+ @context.public_send(key.to_sym)
31
+ else
32
+ inner_hash[key]
33
+ end
34
+ end
35
+
36
+ def []=(key, value)
37
+ if @context.class.bt_passthrough_values.include?(key.to_s)
38
+ @context.public_send("#{key.to_sym}=", value)
39
+ else
40
+ inner_hash[key] = value
41
+ end
42
+ end
43
+
44
+ def merge(other); to_h.merge!(other); end
45
+
46
+ def merge!(other)
47
+ other.each do |k, v|
48
+ self[k] = v
49
+ end
50
+ end
51
+
52
+ def keys
53
+ inner_hash.keys + @context.class.bt_passthrough_values
54
+ end
55
+
56
+ def key?(key)
57
+ if @context.class.bt_passthrough_values.include?(key.to_s)
58
+ @context.public_send(key.to_sym).present?
59
+ else
60
+ inner_hash.key?(key)
61
+ end
62
+ end
63
+
64
+ # def to_h
65
+ # inner_hash.merge(@context.class.bt_passthrough_values.index_with {|k| @context.public_send(k.to_sym) })
66
+ # end
67
+
68
+ private
69
+
70
+ def inner_hash
71
+ @context.extra_options
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,28 @@
1
+ module Joblin
2
+ module BackgroundTask::RetentionPolicy
3
+ extend ActiveSupport::Concern
4
+
5
+ class_methods do
6
+ def record_retention(policy = nil)
7
+ if policy.nil?
8
+ @record_retention || superclass.try(:record_retention)
9
+ else
10
+ @record_retention = policy
11
+ end
12
+ end
13
+ end
14
+
15
+ class BackgroundTaskCleaner < ActiveJob::Base
16
+ def perform
17
+ types = BackgroundTask.distinct.pluck(:type)
18
+ types.each do |type|
19
+ type = type.constantize
20
+ rp = type.record_retention
21
+ next unless rp
22
+
23
+ type.where("created_at < ?", rp.ago).destroy_all
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,72 @@
1
+ module Joblin
2
+ class BackgroundTask < ActiveRecord::Base
3
+ include Options
4
+ include Executor
5
+ include RetentionPolicy
6
+ include Attachments
7
+ include ApiAccess
8
+
9
+ belongs_to :creator, polymorphic: true, optional: true
10
+
11
+ after_initialize do
12
+ self.workflow_state ||= 'unscheduled'
13
+ end
14
+
15
+ scope :grouped_history, lambda { |history_length|
16
+ t = quoted_table_name
17
+ if history_length > 1
18
+ joins("LEFT OUTER JOIN #{t} AS t2 ON #{t}.type = t2.type AND #{t}.created_at < t2.created_at")
19
+ .group(:id).order(:type, created_at: :desc).having('count(*) < ?', history_length)
20
+ else
21
+ select("DISTINCT ON (#{t}.type) #{t}.*")
22
+ end
23
+ }
24
+
25
+ def enqueue!
26
+ self.workflow_state = 'scheduled' if self.workflow_state == 'unscheduled'
27
+
28
+ enter_batch do
29
+ self.class::ExecutorJob.perform_later()
30
+ end
31
+ end
32
+
33
+ def cancel!
34
+ update!(workflow_state: 'cancelled')
35
+ end
36
+
37
+ protected
38
+
39
+ def perform
40
+ raise NotImplementedError
41
+ end
42
+
43
+ def enter_batch(&blk)
44
+ b = batch_id.present? ? Joblin::Batching::Batch.new(batch_id) : Joblin::Batching::Batch.new.tap do |b|
45
+ update!(batch_id: b.bid)
46
+ b.description = "BackgroundTask #{id}"
47
+ b.on(:complete, BackgroundTaskCallbacks, id: id)
48
+ b.on(:success, BackgroundTaskCallbacks, id: id)
49
+ b.context[:background_task_id] = id
50
+ end
51
+ b.jobs(&blk)
52
+ end
53
+
54
+ class BackgroundTaskCallbacks
55
+ def on_complete(status, options)
56
+ tracker = BackgroundTask.find(options['id'])
57
+ return if tracker.workflow_state == 'cancelled'
58
+
59
+ tracker.workflow_state = status.success? ? 'completed' : 'failed'
60
+ tracker.save! if tracker.changed?
61
+ end
62
+
63
+ def on_success(status, options)
64
+ tracker = BackgroundTask.find(options['id'])
65
+ return if tracker.workflow_state == 'cancelled'
66
+
67
+ tracker.workflow_state = 'completed'
68
+ tracker.save! if tracker.changed?
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,21 @@
1
+ module Joblin::Concerns
2
+ module JobWorkingDirs
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ around_perform :cleanup_working_dir
7
+ end
8
+
9
+ def working_dir
10
+ @working_dir ||= Dir.mktmpdir
11
+ end
12
+
13
+ private
14
+
15
+ def cleanup_working_dir
16
+ yield if block_given?
17
+ ensure
18
+ FileUtils.remove_entry @working_dir if @working_dir
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,12 @@
1
+ class CreateBackgroundTasks < ActiveRecord::Migration[5.2]
2
+ def change
3
+ create_table :joblin_background_tasks do |t|
4
+ t.references :creator, polymorphic: true
5
+ t.jsonb :extra_options
6
+ t.string :batch_id
7
+ t.string :type, null: false
8
+ t.string :workflow_state, null: false, default: 'unscheduled'
9
+ t.timestamps
10
+ end
11
+ end
12
+ end
data/joblin.gemspec ADDED
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ begin
6
+ require "joblin/version"
7
+ version = Joblin::VERSION
8
+ rescue LoadError
9
+ version = "0.0.0.docker"
10
+ end
11
+
12
+ Gem::Specification.new do |spec|
13
+ spec.name = "joblin"
14
+ spec.version = version
15
+ spec.authors = ["Ethan Knapp"]
16
+ spec.email = ["eknapp@instructure.com"]
17
+
18
+ spec.summary = "Gem for ActiveJob extensions"
19
+ spec.homepage = "https://instructure.com"
20
+
21
+ spec.files = Dir["{app,config,db,lib}/**/*", "README.md", "*.gemspec"]
22
+ spec.test_files = Dir["spec/**/*"]
23
+ spec.require_paths = ['lib']
24
+
25
+ spec.add_dependency 'rediconn', '~> 0.1.0'
26
+ spec.add_dependency 'rails', '>= 5', '< 9.0'
27
+
28
+ spec.add_development_dependency 'rspec', '~> 3'
29
+ spec.add_development_dependency 'redis'
30
+ spec.add_development_dependency 'rspec-rails'
31
+ spec.add_development_dependency 'pg'
32
+
33
+ spec.add_development_dependency "appraisal"
34
+ spec.add_development_dependency 'combustion', '~> 1.3'
35
+ end