ruby_llm-agents 1.0.0 → 1.2.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 (152) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/ruby_llm/agents/paginatable.rb +9 -3
  3. data/app/controllers/concerns/ruby_llm/agents/sortable.rb +58 -0
  4. data/app/controllers/ruby_llm/agents/agents_controller.rb +59 -16
  5. data/app/controllers/ruby_llm/agents/dashboard_controller.rb +144 -20
  6. data/app/controllers/ruby_llm/agents/executions_controller.rb +13 -16
  7. data/app/controllers/ruby_llm/agents/workflows_controller.rb +279 -90
  8. data/app/helpers/ruby_llm/agents/application_helper.rb +100 -0
  9. data/app/mailers/ruby_llm/agents/alert_mailer.rb +84 -0
  10. data/app/mailers/ruby_llm/agents/application_mailer.rb +28 -0
  11. data/app/models/ruby_llm/agents/execution/analytics.rb +170 -20
  12. data/app/models/ruby_llm/agents/execution/scopes.rb +0 -31
  13. data/app/models/ruby_llm/agents/execution/workflow.rb +0 -129
  14. data/app/models/ruby_llm/agents/execution.rb +50 -14
  15. data/app/models/ruby_llm/agents/tenant/budgetable.rb +277 -0
  16. data/app/models/ruby_llm/agents/tenant/configurable.rb +135 -0
  17. data/app/models/ruby_llm/agents/tenant/trackable.rb +310 -0
  18. data/app/models/ruby_llm/agents/tenant.rb +146 -0
  19. data/app/models/ruby_llm/agents/tenant_budget.rb +12 -253
  20. data/app/services/ruby_llm/agents/agent_registry.rb +18 -12
  21. data/app/views/layouts/ruby_llm/agents/application.html.erb +72 -76
  22. data/app/views/ruby_llm/agents/agents/_agent.html.erb +0 -12
  23. data/app/views/ruby_llm/agents/agents/_sortable_header.html.erb +56 -0
  24. data/app/views/ruby_llm/agents/agents/_workflow.html.erb +5 -15
  25. data/app/views/ruby_llm/agents/agents/index.html.erb +271 -100
  26. data/app/views/ruby_llm/agents/agents/show.html.erb +1 -0
  27. data/app/views/ruby_llm/agents/alert_mailer/alert_notification.html.erb +107 -0
  28. data/app/views/ruby_llm/agents/alert_mailer/alert_notification.text.erb +18 -0
  29. data/app/views/ruby_llm/agents/api_configurations/show.html.erb +4 -1
  30. data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +66 -359
  31. data/app/views/ruby_llm/agents/dashboard/_model_comparison.html.erb +56 -0
  32. data/app/views/ruby_llm/agents/dashboard/_model_cost_breakdown.html.erb +115 -0
  33. data/app/views/ruby_llm/agents/dashboard/_now_strip.html.erb +35 -60
  34. data/app/views/ruby_llm/agents/dashboard/_top_errors.html.erb +17 -6
  35. data/app/views/ruby_llm/agents/dashboard/index.html.erb +373 -72
  36. data/app/views/ruby_llm/agents/executions/_execution.html.erb +0 -1
  37. data/app/views/ruby_llm/agents/executions/_filters.html.erb +51 -39
  38. data/app/views/ruby_llm/agents/executions/_list.html.erb +53 -195
  39. data/app/views/ruby_llm/agents/executions/_workflow_summary.html.erb +5 -20
  40. data/app/views/ruby_llm/agents/executions/index.html.erb +7 -83
  41. data/app/views/ruby_llm/agents/executions/show.html.erb +10 -20
  42. data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +2 -1
  43. data/app/views/ruby_llm/agents/shared/_doc_link.html.erb +12 -0
  44. data/app/views/ruby_llm/agents/shared/_executions_table.html.erb +3 -15
  45. data/app/views/ruby_llm/agents/shared/_filter_dropdown.html.erb +1 -1
  46. data/app/views/ruby_llm/agents/shared/_select_dropdown.html.erb +1 -1
  47. data/app/views/ruby_llm/agents/shared/_sortable_header.html.erb +53 -0
  48. data/app/views/ruby_llm/agents/shared/_status_badge.html.erb +7 -0
  49. data/app/views/ruby_llm/agents/shared/_status_dot.html.erb +1 -1
  50. data/app/views/ruby_llm/agents/shared/_workflow_type_badge.html.erb +9 -35
  51. data/app/views/ruby_llm/agents/system_config/show.html.erb +4 -1
  52. data/app/views/ruby_llm/agents/tenants/index.html.erb +4 -1
  53. data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +7 -15
  54. data/app/views/ruby_llm/agents/workflows/_structure_dsl.html.erb +539 -0
  55. data/app/views/ruby_llm/agents/workflows/_workflow_diagram.html.erb +920 -0
  56. data/app/views/ruby_llm/agents/workflows/index.html.erb +179 -0
  57. data/app/views/ruby_llm/agents/workflows/show.html.erb +164 -139
  58. data/config/routes.rb +1 -1
  59. data/lib/generators/ruby_llm_agents/agent_generator.rb +6 -36
  60. data/lib/generators/ruby_llm_agents/background_remover_generator.rb +7 -37
  61. data/lib/generators/ruby_llm_agents/embedder_generator.rb +5 -38
  62. data/lib/generators/ruby_llm_agents/image_analyzer_generator.rb +7 -37
  63. data/lib/generators/ruby_llm_agents/image_editor_generator.rb +7 -37
  64. data/lib/generators/ruby_llm_agents/image_generator_generator.rb +8 -41
  65. data/lib/generators/ruby_llm_agents/image_pipeline_generator.rb +18 -46
  66. data/lib/generators/ruby_llm_agents/image_transformer_generator.rb +7 -37
  67. data/lib/generators/ruby_llm_agents/image_upscaler_generator.rb +7 -37
  68. data/lib/generators/ruby_llm_agents/image_variator_generator.rb +7 -37
  69. data/lib/generators/ruby_llm_agents/install_generator.rb +33 -56
  70. data/lib/generators/ruby_llm_agents/migrate_structure_generator.rb +480 -0
  71. data/lib/generators/ruby_llm_agents/multi_tenancy_generator.rb +42 -22
  72. data/lib/generators/ruby_llm_agents/restructure_generator.rb +2 -2
  73. data/lib/generators/ruby_llm_agents/speaker_generator.rb +8 -39
  74. data/lib/generators/ruby_llm_agents/templates/add_tenant_to_executions_migration.rb.tt +13 -2
  75. data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +5 -8
  76. data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +40 -42
  77. data/lib/generators/ruby_llm_agents/templates/application_background_remover.rb.tt +20 -22
  78. data/lib/generators/ruby_llm_agents/templates/application_embedder.rb.tt +24 -26
  79. data/lib/generators/ruby_llm_agents/templates/application_image_analyzer.rb.tt +20 -22
  80. data/lib/generators/ruby_llm_agents/templates/application_image_editor.rb.tt +19 -17
  81. data/lib/generators/ruby_llm_agents/templates/application_image_generator.rb.tt +31 -33
  82. data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +125 -127
  83. data/lib/generators/ruby_llm_agents/templates/application_image_transformer.rb.tt +20 -18
  84. data/lib/generators/ruby_llm_agents/templates/application_image_upscaler.rb.tt +19 -17
  85. data/lib/generators/ruby_llm_agents/templates/application_image_variator.rb.tt +19 -17
  86. data/lib/generators/ruby_llm_agents/templates/application_speaker.rb.tt +38 -40
  87. data/lib/generators/ruby_llm_agents/templates/application_transcriber.rb.tt +42 -44
  88. data/lib/generators/ruby_llm_agents/templates/application_workflow.rb.tt +48 -0
  89. data/lib/generators/ruby_llm_agents/templates/background_remover.rb.tt +19 -21
  90. data/lib/generators/ruby_llm_agents/templates/create_tenant_budgets_migration.rb.tt +11 -0
  91. data/lib/generators/ruby_llm_agents/templates/create_tenants_migration.rb.tt +72 -0
  92. data/lib/generators/ruby_llm_agents/templates/embedder.rb.tt +19 -21
  93. data/lib/generators/ruby_llm_agents/templates/image_analyzer.rb.tt +20 -22
  94. data/lib/generators/ruby_llm_agents/templates/image_editor.rb.tt +15 -17
  95. data/lib/generators/ruby_llm_agents/templates/image_generator.rb.tt +25 -27
  96. data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +19 -21
  97. data/lib/generators/ruby_llm_agents/templates/image_transformer.rb.tt +20 -22
  98. data/lib/generators/ruby_llm_agents/templates/image_upscaler.rb.tt +17 -19
  99. data/lib/generators/ruby_llm_agents/templates/image_variator.rb.tt +15 -17
  100. data/lib/generators/ruby_llm_agents/templates/rename_tenant_budgets_to_tenants_migration.rb.tt +34 -0
  101. data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +87 -24
  102. data/lib/generators/ruby_llm_agents/templates/skills/BACKGROUND_REMOVERS.md.tt +21 -27
  103. data/lib/generators/ruby_llm_agents/templates/skills/EMBEDDERS.md.tt +46 -54
  104. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_ANALYZERS.md.tt +31 -39
  105. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_EDITORS.md.tt +22 -28
  106. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_GENERATORS.md.tt +53 -63
  107. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +46 -56
  108. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_TRANSFORMERS.md.tt +23 -31
  109. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_UPSCALERS.md.tt +22 -30
  110. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_VARIATORS.md.tt +23 -31
  111. data/lib/generators/ruby_llm_agents/templates/skills/SPEAKERS.md.tt +38 -46
  112. data/lib/generators/ruby_llm_agents/templates/skills/TOOLS.md.tt +7 -7
  113. data/lib/generators/ruby_llm_agents/templates/skills/TRANSCRIBERS.md.tt +59 -71
  114. data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +274 -23
  115. data/lib/generators/ruby_llm_agents/templates/speaker.rb.tt +29 -31
  116. data/lib/generators/ruby_llm_agents/templates/transcriber.rb.tt +28 -30
  117. data/lib/generators/ruby_llm_agents/transcriber_generator.rb +10 -43
  118. data/lib/generators/ruby_llm_agents/upgrade_generator.rb +26 -0
  119. data/lib/ruby_llm/agents/core/configuration.rb +55 -43
  120. data/lib/ruby_llm/agents/core/llm_tenant.rb +60 -60
  121. data/lib/ruby_llm/agents/core/version.rb +1 -1
  122. data/lib/ruby_llm/agents/infrastructure/alert_manager.rb +26 -0
  123. data/lib/ruby_llm/agents/infrastructure/budget/config_resolver.rb +4 -2
  124. data/lib/ruby_llm/agents/pipeline.rb +69 -0
  125. data/lib/ruby_llm/agents/workflow/approval.rb +205 -0
  126. data/lib/ruby_llm/agents/workflow/approval_store.rb +179 -0
  127. data/lib/ruby_llm/agents/workflow/dsl/executor.rb +467 -0
  128. data/lib/ruby_llm/agents/workflow/dsl/input_schema.rb +244 -0
  129. data/lib/ruby_llm/agents/workflow/dsl/iteration_executor.rb +289 -0
  130. data/lib/ruby_llm/agents/workflow/dsl/parallel_group.rb +107 -0
  131. data/lib/ruby_llm/agents/workflow/dsl/route_builder.rb +150 -0
  132. data/lib/ruby_llm/agents/workflow/dsl/schedule_helpers.rb +187 -0
  133. data/lib/ruby_llm/agents/workflow/dsl/step_config.rb +352 -0
  134. data/lib/ruby_llm/agents/workflow/dsl/step_executor.rb +415 -0
  135. data/lib/ruby_llm/agents/workflow/dsl/wait_config.rb +257 -0
  136. data/lib/ruby_llm/agents/workflow/dsl/wait_executor.rb +317 -0
  137. data/lib/ruby_llm/agents/workflow/dsl.rb +576 -0
  138. data/lib/ruby_llm/agents/workflow/instrumentation.rb +2 -7
  139. data/lib/ruby_llm/agents/workflow/notifiers/base.rb +117 -0
  140. data/lib/ruby_llm/agents/workflow/notifiers/email.rb +117 -0
  141. data/lib/ruby_llm/agents/workflow/notifiers/slack.rb +180 -0
  142. data/lib/ruby_llm/agents/workflow/notifiers/webhook.rb +121 -0
  143. data/lib/ruby_llm/agents/workflow/notifiers.rb +70 -0
  144. data/lib/ruby_llm/agents/workflow/orchestrator.rb +190 -23
  145. data/lib/ruby_llm/agents/workflow/result.rb +202 -0
  146. data/lib/ruby_llm/agents/workflow/throttle_manager.rb +206 -0
  147. data/lib/ruby_llm/agents/workflow/wait_result.rb +213 -0
  148. metadata +43 -6
  149. data/app/views/ruby_llm/agents/dashboard/_execution_item.html.erb +0 -66
  150. data/lib/ruby_llm/agents/workflow/parallel.rb +0 -299
  151. data/lib/ruby_llm/agents/workflow/pipeline.rb +0 -306
  152. data/lib/ruby_llm/agents/workflow/router.rb +0 -429
