Module: Sinatra::Scheduled::ClassMethods
- Defined in:
- lib/sinatra/scheduled.rb
Instance Method Summary collapse
-
#dispatch_scheduled(event, js_env = nil, js_ctx = nil) ⇒ Object
Dispatcher entry point — called by Cloudflare::Scheduled with a Cloudflare::ScheduledEvent and the JS env / ctx objects.
-
#schedule(cron, name: nil, match: nil, &block) ⇒ Object
Register a cron block.
-
#scheduled_jobs ⇒ Object
Returns the per-class array of registered Job instances.
-
#scheduled_jobs_for(cron_string) ⇒ Object
Returns all jobs that match the given cron string.
Instance Method Details
#dispatch_scheduled(event, js_env = nil, js_ctx = nil) ⇒ Object
Dispatcher entry point — called by Cloudflare::Scheduled with a Cloudflare::ScheduledEvent and the JS env / ctx objects. Returns a Hash with ‘fired`, `total`, `errors` for diagnostics.
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 |
# File 'lib/sinatra/scheduled.rb', line 167 def dispatch_scheduled(event, js_env = nil, js_ctx = nil) jobs = scheduled_jobs_for(event.cron) results = [] i = 0 # `while` instead of `each` keeps the per-iteration begin/rescue # straightforward under Opal's `# await: true` translation — # each iteration's `__await__` is awaited inline rather than # through a yielded async block (which has had subtle issues # with rescue propagation in Opal). while i < jobs.length job = jobs[i] start = Time.now.to_f begin # invoke_scheduled_job is an async method (it's defined # inside a `# await: true` file and may itself await an # inner Promise from the user block). Calling it returns # a Promise — we MUST `__await__` it here so: # (a) downstream code sees fully-applied side effects, # (b) the rescue below catches Promise rejections that # propagate as Ruby exceptions. # # The literal `__await__` token is what Opal scans for to # emit a JS `await`; calling a helper that internally does # `__await__` is NOT enough, because the helper's return # value is itself a Promise that the caller would have to # `__await__` again. promise = invoke_scheduled_job(job, event, js_env, js_ctx) if `(#{promise} != null && typeof #{promise}.then === 'function')` promise.__await__ end results << { 'name' => job.name, 'cron' => job.cron, 'ok' => true, 'duration' => Time.now.to_f - start } rescue ::Exception => e results << { 'name' => job.name, 'cron' => job.cron, 'ok' => false, 'error' => "#{e.class}: #{e.}", 'duration' => Time.now.to_f - start } end i += 1 end { 'fired' => results.size, 'total' => scheduled_jobs.size, 'results' => results } end |
#schedule(cron, name: nil, match: nil, &block) ⇒ Object
Register a cron block.
schedule '*/5 * * * *' do |event|
...
end
Options:
:name — human label for logging (default: the cron string)
:match — proc(cron_string) returning truthy if this job
should fire. Defaults to exact-string equality.
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/sinatra/scheduled.rb', line 115 def schedule(cron, name: nil, match: nil, &block) raise ArgumentError, 'schedule requires a block' unless block cron_str = cron.to_s raise ArgumentError, 'cron expression must be non-empty' if cron_str.empty? # Cheap structural sanity-check: 5 or 6 whitespace-separated # fields. Cloudflare allows the standard 5-field form. fields = cron_str.split(/\s+/) unless [5, 6].include?(fields.length) raise ArgumentError, "cron expression must have 5 or 6 fields, got #{fields.length}: #{cron_str.inspect}" end # Fail loudly at registration time if a non-callable `match:` # was passed — otherwise the failure would surface only when # the cron actually fires (as a NoMethodError during dispatch), # which is a much worse debugging experience. if !match.nil? && !match.respond_to?(:call) raise ArgumentError, "match: must respond to #call (got #{match.class})" end loc = block.respond_to?(:source_location) ? block.source_location : nil file = loc.is_a?(Array) ? loc[0] : nil line = loc.is_a?(Array) ? loc[1] : nil # Convert the block into an UnboundMethod bound to # ScheduledContext. `define_method` is what triggers Opal's # `# await: true` machinery to wrap the body as an async # function — without this step, `kv.get(...).__await__` would # never resolve because the surrounding scope isn't async. # See the Job class comment for the full rationale. method_name = "__scheduled_#{cron_str.object_id}_#{scheduled_jobs.length}".to_sym ScheduledContext.send(:define_method, method_name, &block) unbound = ScheduledContext.instance_method(method_name) ScheduledContext.send(:remove_method, method_name) scheduled_jobs << Job.new( cron: cron_str, name: name, block: block, unbound_method: unbound, match_proc: match, file: file, line: line ) end |
#scheduled_jobs ⇒ Object
Returns the per-class array of registered Job instances. Stored in a class instance variable, so subclasses do not inherit the parent’s jobs unless that behavior is implemented explicitly (e.g. via an ‘inherited` hook copying the parent’s ‘@scheduled_jobs`). The current behavior keeps each Sinatra subclass’s schedule registry independent.
101 102 103 |
# File 'lib/sinatra/scheduled.rb', line 101 def scheduled_jobs @scheduled_jobs ||= [] end |
#scheduled_jobs_for(cron_string) ⇒ Object
Returns all jobs that match the given cron string.
160 161 162 |
# File 'lib/sinatra/scheduled.rb', line 160 def scheduled_jobs_for(cron_string) scheduled_jobs.select { |j| j.matches?(cron_string) } end |