fragmentary 0.4.0 → 0.5.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 +4 -4
- data/CHANGELOG.md +13 -0
- data/README.md +14 -8
- data/fragmentary.gemspec +1 -1
- data/lib/fragmentary/config.rb +22 -12
- data/lib/fragmentary/fragment.rb +20 -11
- data/lib/fragmentary/handler.rb +2 -24
- data/lib/fragmentary/jobs/dispatch_handlers_job.rb +2 -4
- data/lib/fragmentary/jobs/send_requests_job.rb +1 -1
- data/lib/fragmentary/publisher.rb +2 -0
- data/lib/fragmentary/request_queue.rb +32 -16
- data/lib/fragmentary/session_user.rb +20 -12
- data/lib/fragmentary/subscriber.rb +20 -7
- data/lib/fragmentary/subscription.rb +7 -5
- data/lib/fragmentary/user_session.rb +5 -2
- data/lib/fragmentary/version.rb +1 -1
- metadata +8 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e4f080639f4562093503dc31128557031c54fc9b3b8c7dca5502a3146645b6a2
|
|
4
|
+
data.tar.gz: e2a5964a78778703ffc96e57d9b760db87b61d1273643c4d5a85f598f7c6ea74
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 68f38663f547ca5961615e046c4f420d99a1d2d8a04b2b43cd143ceea17a3618787d5a58295bcd133b143aa0e1e23936fe2f91519e7def427edfcfe5b4b7e2c5
|
|
7
|
+
data.tar.gz: 932884c05012d780804cd4f6c50819b5b9f8abf90d0535be50d938291300aa5a5c5f26dea327febb7592c43f9805902670fb6967d98e749a0382709fe89ec959
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
### 0.5.0
|
|
2
|
+
- Update to support Rails 7.x and Ruby 3.x
|
|
3
|
+
- Asynchronous internal application requests enqueued while executing a Fragmentary::Handler are now offloaded to a separate async process to prevent them delaying other processes.
|
|
4
|
+
- When sending asynchronous internal requests, any queue_suffix that is present is included in the request parameters so that if the request spawns secondary requests, they can be run on the same ActiveJob queue (requires appropriate handling of the queue_suffix parameter by the application).
|
|
5
|
+
- Allows queued internal application requests to be removed from queues for an individual application instance.
|
|
6
|
+
- Destroying a fragment now destroys all of its children as well.
|
|
7
|
+
- The session scheme for internal requests is set to 'https' if the host_root_url of the request_queue sending the request requires it.
|
|
8
|
+
- Allows multiple session_users per user_type for internal application requests, each assigned to different user_groups, e.g. multiple 'admin' users, multiple 'signed_in' users etc.
|
|
9
|
+
- Allows RequestQueue.send_all to take additional arguments for delay, queue_suffix, priority and user_group
|
|
10
|
+
|
|
11
|
+
### 0.4.1
|
|
12
|
+
- Fixes a problem with session initialization when sending requests generated by Handler subclasses
|
|
13
|
+
|
|
1
14
|
### 0.4.0
|
|
2
15
|
- Uses ActiveJob as the interface for asynchronous processes instead of using Delayed::Job directly.
|
|
3
16
|
- Updates Rails dependency to ~>6.0 as earlier versions lack support for the custom job serialization needed to use ActiveJob. Accordingly, Rails 5.x and earlier are no longer supported.
|
data/README.md
CHANGED
|
@@ -18,7 +18,7 @@ In simple cases, Rails' native support for fragment caching assumes that a fragm
|
|
|
18
18
|
```
|
|
19
19
|
The content is stored in the cache using a _key_ value derived from the record's `id` attribute. The key identifies where the cached content is stored. In addition, a _version_ value derived from the record's `updated_at` attribute is stored along with the content [^1]. If any attributes of the record change, the `updated_at` attribute also changes; the next time a browser requests the content, a mismatch will exist between the calculated and stored values of the version, causing the cache to expire and the fragment to be re-rendered using the current data.
|
|
20
20
|
|
|
21
|
-
[^1]: Prior to Rails 5.2, the value of `updated_at` was incorporated into the cache key rather
|
|
21
|
+
[^1]: Prior to Rails 5.2, the value of `updated_at` was incorporated into the cache key rather than the version. That behavior is still available by setting `config.active_record.cache_versioning = false` in `config/application.rb`.
|
|
22
22
|
|
|
23
23
|
For data models with a `has_one` or `has_many` association, nested (Russian Doll) caching is possible with the inner fragment representing the associated record. For example if a product has many games -
|
|
24
24
|
```
|
|
@@ -712,7 +712,7 @@ end
|
|
|
712
712
|
|
|
713
713
|
Another scenario that occasionally arises is when internal requests created in the course of executing methods defined in a `subscribe_to` block include the path to the same page that the controller is going to render anyway in the normal course of responding to the user's current browser request. In effect, that one internal request is redundant. Although it generally won't cause any real harm to send a redundant request (remember the internal request is usually dispatched asynchronously, so it won't interfere with the synchronous response), redundancy is redundancy and you may wish to avoid it.
|
|
714
714
|
|
|
715
|
-
In this case, it is possible within the controller to remove a request that has already been queued, after the application data has been created, updated or destroyed, by using the class method `Fragment.remove_queued_request`. The method takes two
|
|
715
|
+
In this case, it is possible within the controller to remove a request that has already been queued, after the application data has been created, updated or destroyed, by using the class method `Fragment.remove_queued_request`. The method takes two required keyword parameters, the path of the request to be removed and the current user object, with the latter allowing it to remove the request from only queues corresponding to the current_user's user_type, e.g.
|
|
716
716
|
|
|
717
717
|
```
|
|
718
718
|
class ProductsController < ApplicationController
|
|
@@ -732,6 +732,12 @@ class ProductsController < ApplicationController
|
|
|
732
732
|
end
|
|
733
733
|
end
|
|
734
734
|
```
|
|
735
|
+
As written above, `remove_queued_request` will remove the specified request from all queues defined for the `user_type` of the current user. If you have multiple application instances defined, a request_queue for that user_type will exist for each instance. If you only want to remove the request from the queue for a specific instance, you can include an additional optional keyword parameter `:host_url` specifiying the root_url of that instance, e.g.
|
|
736
|
+
```
|
|
737
|
+
...
|
|
738
|
+
ProductTemplate.remove_queued_request(:request_path => "/products/#{@product.id}", :user => current_user, :host_url => Fragmentary.application_root_url)
|
|
739
|
+
...
|
|
740
|
+
```
|
|
735
741
|
|
|
736
742
|
### Handling AJAX Requests
|
|
737
743
|
|
|
@@ -857,12 +863,6 @@ Again, this configuration will be different for each instance, and so in our cas
|
|
|
857
863
|
|
|
858
864
|
Note that if you configure [Delayed::Job](https://github.com/collectiveidea/delayed_job) (or other backend) to work only from specific queues, you'll need to make sure that _any_ asynchronous tasks created by your application are submitted to one of those queues.
|
|
859
865
|
|
|
860
|
-
## Dependencies
|
|
861
|
-
|
|
862
|
-
- Fragmentary is currently being used in production with Rails 6.0. Versions >=0.4 do not work with earlier versions of Rails for a couple of reasons:
|
|
863
|
-
- The version of ActiveJob in Rails 5.x does not support the custom serialization needed to enqueue asynchronous tasks for sending internal requests. A repository branch 'delayed_job' is available that uses the [Delayed::Job](https://github.com/collectiveidea/delayed_job) gem directly (i.e. without ActiveJob) and this works well with Rails 5.x. However, this branch is no longer maintained.
|
|
864
|
-
- The API for the Rails `ActionDispatch::Integration::Session` class, which is used to handle internal application requests, was changed in Rails 5.0. Consequently the current version of Fragmentary is incompatible with Rails 4.x and earlier. We do have a 'Rails.4.2' branch in the repository that uses the older API. However, this branch is no longer maintained.
|
|
865
|
-
|
|
866
866
|
## Timestamps
|
|
867
867
|
|
|
868
868
|
Fragmentary allows HTML comments to be optionally inserted into cached fragments identifying the time at which the fragment was last cached. Simply add `config.insert_timestamps = true` to the configuration block in your `config/initializers/fragmentary.rb` file.
|
|
@@ -889,6 +889,12 @@ The `Fragment` class and its subclasses provide a number of utility instance met
|
|
|
889
889
|
- `delete_cache` - removes the fragment's content from the cache.
|
|
890
890
|
- `delete_cache_tree` - recursively removes from the cache all content for the fragment and its descendants.
|
|
891
891
|
|
|
892
|
+
## Dependencies
|
|
893
|
+
|
|
894
|
+
- Fragmentary is currently being used in production with Rails 6.0. Versions >=0.4 do not work with earlier versions of Rails for a couple of reasons:
|
|
895
|
+
- The version of ActiveJob in Rails 5.x does not support the custom serialization needed to enqueue asynchronous tasks for sending internal requests. A repository branch 'delayed_job' is available that uses the [Delayed::Job](https://github.com/collectiveidea/delayed_job) gem directly (i.e. without ActiveJob) and this works well with Rails 5.x. However, this branch is no longer maintained.
|
|
896
|
+
- The API for the Rails `ActionDispatch::Integration::Session` class, which is used to handle internal application requests, was changed in Rails 5.0. Consequently the current version of Fragmentary is incompatible with Rails 4.x and earlier. We do have a 'Rails.4.2' branch in the repository that uses the older API. However, this branch is no longer maintained.
|
|
897
|
+
|
|
892
898
|
## Contributing
|
|
893
899
|
|
|
894
900
|
Bug reports and usage questions are welcome at https://github.com/MarkMT/fragmentary.
|
data/fragmentary.gemspec
CHANGED
|
@@ -22,7 +22,7 @@ Gem::Specification.new do |spec|
|
|
|
22
22
|
spec.bindir = "exe"
|
|
23
23
|
spec.require_paths = ["lib"]
|
|
24
24
|
|
|
25
|
-
spec.add_runtime_dependency "rails", "
|
|
25
|
+
spec.add_runtime_dependency "rails", ">= 6.0"
|
|
26
26
|
spec.add_runtime_dependency "wisper-activerecord", "~> 1.0"
|
|
27
27
|
spec.add_runtime_dependency "http", "~> 3.0.0"
|
|
28
28
|
spec.add_runtime_dependency "nokogiri"
|
data/lib/fragmentary/config.rb
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
module Fragmentary
|
|
2
2
|
|
|
3
3
|
class Config
|
|
4
|
+
|
|
4
5
|
include Singleton
|
|
6
|
+
|
|
5
7
|
attr_accessor :current_user_method, :get_sign_in_path, :post_sign_in_path, :sign_out_path,
|
|
6
|
-
:users, :default_user_type_mapping, :
|
|
7
|
-
:
|
|
8
|
+
:users, :default_user_type_mapping, :user_types, :remote_urls, :insert_timestamps,
|
|
9
|
+
:deployed_at, :release_name
|
|
10
|
+
|
|
11
|
+
attr_reader :user_types, :application_root_url_column
|
|
8
12
|
|
|
9
13
|
def initialize
|
|
10
14
|
# default
|
|
@@ -18,8 +22,7 @@ module Fragmentary
|
|
|
18
22
|
|
|
19
23
|
def session_users=(session_users)
|
|
20
24
|
raise "config.session_users must be a Hash" unless session_users.is_a?(Hash)
|
|
21
|
-
Fragmentary.parse_session_users(session_users)
|
|
22
|
-
@session_users = session_users
|
|
25
|
+
@user_types = Fragmentary.parse_session_users(session_users)
|
|
23
26
|
end
|
|
24
27
|
|
|
25
28
|
def application_root_url_column=(column_name)
|
|
@@ -33,12 +36,15 @@ module Fragmentary
|
|
|
33
36
|
|
|
34
37
|
# Parse a set of session_user options, creating session_users where needed, and return a set of user_type keys.
|
|
35
38
|
# session_users may take several forms:
|
|
36
|
-
# (1) a hash whose keys are user_type strings and whose values
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
+
# (1) a hash whose keys are user_type strings and whose values consist of either:
|
|
40
|
+
# (a) a hash with the form {:credentials => credentials},
|
|
41
|
+
# where 'credentials' is either a hash of parameters to be submitted when logging in or a proc that
|
|
42
|
+
# returns those parameters.
|
|
43
|
+
# (b) an array of hashes, each of the form {:credentials => credentials, :group => group_name}
|
|
44
|
+
# where the ':group' element of the hash is optional
|
|
39
45
|
# (2) an array of hashes as described in (1) above.
|
|
40
46
|
# (3) an array of user_type strings corresponding to SessionUser objects already defined.
|
|
41
|
-
# (4) an array containing a mixture of user_type strings and hashes as described in (1) above.
|
|
47
|
+
# (4) an array containing a mixture of user_type strings as described in (3) and hashes as described in (1) above.
|
|
42
48
|
# Non-hash elements that don't represent existing SessionUser objects should raise an exception. Array
|
|
43
49
|
# elements that are hashes should be parsed to create new SessionUser objects. Raise an exception on
|
|
44
50
|
# any attempt to redefine an existing user_type.
|
|
@@ -58,11 +64,15 @@ module Fragmentary
|
|
|
58
64
|
end
|
|
59
65
|
end
|
|
60
66
|
elsif session_users.is_a?(Hash)
|
|
61
|
-
session_users.each_with_object([])
|
|
62
|
-
#
|
|
63
|
-
#
|
|
67
|
+
session_users.each_with_object([]) do |(user_type, users), acc|
|
|
68
|
+
# users is either an options hash that typically looks like {:credentials => credentials} or an array of such hashes,
|
|
69
|
+
# where credentials is either a hash of parameters to be submitted at login or a proc that returns those parameters.
|
|
64
70
|
# In the latter case, the proc is executed when we actually log in to create a new session for the specified user.
|
|
65
|
-
|
|
71
|
+
users = [users] unless users.is_a?(Array)
|
|
72
|
+
users.each do |options|
|
|
73
|
+
SessionUser.create(user_type, options)
|
|
74
|
+
end
|
|
75
|
+
acc << user_type
|
|
66
76
|
end
|
|
67
77
|
end
|
|
68
78
|
end
|
data/lib/fragmentary/fragment.rb
CHANGED
|
@@ -13,13 +13,13 @@ module Fragmentary
|
|
|
13
13
|
base.class_eval do
|
|
14
14
|
include ActionView::Helpers::CacheHelper
|
|
15
15
|
|
|
16
|
-
belongs_to :parent, :class_name => name
|
|
17
|
-
belongs_to :root, :class_name => name
|
|
16
|
+
belongs_to :parent, :class_name => name, :optional => true # because a root fragment doesn't have a parent
|
|
17
|
+
belongs_to :root, :class_name => name, :optional => true # or a root_id
|
|
18
18
|
has_many :children, :class_name => name, :foreign_key => :parent_id, :dependent => :destroy
|
|
19
|
-
belongs_to :user
|
|
19
|
+
belongs_to :user, :optional => true # because only fragments that declare 'needs_user_id' require one
|
|
20
20
|
|
|
21
21
|
# Don't touch the parent when we create the child - the child was created by
|
|
22
|
-
#
|
|
22
|
+
# rendering the parent, which occured because the parent was touched, thus
|
|
23
23
|
# triggering the current request. Touching it again would result in a
|
|
24
24
|
# redundant duplicate request.
|
|
25
25
|
after_commit :touch_parent, :on => [:update, :destroy]
|
|
@@ -52,7 +52,8 @@ module Fragmentary
|
|
|
52
52
|
raise ArgumentError, "You passed Fragment #{fragment.id} to Fragment.root, but it's a child of Fragment #{fragment.parent_id}" if fragment.parent_id
|
|
53
53
|
else
|
|
54
54
|
klass, search_attributes, options = base_class.attributes(options)
|
|
55
|
-
fragment = klass.where(search_attributes).includes(:children).first_or_initialize(options);
|
|
55
|
+
fragment = klass.where(search_attributes).includes(:children).first_or_initialize(options);
|
|
56
|
+
fragment.save if fragment.new_record?
|
|
56
57
|
fragment.set_indexed_children if fragment.child_search_key
|
|
57
58
|
end
|
|
58
59
|
fragment
|
|
@@ -166,8 +167,13 @@ module Fragmentary
|
|
|
166
167
|
super
|
|
167
168
|
end
|
|
168
169
|
|
|
169
|
-
def remove_queued_request(user:, request_path:)
|
|
170
|
-
|
|
170
|
+
def remove_queued_request(host_url: nil, user:, request_path:)
|
|
171
|
+
u_type = user_type(user)
|
|
172
|
+
if host_url.is_a?(String) and (queue = request_queues[host_url][u_type])
|
|
173
|
+
queue.remove_path(request_path)
|
|
174
|
+
else
|
|
175
|
+
request_queues.each{|key, hsh| hsh[u_type].remove_path(request_path)}
|
|
176
|
+
end
|
|
171
177
|
end
|
|
172
178
|
|
|
173
179
|
def subscriber
|
|
@@ -209,7 +215,7 @@ module Fragmentary
|
|
|
209
215
|
end
|
|
210
216
|
@user_types = Fragmentary.parse_session_users(options[:session_users] || options[:types] || options[:user_types])
|
|
211
217
|
def self.user_types
|
|
212
|
-
@user_types || Fragmentary.config.
|
|
218
|
+
@user_types || Fragmentary.config.user_types
|
|
213
219
|
end
|
|
214
220
|
end
|
|
215
221
|
end
|
|
@@ -493,14 +499,15 @@ module Fragmentary
|
|
|
493
499
|
self.class.cache_store
|
|
494
500
|
end
|
|
495
501
|
|
|
496
|
-
def touch(*args, no_request: false)
|
|
502
|
+
def touch(*args, no_request: false, **kwargs)
|
|
497
503
|
@no_request = no_request # stored for use in #touch_parent via the after_commit callback
|
|
498
504
|
request_queues.each{|key, hsh| hsh.each{|key2, queue| queue << request}} if request && !no_request
|
|
499
|
-
super(*args)
|
|
505
|
+
super(*args, **kwargs)
|
|
500
506
|
end
|
|
501
507
|
|
|
502
508
|
# delete the associated cache content before destroying the fragment
|
|
503
509
|
def destroy(options = {})
|
|
510
|
+
children.each(&:destroy)
|
|
504
511
|
options.delete(:delete_matches) ? delete_matched_cache : delete_cache
|
|
505
512
|
@no_request = options.delete(:no_request) # stored for use in #touch_parent via the after_commit callback
|
|
506
513
|
super()
|
|
@@ -596,7 +603,9 @@ module Fragmentary
|
|
|
596
603
|
|
|
597
604
|
private
|
|
598
605
|
def touch_parent
|
|
599
|
-
parent
|
|
606
|
+
if parent
|
|
607
|
+
parent.touch(no_request: @no_request) unless previous_changes["memo"]
|
|
608
|
+
end
|
|
600
609
|
@no_request = false
|
|
601
610
|
end
|
|
602
611
|
|
data/lib/fragmentary/handler.rb
CHANGED
|
@@ -1,26 +1,4 @@
|
|
|
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
|
-
|
|
24
2
|
class Handler
|
|
25
3
|
def self.all
|
|
26
4
|
@@all
|
|
@@ -31,14 +9,14 @@ module Fragmentary
|
|
|
31
9
|
end
|
|
32
10
|
self.clear
|
|
33
11
|
|
|
34
|
-
def self.create(
|
|
12
|
+
def self.create(args)
|
|
35
13
|
@@all << (handler = self.new(args))
|
|
36
14
|
handler
|
|
37
15
|
end
|
|
38
16
|
|
|
39
17
|
attr_reader :args
|
|
40
18
|
|
|
41
|
-
def initialize(
|
|
19
|
+
def initialize(args)
|
|
42
20
|
@args = args
|
|
43
21
|
end
|
|
44
22
|
|
|
@@ -6,12 +6,10 @@ module Fragmentary
|
|
|
6
6
|
|
|
7
7
|
def perform(tasks)
|
|
8
8
|
tasks.each do |task|
|
|
9
|
-
Rails.logger.info "***** Dispatching task for handler class #{task.class.name}"
|
|
9
|
+
Rails.logger.info "\n***** Dispatching task for handler class #{task.class.name}"
|
|
10
10
|
task.call
|
|
11
11
|
end
|
|
12
|
-
RequestQueue.
|
|
13
|
-
queue.start
|
|
14
|
-
end
|
|
12
|
+
RequestQueue.send_all(:between => 10.seconds, :priority => 10)
|
|
15
13
|
end
|
|
16
14
|
|
|
17
15
|
end
|
|
@@ -12,7 +12,7 @@ module Fragmentary
|
|
|
12
12
|
@between = between
|
|
13
13
|
@queue_suffix = queue_suffix
|
|
14
14
|
@priority = priority
|
|
15
|
-
@between ? @queue.send_next_request : @queue.send_all_requests
|
|
15
|
+
(@between && @between.is_a?(ActiveSupport::Duration)) ? @queue.send_next_request : @queue.send_all_requests
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def schedule_next
|
|
@@ -39,7 +39,9 @@ module Fragmentary
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def after_destroy_broadcast
|
|
42
|
+
Rails.logger.info "\n***** #{start = Time.now} broadcasting :after_destroy from #{self.class.name} #{self.id}\n"
|
|
42
43
|
broadcast(:after_destroy, self)
|
|
44
|
+
Rails.logger.info "\n***** #{Time.now} broadcast :after_destroy from #{self.class.name} #{self.id} took #{(Time.now - start) * 1000} ms\n"
|
|
43
45
|
end
|
|
44
46
|
|
|
45
47
|
def after_commit_broadcast
|
|
@@ -6,15 +6,19 @@ module Fragmentary
|
|
|
6
6
|
@@all ||= []
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
-
def self.send_all(between: nil)
|
|
10
|
-
unless between
|
|
11
|
-
all.each{|q| q.start}
|
|
9
|
+
def self.send_all(delay: nil, between: nil, queue_suffix: '', priority: 0, user_group: 'default')
|
|
10
|
+
unless delay or between
|
|
11
|
+
all.each{|q| q.start(queue_suffix: queue_suffix, priority: priority, user_group: user_group)}
|
|
12
12
|
else
|
|
13
|
+
delay ||= 0.seconds
|
|
14
|
+
between ||= 0.seconds
|
|
15
|
+
unless delay.is_a? ActiveSupport::Duration
|
|
16
|
+
raise TypeError, "Fragmentary::RequestQueue.send_all requires the keyword argument :delay to be of class ActiveSupport::Duration. The value provided is of class #{delay.class.name}."
|
|
17
|
+
end
|
|
13
18
|
unless between.is_a? ActiveSupport::Duration
|
|
14
19
|
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
20
|
end
|
|
16
|
-
delay
|
|
17
|
-
all.each{|q| q.start(:delay => delay += between)}
|
|
21
|
+
all.each{|q| q.start(delay: (delay += between), queue_suffix: queue_suffix, priority: priority, user_group: user_group)}
|
|
18
22
|
end
|
|
19
23
|
end
|
|
20
24
|
|
|
@@ -57,12 +61,12 @@ module Fragmentary
|
|
|
57
61
|
@sender ||= Sender.new(self)
|
|
58
62
|
end
|
|
59
63
|
|
|
60
|
-
def send(**
|
|
61
|
-
sender.start(
|
|
64
|
+
def send(**kwargs)
|
|
65
|
+
sender.start(**kwargs)
|
|
62
66
|
end
|
|
63
67
|
|
|
64
|
-
def method_missing(method, *args)
|
|
65
|
-
sender.send(method, *args)
|
|
68
|
+
def method_missing(method, *args, **kwargs)
|
|
69
|
+
sender.send(method, *args, **kwargs)
|
|
66
70
|
end
|
|
67
71
|
|
|
68
72
|
class Sender
|
|
@@ -87,15 +91,17 @@ module Fragmentary
|
|
|
87
91
|
|
|
88
92
|
end
|
|
89
93
|
|
|
90
|
-
attr_reader :queue, :target, :delay, :between, :queue_suffix, :priority
|
|
94
|
+
attr_reader :queue, :target, :delay, :between, :queue_suffix, :priority, :user_group, :queue_name
|
|
91
95
|
|
|
92
96
|
def initialize(queue)
|
|
93
97
|
@queue = queue
|
|
94
98
|
@target = Target.new(queue.host_root_url)
|
|
99
|
+
@user_group = 'default'
|
|
100
|
+
@queue_name = @target.queue_name
|
|
95
101
|
end
|
|
96
102
|
|
|
97
103
|
def session_user
|
|
98
|
-
@session_user ||= Fragmentary::SessionUser.fetch(queue.user_type)
|
|
104
|
+
@session_user ||= Fragmentary::SessionUser.fetch(queue.user_type, user_group)
|
|
99
105
|
end
|
|
100
106
|
|
|
101
107
|
def session
|
|
@@ -103,9 +109,16 @@ module Fragmentary
|
|
|
103
109
|
end
|
|
104
110
|
|
|
105
111
|
# Send all requests, either directly or by schedule
|
|
106
|
-
|
|
112
|
+
# Note that the :between argument here represents the time between sending consecutive *requests*, whereas in RequestQueue#send_all it
|
|
113
|
+
# represents the time between starting consecutive queues.
|
|
114
|
+
# The :between argument here is deprecated. There is no :between value passed from RequestQueue.send_all to RequestQueue::Sender#start
|
|
115
|
+
def start(delay: nil, between: nil, queue_suffix: '', priority: 0, user_group: 'default')
|
|
107
116
|
Rails.logger.info "\n***** Processing request queue for user_type '#{queue.user_type}'\n"
|
|
108
|
-
@delay = delay
|
|
117
|
+
@delay = delay
|
|
118
|
+
@between = between
|
|
119
|
+
@queue_suffix = queue_suffix
|
|
120
|
+
@priority = priority
|
|
121
|
+
@user_group = user_group
|
|
109
122
|
if @delay or @between
|
|
110
123
|
schedule_requests(@delay)
|
|
111
124
|
# sending requests by schedule makes a copy of the sender and queue objects for
|
|
@@ -124,13 +137,16 @@ module Fragmentary
|
|
|
124
137
|
def send_next_request
|
|
125
138
|
if queue.size > 0
|
|
126
139
|
request = queue.next_request
|
|
127
|
-
|
|
140
|
+
parameters = (request.parameters || {}).merge(:queue_suffix => queue_suffix)
|
|
141
|
+
session.send_request(:method => request.method, :path => request.path, :parameters => parameters, :options => request.options)
|
|
128
142
|
end
|
|
129
143
|
end
|
|
130
144
|
|
|
131
145
|
private
|
|
132
146
|
|
|
133
|
-
def send_all_requests
|
|
147
|
+
def send_all_requests(user_group: 'default')
|
|
148
|
+
@user_group = user_group
|
|
149
|
+
clear_session
|
|
134
150
|
while queue.size > 0
|
|
135
151
|
send_next_request
|
|
136
152
|
end
|
|
@@ -140,7 +156,7 @@ module Fragmentary
|
|
|
140
156
|
if queue.size > 0
|
|
141
157
|
clear_session
|
|
142
158
|
job = SendRequestsJob.new(queue, delay: delay, between: between, queue_suffix: queue_suffix, priority: priority)
|
|
143
|
-
job.enqueue(:wait => delay, :queue =>
|
|
159
|
+
job.enqueue({:wait => delay, :queue => queue_name + queue_suffix, :priority => priority})
|
|
144
160
|
end
|
|
145
161
|
end
|
|
146
162
|
|
|
@@ -2,26 +2,34 @@ module Fragmentary
|
|
|
2
2
|
|
|
3
3
|
class SessionUser
|
|
4
4
|
|
|
5
|
+
attr_reader :options, :user_type
|
|
6
|
+
|
|
5
7
|
def self.all
|
|
6
|
-
@@all ||= Hash.new
|
|
8
|
+
@@all ||= Hash.new { |hsh, user_type| hsh[user_type] = Hash.new }
|
|
7
9
|
end
|
|
10
|
+
private_class_method :all
|
|
11
|
+
private_class_method :new
|
|
8
12
|
|
|
9
|
-
def self.fetch(
|
|
10
|
-
all[
|
|
13
|
+
def self.fetch(user_type, user_group = 'default')
|
|
14
|
+
all[user_type][user_group]
|
|
11
15
|
end
|
|
12
16
|
|
|
13
|
-
def
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
else
|
|
18
|
-
user
|
|
19
|
-
end
|
|
17
|
+
def self.create(user_type, options={})
|
|
18
|
+
user_group = options[:group] || options[:user_group] || 'default'
|
|
19
|
+
if user = fetch(user_type, user_group)
|
|
20
|
+
raise RangeError, "You can't redefine an existing SessionUser object: #{user_type.inspect}" unless user.options == options
|
|
20
21
|
else
|
|
21
|
-
@user_type = user_type
|
|
22
|
+
@user_type = user_type # this probably not really necessary
|
|
22
23
|
@options = options
|
|
23
|
-
|
|
24
|
+
user = new(user_type, options)
|
|
25
|
+
all[user_type].merge!(user_group => user)
|
|
24
26
|
end
|
|
27
|
+
user
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def initialize(user_type, options={})
|
|
31
|
+
@user_type = user_type # this probably not really necessary
|
|
32
|
+
@options = options
|
|
25
33
|
end
|
|
26
34
|
|
|
27
35
|
def credentials
|
|
@@ -2,19 +2,32 @@ require 'fragmentary/subscription'
|
|
|
2
2
|
|
|
3
3
|
module Fragmentary
|
|
4
4
|
|
|
5
|
-
# Each fragment subclass has a unique Subscriber instance
|
|
6
|
-
# to publishers.
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# fragment
|
|
5
|
+
# Each fragment subclass has a unique Subscriber instance responsible for handling subscriptions
|
|
6
|
+
# to publishers. The subscriber is instantiated the first time it is referenced within the subscribe_to
|
|
7
|
+
# method called on the fragment. That method calls a method of the same name on the subscriber, which
|
|
8
|
+
# instantiates a new Subscription for each publisher it subscribes to and executes its block against
|
|
9
|
+
# the subscriber, thus defining handlers for each publisher event of interest on the subscriber,
|
|
10
|
+
# (rather than the fragment). Any other method invoked within a handler is delegated to the client, i.e.
|
|
11
|
+
# the Fragment subclass that the subscriber is responsible for.
|
|
12
|
+
#
|
|
13
|
+
# A separate subscription exists for each combination of publisher and subscriber (and thus Fragment subclass).
|
|
14
|
+
# When a subscription is instantiated for a given publisher, it registers itself with a Subscription::Proxy
|
|
15
|
+
# for that publisher. The proxy, is instantiated the first time it is referenced by a subscription to a
|
|
16
|
+
# particular publisher, and it subscribes on behalf of all its registered subscriptions to Wisper events
|
|
17
|
+
# broadcast by that publisher.
|
|
18
|
+
#
|
|
19
|
+
# The Proxy class contains the methods such as 'after_create', 'after_update' etc that are called automatically
|
|
20
|
+
# by Wisper when the publisher broacdcasts the corresponding events. These methods each invoke methods of the
|
|
21
|
+
# same name on each of proxy's registered subscriptions, and it is these that in turn invoke the methods
|
|
22
|
+
# defined by the subscribe_to blocks on the susbcriber associated with the subscription.
|
|
11
23
|
class Subscriber
|
|
12
24
|
attr_reader :client, :subscriptions
|
|
13
25
|
|
|
14
26
|
def initialize(client)
|
|
15
27
|
@client = client
|
|
16
28
|
@subscriptions = Hash.new do |h, key|
|
|
17
|
-
|
|
29
|
+
publisher = key.constantize # Ensures that the model is loaded so that the const below is defined.
|
|
30
|
+
if Object.const_defined?(key) and publisher < ActiveRecord::Base
|
|
18
31
|
h[key] = Subscription.new(publisher, self)
|
|
19
32
|
else
|
|
20
33
|
nil
|
|
@@ -76,11 +76,13 @@ module Fragmentary
|
|
|
76
76
|
|
|
77
77
|
private
|
|
78
78
|
def call_method(method, record)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
if subscriber.respond_to? method
|
|
80
|
+
Rails.logger.info "***** Calling #{method.inspect} on #{subscriber.client.name} with record #{record.class.name} #{record.id}"
|
|
81
|
+
start = Time.now
|
|
82
|
+
subscriber.public_send(method, record)
|
|
83
|
+
finish = Time.now
|
|
84
|
+
Rails.logger.info "***** #{method.inspect} duration: #{(finish - start) * 1000}ms\n\n"
|
|
85
|
+
end
|
|
84
86
|
end
|
|
85
87
|
|
|
86
88
|
end
|
|
@@ -17,6 +17,7 @@ module Fragmentary
|
|
|
17
17
|
@user = user
|
|
18
18
|
@target = URI.parse(target)
|
|
19
19
|
@session.host! session_host
|
|
20
|
+
@session.https! if (@target.scheme == 'https')
|
|
20
21
|
sign_in if session_credentials
|
|
21
22
|
instance_eval(&block) if block_given?
|
|
22
23
|
end
|
|
@@ -43,6 +44,7 @@ module Fragmentary
|
|
|
43
44
|
|
|
44
45
|
def relative_url_root
|
|
45
46
|
@relative_url_root ||= Rails.application.config.relative_url_root
|
|
47
|
+
@relative_url_root ||= Rails.application.routes.default_url_options[:relative_url_root].try(:gsub, /\/$/,'')
|
|
46
48
|
end
|
|
47
49
|
|
|
48
50
|
def session_options
|
|
@@ -56,9 +58,9 @@ module Fragmentary
|
|
|
56
58
|
def sign_in
|
|
57
59
|
raise "Can't sign in without user credentials" unless session_credentials
|
|
58
60
|
send_request(:method => :get, :path => session_sign_in_path, :options => session_options) # necessary in order to get the csrf token
|
|
59
|
-
# NOTE: In Rails 5, params is changed to a named argument, i.e. :params => {...}. Will need to be changed.
|
|
60
61
|
# Note that request is called on session, returning an ActionDispatch::Request; request.session is an ActionDispatch::Request::Session
|
|
61
62
|
puts " * Signing in as #{session_credentials.inspect}"
|
|
63
|
+
Rails.logger.info " * Signing in as #{session_credentials.inspect}"
|
|
62
64
|
parameters = session_credentials.merge(:authenticity_token => request.session[:_csrf_token])
|
|
63
65
|
send_request(:method => :post, :path => session_sign_in_path, :parameters => parameters, :options => session_options)
|
|
64
66
|
if @session.redirect?
|
|
@@ -89,7 +91,7 @@ module Fragmentary
|
|
|
89
91
|
puts " * Sending request '#{method.to_s} #{path}'" + (!parameters.nil? ? " with #{parameters.inspect}" : "")
|
|
90
92
|
Rails.logger.info " * Sending request '#{method.to_s} #{path}'" + (!parameters.nil? ? " with #{parameters.inspect}" : "")
|
|
91
93
|
end
|
|
92
|
-
@session.send(method, path, options)
|
|
94
|
+
@session.send(method, path, **options)
|
|
93
95
|
end
|
|
94
96
|
|
|
95
97
|
def sign_out
|
|
@@ -143,6 +145,7 @@ module Fragmentary
|
|
|
143
145
|
# The first request retrieves the authentication token
|
|
144
146
|
response = send_request(:method => :get, :path => Fragmentary.config.get_sign_in_path)
|
|
145
147
|
puts " * Signing in as #{@credentials.inspect}"
|
|
148
|
+
Rails.logger.info " * Signing in as #{@credentials.inspect}"
|
|
146
149
|
response = send_request(:method => :post, :path => Fragmentary.config.post_sign_in_path,
|
|
147
150
|
:parameters => @credentials.merge(:authenticity_token => @authenticity_token),
|
|
148
151
|
:options => {:headers => {:accept => "text/html,application/xhtml+xml,application/xml"}})
|
data/lib/fragmentary/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fragmentary
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mark Thomson
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-08-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- - "
|
|
17
|
+
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
19
|
version: '6.0'
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
|
-
- - "
|
|
24
|
+
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '6.0'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
@@ -149,7 +149,7 @@ homepage: https://github.com/MarkMT/fragmentary
|
|
|
149
149
|
licenses:
|
|
150
150
|
- MIT
|
|
151
151
|
metadata: {}
|
|
152
|
-
post_install_message:
|
|
152
|
+
post_install_message:
|
|
153
153
|
rdoc_options: []
|
|
154
154
|
require_paths:
|
|
155
155
|
- lib
|
|
@@ -164,8 +164,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
164
164
|
- !ruby/object:Gem::Version
|
|
165
165
|
version: '0'
|
|
166
166
|
requirements: []
|
|
167
|
-
rubygems_version: 3.
|
|
168
|
-
signing_key:
|
|
167
|
+
rubygems_version: 3.3.22
|
|
168
|
+
signing_key:
|
|
169
169
|
specification_version: 4
|
|
170
170
|
summary: Fragment modeling and caching for Rails
|
|
171
171
|
test_files: []
|