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 CHANGED
@@ -15,3 +15,4 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ bin/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --profile
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - jruby-19mode
6
+ - rbx-19mode
data/README.md CHANGED
@@ -1,6 +1,19 @@
1
1
  # Ettu
2
2
 
3
- TODO: Write a gem description
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
- Or install it yourself as:
28
+ ## Usage
16
29
 
17
- $ gem install ettu
30
+ Rails 4 ETags can be used in the following way:
18
31
 
19
- ## Usage
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
- TODO: Write usage instructions here
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. Commit your changes (`git commit -am 'Add some feature'`)
28
- 4. Push to the branch (`git push origin my-new-feature`)
29
- 5. Create new Pull Request
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
@@ -1 +1,6 @@
1
- require "bundler/gem_tasks"
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
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 = "ettu"
7
+ spec.name = 'ettu'
8
8
  spec.version = Ettu::VERSION
9
- spec.authors = ["Justin Ridgewell"]
10
- spec.email = ["jridgewell@cloudspace.com"]
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 = "http://github.com/cloudspace/ettu"
14
- spec.license = "MIT"
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 = ["lib"]
19
+ spec.require_paths = ['lib']
20
20
 
21
- spec.add_dependency 'actionpack', '>= 4.0'
22
- spec.add_dependency 'activesupport', '>= 4.0'
23
- spec.add_development_dependency "bundler", "~> 1.3"
24
- spec.add_development_dependency "rake"
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
@@ -0,0 +1,11 @@
1
+ module Ettu
2
+ class Railtie < ::Rails::Railtie
3
+ initializer 'install' do
4
+ require 'ettu'
5
+
6
+ ActiveSupport.on_load :action_controller do
7
+ ActionController::Base.send :include, Ettu::FreshWhen
8
+ end
9
+ end
10
+ end
11
+ end
data/lib/ettu/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Ettu
2
- VERSION = "0.0.1"
2
+ VERSION = '0.0.2'
3
3
  end
data/lib/ettu.rb CHANGED
@@ -1,5 +1,8 @@
1
- require "ettu/version"
1
+ require 'active_support/concern'
2
+ require 'active_support/core_ext/object/blank'
3
+ require 'active_support/core_ext/object/try'
2
4
 
3
- module Ettu
4
- # Your code goes here...
5
- end
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
@@ -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.1
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-28 00:00:00.000000000 Z
12
+ date: 2013-06-29 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
- name: actionpack
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: 2.0.0
121
+ rubygems_version: 1.8.23
119
122
  signing_key:
120
- specification_version: 4
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