ettu 0.0.1 → 0.0.2
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/.gitignore +1 -0
- data/.rspec +2 -0
- data/.travis.yml +6 -0
- data/README.md +71 -8
- data/Rakefile +6 -1
- data/ettu.gemspec +10 -11
- data/lib/ettu/ettu.rb +68 -0
- data/lib/ettu/fresh_when.rb +29 -0
- data/lib/ettu/railtie.rb +11 -0
- data/lib/ettu/version.rb +1 -1
- data/lib/ettu.rb +7 -4
- data/spec/ettu_spec.rb +99 -0
- data/spec/spec_helper.rb +14 -0
- metadata +34 -29
- checksums.yaml +0 -7
data/.gitignore
CHANGED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -1,6 +1,19 @@
|
|
1
1
|
# Ettu
|
2
2
|
|
3
|
-
|
3
|
+
Using Rails 4's `stale?` or `fresh_when`? Are your users seeing old view
|
4
|
+
code even after new deploys? The Rails way `fresh_when(@product)`
|
5
|
+
doesn't account for changes in your view code, you have to do it
|
6
|
+
yourself.
|
7
|
+
|
8
|
+
Ettu (loosely translated "And calculate you, too?") transparently
|
9
|
+
accounts for the current action's view code (JavaScript, CSS, templates)
|
10
|
+
using the same [asset fingerprints]
|
11
|
+
(http://guides.rubyonrails.org/asset_pipeline.html#what-is-fingerprinting-and-why-should-i-care-questionmark)
|
12
|
+
and Russian Doll [cache digests]
|
13
|
+
(https://github.com/rails/cache_digests#readme) used by Rails. It does
|
14
|
+
all of this while allowing you to use the same syntax you're already
|
15
|
+
accustomed to. So keep doing what you're doing, and let Ettu worry about
|
16
|
+
changes to your view code.
|
4
17
|
|
5
18
|
## Installation
|
6
19
|
|
@@ -12,18 +25,68 @@ And then execute:
|
|
12
25
|
|
13
26
|
$ bundle
|
14
27
|
|
15
|
-
|
28
|
+
## Usage
|
16
29
|
|
17
|
-
|
30
|
+
Rails 4 ETags can be used in the following way:
|
18
31
|
|
19
|
-
|
32
|
+
```ruby
|
33
|
+
class ProductsController < ApplicationController
|
34
|
+
def show
|
35
|
+
@product = Product.find(params[:id])
|
36
|
+
|
37
|
+
# Sugar syntax
|
38
|
+
fresh_when @product
|
39
|
+
|
40
|
+
# Hash syntax
|
41
|
+
fresh_when etag: @product, last_modified: @product.updated_at, public: true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
Ettu wants you to keep using either syntax, and let it worry about the
|
47
|
+
view code. By default, it will add in the asset fingerprints of
|
48
|
+
`application.js` and `application.css` along with the cache digest of
|
49
|
+
the current action into the calculation for the final ETag sent to the
|
50
|
+
browser.
|
51
|
+
|
52
|
+
### Configuring
|
53
|
+
|
54
|
+
Of course, you can override Ettu's behavior. Using a custom js file?
|
55
|
+
Just pass in like so:
|
56
|
+
|
57
|
+
fresh_when @product, js: 'custom.js'
|
58
|
+
|
59
|
+
The same things goes for custom css files:
|
60
|
+
|
61
|
+
fresh_when @product, css: 'custom.css'
|
62
|
+
|
63
|
+
Have several other assets you want to account for? Pass them in as an
|
64
|
+
array with the `assets` key:
|
65
|
+
|
66
|
+
fresh_when @product, assets: ['first.css', 'second.js']
|
67
|
+
|
68
|
+
You can specify a template with the `view` key:
|
69
|
+
|
70
|
+
fresh_when @product, "products/index"
|
71
|
+
|
72
|
+
You can even stop Ettu from accounting for any of them by setting the
|
73
|
+
value to `false`:
|
74
|
+
|
75
|
+
fresh_when @product, js: false, css: false, view: false
|
76
|
+
|
77
|
+
|
78
|
+
### What about Rails' default `fresh_when`?
|
20
79
|
|
21
|
-
|
80
|
+
Ettu tries its darndest to not interfere with Rails' default
|
81
|
+
implementation. Ettu makes sure to pass all the options you specify to
|
82
|
+
Rails (like the `public` option). It's even coded as a drop-in gem that
|
83
|
+
won't cause problems if it's not installed.
|
22
84
|
|
23
85
|
## Contributing
|
24
86
|
|
25
87
|
1. Fork it
|
26
88
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
-
3.
|
28
|
-
4.
|
29
|
-
5.
|
89
|
+
3. Code your feature, and add specs for it
|
90
|
+
4. Commit your changes (`git commit -am 'Add some feature'`)
|
91
|
+
5. Push to the branch (`git push origin my-new-feature`)
|
92
|
+
6. Create new Pull Request
|
data/Rakefile
CHANGED
data/ettu.gemspec
CHANGED
@@ -4,23 +4,22 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
require 'ettu/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name =
|
7
|
+
spec.name = 'ettu'
|
8
8
|
spec.version = Ettu::VERSION
|
9
|
-
spec.authors = [
|
10
|
-
spec.email = [
|
9
|
+
spec.authors = ['Justin Ridgewell']
|
10
|
+
spec.email = ['jridgewell@cloudspace.com']
|
11
11
|
spec.description = %q{Account for js, css, and views when using ETags.}
|
12
12
|
spec.summary = %q{Account for view code when using ETags.}
|
13
|
-
spec.homepage =
|
14
|
-
spec.license =
|
13
|
+
spec.homepage = 'http://github.com/cloudspace/ettu'
|
14
|
+
spec.license = 'MIT'
|
15
15
|
|
16
16
|
spec.files = `git ls-files`.split($/)
|
17
17
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
-
spec.require_paths = [
|
19
|
+
spec.require_paths = ['lib']
|
20
20
|
|
21
|
-
spec.add_dependency '
|
22
|
-
spec.
|
23
|
-
spec.add_development_dependency
|
24
|
-
spec.add_development_dependency
|
25
|
-
spec.add_development_dependency "rspec"
|
21
|
+
spec.add_dependency 'rails', '>= 4.0'
|
22
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
23
|
+
spec.add_development_dependency 'rake'
|
24
|
+
spec.add_development_dependency 'rspec'
|
26
25
|
end
|
data/lib/ettu/ettu.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
module Ettu
|
2
|
+
class Ettu
|
3
|
+
attr_reader :options
|
4
|
+
|
5
|
+
def initialize(record_or_options = nil, additional_options = {})
|
6
|
+
if record_or_options.is_a? Hash
|
7
|
+
@record = nil
|
8
|
+
@options = record_or_options
|
9
|
+
else
|
10
|
+
@record = record_or_options
|
11
|
+
@options = additional_options
|
12
|
+
end
|
13
|
+
@asset_etags = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def response_etag
|
17
|
+
@options.fetch(:etag, @record)
|
18
|
+
end
|
19
|
+
|
20
|
+
def last_modified
|
21
|
+
@options.fetch(:last_modified, @record.try(:updated_at))
|
22
|
+
end
|
23
|
+
|
24
|
+
def view_etag(view)
|
25
|
+
@view_etag ||= view_digest(view)
|
26
|
+
end
|
27
|
+
|
28
|
+
def asset_etag(asset)
|
29
|
+
@asset_etags[asset] ||= asset_digest(asset)
|
30
|
+
end
|
31
|
+
|
32
|
+
def js_etag
|
33
|
+
js = @options.fetch(:js, 'application.js')
|
34
|
+
asset_etag js
|
35
|
+
end
|
36
|
+
|
37
|
+
def css_etag
|
38
|
+
css = @options.fetch(:css, 'application.css')
|
39
|
+
asset_etag css
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# Jeremy Kemper
|
45
|
+
# https://gist.github.com/jeremy/4211803
|
46
|
+
def view_digest(view)
|
47
|
+
CacheDigests::TemplateDigestor.digest(
|
48
|
+
view,
|
49
|
+
request.format.try(:to_sym),
|
50
|
+
lookup_context
|
51
|
+
)
|
52
|
+
rescue ActionView::MissingTemplate
|
53
|
+
'' # Ignore missing templates
|
54
|
+
end
|
55
|
+
|
56
|
+
# Jeremy Kemper
|
57
|
+
# https://gist.github.com/jeremy/4211803
|
58
|
+
# Check precompiled asset manifest (production) or compute the digest (dev).
|
59
|
+
def asset_digest(asset)
|
60
|
+
return nil unless asset.present?
|
61
|
+
if manifest = Rails.application.config.assets.digests
|
62
|
+
manifest[asset]
|
63
|
+
else
|
64
|
+
Rails.application.assets[asset].digest
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Ettu
|
2
|
+
module FreshWhen
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
alias_method :old_fresh_when, :fresh_when
|
7
|
+
|
8
|
+
def fresh_when(record_or_options, additional_options = {})
|
9
|
+
ettu = Ettu.new(record_or_options, additional_options)
|
10
|
+
|
11
|
+
etags = [*ettu.response_etag]
|
12
|
+
if view = ettu.options.fetch(:view, "#{controller_name}/#{action_name}")
|
13
|
+
etags << ettu.view_etag(view)
|
14
|
+
end
|
15
|
+
if request.format.try(:html?)
|
16
|
+
etags << ettu.js_etag
|
17
|
+
etags << ettu.css_etag
|
18
|
+
end
|
19
|
+
assets = ettu.options.fetch(:assets, []).map { |asset| ettu.asset_etag(asset) }
|
20
|
+
etags.concat assets
|
21
|
+
|
22
|
+
ettu_params = {etag: etags, last_modified: ettu.last_modified}
|
23
|
+
|
24
|
+
old_fresh_when nil, ettu.options.merge(ettu_params)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/ettu/railtie.rb
ADDED
data/lib/ettu/version.rb
CHANGED
data/lib/ettu.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
|
-
require
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'active_support/core_ext/object/blank'
|
3
|
+
require 'active_support/core_ext/object/try'
|
2
4
|
|
3
|
-
|
4
|
-
|
5
|
-
|
5
|
+
require 'ettu/version'
|
6
|
+
require 'ettu/ettu'
|
7
|
+
require 'ettu/fresh_when'
|
8
|
+
require 'ettu/railtie' if defined? Rails
|
data/spec/ettu_spec.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
Record = Struct.new(:updated_at)
|
4
|
+
Nested = Struct.new(:nil) do
|
5
|
+
def [](name)
|
6
|
+
nil
|
7
|
+
end
|
8
|
+
def method_missing(name, *args, &block)
|
9
|
+
return self
|
10
|
+
end
|
11
|
+
end
|
12
|
+
module Rails
|
13
|
+
module Railtie; end
|
14
|
+
|
15
|
+
def self.application
|
16
|
+
Nested.new(nil)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe Ettu::Ettu do
|
21
|
+
let(:record) { Record.new(DateTime.now) }
|
22
|
+
let(:hash) { { etag: record, last_modified: DateTime.now, public: false, random: true } }
|
23
|
+
|
24
|
+
context 'when supplied with :js option' do
|
25
|
+
xit "returns hash[:js]'s fingerprint as #js_etag"
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'when supplied with :css option' do
|
29
|
+
xit "returns hash[:css]'s fingerprint as #css_etag"
|
30
|
+
end
|
31
|
+
|
32
|
+
xit '#view_etag'
|
33
|
+
|
34
|
+
context 'when given only a record' do
|
35
|
+
subject(:ettu) { Ettu::Ettu.new(record) }
|
36
|
+
|
37
|
+
it 'makes #options an empty hash' do
|
38
|
+
expect(ettu.options).to be_a(Hash)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'uses record as #response_etag' do
|
42
|
+
expect(ettu.response_etag).to eq(record)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "uses record's #updated_at for #last_modified" do
|
46
|
+
expect(ettu.last_modified).to eq(record.updated_at)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'when given only a hash' do
|
51
|
+
subject(:ettu) { Ettu::Ettu.new(hash) }
|
52
|
+
|
53
|
+
it 'sets #options to that hash' do
|
54
|
+
expect(ettu.options).to eq(hash)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'uses hash[:etag] as #response_etag' do
|
58
|
+
expect(ettu.response_etag).to eq(hash[:etag])
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'uses hash[:last_modified] as #last_modified' do
|
62
|
+
expect(ettu.last_modified).to eq(hash[:last_modified])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'when given a record and hash' do
|
67
|
+
let(:hash) { { public: true } }
|
68
|
+
subject(:ettu) { Ettu::Ettu.new(record, hash) }
|
69
|
+
|
70
|
+
describe '#response_etag' do
|
71
|
+
it 'uses record as #response_etag' do
|
72
|
+
expect(ettu.response_etag).to eq(record)
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'when supplied with hash with :etag' do
|
76
|
+
let(:hash) { { etag: 'hash' } }
|
77
|
+
|
78
|
+
it 'overrides #response_etag with hash[:etag]' do
|
79
|
+
expect(ettu.response_etag).to eq(hash[:etag])
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe '#last_modified' do
|
85
|
+
it "uses record's #updated_at as #last_modified" do
|
86
|
+
expect(ettu.last_modified).to eq(record.updated_at)
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'when supplied with hash with :last_modified' do
|
90
|
+
let(:hash) { { last_modified: 'now' } }
|
91
|
+
|
92
|
+
it 'overrides #last_modified with hash[:last_modified]' do
|
93
|
+
expect(ettu.last_modified).to eq(hash[:last_modified])
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
|
8
|
+
require 'ettu'
|
9
|
+
require 'date'
|
10
|
+
|
11
|
+
RSpec.configure do |config|
|
12
|
+
config.run_all_when_everything_filtered = true
|
13
|
+
config.order = 'random'
|
14
|
+
end
|
metadata
CHANGED
@@ -1,46 +1,36 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ettu
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
5
6
|
platform: ruby
|
6
7
|
authors:
|
7
8
|
- Justin Ridgewell
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date: 2013-06-
|
12
|
+
date: 2013-06-29 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
15
|
+
name: rails
|
15
16
|
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
16
18
|
requirements:
|
17
|
-
- - '>='
|
19
|
+
- - ! '>='
|
18
20
|
- !ruby/object:Gem::Version
|
19
21
|
version: '4.0'
|
20
22
|
type: :runtime
|
21
23
|
prerelease: false
|
22
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
23
26
|
requirements:
|
24
|
-
- - '>='
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '4.0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: activesupport
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - '>='
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '4.0'
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - '>='
|
27
|
+
- - ! '>='
|
39
28
|
- !ruby/object:Gem::Version
|
40
29
|
version: '4.0'
|
41
30
|
- !ruby/object:Gem::Dependency
|
42
31
|
name: bundler
|
43
32
|
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
44
34
|
requirements:
|
45
35
|
- - ~>
|
46
36
|
- !ruby/object:Gem::Version
|
@@ -48,6 +38,7 @@ dependencies:
|
|
48
38
|
type: :development
|
49
39
|
prerelease: false
|
50
40
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
51
42
|
requirements:
|
52
43
|
- - ~>
|
53
44
|
- !ruby/object:Gem::Version
|
@@ -55,29 +46,33 @@ dependencies:
|
|
55
46
|
- !ruby/object:Gem::Dependency
|
56
47
|
name: rake
|
57
48
|
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
58
50
|
requirements:
|
59
|
-
- - '>='
|
51
|
+
- - ! '>='
|
60
52
|
- !ruby/object:Gem::Version
|
61
53
|
version: '0'
|
62
54
|
type: :development
|
63
55
|
prerelease: false
|
64
56
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
65
58
|
requirements:
|
66
|
-
- - '>='
|
59
|
+
- - ! '>='
|
67
60
|
- !ruby/object:Gem::Version
|
68
61
|
version: '0'
|
69
62
|
- !ruby/object:Gem::Dependency
|
70
63
|
name: rspec
|
71
64
|
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
72
66
|
requirements:
|
73
|
-
- - '>='
|
67
|
+
- - ! '>='
|
74
68
|
- !ruby/object:Gem::Version
|
75
69
|
version: '0'
|
76
70
|
type: :development
|
77
71
|
prerelease: false
|
78
72
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
79
74
|
requirements:
|
80
|
-
- - '>='
|
75
|
+
- - ! '>='
|
81
76
|
- !ruby/object:Gem::Version
|
82
77
|
version: '0'
|
83
78
|
description: Account for js, css, and views when using ETags.
|
@@ -88,35 +83,45 @@ extensions: []
|
|
88
83
|
extra_rdoc_files: []
|
89
84
|
files:
|
90
85
|
- .gitignore
|
86
|
+
- .rspec
|
87
|
+
- .travis.yml
|
91
88
|
- Gemfile
|
92
89
|
- LICENSE.txt
|
93
90
|
- README.md
|
94
91
|
- Rakefile
|
95
92
|
- ettu.gemspec
|
96
93
|
- lib/ettu.rb
|
94
|
+
- lib/ettu/ettu.rb
|
95
|
+
- lib/ettu/fresh_when.rb
|
96
|
+
- lib/ettu/railtie.rb
|
97
97
|
- lib/ettu/version.rb
|
98
|
+
- spec/ettu_spec.rb
|
99
|
+
- spec/spec_helper.rb
|
98
100
|
homepage: http://github.com/cloudspace/ettu
|
99
101
|
licenses:
|
100
102
|
- MIT
|
101
|
-
metadata: {}
|
102
103
|
post_install_message:
|
103
104
|
rdoc_options: []
|
104
105
|
require_paths:
|
105
106
|
- lib
|
106
107
|
required_ruby_version: !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
107
109
|
requirements:
|
108
|
-
- - '>='
|
110
|
+
- - ! '>='
|
109
111
|
- !ruby/object:Gem::Version
|
110
112
|
version: '0'
|
111
113
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
114
|
+
none: false
|
112
115
|
requirements:
|
113
|
-
- - '>='
|
116
|
+
- - ! '>='
|
114
117
|
- !ruby/object:Gem::Version
|
115
118
|
version: '0'
|
116
119
|
requirements: []
|
117
120
|
rubyforge_project:
|
118
|
-
rubygems_version:
|
121
|
+
rubygems_version: 1.8.23
|
119
122
|
signing_key:
|
120
|
-
specification_version:
|
123
|
+
specification_version: 3
|
121
124
|
summary: Account for view code when using ETags.
|
122
|
-
test_files:
|
125
|
+
test_files:
|
126
|
+
- spec/ettu_spec.rb
|
127
|
+
- spec/spec_helper.rb
|
checksums.yaml
DELETED
@@ -1,7 +0,0 @@
|
|
1
|
-
---
|
2
|
-
SHA1:
|
3
|
-
metadata.gz: 24ca5006437908efcc5679dd16d5ff35e4190f26
|
4
|
-
data.tar.gz: 331355145e661214c76452e0989dc43fd983c9e4
|
5
|
-
SHA512:
|
6
|
-
metadata.gz: afce7dcd1072974e6c70317a43715d82cd9c4c1cfa1c7dc4d2ff4579109b2b89f9983e7e7274935178f6f7f15f718cfbd09f9acf8caa4b453f891c9af3cbc2ac
|
7
|
-
data.tar.gz: bdbbdf8a4f77456dcb144c59d232258e08a44b3687035b63767e5b23b7f3213f0459e44edc6521896b2f92f535fc89c72daf7573e6248eb795b2e1110035665d
|