fragmentary 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +33 -18
- data/lib/fragmentary/fragment.rb +31 -19
- data/lib/fragmentary/fragments_helper.rb +22 -27
- data/lib/fragmentary/publisher.rb +1 -1
- data/lib/fragmentary/version.rb +1 -1
- data/lib/fragmentary/widget.rb +1 -2
- data/lib/fragmentary/widget_parser.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ce71afcd11d78930295c8839ee9519b2bb504f40
|
4
|
+
data.tar.gz: 354eee986e23c365e958807f3024ebb571e6b7e8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cecfed68ceed46c1d174c5f967769ae4c6bc9608f09675e3d693958c931be25e8adaf58c497755197549e83959756a14e67e4cf50fd52cd58ac9b1e5cfe31c6e
|
7
|
+
data.tar.gz: fb4fca48abf783d5fd7faa24147b4c55164444341c21bdf936107562cf112fef55a183f34855bf42076bf8bc3a747ece32f1d61d818bd3e507b7411610163d3c
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
### 0.2.1
|
2
|
+
- Fixes a syntax bug in validating a fragment's root_id
|
3
|
+
- Adds README documentation on the use of methods for explicitly retreving user-dependent fragments.
|
4
|
+
- Adds README documentation on the needs_user_id declaration
|
5
|
+
- Deprecates the :template parameter in FragmentsHelper#fragment_builder
|
6
|
+
- Adds minor performance enhancements
|
7
|
+
|
1
8
|
### 0.2.0
|
2
9
|
- Makes the following configurable:
|
3
10
|
- current_user_method - called on current template to obtain the current authenticated user.
|
data/README.md
CHANGED
@@ -110,7 +110,7 @@ class Product < ActiveRecord::Base
|
|
110
110
|
end
|
111
111
|
```
|
112
112
|
|
113
|
-
Create a Fragment subclass (e.g. in app/models/fragments.rb or elsewhere) for each distinct fragment type that your views contain. If a particular fragment type needs to be unique by the id of some application data record, specify the class of the application model as shown in the example below.
|
113
|
+
Create a `Fragment` subclass (e.g. in app/models/fragments.rb or elsewhere) for each distinct fragment type that your views contain. If a particular fragment type needs to be unique by the id of some application data record, specify the class of the application model as shown in the example below.
|
114
114
|
```
|
115
115
|
class ProductTemplate < Fragment
|
116
116
|
needs_record_id :type => 'Product'
|
@@ -122,7 +122,7 @@ Here `needs_record_id` indicates that there is a separate `ProductTemplate` frag
|
|
122
122
|
|
123
123
|
We've used this, for example, for fragment types representing different kinds of list items that have certain generic characteristics but are used in different contexts to represent different kinds of content.
|
124
124
|
|
125
|
-
Within the body of the fragment
|
125
|
+
Within the body of the fragment subclass definition, for each application model whose records the content of the fragment depends upon, use the `subscribe_to` method with a block containing method definitions to handle create, update and destroy events on your application data, typically to touch the fragment records affected by the application data change. The names of these methods follow the form used in the wisper-activerecord gem, i.e. `create_<model_name>_successful`, `update_<model_name>_successful` and `destroy_<model_name>_successful`, each taking a single argument representing the application data record that has changed.
|
126
126
|
|
127
127
|
Within the body of each method you define within the `subscribe_to` block, you can retrieve and touch the fragment records affected by the change in application data. The method `touch_fragments_for_record` can be used for convenience. That method takes an individual application data record or record_id or an array of either. So, for example, if product listings include the names of all the categories the products belong to, with those categories being represented by a separate ActiveRecord model, and the wording of a category name changes, you could handle that as follows.
|
128
128
|
```
|
@@ -140,7 +140,7 @@ The effect of this will be to expire the product template fragment for every pro
|
|
140
140
|
|
141
141
|
When implementing the method definitions within the `subscribe_to` block, note that the block will be executed against an instance of a separate `Fragmentary::Subscriber` class that acts on behalf of the particular `Fragment` subclass we are defining (so the methods we define within the block are actually defined on that subscriber object). However, _all other_ methods called on the `Subscriber` object, including those called from within the methods we define in the block, are delegated by `method_missing` to the fragment subclass. So in the example, `touch_fragments_for_record` called from within `update_product_category_successful` represents `ProductTemplate.touch_fragments_for_record`. This method is defined by the `Fragment` class, so is available to all fragment subclasses.
|
142
142
|
|
143
|
-
Note also that for fragment subclasses that declare `needs_record_id`, there is no need define a `destroy_<model_name>_successful` method simply to remove a fragment whose `record_id` matches the `id` of a <model_name> application record that is destroyed, e.g. to destroy a `ProductTemplate` whose `record_id` matches the `id` of a destroyed `Product` object. Fragmentary handles this clean-up automatically. This is not to say, however, that 'destroy' handlers are never needed at all. The destruction of an application data record will often require other fragments to be touched.
|
143
|
+
Note also that for fragment subclasses that declare `needs_record_id`, there is no need to define a `destroy_<model_name>_successful` method simply to remove a fragment whose `record_id` matches the `id` of a <model_name> application record that is destroyed, e.g. to destroy a `ProductTemplate` whose `record_id` matches the `id` of a destroyed `Product` object. Fragmentary handles this clean-up automatically. This is not to say, however, that 'destroy' handlers are never needed at all. The destruction of an application data record will often require other fragments to be touched.
|
144
144
|
|
145
145
|
### View Setup
|
146
146
|
|
@@ -157,7 +157,7 @@ A 'root' fragment is one that has no parent. In the template in which a root fra
|
|
157
157
|
|
158
158
|
#### Nested Fragments
|
159
159
|
|
160
|
-
The variable `fragment` that is yielded to the block above is an object of class `Fragmentary::FragmentsHelper::CacheBuilder`, which contains both the actual ActiveRecord fragment record found or created by `cache_fragment` *and* the current template as instance variables.
|
160
|
+
The variable `fragment` that is yielded to the block above is an object of class `Fragmentary::FragmentsHelper::CacheBuilder`, which contains both the actual ActiveRecord fragment record found or created by `cache_fragment` *and* the current template as instance variables. The class has one public instance method defined, `cache_child`, which can be used to define a child fragment nested within the first. The method is used _within_ a block of content defined by `cache_fragment` and much like `cache_fragment` it takes a hash of options that uniquely identify the child fragment. Also like `cache_fragment` it yields another `CacheBuilder` object and wraps a block containing the content of the child fragment to be cached.
|
161
161
|
```
|
162
162
|
<% cache_fragment :type => 'ProductTemplate', :record_id => @product.id do |fragment| %>
|
163
163
|
<% fragment.cache_child :type => 'StoresAvailable' do |child_fragment| %>
|
@@ -310,9 +310,16 @@ Whether you specify the user type mapping in Fragmentary's configuration or in a
|
|
310
310
|
|
311
311
|
|
312
312
|
#### Per-User Customization
|
313
|
-
While storing different versions of fragments is a workable solution for different groups of users, sometimes content needs to be customized for individual users.
|
313
|
+
While storing different versions of fragments is a workable solution for different groups of users, sometimes content needs to be customized for individual users. It is _possible_ to accomplish this by defining a fragment subclass with the declaration `needs_user_id`:
|
314
|
+
```
|
315
|
+
class MyFragment < Fragment
|
316
|
+
needs_user_id
|
317
|
+
...
|
318
|
+
end
|
319
|
+
```
|
320
|
+
You will also need to include a `user_id` column in the migration used to create your fragments database table. The `needs_user_id` declaration will result in separate fragments being created for each individual user. As in the case of `needs_user_type`, the `user_id` parameter is automatically extracted from your configured (or default) `current_user_method`.
|
314
321
|
|
315
|
-
To accomplish this, two classes are defined, `Fragmentary::Widget` and a subclass `Fragmentary::UserWidget`. Instances of these classes represent chunks of content that will be inserted into a fragment after it has been either rendered and stored or retrieved from the cache. For each type of insertable content you wish your application to support, define a specific subclass of one of these classes (`UserWidget` is preferred if the content is to be user-specific) with two instance methods:
|
322
|
+
However this approach is _not_ generally recommended as doing so at any realistic scale is likely to introduce significant cost and performance challenges. Instead, we provide a way for user-specific content (in general any content) to be inserted into a cached fragment _after_ it has been retrieved from the cache through the use of a special placeholder string in a view template. To accomplish this, two classes are defined, `Fragmentary::Widget` and a subclass `Fragmentary::UserWidget`. Instances of these classes represent chunks of content that will be inserted into a fragment after it has been either rendered and stored or retrieved from the cache. For each type of insertable content you wish your application to support, define a specific subclass of one of these classes (`UserWidget` is preferred if the content is to be user-specific) with two instance methods:
|
316
323
|
- `pattern`, which returns a `Regexp` that will be used to detect a placeholder you will use in your template at the point where you wish the inserted content to be placed. The placeholder consists of a string matching the regular expression you specify wrapped in special delimiter characters `%{...}`.
|
317
324
|
- `content`, which returns the string that will be inserted in place of the placeholder.
|
318
325
|
|
@@ -412,7 +419,15 @@ Of course if you have to render the content in order to determine whether it nee
|
|
412
419
|
<% end %>
|
413
420
|
```
|
414
421
|
|
415
|
-
Note
|
422
|
+
Note that we update the fragment's `memo` attribute by calling `update_attribute` on the `fragment` object. Although this is actually a `CacheBuilder` object, that class passes any methods such as `update_attribute` to the underlying fragment.
|
423
|
+
|
424
|
+
Also note the use above of the method `Fragment.root` to retrieve the fragment matching the supplied parameters after caching has occurred. We have to retrieve the fragment explicitly since the variable `fragment` is local to the `cache_fragment` block to which it is yielded. The method takes the same parameters as `cache_fragment` with one exception: At present, if your fragment class declares `needs_user_type` or `needs_user_id`, you need to explicitly pass either the respective parameter or the current user object (this may change in a future release). e.g.
|
425
|
+
|
426
|
+
```
|
427
|
+
...
|
428
|
+
<% root_fragment = Fragment.root(:type => 'ProductTemplate', :record_id => @product.id, :user => current_user).memo == required_memo_value %>
|
429
|
+
...
|
430
|
+
```
|
416
431
|
|
417
432
|
The process for child fragments is similar, except that an instance method Fragment#child can be called on the parent fragment in order to retrieve the fragment to be tested. e.g.
|
418
433
|
|
@@ -430,7 +445,7 @@ The process for child fragments is similar, except that an instance method Fragm
|
|
430
445
|
<% end %>
|
431
446
|
```
|
432
447
|
|
433
|
-
In both of these examples, the fragment to be tested is retrieved after caching. If relying on side-effects doesn't make you too queasy, you can alternatively retrieve it before caching, in which case the fragment can be passed to `cache_fragment` or `cache_child` as appropriate instead of the set of fragment attributes we used before. This avoids the need for `cache_fragment` or `cache_child` to retrieve the fragment internally
|
448
|
+
In both of these examples, the fragment to be tested is retrieved after caching. If relying on side-effects doesn't make you too queasy, you can alternatively retrieve it before caching, in which case the retrieved fragment can be passed to `cache_fragment` or `cache_child` as appropriate instead of the set of fragment attributes we used before. This avoids the need for `cache_fragment` or `cache_child` to retrieve the fragment themselves internally, e.g.
|
434
449
|
```
|
435
450
|
<% root_fragment = Fragment.root(:type => 'ProductTemplate', :record_id => @product.id) %>
|
436
451
|
<% conditional_content = capture do %>
|
@@ -456,7 +471,7 @@ Note that both `Fragment.root` and `Fragment#child` will instantiate a matching
|
|
456
471
|
<% end %>
|
457
472
|
|
458
473
|
<% if child.memo == required_memo_value %>
|
459
|
-
|
474
|
+
<%= conditional_content %>
|
460
475
|
<% end %>
|
461
476
|
|
462
477
|
<% else %>
|
@@ -471,7 +486,7 @@ Note that both `Fragment.root` and `Fragment#child` will instantiate a matching
|
|
471
486
|
|
472
487
|
When application data that a cached fragment depends upon changes, i.e. as a result of a POST, PATCH or DELETE request, the `subscribe_to` declarations in your `Fragment` subclass definitions ensure that the `updated_at` attribute of any existing fragment records affected will be updated. Then on subsequent browser requests, the cached content itself will be refreshed.
|
473
488
|
|
474
|
-
As part of this process, new fragment records may be created as *children* of an existing fragment if the existing fragment contains newly added list items. A new *root* fragment, on the other hand, will be created for any root fragment class defined with `needs_record_id` if an application data record of the associated `record_type` is created, as soon as a browser request is made
|
489
|
+
As part of this process, new fragment records may be created as *children* of an existing fragment if the existing fragment contains newly added list items. A new *root* fragment, on the other hand, will be created for any root fragment class defined with `needs_record_id` if an application data record of the associated `record_type` is created, as soon as a browser request is made for the new content (often a new page) containing that fragment.
|
475
490
|
|
476
491
|
Sometimes, however, it is desirable to avoid having to wait for an explicit request from a user in order to update the cache or to create new cached content. To deal with this, Fragmentary provides a mechanism to automatically create or refresh cached content preemptively, essentially as soon as a change in application data occurs. Rails' `ActionDispatch::Integration::Session` [class](https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/testing/integration.rb) provides an interface that allows requests to be sent directly to the application programmatically, without generating any external network traffic. Fragmentary uses this interface to automatically send requests needed to update the cache whenever changes in application data occur.
|
477
492
|
|
@@ -487,7 +502,7 @@ end
|
|
487
502
|
```
|
488
503
|
Since nested child fragments automatically touch their parent fragments when they themselves are updated, internal requests can be initiated by an update to application data that affects a fragment anywhere within a page. Only the root fragment needs to define the request path.
|
489
504
|
|
490
|
-
The request that is generated will be sent to the application at the `request_path` specified, but it may also include additional request parameters and options. To send HTTP request parameters with the request, the Fragment subclass should define an additional instance method `request_parameters` that returns a hash of named parameters. To send an XMLHttpRequest, a class should define an instance method `request_options` that returns the hash `{:xhr => true}`.
|
505
|
+
The request that is generated will be sent to the application at the `request_path` specified, but it may also include additional request parameters and options. To send HTTP request parameters with the request, the `Fragment` subclass should define an additional instance method `request_parameters` that returns a hash of named parameters. To send an XMLHttpRequest, a class should define an instance method `request_options` that returns the hash `{:xhr => true}`.
|
491
506
|
|
492
507
|
In the case of a root fragment that *does not* yet exist, i.e. for a `Fragment` subclass defined with `needs_record_id` and an associated `record_type` when a new application record of that type is first created, a request will be generated in order to create the new fragment automatically if the subclass has a *class* method `request_path` defined that takes the `id` of the newly created application record and returns a string representing the path to which the request should be sent. For example:
|
493
508
|
```
|
@@ -515,7 +530,7 @@ The set of user types that an individual `Fragment` subclass is expected to supp
|
|
515
530
|
|
516
531
|
Configuration of the `user_types` method is discussed in the next section. Fragmentary uses the types returned by this method to identify the request queues that each internal application request needs to be added to when instances of each particular `Fragment` subclass are updated. `Fragment` subclasses inherit both class and instance methods `request_queues` that return a hash of queues keyed by each of the specific user type strings that that specific subclass supports.
|
517
532
|
|
518
|
-
The request queue for any given user type is shared across all Fragment subclasses within the application. So for example, a `ProductTemplate` fragment may have two different request queues, `ProductTemplate.request_queues["admin"]` and `ProductTemplate.request_queues["signed_in"]`. If a `StoreTemplate` fragment has one request queue `StoreTemplate.request_queues["signed_in"]`, the `"signed_in"` queues for both classes represent the same object.
|
533
|
+
The request queue for any given user type is shared across all `Fragment` subclasses within the application. So for example, a `ProductTemplate` fragment may have two different request queues, `ProductTemplate.request_queues["admin"]` and `ProductTemplate.request_queues["signed_in"]`. If a `StoreTemplate` fragment has one request queue `StoreTemplate.request_queues["signed_in"]`, the `"signed_in"` queues for both classes represent the same object.
|
519
534
|
|
520
535
|
#### Configuring Internal Request Users
|
521
536
|
|
@@ -591,7 +606,7 @@ MyFragment.queue_request(request)
|
|
591
606
|
|
592
607
|
Off-loading internal application requests to an asynchronous process as noted in the previous section means they can occur without delaying the server's response to the user's initial external request. However, in a complex application there may also be some overhead just in the process of updating fragment records that is not critical in order to send a response back to the user. For example, some application data changes may require a large number of fragments to be updated on pages other than the one the user is going to be sent or redirected to right away. In this case, we may wish to offload the task of updating these fragments to an asynchronous process as well.
|
593
608
|
|
594
|
-
Fragmentary accomplishes this by means of the `Fragmentary::Handler` class. An individual task you wish to be handled asynchronously is created by defining a subclass of `Fragmentary::Handler`, typically within the scope of the Fragment subclass in which you wish to use it. The `Handler` subclass you define requires a single instance method `call`, which defines the task to be performed.
|
609
|
+
Fragmentary accomplishes this by means of the `Fragmentary::Handler` class. An individual task you wish to be handled asynchronously is created by defining a subclass of `Fragmentary::Handler`, typically within the scope of the `Fragment` subclass in which you wish to use it. The `Handler` subclass you define requires a single instance method `call`, which defines the task to be performed.
|
595
610
|
|
596
611
|
To use the handler, typically within a fragment's `subscribe_to` declaration, call the inherited class method `create`, passing it a hash of named arguments. Those arguments can then be accessed within your `call` method as the instance variable `@args`. Consider our earlier example in which each product template contains a list of stores in which the product is available. Suppose an administrator fixes a typographical error in the name of a store. That correction needs to propagate to every `AvailableStore` fragment for every product that is available in that store. We can do this using a Handler as follows:
|
597
612
|
```
|
@@ -601,7 +616,7 @@ class AvailableStore < Fragment
|
|
601
616
|
class UpdateStoreHandler < Fragmentary::Handler
|
602
617
|
def call
|
603
618
|
product_stores = ProductStore.where(:store_id => @args[:id])
|
604
|
-
|
619
|
+
touch_fragments_for_record(product_stores)
|
605
620
|
end
|
606
621
|
end
|
607
622
|
|
@@ -649,7 +664,7 @@ end
|
|
649
664
|
|
650
665
|
### Updating Fragments Explicitly Within a Controller
|
651
666
|
|
652
|
-
The typical usage scenario for Fragmentary is for fragment records to be updated by the methods defined in the `subscribe_to` blocks you create in your fragment subclasses, in response to user requests that modify the application data. This involves very little coupling between your application's controllers and models and the fragment caching process. Models need only include the `Fragmentary::Publisher` module to ensure that `Fragment` subclasses can subscribe to the application data events that trigger fragment updates. Your `ApplicationController` only needs to ensure that any queued internal requests and asynchronous fragment update tasks are dispatched (if you choose to use either of those features) before sending a response to the user's browser. Application models and controllers generally have no need to access the Fragment class API.
|
667
|
+
The typical usage scenario for Fragmentary is for fragment records to be updated by the methods defined in the `subscribe_to` blocks you create in your fragment subclasses, in response to user requests that modify the application data. This involves very little coupling between your application's controllers and models and the fragment caching process. Models need only include the `Fragmentary::Publisher` module to ensure that `Fragment` subclasses can subscribe to the application data events that trigger fragment updates. Your `ApplicationController` only needs to ensure that any queued internal requests and asynchronous fragment update tasks are dispatched (if you choose to use either of those features) before sending a response to the user's browser. Application models and controllers generally have no need to access the `Fragment` class API.
|
653
668
|
|
654
669
|
Ocassionally, however, a situation may arise in which it is useful to touch fragments directly from a controller. For example, an application data event may occur that requires fragments to be updated on a large number of different pages within your site. This is the kind of scenario in which you might choose to offload fragment updating to a `Fragmentary::Handler` and execute the task asynchronously as described in the previous section. However, it may also be that *one* of those pages is the one that is going to be sent back to the user's browser immediately in response to the current request. You can't put off touching the affected fragment on that one page until the asynchronous process executes because then the user won't see the updated content. In a scenario like this, you also can't detect within the `subscribe_to` block in your fragment subclass definition which one fragment needs to be touched immediately rather than asynchronously, since the fragment class has no knowledge of the internal state of the controller.
|
655
670
|
|
@@ -710,9 +725,9 @@ However, it's possible for the partial to contain a cached fragment that happens
|
|
710
725
|
</ul>
|
711
726
|
<% end %>
|
712
727
|
```
|
713
|
-
In the Javascript case, however, since the template only renders the new list item and not the list as a whole, there is no `cache_fragment` method invoked in order to yield the `CacheBuilder` object, and so we have to construct it explicitly. To do this, Fragmentary provides a helper method `fragment_builder` that takes an options hash containing the parameters that define the fragment (the same ones passed to `cache_fragment`)
|
728
|
+
In the Javascript case, however, since the template only renders the new list item and not the list as a whole, there is no `cache_fragment` method invoked in order to yield the `CacheBuilder` object, and so we have to construct it explicitly. To do this, Fragmentary provides a helper method `fragment_builder` that takes an options hash containing the parameters that define the fragment (the same ones passed to `cache_fragment`) and returns the `CacheBuilder` object that the partial needs.
|
714
729
|
```
|
715
|
-
<% parent_fragment = fragment_builder(:type => 'ProductList'
|
730
|
+
<% parent_fragment = fragment_builder(:type => 'ProductList') %>
|
716
731
|
$('ul.product_list').append('<%= j(render 'product/summary', :product => @product,
|
717
732
|
:parent_fragment => parent_fragment) %>')
|
718
733
|
```
|
@@ -723,7 +738,7 @@ The second challenge in dealing with child fragments rendered without the contex
|
|
723
738
|
|
724
739
|
To address this, `cache_child` can take an additional boolean option, `:insert_widgets` that can be used to force the insertion of widgets into the child. Typically a local variable containing the value of this option would be passed to the partial in which `cache_child` appears. In the Javascript template:
|
725
740
|
```
|
726
|
-
<% parent_fragment = fragment_builder(:type => 'ProductList'
|
741
|
+
<% parent_fragment = fragment_builder(:type => 'ProductList') %>
|
727
742
|
$('ul.product_list').append('<%= j(render 'product/summary', :product => @product,
|
728
743
|
:parent_fragment => parent_fragment,
|
729
744
|
:insert_widgets => true) %>')
|
data/lib/fragmentary/fragment.rb
CHANGED
@@ -28,9 +28,9 @@ module Fragmentary
|
|
28
28
|
|
29
29
|
attr_accessor :indexed_children
|
30
30
|
|
31
|
-
|
31
|
+
validates :root_id, :presence => true
|
32
32
|
|
33
|
-
cache_timestamp_format = :usec # Probably not needed for Rails 5, which uses :usec by default.
|
33
|
+
self.cache_timestamp_format = :usec # Probably not needed for Rails 5, which uses :usec by default.
|
34
34
|
|
35
35
|
end
|
36
36
|
|
@@ -49,9 +49,13 @@ module Fragmentary
|
|
49
49
|
module ClassMethods
|
50
50
|
|
51
51
|
def root(options)
|
52
|
-
|
53
|
-
|
54
|
-
|
52
|
+
if fragment = options[:fragment]
|
53
|
+
raise ArgumentError, "You passed Fragment #{fragment.id} to Fragment.root, but it's a child of Fragment #{fragment.parent_id}" if fragment.parent_id
|
54
|
+
else
|
55
|
+
klass, search_attributes, options = base_class.attributes(options)
|
56
|
+
fragment = klass.where(search_attributes).includes(:children).first_or_initialize(options); fragment.save if fragment.new_record?
|
57
|
+
fragment.set_indexed_children if fragment.child_search_key
|
58
|
+
end
|
55
59
|
fragment
|
56
60
|
end
|
57
61
|
|
@@ -90,16 +94,21 @@ module Fragmentary
|
|
90
94
|
@@cache_store ||= Rails.application.config.action_controller.cache_store
|
91
95
|
end
|
92
96
|
|
97
|
+
# ToDo: combine this with Fragment.root
|
93
98
|
def existing(options)
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
99
|
+
if fragment = options[:fragment]
|
100
|
+
raise ArgumentError, "You passed Fragment #{fragment.id} to Fragment.existing, but it's a child of Fragment #{fragment.parent_id}" if fragment.parent_id
|
101
|
+
else
|
102
|
+
options.merge!(:type => name) unless self == base_class
|
103
|
+
raise ArgumentError, "A 'type' attribute is needed in order to retrieve a fragment" unless options[:type]
|
104
|
+
klass, search_attributes, options = base_class.attributes(options)
|
105
|
+
# We merge options because it may include :record_id, which may be needed for uniqueness even
|
106
|
+
# for classes that don't 'need_record_id' if the parent_id isn't available.
|
107
|
+
fragment = klass.where(search_attributes.merge(options)).includes(:children).first
|
108
|
+
# Unlike Fragment.root and Fragment#child we don't instantiate a record if none is found,
|
109
|
+
# so fragment may be nil.
|
110
|
+
fragment.try :set_indexed_children if fragment.try :child_search_key
|
111
|
+
end
|
103
112
|
fragment
|
104
113
|
end
|
105
114
|
|
@@ -262,7 +271,7 @@ module Fragmentary
|
|
262
271
|
# hasn't yet been requested, e.g. an assumption created on an article page won't necessarily have been rendered on the
|
263
272
|
# opinion analysis page.
|
264
273
|
def touch_fragments_for_record(record_id)
|
265
|
-
fragments_for_record(record_id).each(&:touch)
|
274
|
+
fragments_for_record(record_id).includes({:parent => :parent}).each(&:touch)
|
266
275
|
end
|
267
276
|
|
268
277
|
def fragments_for_record(record_id)
|
@@ -389,7 +398,10 @@ module Fragmentary
|
|
389
398
|
# rendered on its own, e.g. inserted by ajax into a parent that is already on the page. In this case the
|
390
399
|
# children won't have already been loaded or indexed.
|
391
400
|
def child(options)
|
392
|
-
|
401
|
+
if child = options[:child]
|
402
|
+
raise ArgumentError, "You passed a child fragment to a parent it's not a child of." unless child.parent_id == self.id
|
403
|
+
child
|
404
|
+
else
|
393
405
|
existing = options.delete(:existing)
|
394
406
|
# root_id and parent_id are passed from parent to child. For all except root fragments, root_id is stored explicitly.
|
395
407
|
derived_options = {:root_id => root_id || id}
|
@@ -419,10 +431,7 @@ module Fragmentary
|
|
419
431
|
fragment_children = fragment.children
|
420
432
|
fragment.set_indexed_children if fragment.child_search_key
|
421
433
|
end
|
422
|
-
|
423
434
|
fragment
|
424
|
-
rescue => e
|
425
|
-
Rails.logger.error e.message + "\n " + e.backtrace.join("\n ")
|
426
435
|
end
|
427
436
|
end
|
428
437
|
|
@@ -466,10 +475,13 @@ module Fragmentary
|
|
466
475
|
touch(:no_request => no_request) unless children.any?
|
467
476
|
end
|
468
477
|
|
478
|
+
# Touch this fragment and all descendants that have entries in the cache. Destroy any that
|
479
|
+
# don't have cache entries.
|
469
480
|
def touch_or_destroy
|
470
481
|
puts " touch_or_destroy #{self.class.name} #{id}"
|
471
482
|
if cache_exist?
|
472
483
|
children.each(&:touch_or_destroy)
|
484
|
+
# if there are children, this will be touched automatically once they are.
|
473
485
|
touch(:no_request => true) unless children.any?
|
474
486
|
else
|
475
487
|
destroy # will also destroy all children because of :dependent => :destroy
|
@@ -2,56 +2,52 @@ module Fragmentary
|
|
2
2
|
|
3
3
|
module FragmentsHelper
|
4
4
|
|
5
|
-
def cache_fragment(options)
|
6
|
-
|
7
|
-
options.reverse_merge!(:user => Template.new(self).current_user)
|
8
|
-
fragment = options.delete(:fragment) || Fragmentary::Fragment.base_class.root(options)
|
9
|
-
builder = CacheBuilder.new(fragment, template = self)
|
10
|
-
unless no_cache
|
11
|
-
cache fragment, :skip_digest => true do
|
12
|
-
yield(builder)
|
13
|
-
end
|
14
|
-
else
|
15
|
-
yield(builder)
|
16
|
-
end
|
17
|
-
self.output_buffer = WidgetParser.new(self).parse_buffer
|
5
|
+
def cache_fragment(options, &block)
|
6
|
+
CacheBuilder.new(self).cache_fragment(options, &block)
|
18
7
|
end
|
19
8
|
|
20
9
|
def fragment_builder(options)
|
21
|
-
template
|
10
|
+
# the template option is deprecated but avoids breaking prior usage
|
11
|
+
template = options.delete(:template) || self
|
22
12
|
options.reverse_merge!(:user => Template.new(template).current_user)
|
23
|
-
CacheBuilder.new(Fragmentary::Fragment.base_class.existing(options)
|
13
|
+
CacheBuilder.new(template, Fragmentary::Fragment.base_class.existing(options))
|
24
14
|
end
|
25
15
|
|
26
16
|
class CacheBuilder
|
27
17
|
include ::ActionView::Helpers::CacheHelper
|
28
18
|
include ::ActionView::Helpers::TextHelper
|
29
19
|
|
30
|
-
|
20
|
+
attr_reader :fragment
|
31
21
|
|
32
|
-
def initialize(
|
22
|
+
def initialize(template, fragment = nil)
|
33
23
|
@fragment = fragment
|
34
24
|
@template = template
|
35
25
|
end
|
36
26
|
|
37
|
-
def
|
27
|
+
def cache_fragment(options, &block)
|
38
28
|
no_cache = options.delete(:no_cache)
|
39
29
|
insert_widgets = options.delete(:insert_widgets)
|
40
|
-
options.reverse_merge!(:user => Template.new(template).current_user)
|
41
|
-
|
42
|
-
|
30
|
+
options.reverse_merge!(:user => Template.new(@template).current_user)
|
31
|
+
# If the CacheBuilder was instantiated with an existing fragment, next_fragment is its child;
|
32
|
+
# otherwise it is the root fragment specified by the options provided.
|
33
|
+
next_fragment = @fragment.try(:child, options) || Fragmentary::Fragment.base_class.root(options)
|
34
|
+
builder = CacheBuilder.new(@template, next_fragment)
|
43
35
|
unless no_cache
|
44
|
-
template.cache
|
36
|
+
@template.cache next_fragment, :skip_digest => true do
|
45
37
|
yield(builder)
|
46
38
|
end
|
47
39
|
else
|
48
40
|
yield(builder)
|
49
41
|
end
|
50
|
-
template.output_buffer = WidgetParser.new(template).parse_buffer if insert_widgets
|
42
|
+
@template.output_buffer = WidgetParser.new(@template).parse_buffer if (!@fragment || insert_widgets)
|
51
43
|
end
|
52
44
|
|
45
|
+
alias cache_child cache_fragment
|
46
|
+
|
47
|
+
private
|
48
|
+
|
53
49
|
def method_missing(method, *args)
|
54
|
-
fragment.send(method, *args)
|
50
|
+
@fragment.send(method, *args)
|
55
51
|
end
|
56
52
|
|
57
53
|
end
|
@@ -61,7 +57,6 @@ module Fragmentary
|
|
61
57
|
|
62
58
|
# Just a wrapper to allow us to call a configurable current_user_method on the template
|
63
59
|
class Template
|
64
|
-
attr_reader :template
|
65
60
|
|
66
61
|
def initialize(template)
|
67
62
|
@template = template
|
@@ -69,8 +64,8 @@ module Fragmentary
|
|
69
64
|
|
70
65
|
def current_user
|
71
66
|
return nil unless methd = Fragmentary.current_user_method
|
72
|
-
if template.respond_to? methd
|
73
|
-
template.send methd
|
67
|
+
if @template.respond_to? methd
|
68
|
+
@template.send methd
|
74
69
|
else
|
75
70
|
raise NoMethodError, "The current_user_method '#{methd.to_s}' specified doesn't exist"
|
76
71
|
end
|
data/lib/fragmentary/version.rb
CHANGED
data/lib/fragmentary/widget.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Fragmentary
|
2
2
|
|
3
3
|
class Widget
|
4
|
-
attr_reader :template, :
|
4
|
+
attr_reader :template, :match
|
5
5
|
|
6
6
|
def self.inherited subclass
|
7
7
|
super if defined? super
|
@@ -18,7 +18,6 @@ module Fragmentary
|
|
18
18
|
|
19
19
|
def initialize(template, key)
|
20
20
|
@template = template
|
21
|
-
@key = key
|
22
21
|
@match = key.match(pattern)
|
23
22
|
end
|
24
23
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fragmentary
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
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: 2020-07-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|