fragmentary 0.2.2 → 0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +5 -0
- data/README.md +86 -9
- data/fragmentary.gemspec +3 -1
- data/lib/fragmentary/config.rb +25 -10
- data/lib/fragmentary/fragment.rb +73 -32
- data/lib/fragmentary/fragments_helper.rb +2 -0
- data/lib/fragmentary/publisher.rb +2 -0
- data/lib/fragmentary/request.rb +1 -16
- data/lib/fragmentary/request_queue.rb +42 -25
- data/lib/fragmentary/session_user.rb +38 -0
- data/lib/fragmentary/subscription.rb +5 -2
- data/lib/fragmentary/user_session.rb +109 -33
- data/lib/fragmentary/version.rb +1 -1
- data/lib/fragmentary.rb +1 -0
- metadata +36 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0005ce07a2de70b95fc9b105b3ab12a7d763fb2ededa0423845bbfc3c0b45e8d
|
4
|
+
data.tar.gz: e2202f4d38a1189ba956e7b88150178e962581ed93e00f3d74fb8144b193055e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1665f0be866246efc7a1ba157d1216f63a39162261a680e6e8542884461c2285137ce350efdd4ed233ee6cb4b88484664ab5b7436a0f84e5fbb80c892ccab7ae
|
7
|
+
data.tar.gz: 0e4d6a8bd99ae54ff6c3715e203cde760382ef9c48a5ee241ae645ce58dd07dcb1d17b819f10771edcf5d5d113016d0d115fd5a08b1218e3060fef0bf57553de
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
### 0.3.0
|
2
|
+
- Updates gem to support Rails 5.x (Rails 4.x and earlier are no longer supported due to Rails API changes).
|
3
|
+
- Adds support for multiple application instances, allowing pre-release application code to be staged for testing.
|
4
|
+
- Fixes a bug in Fragment.set_record_type affecting some application data handlers for fragment classes that are subclassed from another.
|
5
|
+
|
1
6
|
### 0.2.2
|
2
7
|
- Removes validation of the fragment's root_id (only relevant to child fragments); ignore v0.2.1
|
3
8
|
|
data/README.md
CHANGED
@@ -5,11 +5,12 @@ Fragmentary augments the fragment caching capabilities of Ruby on Rails to suppo
|
|
5
5
|
* multiple versions of individual fragments for different groups of users, e.g. admin vs regular users
|
6
6
|
* post-cache insertion of user-specific content
|
7
7
|
* automatic refreshing of cached content when application data changes, without an external client request
|
8
|
+
* multiple application instances running concurrently with shared application data
|
8
9
|
|
9
|
-
|
10
|
+
Fragmentary has been extracted from [Persuasive Thinking](http://persuasivethinking.com) where it is currently in active use.
|
10
11
|
|
11
12
|
## Background
|
12
|
-
In simple cases, Rails' native support for fragment caching assumes that a fragment's content is a representation of a specific application data record. The content is stored in the cache with a key value derived from the `updated_at`
|
13
|
+
In simple cases, Rails' native support for fragment caching assumes that a fragment's content is a representation of a specific application data record. The content is stored in the cache with a key value derived from the`id` and `updated_at` attributes of that record. If any attributes of the record change, the cached entry automatically expires and on the next browser request for that content the fragment is re-rendered using the current data. In the view, the `cache` helper is used to specify the record used to determine the key and define the content to be rendered within the fragment, e.g.:
|
13
14
|
```
|
14
15
|
<% cache product do %>
|
15
16
|
<%= render product %>
|
@@ -577,7 +578,7 @@ def send_queued_requests
|
|
577
578
|
Fragmentary::RequestQueue.all.each{|q| q.send(:delay => delay += 10.seconds)}
|
578
579
|
end
|
579
580
|
```
|
580
|
-
The `send` method takes two optional named arguments, `delay` and `between`. If neither are present, all requests held in the queue are sent immediately. If either are present, sending of requests is off-loaded to an asynchronous process using the [Delayed::Job gem](https://github.com/collectiveidea/delayed_job) and scheduled according to the parameters provided: `delay` represents the delay before the queue begins sending requests and `between` represents the interval between individual requests in the queue being sent. In the example above, we choose to delay the sending of requests from each queue by 10 seconds each. You may customize as appropriate.
|
581
|
+
The `send` method takes two optional named arguments, `delay` and `between`. If neither are present, all requests held in the queue are sent immediately. If either are present, sending of requests is off-loaded to an asynchronous process using the [Delayed::Job gem](https://github.com/collectiveidea/delayed_job) (i.e. we are not currently using Active Job) and scheduled according to the parameters provided: `delay` represents the delay before the queue begins sending requests and `between` represents the interval between individual requests in the queue being sent. In the example above, we choose to delay the sending of requests from each queue by 10 seconds each. You may customize as appropriate.
|
581
582
|
|
582
583
|
#### Queuing Requests Explicitly
|
583
584
|
|
@@ -758,13 +759,89 @@ Note that if the partial page content being generated contains several nested ch
|
|
758
759
|
|
759
760
|
It is possible to define a fragment without actually storing its content in the cache store. This can be useful, for example if you wish to cache several sibling children within a page but don't need to store the entire root fragment that contains them. Simply include the option `:no_cache => true` in the hash passed to `cache_fragment` or `cache_child`.
|
760
761
|
|
761
|
-
|
762
|
+
### Support for Multiple Application Instances
|
762
763
|
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
764
|
+
In many practical deployment scenarios it is desirable to maintain a live pre-release version of the application online separate from the public-facing production website. This allows new software releases to be staged for either internal or beta testing prior to final deployment to the production environment. For example, if the public-facing application is accessed at a root URL of http://myapp.com/, a separate pre-release version might be deployed say to http://prerelease.myapp.com/ or http://myapp.com/prerelease/.
|
765
|
+
|
766
|
+
In the specific scenario in which Fragmentary was developed, it was important for the pre-release version of the application to share the same application database as the production site, i.e. both production and pre-release versions render exactly the same data, and any changes to application data initiated by a user of one version of the application will be reflected in the content seen by a user of the other version as well. For caching, this leads to some additional challenges which we discuss below.
|
767
|
+
|
768
|
+
#### Storing Multiple Versions of Content in the Cache
|
769
|
+
|
770
|
+
There is an important difference between the content rendered by each instance of an application: any HTML links to other pages on the site must be to URLs representing the particular version being rendered. For example, on a 'products' index page, a link to an individual product page on the production website might look like http://myapp.com/products/123, while the same link on the index page of the pre-release version might be http://myapp.com/prerelease/products/123.
|
771
|
+
|
772
|
+
In general, as long as links contained in the view are created using Rails' standard path or url helpers, e.g. `product_path(@product)` etc, they will automatically be based on the root URL of the application instance (production or pre-release) in which they are generated. However, in the context of caching, the fact that differences exist in the content generated by different application instances implies that for each fragment defined in the view by calling `cache_fragment` or `cache_child`, different versions of fragment content need to be stored in the cache. Also, each of these distinct versions needs to be associated with a unique record in the `fragments` database table identifying which application instance generated that content.
|
773
|
+
|
774
|
+
In Fragmentary we can accomplish this simply by adding a column to the `fragments` table to store the root URL of the particular application instance that created the fragment content.
|
775
|
+
|
776
|
+
```
|
777
|
+
class AddAppUrlToFragment < ActiveRecord::Migration
|
778
|
+
def change
|
779
|
+
change_table :fragments do |t|
|
780
|
+
t.string :application_root_url
|
781
|
+
end
|
782
|
+
end
|
783
|
+
end
|
784
|
+
```
|
785
|
+
|
786
|
+
The column name `application_root_url` shown above is the default assumed by Fragmentary. You can use a different column name if you wish, as long as you tell Fragmentary in initializers/fragmentary.rb, e.g.:
|
787
|
+
|
788
|
+
```
|
789
|
+
Fragmentary.setup do |config|
|
790
|
+
...
|
791
|
+
config.application_root_url_column = 'app_root'
|
792
|
+
end
|
793
|
+
|
794
|
+
```
|
795
|
+
|
796
|
+
Once this column has been added to the `fragments` table, any fragment records subsequently created by calls to `cache_fragment` or `cache_child` will have the column populated automatically based on the particular application instance in which the code is executed, and the content stored in the cache for each fragment record will be that generated by the corresponding instance. As long as any links rendered within the content are generated using Rails' path or url helpers, the cached content will be rendered correctly both when initially created and when retrieved subsequently.
|
797
|
+
|
798
|
+
Note that it is possible to store cached content for each of the different application instances in different places. Simply set `config.cache_store` in config/environments/production.rb (or alternative environment file) as required.
|
799
|
+
|
800
|
+
#### Automatically Refreshing Multiple Versions of Cached Content
|
801
|
+
|
802
|
+
The fact that two versions of content exist in the cache for each fragment means that whenever a change in application data occurs that triggers the generation of an internal application request to refresh a piece of cached content (i.e. for any fragment that has a `request_path` method defined), _both_ (in general all) versions need to be refreshed. So for example, a change to application data caused by a user action on the production website needs to trigger internal requests to _both_ the production _and_ the pre-release instances in order to refresh their respective content. The converse is true for changes initiated from the pre-release website.
|
803
|
+
|
804
|
+
(Note: for better or worse, we've stuck with the term 'internal request' to mean any request delivered to the application programmatically in order to refresh cached content. This includes requests created by one application instance that are intended to be processed by another instance.)
|
805
|
+
|
806
|
+
Our approach to sending requests between application instances relies on requests being processed asynchronously (i.e. by passing a `delay` value to RequestQueue#send in the controller method `send_queued_requests` discussed earlier). The reason is that asynchronous tasks used to process internal requests can be directed to specific task queues associated with the application instance they are intended to be processed by. Each application instance has an associated asynchronous task process. By configuring that process to run tasks from just the queue(s) designated for it, we ensure that any application instance can direct requests to any other.
|
807
|
+
|
808
|
+
As noted earlier, our current implementation relies on [Delayed::Job](https://github.com/collectiveidea/delayed_job) for creating and processing asynchronous tasks. Queued tasks are stored in a database table, so as long as each application and [Delayed::Job](https://github.com/collectiveidea/delayed_job) instance have access to the same database, this approach will be successful.
|
809
|
+
|
810
|
+
To configure Fragmentary to automatically refresh cached content for multiple instances, first set `remote_urls` in `Fragmentary.config`. This is an array of root URLs for all _other_ instances of the application that requests should be sent to. For example, in order to allow requests to be sent from the production instance to the pre-release instance, in initializers/fragmentary.rb in the production code we would add the following configuration:
|
811
|
+
|
812
|
+
```
|
813
|
+
Fragmentary.setup do |config|
|
814
|
+
...
|
815
|
+
config.remote_urls << 'http://myapp.com/prerelease/'
|
816
|
+
end
|
817
|
+
```
|
818
|
+
|
819
|
+
Our current approach to application deployment is to maintain different branches in our source repository for each application instance. This allows us to keep custom configurations like `config.remote_urls` above for each instance on their own branches. So in contrast to the case above, to allow requests to be sent from the pre-release instance to the production instance, in initializers/fragmentary.rb on the pre-release branch the `remote_urls` array would be set to `['http://myapp.com/']` .
|
820
|
+
|
821
|
+
As an alternative, it may be possible to create a separate environment for the pre-release deployment in your config/environments directory and thus maintain the configuration for all application instances in a single repository branch. We have not investigated this approach.
|
822
|
+
|
823
|
+
By specifying `remote_urls` as described above, for each internal application request Fragmentary will automatically create tasks to process the request not only on the application instance where it was created but also on all other instances corresponding to the elements in `remote_urls`. Fragmentary sends these tasks to [Delayed::Job](https://github.com/collectiveidea/delayed_job) queues that have names formed from the domain name and path of both the root URL of the current instance and the values in `remote_urls`. So in the example, a request to '/products/123', created say by editing a product name on the production website, would be sent to [Delayed::Job](https://github.com/collectiveidea/delayed_job) queues with names 'myapp.com' and 'myapp.com/prerelease/'.
|
824
|
+
|
825
|
+
The final step needed to allow internal requests to be processed by the instance they are intended for is to configure the [Delayed::Job](https://github.com/collectiveidea/delayed_job) process associated with each instance to process tasks from the correct queue.
|
826
|
+
|
827
|
+
In our case we use Capistrano 2 for deployment and use `delayed_job_args` in config/deploy.rb to configure queue names (see documentation [here](https://github.com/collectiveidea/delayed_job/blob/master/lib/delayed/recipes.rb)):
|
828
|
+
|
829
|
+
```
|
830
|
+
queue_prefix = "myapp.com/prerelease/"
|
831
|
+
set :delayed_job_args, "--queue=#{queue_prefix},#{queue_prefix}_overnight"
|
832
|
+
after "deploy:stop", "delayed_job:stop"
|
833
|
+
after "deploy:start", "delayed_job:start"
|
834
|
+
after "deploy:restart", "delayed_job:restart"
|
835
|
+
```
|
836
|
+
|
837
|
+
Again, this configuration will be different for each instance and so in our case each one will be stored in different source repository branches.
|
838
|
+
|
839
|
+
Note that if you configure [Delayed::Job](https://github.com/collectiveidea/delayed_job) 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.
|
840
|
+
|
841
|
+
## Dependencies
|
842
|
+
|
843
|
+
- The current implementation of Fragmentary has been tested using Rails 5. It does not work with earlier versions of Rails due to a change in the API for Rails `ActionDispatch::Integration::Session` class. We do have a 'Rails.4.2' branch that uses the older API in the github repository. However this branch is no longer maintained.
|
844
|
+
- As noted, Fragmentary uses the [Delayed::Job](https://github.com/collectiveidea/delayed_job) gem to execute background tasks asynchronously.
|
768
845
|
|
769
846
|
## Contributing
|
770
847
|
|
data/fragmentary.gemspec
CHANGED
@@ -22,9 +22,11 @@ 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", "~> 5.0"
|
26
26
|
spec.add_runtime_dependency "delayed_job_active_record", "~> 4.1"
|
27
27
|
spec.add_runtime_dependency "wisper-activerecord", "~> 1.0"
|
28
|
+
spec.add_runtime_dependency "http", "~> 3.0.0"
|
29
|
+
spec.add_runtime_dependency "nokogiri"
|
28
30
|
spec.add_development_dependency "bundler", "~> 1.17"
|
29
31
|
spec.add_development_dependency "rake", "~> 10.0"
|
30
32
|
spec.add_development_dependency "rspec", "~> 3.0"
|
data/lib/fragmentary/config.rb
CHANGED
@@ -2,12 +2,14 @@ module Fragmentary
|
|
2
2
|
|
3
3
|
class Config
|
4
4
|
include Singleton
|
5
|
-
attr_accessor :current_user_method, :get_sign_in_path, :post_sign_in_path,
|
6
|
-
:
|
5
|
+
attr_accessor :current_user_method, :get_sign_in_path, :post_sign_in_path, :sign_out_path,
|
6
|
+
:users, :default_user_type_mapping, :session_users, :application_root_url_column, :remote_urls
|
7
7
|
|
8
8
|
def initialize
|
9
9
|
# default
|
10
10
|
@current_user_method = :current_user
|
11
|
+
@application_root_url_column = :application_root_url
|
12
|
+
@remote_urls = []
|
11
13
|
end
|
12
14
|
|
13
15
|
def session_users=(session_users)
|
@@ -15,34 +17,47 @@ module Fragmentary
|
|
15
17
|
Fragmentary.parse_session_users(session_users)
|
16
18
|
@session_users = session_users
|
17
19
|
end
|
20
|
+
|
21
|
+
def application_root_url_column=(column_name)
|
22
|
+
@application_root_url_column = column_name.to_sym
|
23
|
+
end
|
18
24
|
end
|
19
25
|
|
20
26
|
def self.current_user_method
|
21
27
|
self.config.current_user_method
|
22
28
|
end
|
23
29
|
|
24
|
-
# Parse a
|
25
|
-
# session_users
|
26
|
-
#
|
27
|
-
#
|
30
|
+
# Parse a set of session_user options, creating session_users where needed, and return a set of user_type keys.
|
31
|
+
# session_users may take several forms:
|
32
|
+
# (1) a hash whose keys are user_type strings and whose values have the form {:credentials => credentials},
|
33
|
+
# where 'credentials' is either a hash of parameters to be submitted when logging in or a proc that
|
34
|
+
# returns those parameters.
|
35
|
+
# (2) an array of hashes as described in (1) above.
|
36
|
+
# (3) an array of user_type strings corresponding to SessionUser objects already defined.
|
37
|
+
# (4) an array containing a mixture of user_type strings and hashes as described in (1) above.
|
28
38
|
# Non-hash elements that don't represent existing SessionUser objects should raise an exception. Array
|
29
|
-
# elements that are hashes should be parsed to create new SessionUser objects. Raise an exception
|
39
|
+
# elements that are hashes should be parsed to create new SessionUser objects. Raise an exception on
|
30
40
|
# any attempt to redefine an existing user_type.
|
31
41
|
def self.parse_session_users(session_users = nil)
|
32
42
|
return nil unless session_users
|
33
43
|
if session_users.is_a?(Array)
|
34
|
-
# Fun fact: can't use 'each_with_object' here because 'acc += parse_session_users(v)'
|
35
|
-
# different object to 'acc', while 'each_with_object' passes the *same* object
|
36
|
-
# each iteration.
|
44
|
+
# Fun fact: can't use 'each_with_object' here because 'acc += parse_session_users(v)' would assign
|
45
|
+
# a different object to 'acc' on each iteration, while 'each_with_object' passes the *same* object
|
46
|
+
# to the block on each iteration.
|
37
47
|
session_users.inject([]) do |acc, v|
|
38
48
|
if v.is_a?(Hash)
|
39
49
|
acc + parse_session_users(v)
|
40
50
|
else
|
51
|
+
# v is a user_type, e.g. :admin
|
52
|
+
raise "No SessionUser exists for user_type '#{v}'" unless SessionUser.fetch(v)
|
41
53
|
acc << v
|
42
54
|
end
|
43
55
|
end
|
44
56
|
elsif session_users.is_a?(Hash)
|
45
57
|
session_users.each_with_object([]) do |(k,v), acc|
|
58
|
+
# k is the user_type, v is an options hash that typically looks like {:credentials => login_credentials} where
|
59
|
+
# login_credentials is either a hash of parameters to be submitted at login or a proc that returns those parameters.
|
60
|
+
# In the latter case, the proc is executed when we actually log in to create a new session for the specified user.
|
46
61
|
acc << k if user = SessionUser.new(k,v)
|
47
62
|
end
|
48
63
|
end
|
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 = Rails.application.routes.url_helpers.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,7 +494,7 @@ module Fragmentary
|
|
453
494
|
end
|
454
495
|
|
455
496
|
def touch(*args, no_request: false)
|
456
|
-
request_queues.each{|key, queue| queue << request} if request && !no_request
|
497
|
+
request_queues.each{|key, hsh| hsh.each{|key2, queue| queue << request}} if request && !no_request
|
457
498
|
super(*args)
|
458
499
|
end
|
459
500
|
|
@@ -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 => self.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 => self.root_url.gsub(%r{https?://}, ''))
|
13
15
|
CacheBuilder.new(template, Fragmentary::Fragment.base_class.existing(options))
|
14
16
|
end
|
15
17
|
|
@@ -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,15 @@ module Fragmentary
|
|
8
6
|
@@all ||= []
|
9
7
|
end
|
10
8
|
|
11
|
-
attr_reader :requests, :user_type, :
|
9
|
+
attr_reader :requests, :user_type, :host_root_url
|
12
10
|
|
13
|
-
def initialize(user_type)
|
11
|
+
def initialize(user_type, host_root_url)
|
14
12
|
@user_type = user_type
|
13
|
+
# host_root_url represents where the queued *requests* are to be processed. For internal sessions it also represents where
|
14
|
+
# the *queue* will be processed by delayed_job. For external requests, the queue will be processed by the host creating the
|
15
|
+
# queue and the requests will be explicitly sent to the host_root_url.
|
16
|
+
@host_root_url = host_root_url
|
15
17
|
@requests = []
|
16
|
-
@sender = Sender.new(self)
|
17
18
|
self.class.all << self
|
18
19
|
end
|
19
20
|
|
@@ -40,6 +41,10 @@ module Fragmentary
|
|
40
41
|
requests.delete_if{|r| r.path == path}
|
41
42
|
end
|
42
43
|
|
44
|
+
def sender
|
45
|
+
@sender ||= Sender.new(self)
|
46
|
+
end
|
47
|
+
|
43
48
|
def send(**args)
|
44
49
|
sender.start(args)
|
45
50
|
end
|
@@ -49,16 +54,39 @@ module Fragmentary
|
|
49
54
|
end
|
50
55
|
|
51
56
|
class Sender
|
57
|
+
|
52
58
|
class << self
|
53
59
|
def jobs
|
54
60
|
::Delayed::Job.where("(handler LIKE ?) OR (handler LIKE ?)", "--- !ruby/object:#{name} %", "--- !ruby/object:#{name}\n%")
|
55
61
|
end
|
56
62
|
end
|
57
63
|
|
64
|
+
class Target
|
65
|
+
|
66
|
+
attr_reader :url
|
67
|
+
|
68
|
+
def initialize(url)
|
69
|
+
@url = url
|
70
|
+
end
|
71
|
+
|
72
|
+
def queue_name
|
73
|
+
@url.gsub(%r{https?://}, '')
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
58
77
|
attr_reader :queue
|
59
78
|
|
60
79
|
def initialize(queue)
|
61
80
|
@queue = queue
|
81
|
+
@target = Target.new(queue.host_root_url)
|
82
|
+
end
|
83
|
+
|
84
|
+
def session_user
|
85
|
+
@session_user ||= Fragmentary::SessionUser.fetch(queue.user_type)
|
86
|
+
end
|
87
|
+
|
88
|
+
def session
|
89
|
+
@session ||= InternalUserSession.new(@target.url, session_user)
|
62
90
|
end
|
63
91
|
|
64
92
|
# Send all requests, either directly or by schedule
|
@@ -80,22 +108,19 @@ module Fragmentary
|
|
80
108
|
@between ? send_next_request : send_all_requests
|
81
109
|
end
|
82
110
|
|
111
|
+
def send_next_request
|
112
|
+
if queue.size > 0
|
113
|
+
request = queue.next_request
|
114
|
+
session.send_request(:method => request.method, :path => request.path, :parameters => request.parameters, :options => request.options)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
83
118
|
def success
|
84
119
|
schedule_requests(@between) if queue.size > 0
|
85
120
|
end
|
86
121
|
|
87
122
|
private
|
88
123
|
|
89
|
-
def next_request
|
90
|
-
queue.next_request.to_proc
|
91
|
-
end
|
92
|
-
|
93
|
-
def send_next_request
|
94
|
-
if queue.size > 0
|
95
|
-
session.instance_exec(&(next_request))
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
124
|
def send_all_requests
|
100
125
|
while queue.size > 0
|
101
126
|
send_next_request
|
@@ -107,20 +132,11 @@ module Fragmentary
|
|
107
132
|
clear_session
|
108
133
|
Delayed::Job.transaction do
|
109
134
|
self.class.jobs.destroy_all
|
110
|
-
Delayed::Job.enqueue self, :run_at => delay.from_now
|
135
|
+
Delayed::Job.enqueue self, :run_at => delay.from_now, :queue => @target.queue_name
|
111
136
|
end
|
112
137
|
end
|
113
138
|
end
|
114
139
|
|
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
140
|
def clear_session
|
125
141
|
@session = nil
|
126
142
|
end
|
@@ -128,4 +144,5 @@ module Fragmentary
|
|
128
144
|
end
|
129
145
|
|
130
146
|
end
|
147
|
+
|
131
148
|
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
|
@@ -44,7 +44,7 @@ module Fragmentary
|
|
44
44
|
end
|
45
45
|
|
46
46
|
include ActiveSupport::Callbacks
|
47
|
-
define_callbacks :after_destroy
|
47
|
+
define_callbacks :after_create, :after_destroy
|
48
48
|
|
49
49
|
attr_reader :subscriber
|
50
50
|
attr_accessor :record
|
@@ -55,7 +55,10 @@ module Fragmentary
|
|
55
55
|
end
|
56
56
|
|
57
57
|
def after_create(record)
|
58
|
-
|
58
|
+
run_callbacks :after_create do
|
59
|
+
@record = record
|
60
|
+
call_method(:"create_#{record.class.model_name.param_key}_successful", record)
|
61
|
+
end
|
59
62
|
end
|
60
63
|
|
61
64
|
def after_update(record)
|
@@ -1,38 +1,66 @@
|
|
1
1
|
require 'rails/console/app'
|
2
|
+
require 'http'
|
3
|
+
require 'nokogiri'
|
2
4
|
|
3
5
|
module Fragmentary
|
4
6
|
|
5
|
-
class
|
7
|
+
class InternalUserSession
|
6
8
|
|
7
9
|
include Rails::ConsoleMethods
|
8
10
|
|
9
|
-
def initialize(user, &block)
|
11
|
+
def initialize(target, user=nil, &block)
|
10
12
|
# app is from Rails::ConsoleMethods. It returns an object ActionDispatch::Integration::Session.new(Rails.application)
|
11
13
|
# with some extensions. See https://github.com/rails/rails/blob/master/railties/lib/rails/console/app.rb
|
12
14
|
# The session object has instance methods get, post etc.
|
13
15
|
# See https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/testing/integration.rb
|
14
16
|
@session = app
|
15
|
-
|
17
|
+
@user = user
|
18
|
+
@target = URI.parse(target)
|
19
|
+
@session.host! session_host
|
20
|
+
sign_in if session_credentials
|
16
21
|
instance_eval(&block) if block_given?
|
17
22
|
end
|
18
23
|
|
19
|
-
def
|
20
|
-
|
21
|
-
credentials.is_a?(Proc) ? credentials.call : credentials
|
24
|
+
def session_host
|
25
|
+
@session_host ||= @target.host + (port=@target.port ? ":#{port}" : "")
|
22
26
|
end
|
23
27
|
|
24
|
-
def
|
25
|
-
@
|
28
|
+
def session_sign_in_path
|
29
|
+
@sign_in_path ||= Fragmentary.config.get_sign_in_path
|
26
30
|
end
|
27
31
|
|
28
|
-
def
|
29
|
-
|
32
|
+
def session_sign_out_path
|
33
|
+
@sign_out_path ||= Fragmentary.config.sign_out_path
|
34
|
+
end
|
35
|
+
|
36
|
+
def session_credentials
|
37
|
+
return nil unless @user
|
38
|
+
@credentials ||= begin
|
39
|
+
credentials = @user.credentials
|
40
|
+
credentials.is_a?(Proc) ? credentials.call : credentials
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def relative_url_root
|
45
|
+
@relative_url_root ||= Rails.application.config.relative_url_root
|
46
|
+
end
|
47
|
+
|
48
|
+
def session_options
|
49
|
+
@session_options ||= relative_url_root ? {:env => {'SCRIPT_NAME' => relative_url_root}} : {}
|
50
|
+
end
|
51
|
+
|
52
|
+
def method_missing(method, *args)
|
53
|
+
@session.send(method, *args)
|
30
54
|
end
|
31
55
|
|
32
56
|
def sign_in
|
33
|
-
|
57
|
+
raise "Can't sign in without user credentials" unless session_credentials
|
58
|
+
send_request(:method => :get, :path => session_sign_in_path, :options => session_options) # necessary in order to get the csrf token
|
34
59
|
# NOTE: In Rails 5, params is changed to a named argument, i.e. :params => {...}. Will need to be changed.
|
35
|
-
|
60
|
+
# Note that request is called on session, returning an ActionDispatch::Request; request.session is an ActionDispatch::Request::Session
|
61
|
+
puts " * Signing in as #{session_credentials.inspect}"
|
62
|
+
parameters = session_credentials.merge(:authenticity_token => request.session[:_csrf_token])
|
63
|
+
send_request(:method => :post, :path => session_sign_in_path, :parameters => parameters, :options => session_options)
|
36
64
|
if @session.redirect?
|
37
65
|
follow_redirect!
|
38
66
|
else
|
@@ -40,39 +68,87 @@ module Fragmentary
|
|
40
68
|
end
|
41
69
|
end
|
42
70
|
|
43
|
-
|
71
|
+
def follow_redirect!
|
72
|
+
raise "not a redirect! #{status} #{status_message}" unless redirect?
|
73
|
+
if (url = response.location) =~ %r{://}
|
74
|
+
destination = URI.parse(url)
|
75
|
+
path = destination.query ? "#{destination.path}?#{destination.query}" : destination.path
|
76
|
+
end
|
77
|
+
path = relative_url_root ? path.gsub(Regexp.new("^#{relative_url_root}"), "") : path
|
78
|
+
send_request(:method => :get, :path => path, :options => session_options)
|
79
|
+
status
|
80
|
+
end
|
44
81
|
|
45
|
-
|
82
|
+
def send_request(method:, path:, parameters: nil, options: {})
|
83
|
+
options.merge!({:params => parameters})
|
84
|
+
options.merge!(session_options)
|
85
|
+
if options.try(:[], :xhr)
|
86
|
+
Rails.logger.info " * Sending xhr request '#{method.to_s} #{path}'" + (!parameters.nil? ? " with #{parameters.inspect}" : "")
|
87
|
+
else
|
88
|
+
Rails.logger.info " * Sending request '#{method.to_s} #{path}'" + (!parameters.nil? ? " with #{parameters.inspect}" : "")
|
89
|
+
end
|
90
|
+
@session.send(method, path, options)
|
91
|
+
end
|
46
92
|
|
47
|
-
def
|
48
|
-
|
93
|
+
def sign_out
|
94
|
+
# request is called on session, returning an ActionDispatch::Request; request.session is an ActionDispatch::Request::Session
|
95
|
+
parameters = {:_method => 'delete', :authenticity_token => request.session[:_csrf_token]}
|
96
|
+
send_request(:method => :post, :path => session_sign_out_path, :parameters => parameters, :options => session_options)
|
49
97
|
end
|
50
98
|
|
51
|
-
|
52
|
-
|
99
|
+
end
|
100
|
+
|
101
|
+
class ExternalUserSession
|
102
|
+
|
103
|
+
def initialize(target, user=nil)
|
104
|
+
@target = URI.parse(target)
|
105
|
+
@relative_url_root = @target.path
|
106
|
+
@session = HTTP.persistent(target)
|
107
|
+
@cookie = nil
|
108
|
+
@authenticity_token = nil
|
109
|
+
sign_in if @credentials = session_credentials(user)
|
53
110
|
end
|
54
111
|
|
55
|
-
def
|
56
|
-
if
|
57
|
-
|
58
|
-
raise RangeError, "You can't redefine an existing SessionUser object: #{user_type.inspect}"
|
59
|
-
else
|
60
|
-
user
|
61
|
-
end
|
112
|
+
def send_request(method:, path:, parameters: nil, options: {})
|
113
|
+
if options.try(:[], :xhr)
|
114
|
+
Rails.logger.info " * Sending xhr request '#{method.to_s} #{path}'" + (!parameters.nil? ? " with #{parameters.inspect}" : "")
|
62
115
|
else
|
63
|
-
|
64
|
-
@options = options
|
65
|
-
self.class.all.merge!({user_type => self})
|
116
|
+
Rails.logger.info " * Sending request '#{method.to_s} #{path}'" + (!parameters.nil? ? " with #{parameters.inspect}" : "")
|
66
117
|
end
|
118
|
+
unless path =~ %r{://}
|
119
|
+
path = @relative_url_root + path
|
120
|
+
end
|
121
|
+
cookies = @cookie ? {@cookie.name.to_sym => @cookie.value} : {}
|
122
|
+
headers = options.try(:delete, :headers) || {}
|
123
|
+
headers.merge!({:'X-Requested-With' => 'XMLHttpRequest'}) if options.try(:delete, :xhr)
|
124
|
+
response = @session.cookies(cookies).headers(headers).send(method, path, {:json => parameters})
|
125
|
+
@cookie = response.cookies.first
|
126
|
+
@authenticity_token = Nokogiri::HTML.parse(response.to_s).css('head meta[name="csrf-token"]').first.try(:[], 'content')
|
127
|
+
if (response.code >=300) && (response.code <=399)
|
128
|
+
location = response.headers[:location]
|
129
|
+
options = {:headers => {:accept => "text/html,application/xhtml+xml,application/xml"}}
|
130
|
+
response = send_request(:method => :get, :path => location, :parameters => nil, :options => options)
|
131
|
+
end
|
132
|
+
response
|
67
133
|
end
|
68
134
|
|
69
|
-
def
|
70
|
-
|
135
|
+
def session_credentials(user)
|
136
|
+
credentials = user.try(:credentials)
|
137
|
+
credentials.is_a?(Proc) ? credentials.call : credentials
|
71
138
|
end
|
72
139
|
|
73
|
-
|
74
|
-
|
75
|
-
|
140
|
+
def sign_in
|
141
|
+
# The first request retrieves the authentication token
|
142
|
+
response = send_request(:method => :get, :path => Fragmentary.config.get_sign_in_path)
|
143
|
+
puts " * Signing in as #{@credentials.inspect}"
|
144
|
+
response = send_request(:method => :post, :path => Fragmentary.config.post_sign_in_path,
|
145
|
+
:parameters => @credentials.merge(:authenticity_token => @authenticity_token),
|
146
|
+
:options => {:headers => {:accept => "text/html,application/xhtml+xml,application/xml"}})
|
147
|
+
end
|
148
|
+
|
149
|
+
def sign_out
|
150
|
+
send_request(:method => :delete, :path => Fragmentary.config.sign_out_path, :parameters => {:authenticity_token => @authenticity_token})
|
151
|
+
@session.close
|
76
152
|
end
|
77
153
|
end
|
78
154
|
|
data/lib/fragmentary/version.rb
CHANGED
data/lib/fragmentary.rb
CHANGED
@@ -7,6 +7,7 @@ require 'fragmentary/request'
|
|
7
7
|
require 'fragmentary/fragment'
|
8
8
|
require 'fragmentary/handler'
|
9
9
|
require 'fragmentary/user_session'
|
10
|
+
require 'fragmentary/session_user'
|
10
11
|
require 'fragmentary/widget_parser'
|
11
12
|
require 'fragmentary/widget'
|
12
13
|
require 'fragmentary/publisher'
|
metadata
CHANGED
@@ -1,35 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fragmentary
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: '0.3'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mark Thomson
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-01-13 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
|
-
- - "
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: 4.0.0
|
20
|
-
- - "<"
|
17
|
+
- - "~>"
|
21
18
|
- !ruby/object:Gem::Version
|
22
|
-
version: '5'
|
19
|
+
version: '5.0'
|
23
20
|
type: :runtime
|
24
21
|
prerelease: false
|
25
22
|
version_requirements: !ruby/object:Gem::Requirement
|
26
23
|
requirements:
|
27
|
-
- - "
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
version: 4.0.0
|
30
|
-
- - "<"
|
24
|
+
- - "~>"
|
31
25
|
- !ruby/object:Gem::Version
|
32
|
-
version: '5'
|
26
|
+
version: '5.0'
|
33
27
|
- !ruby/object:Gem::Dependency
|
34
28
|
name: delayed_job_active_record
|
35
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -58,6 +52,34 @@ dependencies:
|
|
58
52
|
- - "~>"
|
59
53
|
- !ruby/object:Gem::Version
|
60
54
|
version: '1.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: http
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 3.0.0
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 3.0.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: nokogiri
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
61
83
|
- !ruby/object:Gem::Dependency
|
62
84
|
name: bundler
|
63
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -127,6 +149,7 @@ files:
|
|
127
149
|
- lib/fragmentary/publisher.rb
|
128
150
|
- lib/fragmentary/request.rb
|
129
151
|
- lib/fragmentary/request_queue.rb
|
152
|
+
- lib/fragmentary/session_user.rb
|
130
153
|
- lib/fragmentary/subscriber.rb
|
131
154
|
- lib/fragmentary/subscription.rb
|
132
155
|
- lib/fragmentary/user_session.rb
|
@@ -152,8 +175,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
152
175
|
- !ruby/object:Gem::Version
|
153
176
|
version: '0'
|
154
177
|
requirements: []
|
155
|
-
|
156
|
-
rubygems_version: 2.4.8
|
178
|
+
rubygems_version: 3.0.8
|
157
179
|
signing_key:
|
158
180
|
specification_version: 4
|
159
181
|
summary: Fragment modeling and caching for Rails
|