@@ -0,0 +1,205 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+
5
+ module RubyLLM
6
+ module Agents
7
+ class Workflow
8
+ # Represents an approval request for human-in-the-loop workflows
9
+ #
10
+ # Tracks the state of an approval including who created it, who can approve it,
11
+ # and the final decision with timestamp and reason.
12
+ #
13
+ # @example Creating an approval
14
+ # approval = Approval.new(
15
+ # workflow_id: "order-123",
16
+ # workflow_type: "OrderApprovalWorkflow",
17
+ # name: :manager_approval,
18
+ # metadata: { order_total: 5000 }
19
+ # )
20
+ #
21
+ # @example Approving
22
+ # approval.approve!("manager@example.com")
23
+ #
24
+ # @example Rejecting
25
+ # approval.reject!("manager@example.com", reason: "Budget exceeded")
26
+ #
27
+ # @api public
28
+ class Approval
29
+ STATUSES = %i[pending approved rejected expired].freeze
30
+
31
+ attr_reader :id, :workflow_id, :workflow_type, :name, :status,
32
+ :created_at, :metadata, :approvers, :expires_at
33
+ attr_accessor :approved_by, :approved_at, :rejected_by, :rejected_at,
34
+ :reason, :reminded_at
35
+
36
+ # @param workflow_id [String] The workflow instance ID
37
+ # @param workflow_type [String] The workflow class name
38
+ # @param name [Symbol] The approval point name
39
+ # @param approvers [Array<String>] List of user IDs who can approve
40
+ # @param expires_at [Time, nil] When the approval expires
41
+ # @param metadata [Hash] Additional context for the approval
42
+ def initialize(workflow_id:, workflow_type:, name:, approvers: [], expires_at: nil, metadata: {})
43
+ @id = SecureRandom.uuid
44
+ @workflow_id = workflow_id
45
+ @workflow_type = workflow_type
46
+ @name = name
47
+ @status = :pending
48
+ @approvers = approvers
49
+ @expires_at = expires_at
50
+ @metadata = metadata
51
+ @created_at = Time.now
52
+ end
53
+
54
+ # Approve the request
55
+ #
56
+ # @param user_id [String] The user approving
57
+ # @param comment [String, nil] Optional comment
58
+ # @return [void]
59
+ def approve!(user_id, comment: nil)
60
+ raise InvalidStateError, "Cannot approve: status is #{status}" unless pending?
61
+
62
+ @status = :approved
63
+ @approved_by = user_id
64
+ @approved_at = Time.now
65
+ @metadata[:approval_comment] = comment if comment
66
+ end
67
+
68
+ # Reject the request
69
+ #
70
+ # @param user_id [String] The user rejecting
71
+ # @param reason [String, nil] Reason for rejection
72
+ # @return [void]
73
+ def reject!(user_id, reason: nil)
74
+ raise InvalidStateError, "Cannot reject: status is #{status}" unless pending?
75
+
76
+ @status = :rejected
77
+ @rejected_by = user_id
78
+ @rejected_at = Time.now
79
+ @reason = reason
80
+ end
81
+
82
+ # Expire the approval request
83
+ #
84
+ # @return [void]
85
+ def expire!
86
+ raise InvalidStateError, "Cannot expire: status is #{status}" unless pending?
87
+
88
+ @status = :expired
89
+ end
90
+
91
+ # Check if approval is still pending
92
+ #
93
+ # @return [Boolean]
94
+ def pending?
95
+ status == :pending
96
+ end
97
+
98
+ # Check if approval was granted
99
+ #
100
+ # @return [Boolean]
101
+ def approved?
102
+ status == :approved
103
+ end
104
+
105
+ # Check if approval was rejected
106
+ #
107
+ # @return [Boolean]
108
+ def rejected?
109
+ status == :rejected
110
+ end
111
+
112
+ # Check if approval has expired
113
+ #
114
+ # @return [Boolean]
115
+ def expired?
116
+ status == :expired
117
+ end
118
+
119
+ # Check if the approval has timed out
120
+ #
121
+ # @return [Boolean]
122
+ def timed_out?
123
+ return false unless expires_at
124
+
125
+ Time.now > expires_at && pending?
126
+ end
127
+
128
+ # Check if a user can approve this request
129
+ #
130
+ # @param user_id [String] The user to check
131
+ # @return [Boolean]
132
+ def can_approve?(user_id)
133
+ return true if approvers.empty? # Anyone can approve if no restrictions
134
+
135
+ approvers.include?(user_id)
136
+ end
137
+
138
+ # Duration since creation
139
+ #
140
+ # @return [Float] Seconds since creation
141
+ def age
142
+ Time.now - created_at
143
+ end
144
+
145
+ # Duration until expiry
146
+ #
147
+ # @return [Float, nil] Seconds until expiry, nil if no expiry
148
+ def time_until_expiry
149
+ return nil unless expires_at
150
+
151
+ expires_at - Time.now
152
+ end
153
+
154
+ # Mark that a reminder was sent
155
+ #
156
+ # @return [void]
157
+ def mark_reminded!
158
+ @reminded_at = Time.now
159
+ end
160
+
161
+ # Check if a reminder should be sent
162
+ #
163
+ # @param reminder_after [Integer] Seconds after creation to send reminder
164
+ # @param reminder_interval [Integer, nil] Interval between reminders
165
+ # @return [Boolean]
166
+ def should_remind?(reminder_after, reminder_interval: nil)
167
+ return false unless pending?
168
+ return false if age < reminder_after
169
+
170
+ if reminded_at && reminder_interval
171
+ Time.now - reminded_at >= reminder_interval
172
+ else
173
+ reminded_at.nil?
174
+ end
175
+ end
176
+
177
+ # Convert to hash for serialization
178
+ #
179
+ # @return [Hash]
180
+ def to_h
181
+ {
182
+ id: id,
183
+ workflow_id: workflow_id,
184
+ workflow_type: workflow_type,
185
+ name: name,
186
+ status: status,
187
+ approvers: approvers,
188
+ approved_by: approved_by,
189
+ approved_at: approved_at,
190
+ rejected_by: rejected_by,
191
+ rejected_at: rejected_at,
192
+ reason: reason,
193
+ expires_at: expires_at,
194
+ reminded_at: reminded_at,
195
+ metadata: metadata,
196
+ created_at: created_at
197
+ }.compact
198
+ end
199
+
200
+ # Error for invalid state transitions
201
+ class InvalidStateError < StandardError; end
202
+ end
203
+ end
204
+ end
205
+ end
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module Agents
5
+ class Workflow
6
+ # Abstract base class for approval storage
7
+ #
8
+ # Provides a common interface for storing and retrieving approval requests.
9
+ # Implementations can use in-memory storage, databases, Redis, etc.
10
+ #
11
+ # @example Setting a custom store
12
+ # RubyLLM::Agents::Workflow::ApprovalStore.store = MyRedisStore.new
13
+ #
14
+ # @example Using the default store
15
+ # store = RubyLLM::Agents::Workflow::ApprovalStore.store
16
+ # store.save(approval)
17
+ #
18
+ # @api public
19
+ class ApprovalStore
20
+ class << self
21
+ # Returns the configured store instance
22
+ #
23
+ # @return [ApprovalStore]
24
+ def store
25
+ @store ||= default_store
26
+ end
27
+
28
+ # Sets the store instance
29
+ #
30
+ # @param store [ApprovalStore] The store to use
31
+ # @return [void]
32
+ def store=(store)
33
+ @store = store
34
+ end
35
+
36
+ # Resets to the default store (useful for testing)
37
+ #
38
+ # @return [void]
39
+ def reset!
40
+ @store = nil
41
+ end
42
+
43
+ private
44
+
45
+ def default_store
46
+ MemoryApprovalStore.new
47
+ end
48
+ end
49
+
50
+ # Save an approval
51
+ #
52
+ # @param approval [Approval] The approval to save
53
+ # @return [Approval] The saved approval
54
+ def save(approval)
55
+ raise NotImplementedError, "#{self.class}#save must be implemented"
56
+ end
57
+
58
+ # Find an approval by ID
59
+ #
60
+ # @param id [String] The approval ID
61
+ # @return [Approval, nil]
62
+ def find(id)
63
+ raise NotImplementedError, "#{self.class}#find must be implemented"
64
+ end
65
+
66
+ # Find all approvals for a workflow
67
+ #
68
+ # @param workflow_id [String] The workflow ID
69
+ # @return [Array<Approval>]
70
+ def find_by_workflow(workflow_id)
71
+ raise NotImplementedError, "#{self.class}#find_by_workflow must be implemented"
72
+ end
73
+
74
+ # Find pending approvals for a user
75
+ #
76
+ # @param user_id [String] The user ID
77
+ # @return [Array<Approval>]
78
+ def pending_for_user(user_id)
79
+ raise NotImplementedError, "#{self.class}#pending_for_user must be implemented"
80
+ end
81
+
82
+ # Find all pending approvals
83
+ #
84
+ # @return [Array<Approval>]
85
+ def all_pending
86
+ raise NotImplementedError, "#{self.class}#all_pending must be implemented"
87
+ end
88
+
89
+ # Delete an approval
90
+ #
91
+ # @param id [String] The approval ID
92
+ # @return [Boolean] true if deleted
93
+ def delete(id)
94
+ raise NotImplementedError, "#{self.class}#delete must be implemented"
95
+ end
96
+
97
+ # Delete all approvals (useful for testing)
98
+ #
99
+ # @return [void]
100
+ def clear!
101
+ raise NotImplementedError, "#{self.class}#clear! must be implemented"
102
+ end
103
+ end
104
+
105
+ # In-memory approval store for development and testing
106
+ #
107
+ # Thread-safe storage using a Mutex.
108
+ #
109
+ # @api public
110
+ class MemoryApprovalStore < ApprovalStore
111
+ def initialize
112
+ @approvals = {}
113
+ @mutex = Mutex.new
114
+ end
115
+
116
+ # @see ApprovalStore#save
117
+ def save(approval)
118
+ @mutex.synchronize do
119
+ @approvals[approval.id] = approval
120
+ end
121
+ approval
122
+ end
123
+
124
+ # @see ApprovalStore#find
125
+ def find(id)
126
+ @mutex.synchronize do
127
+ @approvals[id]
128
+ end
129
+ end
130
+
131
+ # @see ApprovalStore#find_by_workflow
132
+ def find_by_workflow(workflow_id)
133
+ @mutex.synchronize do
134
+ @approvals.values.select { |a| a.workflow_id == workflow_id }
135
+ end
136
+ end
137
+
138
+ # @see ApprovalStore#pending_for_user
139
+ def pending_for_user(user_id)
140
+ @mutex.synchronize do
141
+ @approvals.values.select do |a|
142
+ a.pending? && a.can_approve?(user_id)
143
+ end
144
+ end
145
+ end
146
+
147
+ # @see ApprovalStore#all_pending
148
+ def all_pending
149
+ @mutex.synchronize do
150
+ @approvals.values.select(&:pending?)
151
+ end
152
+ end
153
+
154
+ # @see ApprovalStore#delete
155
+ def delete(id)
156
+ @mutex.synchronize do
157
+ !!@approvals.delete(id)
158
+ end
159
+ end
160
+
161
+ # @see ApprovalStore#clear!
162
+ def clear!
163
+ @mutex.synchronize do
164
+ @approvals.clear
165
+ end
166
+ end
167
+
168
+ # Returns the count of stored approvals
169
+ #
170
+ # @return [Integer]
171
+ def count
172
+ @mutex.synchronize do
173
+ @approvals.size
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end