curly-templates 0.2.1 → 0.3.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.
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source :rubygems
1
+ source 'https://rubygems.org/'
2
2
 
3
3
  gemspec
4
4
 
data/README.md CHANGED
@@ -29,7 +29,7 @@ Installing Curly is as simple as running `gem install curly-templates`. If you'r
29
29
  using Bundler to manage your dependencies, add this to your Gemfile
30
30
 
31
31
  ```ruby
32
- gem 'curly-templates', '~> 0.2.0'
32
+ gem 'curly-templates'
33
33
  ```
34
34
 
35
35
 
@@ -144,11 +144,24 @@ end
144
144
  Caching
145
145
  -------
146
146
 
147
+ Caching is handled at two levels in Curly – statically and dynamically. Static caching
148
+ concerns changes to your code and templates introduced by deploys. If you do not wish
149
+ to clear your entire cache every time you deploy, you need a way to indicate that some
150
+ view, helper, or other piece of logic has changed.
151
+
152
+ Dynamic caching concerns changes that happen on the fly, usually made by your users in
153
+ the running system. You wish to cache a view or a partial and have it expire whenever
154
+ some data is updated – usually whenever a specific record is changed.
155
+
156
+
157
+ ### Dynamic Caching
158
+
147
159
  Because of the way logic is contained in presenters, caching entire views or partials
148
- becomes exceedingly straightforward. Simply define a `#cache_key` method that returns
149
- a non-nil object, and the return value will be used to cache the template.
160
+ by the data they present becomes exceedingly straightforward. Simply define a
161
+ `#cache_key` method that returns a non-nil object, and the return value will be used to
162
+ cache the template.
150
163
 
151
- Whereas in ERB your would include the `cache` call in the template itself:
164
+ Whereas in ERB you would include the `cache` call in the template itself:
152
165
 
153
166
  ```erb
154
167
  <% cache([@post, signed_in?]) do %>
@@ -182,6 +195,63 @@ end
182
195
  ```
183
196
 
184
197
 
