fragmentary 0.2.2 → 0.4.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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +13 -0
- data/README.md +180 -61
- data/fragmentary.gemspec +3 -2
- data/lib/fragmentary/config.rb +29 -10
- data/lib/fragmentary/fragment.rb +105 -37
- data/lib/fragmentary/fragments_helper.rb +19 -1
- data/lib/fragmentary/handler.rb +24 -0
- data/lib/fragmentary/{dispatcher.rb → jobs/dispatch_handlers_job.rb} +6 -6
- data/lib/fragmentary/jobs/send_requests_job.rb +26 -0
- data/lib/fragmentary/publisher.rb +2 -0
- data/lib/fragmentary/request.rb +1 -16
- data/lib/fragmentary/request_queue.rb +56 -32
- data/lib/fragmentary/serializers/handler_serializer.rb +24 -0
- data/lib/fragmentary/serializers/request_queue_serializer.rb +36 -0
- data/lib/fragmentary/session_user.rb +38 -0
- data/lib/fragmentary/subscription.rb +5 -2
- data/lib/fragmentary/user_session.rb +111 -33
- data/lib/fragmentary/version.rb +1 -1
- data/lib/fragmentary.rb +11 -0
- metadata +32 -21
data/lib/fragmentary/fragment.rb
CHANGED
@@ -24,8 +24,6 @@ module Fragmentary
|
|
24
24
|
# redundant duplicate request.
|
25
25
|
after_commit :touch_parent, :on => [:update, :destroy]
|
26
26
|
|
27
|
-
attr_accessible :parent_id, :root_id, :record_id, :user_id, :user_type, :key
|
28
|
-
|
29
27
|
attr_accessor :indexed_children
|
30
28
|
|
31
29
|
# Set cache timestamp format to :usec instead of :nsec because the latter is greater precision than Postgres supports,
|
@@ -73,8 +71,14 @@ module Fragmentary
|
|
73
71
|
# Collect the attributes to be used when searching for an existing fragment. Fragments are unique by these values.
|
74
72
|
search_attributes = {}
|
75
73
|
|
76
|
-
parent_id = options.delete(:parent_id)
|
77
|
-
|
74
|
+
if (parent_id = options.delete(:parent_id))
|
75
|
+
search_attributes.merge!(:parent_id => parent_id)
|
76
|
+
else
|
77
|
+
application_root_url_column = Fragmentary.config.application_root_url_column
|
78
|
+
if (application_root_url = options.delete(application_root_url_column)) && column_names.include?(application_root_url_column.to_s)
|
79
|
+
search_attributes.merge!(application_root_url_column => application_root_url)
|
80
|
+
end
|
81
|
+
end
|
78
82
|
|
79
83
|
[:record_id, :user_id, :user_type, :key].each do |attribute_name|
|
80
84
|
if klass.needs?(attribute_name)
|
@@ -117,20 +121,53 @@ module Fragmentary
|
|
117
121
|
self
|
118
122
|
end
|
119
123
|
|
124
|
+
# There is one queue per user_type per application instance (the current app and any external instances). The queues
|
125
|
+
# for all fragments are held in common by the Fragment base class here in @@request_queues but are also indexed on a
|
126
|
+
# subclass basis by an individual subclass's user_types (see the inherited hook below). As well as being accessible
|
127
|
+
# here as Fragment.request_queues, the queues are also available without indexation as RequestQueue.all.
|
120
128
|
def request_queues
|
121
|
-
@@request_queues ||= Hash.new do |
|
122
|
-
hash
|
129
|
+
@@request_queues ||= Hash.new do |hsh, host_url|
|
130
|
+
# As well as acting as a hash key to index the set of request queues for a given target application instance
|
131
|
+
# (for which its uniqueness is the only requirement), host_url is also passed to the RequestQueue constructor,
|
132
|
+
# from which it is used:
|
133
|
+
# (i) by the RequestQueue::Sender to derive the name of the delayed_job queue that will be used to process the
|
134
|
+
# queued requests if the sender is invoked in asynchronous mode - see RequestQueue::Sender#schedulerequests.
|
135
|
+
# (ii) by the Fragmentary::InternalUserSession instantiated by the Sender to configure the session_host.
|
136
|
+
hsh[host_url] = Hash.new do |hsh2, user_type|
|
137
|
+
hsh2[user_type] = RequestQueue.new(user_type, host_url)
|
138
|
+
end
|
123
139
|
end
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
140
|
+
end
|
141
|
+
|
142
|
+
# Subclass-specific request_queues
|
143
|
+
def inherited(subclass)
|
144
|
+
subclass.instance_eval do
|
145
|
+
|
146
|
+
def request_queues
|
147
|
+
super # ensure that @@request_queues has been defined
|
148
|
+
@request_queues ||= begin
|
149
|
+
app_root_url = Fragmentary.application_root_url
|
150
|
+
remote_urls = Fragmentary.config.remote_urls
|
151
|
+
user_types.each_with_object( Hash.new {|hsh0, url| hsh0[url] = {}} ) do |user_type, hsh|
|
152
|
+
# Internal request queues
|
153
|
+
hsh[app_root_url][user_type] = @@request_queues[app_root_url][user_type]
|
154
|
+
# External request queues
|
155
|
+
if remote_urls.any?
|
156
|
+
unless Rails.application.routes.default_url_options[:host]
|
157
|
+
raise "Can't create external request queues without setting Rails.application.routes.default_url_options[:host]"
|
158
|
+
end
|
159
|
+
remote_urls.each {|remote_url| hsh[remote_url][user_type] = @@request_queues[remote_url][user_type]}
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
129
165
|
end
|
166
|
+
super
|
130
167
|
end
|
131
168
|
|
132
169
|
def remove_queued_request(user:, request_path:)
|
133
|
-
request_queues[user_type(user)].remove_path(request_path)
|
170
|
+
request_queues.each{|key, hsh| hsh[user_type(user)].remove_path(request_path)}
|
134
171
|
end
|
135
172
|
|
136
173
|
def subscriber
|
@@ -158,6 +195,11 @@ module Fragmentary
|
|
158
195
|
# signed in. When the fragment is instantiated using FragmentsHelper methods 'cache_fragment' or 'CacheBuilder.cache_child',
|
159
196
|
# a :user option is added to the options hash automatically from the value of 'current_user'. The user_type is extracted
|
160
197
|
# from this option in Fragment.attributes.
|
198
|
+
#
|
199
|
+
# For each class that declares 'needs_user_type', a set of user_types is defined that determines the set of request_queues
|
200
|
+
# that will be used to send requests to the application when a fragment is touched. By default these user_types are defined
|
201
|
+
# globally using 'Fragmentary.setup' but they can alternatively be set on a class-specific basis by passing a :session_users
|
202
|
+
# option to 'needs_user_type'. See 'Fragmentary.parse_session_users' for details.
|
161
203
|
def needs_user_type(options = {})
|
162
204
|
self.extend NeedsUserType
|
163
205
|
instance_eval do
|
@@ -218,28 +260,25 @@ module Fragmentary
|
|
218
260
|
# by the fragment.
|
219
261
|
class << record_type_subscription
|
220
262
|
set_callback :after_destroy, :after, ->{subscriber.client.remove_fragments_for_record(record.id)}
|
263
|
+
set_callback :after_create, :after, ->{subscriber.client.try_request_for_record(record.id)}
|
221
264
|
end
|
222
265
|
end
|
223
266
|
|
224
|
-
|
225
|
-
record_class = record_type.constantize
|
226
|
-
instance_eval <<-HEREDOC
|
227
|
-
subscribe_to #{record_class} do
|
228
|
-
def create_#{record_class.model_name.param_key}_successful(record)
|
229
|
-
request = Fragmentary::Request.new(request_method, request_path(record.id),
|
230
|
-
request_parameters(record.id), request_options)
|
231
|
-
queue_request(request)
|
232
|
-
end
|
233
|
-
end
|
234
|
-
HEREDOC
|
235
|
-
end
|
236
|
-
|
267
|
+
self.extend RecordClassMethods
|
237
268
|
define_method(:record){record_type.constantize.find(record_id)}
|
238
269
|
end
|
239
270
|
end
|
240
271
|
|
241
|
-
|
242
|
-
|
272
|
+
module RecordClassMethods
|
273
|
+
def remove_fragments_for_record(record_id)
|
274
|
+
where(:record_id => record_id).each(&:destroy)
|
275
|
+
end
|
276
|
+
|
277
|
+
def try_request_for_record(record_id)
|
278
|
+
if requestable?
|
279
|
+
queue_request(request(record_id))
|
280
|
+
end
|
281
|
+
end
|
243
282
|
end
|
244
283
|
|
245
284
|
def needs_record_id?
|
@@ -288,9 +327,7 @@ module Fragmentary
|
|
288
327
|
end
|
289
328
|
|
290
329
|
def queue_request(request=nil)
|
291
|
-
if request
|
292
|
-
request_queues.each{|key, queue| queue << request}
|
293
|
-
end
|
330
|
+
request_queues.each{|key, hsh| hsh.each{|key2, queue| queue << request}} if request
|
294
331
|
end
|
295
332
|
|
296
333
|
def requestable?
|
@@ -308,7 +345,11 @@ module Fragmentary
|
|
308
345
|
|
309
346
|
# The instance method 'request_options' is defined in terms of this.
|
310
347
|
def request_options
|
311
|
-
|
348
|
+
{}
|
349
|
+
end
|
350
|
+
|
351
|
+
def request
|
352
|
+
raise NotImplementedError
|
312
353
|
end
|
313
354
|
|
314
355
|
# This method defines the handler for the creation of new list items. The method takes:
|
@@ -453,12 +494,15 @@ module Fragmentary
|
|
453
494
|
end
|
454
495
|
|
455
496
|
def touch(*args, no_request: false)
|
456
|
-
|
497
|
+
@no_request = no_request # stored for use in #touch_parent via the after_commit callback
|
498
|
+
request_queues.each{|key, hsh| hsh.each{|key2, queue| queue << request}} if request && !no_request
|
457
499
|
super(*args)
|
458
500
|
end
|
459
501
|
|
502
|
+
# delete the associated cache content before destroying the fragment
|
460
503
|
def destroy(options = {})
|
461
504
|
options.delete(:delete_matches) ? delete_matched_cache : delete_cache
|
505
|
+
@no_request = options.delete(:no_request) # stored for use in #touch_parent via the after_commit callback
|
462
506
|
super()
|
463
507
|
end
|
464
508
|
|
@@ -467,9 +511,17 @@ module Fragmentary
|
|
467
511
|
end
|
468
512
|
|
469
513
|
def delete_cache
|
470
|
-
cache_store.delete(
|
514
|
+
cache_store.delete(fragment_key)
|
515
|
+
end
|
516
|
+
|
517
|
+
# Recursively delete the cache entry for this fragment and all of its children
|
518
|
+
# Does NOT destroy the fragment or its children
|
519
|
+
def delete_cache_tree
|
520
|
+
children.each(&:delete_cache_tree)
|
521
|
+
delete_cache if cache_exist?
|
471
522
|
end
|
472
523
|
|
524
|
+
# Recursively touch the fragment and all of its children
|
473
525
|
def touch_tree(no_request: false)
|
474
526
|
children.each{|child| child.touch_tree(:no_request => no_request)}
|
475
527
|
# If there are children, we'll have already touched this fragment in the process of touching them.
|
@@ -479,19 +531,34 @@ module Fragmentary
|
|
479
531
|
# Touch this fragment and all descendants that have entries in the cache. Destroy any that
|
480
532
|
# don't have cache entries.
|
481
533
|
def touch_or_destroy
|
482
|
-
puts " touch_or_destroy #{self.class.name} #{id}"
|
483
534
|
if cache_exist?
|
484
535
|
children.each(&:touch_or_destroy)
|
485
536
|
# if there are children, this will be touched automatically once they are.
|
486
537
|
touch(:no_request => true) unless children.any?
|
487
538
|
else
|
488
|
-
destroy # will also destroy all children because of :dependent => :destroy
|
539
|
+
destroy(:no_request => true) # will also destroy all children because of :dependent => :destroy
|
489
540
|
end
|
490
541
|
end
|
491
542
|
|
492
543
|
def cache_exist?
|
493
544
|
# expand_cache_key calls cache_key and prepends "views/"
|
494
|
-
cache_store.exist?(
|
545
|
+
cache_store.exist?(fragment_key)
|
546
|
+
end
|
547
|
+
|
548
|
+
|
549
|
+
# Typically used along with #cache_exist? when testing from the console.
|
550
|
+
# Note that both methods will only return correct results for fragments associated with the application_root_url
|
551
|
+
# (either root or children) corresponding to the particular console session in use. i.e. you can't see into the
|
552
|
+
# production cache from a prerelease console session and vice versa.
|
553
|
+
def content
|
554
|
+
cache_store.read(fragment_key)
|
555
|
+
end
|
556
|
+
|
557
|
+
# This emulates the result of passing the fragment object to AbstractController::Caching::Fragments#combined_fragment_cache_key
|
558
|
+
# when the cache helper method invokes controller.read_fragment from the view. The result can be passed to ActiveSupport::Cache methods
|
559
|
+
# #read, #write, #fetch, #delete, and #exist?
|
560
|
+
def fragment_key
|
561
|
+
['views', self]
|
495
562
|
end
|
496
563
|
|
497
564
|
# Request-related methods...
|
@@ -529,7 +596,8 @@ module Fragmentary
|
|
529
596
|
|
530
597
|
private
|
531
598
|
def touch_parent
|
532
|
-
parent.try
|
599
|
+
parent.try(:touch, {:no_request => @no_request}) unless previous_changes["memo"]
|
600
|
+
@no_request = false
|
533
601
|
end
|
534
602
|
|
535
603
|
module NeedsRecordId
|
@@ -3,6 +3,7 @@ module Fragmentary
|
|
3
3
|
module FragmentsHelper
|
4
4
|
|
5
5
|
def cache_fragment(options, &block)
|
6
|
+
options.reverse_merge!(Fragmentary.config.application_root_url_column => Fragmentary.application_root_url.gsub(%r{https?://}, ''))
|
6
7
|
CacheBuilder.new(self).cache_fragment(options, &block)
|
7
8
|
end
|
8
9
|
|
@@ -10,6 +11,7 @@ module Fragmentary
|
|
10
11
|
# the template option is deprecated but avoids breaking prior usage
|
11
12
|
template = options.delete(:template) || self
|
12
13
|
options.reverse_merge!(:user => Template.new(template).current_user)
|
14
|
+
options.reverse_merge!(Fragmentary.config.application_root_url_column => Fragmentary.application_root_url.gsub(%r{https?://}, ''))
|
13
15
|
CacheBuilder.new(template, Fragmentary::Fragment.base_class.existing(options))
|
14
16
|
end
|
15
17
|
|
@@ -34,7 +36,16 @@ module Fragmentary
|
|
34
36
|
builder = CacheBuilder.new(@template, next_fragment)
|
35
37
|
unless no_cache
|
36
38
|
@template.cache next_fragment, :skip_digest => true do
|
37
|
-
|
39
|
+
if Fragmentary.config.insert_timestamps
|
40
|
+
@template.safe_concat("<!-- #{next_fragment.type} #{next_fragment.id} cached by Fragmentary version #{VERSION} at #{Time.now.utc} -->")
|
41
|
+
if deployed_at && release_name
|
42
|
+
@template.safe_concat("<!-- Cached using application release #{release_name} deployed at #{deployed_at} -->")
|
43
|
+
end
|
44
|
+
yield(builder)
|
45
|
+
@template.safe_concat("<!-- #{next_fragment.type} #{next_fragment.id} ends -->")
|
46
|
+
else
|
47
|
+
yield(builder)
|
48
|
+
end
|
38
49
|
end
|
39
50
|
else
|
40
51
|
yield(builder)
|
@@ -50,6 +61,13 @@ module Fragmentary
|
|
50
61
|
@fragment.send(method, *args)
|
51
62
|
end
|
52
63
|
|
64
|
+
def deployed_at
|
65
|
+
@deployed_at ||= Fragmentary.config.deployed_at
|
66
|
+
end
|
67
|
+
|
68
|
+
def release_name
|
69
|
+
@release_name ||= Fragmentary.config.release_name
|
70
|
+
end
|
53
71
|
end
|
54
72
|
|
55
73
|
end
|
data/lib/fragmentary/handler.rb
CHANGED
@@ -1,4 +1,26 @@
|
|
1
1
|
module Fragmentary
|
2
|
+
|
3
|
+
class HandlerSerializer < ActiveJob::Serializers::ObjectSerializer
|
4
|
+
|
5
|
+
def serialize?(arg)
|
6
|
+
arg.is_a? Fragmentary::Handler
|
7
|
+
end
|
8
|
+
|
9
|
+
def serialize(handler)
|
10
|
+
super(
|
11
|
+
{
|
12
|
+
:class_name => handler.class.name,
|
13
|
+
:args => handler.args
|
14
|
+
}
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
def deserialize(hsh)
|
19
|
+
hsh[:class_name].constantize.new(hsh[:args])
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
2
24
|
class Handler
|
3
25
|
def self.all
|
4
26
|
@@all
|
@@ -14,6 +36,8 @@ module Fragmentary
|
|
14
36
|
handler
|
15
37
|
end
|
16
38
|
|
39
|
+
attr_reader :args
|
40
|
+
|
17
41
|
def initialize(**args)
|
18
42
|
@args = args
|
19
43
|
end
|
@@ -1,12 +1,11 @@
|
|
1
|
+
require 'fragmentary/serializers/handler_serializer'
|
2
|
+
|
1
3
|
module Fragmentary
|
2
4
|
|
3
|
-
class
|
4
|
-
def initialize(tasks)
|
5
|
-
@tasks = tasks
|
6
|
-
end
|
5
|
+
class DispatchHandlersJob < ActiveJob::Base
|
7
6
|
|
8
|
-
def perform
|
9
|
-
|
7
|
+
def perform(tasks)
|
8
|
+
tasks.each do |task|
|
10
9
|
Rails.logger.info "***** Dispatching task for handler class #{task.class.name}"
|
11
10
|
task.call
|
12
11
|
end
|
@@ -14,6 +13,7 @@ module Fragmentary
|
|
14
13
|
queue.start
|
15
14
|
end
|
16
15
|
end
|
16
|
+
|
17
17
|
end
|
18
18
|
|
19
19
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'fragmentary/serializers/request_queue_serializer'
|
2
|
+
|
3
|
+
module Fragmentary
|
4
|
+
|
5
|
+
class SendRequestsJob < ActiveJob::Base
|
6
|
+
|
7
|
+
after_perform :schedule_next
|
8
|
+
|
9
|
+
def perform(queue, delay: nil, between: nil, queue_suffix: '', priority: 0)
|
10
|
+
@queue = queue
|
11
|
+
@delay = delay
|
12
|
+
@between = between
|
13
|
+
@queue_suffix = queue_suffix
|
14
|
+
@priority = priority
|
15
|
+
@between ? @queue.send_next_request : @queue.send_all_requests
|
16
|
+
end
|
17
|
+
|
18
|
+
def schedule_next
|
19
|
+
if @queue.size > 0
|
20
|
+
self.enqueue(:wait => @between, :queue => @queue.target.queue_name + @queue_suffix, :priority => @priority)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -33,7 +33,9 @@ module Fragmentary
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def after_update_broadcast
|
36
|
+
Rails.logger.info "\n***** #{start = Time.now} broadcasting :after_update from #{self.class.name} #{self.id}\n"
|
36
37
|
broadcast(:after_update, self) if self.previous_changes.any?
|
38
|
+
Rails.logger.info "\n***** #{Time.now} broadcast :after_update from #{self.class.name} #{self.id} took #{(Time.now - start) * 1000} ms\n"
|
37
39
|
end
|
38
40
|
|
39
41
|
def after_destroy_broadcast
|
data/lib/fragmentary/request.rb
CHANGED
@@ -3,28 +3,13 @@ module Fragmentary
|
|
3
3
|
class Request
|
4
4
|
attr_reader :method, :path, :options, :parameters
|
5
5
|
|
6
|
-
def initialize(method, path, parameters=nil, options=
|
6
|
+
def initialize(method, path, parameters=nil, options={})
|
7
7
|
@method, @path, @parameters, @options = method, path, parameters, options
|
8
8
|
end
|
9
9
|
|
10
10
|
def ==(other)
|
11
11
|
method == other.method and path == other.path and parameters == other.parameters and options == other.options
|
12
12
|
end
|
13
|
-
|
14
|
-
def to_proc
|
15
|
-
method = @method; path = @path; parameters = @parameters; options = @options.try :dup
|
16
|
-
if @options.try(:[], :xhr)
|
17
|
-
Proc.new do
|
18
|
-
puts " * Sending xhr request '#{method.to_s} #{path}'" + (!parameters.nil? ? " with #{parameters.inspect}" : "")
|
19
|
-
send(:xhr, method, path, parameters, options)
|
20
|
-
end
|
21
|
-
else
|
22
|
-
Proc.new do
|
23
|
-
puts " * Sending request '#{method.to_s} #{path}'" + (!parameters.nil? ? " with #{parameters.inspect}" : "")
|
24
|
-
send(method, path, parameters, options)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
13
|
end
|
29
14
|
|
30
15
|
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'fragmentary/user_session'
|
2
|
-
|
3
1
|
module Fragmentary
|
4
2
|
|
5
3
|
class RequestQueue
|
@@ -8,12 +6,27 @@ module Fragmentary
|
|
8
6
|
@@all ||= []
|
9
7
|
end
|
10
8
|
|
11
|
-
|
9
|
+
def self.send_all(between: nil)
|
10
|
+
unless between
|
11
|
+
all.each{|q| q.start}
|
12
|
+
else
|
13
|
+
unless between.is_a? ActiveSupport::Duration
|
14
|
+
raise TypeError, "Fragmentary::RequestQueue.send_all requires the keyword argument :between to be of class ActiveSupport::Duration. The value provided is of class #{between.class.name}."
|
15
|
+
end
|
16
|
+
delay = 0.seconds
|
17
|
+
all.each{|q| q.start(:delay => delay += between)}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :requests, :user_type, :host_root_url
|
12
22
|
|
13
|
-
def initialize(user_type)
|
23
|
+
def initialize(user_type, host_root_url)
|
14
24
|
@user_type = user_type
|
25
|
+
# host_root_url represents where the queued *requests* are to be processed. For internal sessions it also represents where
|
26
|
+
# the *queue* will be processed by delayed_job. For external requests, the queue will be processed by the host creating the
|
27
|
+
# queue and the requests will be explicitly sent to the host_root_url.
|
28
|
+
@host_root_url = host_root_url
|
15
29
|
@requests = []
|
16
|
-
@sender = Sender.new(self)
|
17
30
|
self.class.all << self
|
18
31
|
end
|
19
32
|
|
@@ -40,6 +53,10 @@ module Fragmentary
|
|
40
53
|
requests.delete_if{|r| r.path == path}
|
41
54
|
end
|
42
55
|
|
56
|
+
def sender
|
57
|
+
@sender ||= Sender.new(self)
|
58
|
+
end
|
59
|
+
|
43
60
|
def send(**args)
|
44
61
|
sender.start(args)
|
45
62
|
end
|
@@ -49,22 +66,46 @@ module Fragmentary
|
|
49
66
|
end
|
50
67
|
|
51
68
|
class Sender
|
69
|
+
|
52
70
|
class << self
|
53
71
|
def jobs
|
54
72
|
::Delayed::Job.where("(handler LIKE ?) OR (handler LIKE ?)", "--- !ruby/object:#{name} %", "--- !ruby/object:#{name}\n%")
|
55
73
|
end
|
56
74
|
end
|
57
75
|
|
58
|
-
|
76
|
+
class Target
|
77
|
+
|
78
|
+
attr_reader :url
|
79
|
+
|
80
|
+
def initialize(url)
|
81
|
+
@url = url
|
82
|
+
end
|
83
|
+
|
84
|
+
def queue_name
|
85
|
+
@url.gsub(%r{https?://}, '')
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
attr_reader :queue, :target, :delay, :between, :queue_suffix, :priority
|
59
91
|
|
60
92
|
def initialize(queue)
|
61
93
|
@queue = queue
|
94
|
+
@target = Target.new(queue.host_root_url)
|
95
|
+
end
|
96
|
+
|
97
|
+
def session_user
|
98
|
+
@session_user ||= Fragmentary::SessionUser.fetch(queue.user_type)
|
99
|
+
end
|
100
|
+
|
101
|
+
def session
|
102
|
+
@session ||= InternalUserSession.new(@target.url, session_user)
|
62
103
|
end
|
63
104
|
|
64
105
|
# Send all requests, either directly or by schedule
|
65
|
-
def start(delay: nil, between: nil)
|
106
|
+
def start(delay: nil, between: nil, queue_suffix: '', priority: 0)
|
66
107
|
Rails.logger.info "\n***** Processing request queue for user_type '#{queue.user_type}'\n"
|
67
|
-
@delay = delay; @between = between
|
108
|
+
@delay = delay; @between = between; @queue_suffix = queue_suffix; @priority = priority
|
68
109
|
if @delay or @between
|
69
110
|
schedule_requests(@delay)
|
70
111
|
# sending requests by schedule makes a copy of the sender and queue objects for
|
@@ -80,22 +121,15 @@ module Fragmentary
|
|
80
121
|
@between ? send_next_request : send_all_requests
|
81
122
|
end
|
82
123
|
|
83
|
-
def success
|
84
|
-
schedule_requests(@between) if queue.size > 0
|
85
|
-
end
|
86
|
-
|
87
|
-
private
|
88
|
-
|
89
|
-
def next_request
|
90
|
-
queue.next_request.to_proc
|
91
|
-
end
|
92
|
-
|
93
124
|
def send_next_request
|
94
125
|
if queue.size > 0
|
95
|
-
|
126
|
+
request = queue.next_request
|
127
|
+
session.send_request(:method => request.method, :path => request.path, :parameters => request.parameters, :options => request.options)
|
96
128
|
end
|
97
129
|
end
|
98
130
|
|
131
|
+
private
|
132
|
+
|
99
133
|
def send_all_requests
|
100
134
|
while queue.size > 0
|
101
135
|
send_next_request
|
@@ -105,22 +139,11 @@ module Fragmentary
|
|
105
139
|
def schedule_requests(delay=0.seconds)
|
106
140
|
if queue.size > 0
|
107
141
|
clear_session
|
108
|
-
|
109
|
-
|
110
|
-
Delayed::Job.enqueue self, :run_at => delay.from_now
|
111
|
-
end
|
142
|
+
job = SendRequestsJob.new(queue, delay: delay, between: between, queue_suffix: queue_suffix, priority: priority)
|
143
|
+
job.enqueue(:wait => delay, :queue => target.queue_name + queue_suffix, :priority => priority)
|
112
144
|
end
|
113
145
|
end
|
114
146
|
|
115
|
-
def session
|
116
|
-
@session ||= new_session
|
117
|
-
end
|
118
|
-
|
119
|
-
def new_session
|
120
|
-
session_user = Fragmentary::SessionUser.fetch(queue.user_type)
|
121
|
-
UserSession.new(session_user)
|
122
|
-
end
|
123
|
-
|
124
147
|
def clear_session
|
125
148
|
@session = nil
|
126
149
|
end
|
@@ -128,4 +151,5 @@ module Fragmentary
|
|
128
151
|
end
|
129
152
|
|
130
153
|
end
|
154
|
+
|
131
155
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Fragmentary
|
2
|
+
|
3
|
+
class HandlerSerializer < ActiveJob::Serializers::ObjectSerializer
|
4
|
+
|
5
|
+
def serialize?(arg)
|
6
|
+
arg.is_a? Fragmentary::Handler
|
7
|
+
end
|
8
|
+
|
9
|
+
def serialize(handler)
|
10
|
+
super(
|
11
|
+
{
|
12
|
+
:class_name => handler.class.name,
|
13
|
+
:args => handler.args
|
14
|
+
}
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
def deserialize(hsh)
|
19
|
+
hsh[:class_name].constantize.new(hsh[:args])
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Fragmentary
|
2
|
+
|
3
|
+
class RequestQueueSerializer < ActiveJob::Serializers::ObjectSerializer
|
4
|
+
|
5
|
+
def serialize?(arg)
|
6
|
+
arg.is_a? Fragmentary::RequestQueue
|
7
|
+
end
|
8
|
+
|
9
|
+
def serialize(queue)
|
10
|
+
super(
|
11
|
+
{
|
12
|
+
:user_type => queue.user_type,
|
13
|
+
:host_root_url => queue.host_root_url,
|
14
|
+
:requests => queue.requests.map do |r|
|
15
|
+
{
|
16
|
+
:method => r.method,
|
17
|
+
:path => r.path,
|
18
|
+
:parameters => r.parameters,
|
19
|
+
:opinions => r.options
|
20
|
+
}
|
21
|
+
end
|
22
|
+
}
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def deserialize(hsh)
|
27
|
+
queue = RequestQueue.new(hsh[:user_type], hsh[:host_root_url])
|
28
|
+
hsh[:requests].each do |r|
|
29
|
+
queue << Request.new(r[:method], r[:path], r[:parameters], r[:options] || {})
|
30
|
+
end
|
31
|
+
queue
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Fragmentary
|
2
|
+
|
3
|
+
class SessionUser
|
4
|
+
|
5
|
+
def self.all
|
6
|
+
@@all ||= Hash.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.fetch(key)
|
10
|
+
all[key]
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(user_type, options={})
|
14
|
+
if user = self.class.fetch(user_type)
|
15
|
+
if user.options != options
|
16
|
+
raise RangeError, "You can't redefine an existing SessionUser object: #{user_type.inspect}"
|
17
|
+
else
|
18
|
+
user
|
19
|
+
end
|
20
|
+
else
|
21
|
+
@user_type = user_type
|
22
|
+
@options = options
|
23
|
+
self.class.all.merge!({user_type => self})
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def credentials
|
28
|
+
options[:credentials]
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
def options
|
33
|
+
@options
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|