curly-templates 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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!