198
+ ### Static Caching
199
+
200
+ Static caching will only be enabled for presenters that define a non-nil `#cache_key`
201
+ method (see "Dynamic Caching.")
202
+
203
+ In order to make a deploy expire the cache for a specific view, set the version of the
204
+ view to something new, usually by incrementing by one:
205
+
206
+ ```ruby
207
+ class Posts::ShowPresenter < Curly::Presenter
208
+ version 3
209
+
210
+ def cache_key
211
+ # Some objects
212
+ end
213
+ end
214
+ ```
215
+
216
+ This will change the cache keys for all instances of that view, effectively expiring
217
+ the old cache entries.
218
+
219
+ This works well for views, or for partials that are rendered in views that themselves
220
+ are not cached. If the partial is nested within a view that _is_ cached, however, the
221
+ outer cache will not be expired. The solution is to register that the inner partial
222
+ is a dependency of the outer one such that Curly can automatically deduce that the
223
+ outer partial cache should be expired:
224
+
225
+ ```ruby
226
+ class Posts::ShowPresenter < Curly::Presenter
227
+ version 3
228
+ depends_on 'posts/comment'
229
+
230
+ def cache_key
231
+ # Some objects
232
+ end
233
+ end
234
+
235
+ class Posts::CommentPresenter < Curly::Presenter
236
+ version 4
237
+ depends_on 'posts/comment'
238
+
239
+ def cache_key
240
+ # Some objects
241
+ end
242
+ end
243
+ ```
244
+
245
+ Now, if the version of `Posts::CommentPresenter` is bumped, the cache keys for both
246
+ presenters would change. You can register any number of view paths with `depends_on`.
247
+
248
+
249
+ Thanks
250
+ ------
251
+
252
+ Thanks to [Zendesk](http://zendesk.com/) for sponsoring the work on Curly.
253
+
254
+
185
255
  Copyright and License
186
256
  ---------------------
187
257
 
@@ -4,15 +4,16 @@ Gem::Specification.new do |s|
4
4
  s.rubygems_version = '1.3.5'
5
5
 
6
6
  s.name = 'curly-templates'
7
- s.version = '0.2.1'
8
- s.date = '2013-02-09'
7
+ s.version = '0.3.0'
8
+ s.date = '2013-04-18'
9
9
 
10
10
  s.summary = "Free your views!"
11
11
  s.description = "A view layer for your Rails apps that separates structure and logic."
12
+ s.license = "apache2"
12
13
 
13
14
  s.authors = ["Daniel Schierbeck"]
14
- s.email = 'daniel.schierbeck@gmail.com.com'
15
- s.homepage = 'https://github.com/dasch/curly'
15
+ s.email = 'daniel.schierbeck@gmail.com'
16
+ s.homepage = 'https://github.com/zendesk/curly'
16
17
 
17
18
  s.require_paths = %w[lib]
18
19
 
data/lib/curly.rb CHANGED
@@ -26,7 +26,7 @@
26
26
  # See Curly::Presenter for more information on presenters.
27
27
  #
28
28
  module Curly
29
- VERSION = "0.2.1"
29
+ VERSION = "0.3.0"
30
30
 
31
31
  REFERENCE_REGEX = %r(\{\{(\w+)\}\})
32
32
 
@@ -102,11 +102,91 @@ module Curly
102
102
  self.class.available_methods.include?(method)
103
103
  end
104
104
 
105
- # A list of methods available to templates rendered with the presenter.
106
- #
107
- # Returns an Array of Symbol method names.
108
- def self.available_methods
109
- public_instance_methods - Curly::Presenter.public_instance_methods
105
+ class << self
106
+
107
+ # The name of the presenter class for a given view path.
108
+ #
109
+ # path - The String path of a view.
110
+ #
111
+ # Examples
112
+ #
113
+ # Curly::TemplateHandler.presenter_name_for_path("foo/bar")
114
+ # #=> "Foo::BarPresenter"
115
+ #
116
+ # Returns the String name of the matching presenter class.
117
+ def presenter_name_for_path(path)
118
+ "#{path}_presenter".camelize
119
+ end
120
+
121
+ def presenter_for_path(path)
122
+ presenter_name_for_path(path).constantize
123
+ end
124
+
125
+ # A list of methods available to templates rendered with the presenter.
126
+ #
127
+ # Returns an Array of Symbol method names.
128
+ def available_methods
129
+ public_instance_methods - Curly::Presenter.public_instance_methods
130
+ end
131
+
132
+ # The set of view paths that the presenter depends on.
133
+ #
134
+ # Example
135
+ #
136
+ # class Posts::ShowPresenter < Curly::Presenter
137
+ # version 2
138
+ # depends_on 'posts/comment', 'posts/comment_form'
139
+ # end
140
+ #
141
+ # Posts::ShowPresenter.dependencies
142
+ # #=> ['posts/comment', 'posts/comment_form']
143
+ #
144
+ # Returns a Set of String view paths.
145
+ def dependencies
146
+ @dependencies ||= Set.new
147
+ end
148
+
149
+ # Indicate that the presenter depends a list of other views.
150
+ #
151
+ # deps - A list of String view paths that the presenter depends on.
152
+ #
153
+ # Returns nothing.
154
+ def depends_on(*deps)
155
+ dependencies.merge(deps)
156
+ end
157
+
158
+ # Get or set the version of the presenter.
159
+ #
160
+ # version - The Integer version that should be set. If nil, no version
161
+ # is set.
162
+ #
163
+ # Returns the current Integer version of the presenter.
164
+ def version(version = nil)
165
+ @version = version if version.present?
166
+ @version || 0
167
+ end
168
+
169
+ # The cache key for the presenter class. Includes all dependencies as well.
170
+ #
171
+ # Returns a String cache key.
172
+ def cache_key
173
+ @cache_key ||= compute_cache_key
174
+ end
175
+
176
+ private
177
+
178
+ def compute_cache_key
179
+ dependency_cache_keys = dependencies.map do |path|
180
+ presenter = presenter_for_path(path)
181
+ presenter.cache_key
182
+ end
183
+
184
+ [name, version, dependency_cache_keys].flatten.join("/")
185
+ end
186
+
187
+ def presents(*args)
188
+ self.presented_names += args
189
+ end
110
190
  end
111
191
 
112
192
  private
@@ -114,10 +194,6 @@ module Curly
114
194
  class_attribute :presented_names
115
195
  self.presented_names = [].freeze
116
196
 
117
- def self.presents(*args)
118
- self.presented_names += args
119
- end
120
-
121
197
  # Delegates private method calls to the current view context.
122
198
  #
123
199
  # The view context, an instance of ActionView::Base, is set by Rails.
@@ -4,20 +4,6 @@ require 'curly'
4
4
 
5
5
  class Curly::TemplateHandler
6
6
 
7
- # The name of the presenter class for a given view path.
8
- #
9
- # path - The String path of a view.
10
- #
11
- # Examples
12
- #
13
- # Curly::TemplateHandler.presenter_name_for_path("foo/bar")
14
- # #=> "Foo::BarPresenter"
15
- #
16
- # Returns the String name of the matching presenter class.
17
- def self.presenter_name_for_path(path)
18
- "#{path}_presenter".camelize
19
- end
20
-
21
7
  # Handles a Curly template, compiling it to Ruby code. The code will be
22
8
  # evaluated in the context of an ActionView::Base instance, having access
23
9
  # to a number of variables.
@@ -26,7 +12,8 @@ class Curly::TemplateHandler
26
12
  #
27
13
  # Returns a String containing the Ruby code representing the template.
28
14
  def self.call(template)
29
- presenter_class = presenter_name_for_path(template.virtual_path)
15
+ path = template.virtual_path
16
+ presenter_class = Curly::Presenter.presenter_name_for_path(path)
30
17
 
31
18
  source = Curly.compile(template.source)
32
19
  template_digest = Digest::MD5.hexdigest(template.source)
@@ -52,11 +39,17 @@ class Curly::TemplateHandler
52
39
 
53
40
  template_digest = #{template_digest.inspect}
54
41
 
42
+ if #{presenter_class}.respond_to?(:cache_key)
43
+ presenter_key = #{presenter_class}.cache_key
44
+ else
45
+ presenter_key = nil
46
+ end
47
+
55
48
  options = {
56
49
  expires_in: presenter.cache_duration
57
50
  }
58
51
 
59
- cache([template_digest, key], options) do
52
+ cache([template_digest, key, presenter_key].compact, options) do
60
53
  safe_concat(view_function.call)
61
54
  end
62
55
 
@@ -24,4 +24,59 @@ describe Curly::Presenter do
24
24
  presenter.midget.should == "Meek Harolson"
25
25
  presenter.clown.should == "Bubbles"
26
26
  end
27
+
28
+ describe ".presenter_for_path" do
29
+ it "returns the presenter class for the given path" do
30
+ presenter = double("presenter")
31
+ stub_const("Foo::BarPresenter", presenter)
32
+
33
+ Curly::Presenter.presenter_for_path("foo/bar").should == presenter
34
+ end
35
+ end
36
+
37
+ describe ".version" do
38
+ it "sets the version of the presenter" do
39
+ presenter1 = Class.new(Curly::Presenter) do
40
+ version 42
41
+ end
42
+
43
+ presenter2 = Class.new(Curly::Presenter) do
44
+ version 1337
45
+ end
46
+
47
+ presenter1.version.should == 42
48
+ presenter2.version.should == 1337
49
+ end
50
+
51
+ it "returns 0 if no version has been set" do
52
+ presenter = Class.new(Curly::Presenter)
53
+ presenter.version.should == 0
54
+ end
55
+ end
56
+
57
+ describe ".cache_key" do
58
+ it "includes the presenter's class name and version" do
59
+ presenter = Class.new(Curly::Presenter) { version 42 }
60
+ stub_const("Foo::BarPresenter", presenter)
61
+
62
+ Foo::BarPresenter.cache_key.should == "Foo::BarPresenter/42"
63
+ end
64
+
65
+ it "includes the cache keys of presenters in the dependency list" do
66
+ presenter = Class.new(Curly::Presenter) do
67
+ version 42
68
+ depends_on 'foo/bum'
69
+ end
70
+
71
+ dependency = Class.new(Curly::Presenter) do
72
+ version 1337
73
+ end
74
+
75
+ stub_const("Foo::BarPresenter", presenter)
76
+ stub_const("Foo::BumPresenter", dependency)
77
+
78
+ cache_key = Foo::BarPresenter.cache_key
79
+ cache_key.should == "Foo::BarPresenter/42/Foo::BumPresenter/1337"
80
+ end
81
+ end
27
82
  end
data/spec/spec_helper.rb CHANGED
@@ -1 +1,2 @@
1
1
  require 'active_support/all'
2
+ require 'curly'
@@ -133,6 +133,20 @@ describe Curly::TemplateHandler do
133
133
  output.should == "FOOBAR"
134
134
  end
135
135
 
136
+ it "adds the presenter class' cache key to the instance's cache key" do
137
+ # Make sure caching is enabled
138
+ context.assigns[:cache_key] = "x"
139
+
140
+ presenter_class.stub(:cache_key) { "foo" }
141
+
142
+ output.should == "BAR"
143
+
144
+ presenter_class.stub(:cache_key) { "bar" }
145
+
146
+ context.stub(:bar) { "FOOBAR" }
147
+ output.should == "FOOBAR"
148
+ end
149
+
136
150
  it "expires the cache keys after #cache_duration" do
137
151
  context.assigns[:cache_key] = "x"
138
152
  context.assigns[:cache_duration] = 42
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: curly-templates
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-09 00:00:00.000000000 Z
12
+ date: 2013-04-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: actionpack
@@ -104,7 +104,7 @@ dependencies:
104
104
  - !ruby/object:Gem::Version
105
105
  version: '0'
106
106
  description: A view layer for your Rails apps that separates structure and logic.
107
- email: daniel.schierbeck@gmail.com.com
107
+ email: daniel.schierbeck@gmail.com
108
108
  executables: []
109
109
  extensions: []
110
110
  extra_rdoc_files: []
@@ -126,8 +126,9 @@ files:
126
126
  - spec/presenter_spec.rb
127
127
  - spec/spec_helper.rb
128
128
  - spec/template_handler_spec.rb
129
- homepage: https://github.com/dasch/curly
130
- licenses: []
129
+ homepage: https://github.com/zendesk/curly
130
+ licenses:
131
+ - apache2
131
132
  post_install_message:
132
133
  rdoc_options:
133
134
  - --charset=UTF-8
@@ -141,7 +142,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
141
142
  version: '0'
142
143
  segments:
143
144
  - 0
144
- hash: 2816901240336629131
145
+ hash: 1050384954956597031
145
146
  required_rubygems_version: !ruby/object:Gem::Requirement
146
147
  none: false
147
148
  requirements:
@@ -150,7 +151,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
150
151
  version: '0'
151
152
  requirements: []
152
153
  rubyforge_project:
153
- rubygems_version: 1.8.24
154
+ rubygems_version: 1.8.25
154
155
  signing_key:
155
156
  specification_version: 2
156
157
  summary: Free your views!