virtual_asset_path 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +9 -0
- data/Gemfile.lock +55 -0
- data/Rakefile +27 -0
- data/Readme.md +56 -0
- data/VERSION +1 -0
- data/init.rb +1 -0
- data/lib/virtual_asset_path.rb +59 -0
- data/test/public/bar/foo.jpg +0 -0
- data/test/test_helper.rb +76 -0
- data/test/views/virtual_asset_path_test/index.erb +1 -0
- data/test/virtual_asset_path_test.rb +61 -0
- data/virtual_asset_path.gemspec +42 -0
- metadata +78 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
abstract (1.0.0)
|
5
|
+
actionpack (3.0.8)
|
6
|
+
activemodel (= 3.0.8)
|
7
|
+
activesupport (= 3.0.8)
|
8
|
+
builder (~> 2.1.2)
|
9
|
+
erubis (~> 2.6.6)
|
10
|
+
i18n (~> 0.5.0)
|
11
|
+
rack (~> 1.2.1)
|
12
|
+
rack-mount (~> 0.6.14)
|
13
|
+
rack-test (~> 0.5.7)
|
14
|
+
tzinfo (~> 0.3.23)
|
15
|
+
activemodel (3.0.8)
|
16
|
+
activesupport (= 3.0.8)
|
17
|
+
builder (~> 2.1.2)
|
18
|
+
i18n (~> 0.5.0)
|
19
|
+
activesupport (3.0.8)
|
20
|
+
builder (2.1.2)
|
21
|
+
diff-lcs (1.1.2)
|
22
|
+
erubis (2.6.6)
|
23
|
+
abstract (>= 1.0.0)
|
24
|
+
git (1.2.5)
|
25
|
+
i18n (0.5.0)
|
26
|
+
jeweler (1.6.2)
|
27
|
+
bundler (~> 1.0)
|
28
|
+
git (>= 1.2.5)
|
29
|
+
rake
|
30
|
+
rack (1.2.3)
|
31
|
+
rack-mount (0.6.14)
|
32
|
+
rack (>= 1.0.0)
|
33
|
+
rack-test (0.5.7)
|
34
|
+
rack (>= 1.0)
|
35
|
+
rake (0.9.2)
|
36
|
+
redgreen (1.2.2)
|
37
|
+
rspec (2.6.0)
|
38
|
+
rspec-core (~> 2.6.0)
|
39
|
+
rspec-expectations (~> 2.6.0)
|
40
|
+
rspec-mocks (~> 2.6.0)
|
41
|
+
rspec-core (2.6.4)
|
42
|
+
rspec-expectations (2.6.0)
|
43
|
+
diff-lcs (~> 1.1.2)
|
44
|
+
rspec-mocks (2.6.0)
|
45
|
+
tzinfo (0.3.28)
|
46
|
+
|
47
|
+
PLATFORMS
|
48
|
+
ruby
|
49
|
+
|
50
|
+
DEPENDENCIES
|
51
|
+
actionpack
|
52
|
+
jeweler
|
53
|
+
rake
|
54
|
+
redgreen
|
55
|
+
rspec (~> 2)
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
Rake::TestTask.new(:default) do |test|
|
3
|
+
test.libs << 'lib'
|
4
|
+
test.pattern = 'test/**/*_test.rb'
|
5
|
+
test.verbose = true
|
6
|
+
end
|
7
|
+
|
8
|
+
task :all do
|
9
|
+
sh "RAILS=2.3.12 bundle && bundle exec rake"
|
10
|
+
sh "RAILS=3.0.8 bundle && bundle exec rake"
|
11
|
+
# sh "RAILS=3.1.0.rc4 bundle && bundle exec rake"
|
12
|
+
end
|
13
|
+
|
14
|
+
begin
|
15
|
+
require 'jeweler'
|
16
|
+
Jeweler::Tasks.new do |gem|
|
17
|
+
gem.name = 'virtual_asset_path'
|
18
|
+
gem.summary = "Instantly expired, cacheable assets, without query params"
|
19
|
+
gem.email = "michael@grosser.it"
|
20
|
+
gem.homepage = "http://github.com/grosser/#{gem.name}"
|
21
|
+
gem.authors = ["Michael Grosser"]
|
22
|
+
end
|
23
|
+
|
24
|
+
Jeweler::GemcutterTasks.new
|
25
|
+
rescue LoadError
|
26
|
+
puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install jeweler"
|
27
|
+
end
|
data/Readme.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
Virtual asset paths for great caching.
|
2
|
+
|
3
|
+
- cacheable without query params
|
4
|
+
- cached by MD5 of content (only change when the content changes / same on all servers)
|
5
|
+
|
6
|
+
|
7
|
+
# Results
|
8
|
+
|
9
|
+
/foo/bar.jpg ... will be ...
|
10
|
+
|
11
|
+
# default + simple rewrite rules
|
12
|
+
/asset-v1afd23/foo/bar.jpg # style: folder method: MD5
|
13
|
+
|
14
|
+
# rails 3.1-ish, but complicated rewrite rules
|
15
|
+
/foo/bar-asset-v1afd23.jpg # style: suffix method: MD5
|
16
|
+
|
17
|
+
# no rewrite rule needed (great for development)
|
18
|
+
/foo/bar.jpg?1afd23 # style: query method: MD5
|
19
|
+
|
20
|
+
# different methods ...
|
21
|
+
/asset-v1308942465/foo/bar.jpg # style: folder method: mtime
|
22
|
+
/asset-vxxx/foo/bar.jpg # style: folder method: ENV['RAILS_ASSET_ID'] = 'xxx'
|
23
|
+
|
24
|
+
# configure
|
25
|
+
VirtualAssetPath.style = :folder # default
|
26
|
+
VirtualAssetPath.style = :MD5 # default
|
27
|
+
|
28
|
+
# Install
|
29
|
+
(only tested on rails 2 atm)
|
30
|
+
|
31
|
+
./script/plugin install git://github.com/grosser/virtual_asset_path.git
|
32
|
+
Or
|
33
|
+
gem install virtual_asset_path
|
34
|
+
|
35
|
+
# Nginx config
|
36
|
+
|
37
|
+
rewrite ^/asset-v[a-z0-9]+(.*) $1; # style: folder
|
38
|
+
??? # style: suffix
|
39
|
+
|
40
|
+
# Apache config
|
41
|
+
|
42
|
+
??? # style: folder
|
43
|
+
??? # style: suffix
|
44
|
+
|
45
|
+
# Tips
|
46
|
+
- [Speed] MD5 takes ~0.1 ms/file (results are cached per instance if cache_asset_timestamps is active)
|
47
|
+
- [Speed] mtime takes ~0.005 ms/file (...)
|
48
|
+
|
49
|
+
# TODO
|
50
|
+
- Rails 3.1 support -- things got much more complicated ...
|
51
|
+
|
52
|
+
Author
|
53
|
+
======
|
54
|
+
[Michael Grosser](http://grosser.it)<br/>
|
55
|
+
michael@grosser.it<br/>
|
56
|
+
Hereby placed under public domain, do what you want, just do not hold me accountable...
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'virtual_asset_path'
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module VirtualAssetPath
|
2
|
+
VERSION = File.read( File.join(File.dirname(__FILE__),'..','VERSION') ).strip
|
3
|
+
|
4
|
+
class << self
|
5
|
+
attr_accessor :method, :style
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
module ActionView::Helpers::AssetTagHelper
|
10
|
+
def rewrite_asset_path_with_virtual_folder(*args)
|
11
|
+
asset_path = rewrite_asset_path_without_virtual_folder(*args)
|
12
|
+
id = virtual_asset_folder(asset_path)
|
13
|
+
if id.present?
|
14
|
+
case VirtualAssetPath.style
|
15
|
+
when :folder, nil then "/asset-v#{id}#{asset_path}"
|
16
|
+
when :query then "#{asset_path}?#{id}"
|
17
|
+
end
|
18
|
+
else
|
19
|
+
asset_path
|
20
|
+
end
|
21
|
+
end
|
22
|
+
alias_method_chain :rewrite_asset_path, :virtual_folder
|
23
|
+
|
24
|
+
def virtual_asset_folder(source)
|
25
|
+
if asset_id = ENV["RAILS_ASSET_ID"]
|
26
|
+
asset_id
|
27
|
+
else
|
28
|
+
if @@cache_asset_timestamps && (asset_id = @@asset_timestamps_cache[source])
|
29
|
+
asset_id
|
30
|
+
else
|
31
|
+
path = File.join(ASSETS_DIR, source)
|
32
|
+
asset_id = File.exist?(path) ? virtual_asset_folder_id(path) : ''
|
33
|
+
|
34
|
+
if @@cache_asset_timestamps
|
35
|
+
@@asset_timestamps_cache_guard.synchronize do
|
36
|
+
@@asset_timestamps_cache[source] = asset_id
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
asset_id
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# overwrite to disable / use mtime ?
|
46
|
+
def virtual_asset_folder_id(path)
|
47
|
+
case VirtualAssetPath.method
|
48
|
+
when :md5,nil then Digest::MD5.file(path).hexdigest[0..6]
|
49
|
+
when :mtime then File.mtime(path).to_i.to_s
|
50
|
+
else
|
51
|
+
raise "unsupported hashing method"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# no longer needed <-> would conflict
|
56
|
+
def rails_asset_id(*args)
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
end
|
File without changes
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rspec'
|
3
|
+
require 'active_support/all'
|
4
|
+
require 'action_pack'
|
5
|
+
require 'action_controller'
|
6
|
+
require 'test/unit'
|
7
|
+
require 'redgreen'
|
8
|
+
$LOAD_PATH << 'lib'
|
9
|
+
require 'init'
|
10
|
+
|
11
|
+
class ActionView::Base
|
12
|
+
def debug(*args)
|
13
|
+
args.inspect
|
14
|
+
end
|
15
|
+
|
16
|
+
include ActionView::Helpers::AssetTagHelper
|
17
|
+
include ActionView::Helpers::TagHelper
|
18
|
+
end
|
19
|
+
|
20
|
+
silence_warnings do
|
21
|
+
ActionView::Helpers::AssetTagHelper::ASSETS_DIR = 'test/public'
|
22
|
+
end
|
23
|
+
|
24
|
+
if ActionPack::VERSION::MAJOR > 2
|
25
|
+
require 'action_dispatch/testing/test_process'
|
26
|
+
|
27
|
+
# fake checks
|
28
|
+
require 'ostruct'
|
29
|
+
module ActionController::UrlFor
|
30
|
+
def _routes
|
31
|
+
helpers = OpenStruct.new
|
32
|
+
helpers.url_helpers = Module.new
|
33
|
+
helpers
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
ROUTES = ActionDispatch::Routing::RouteSet.new
|
38
|
+
ROUTES.draw do
|
39
|
+
match ':controller(/:action(/:id(.:format)))'
|
40
|
+
end
|
41
|
+
ROUTES.finalize!
|
42
|
+
|
43
|
+
# funky patch to get @routes working, in 'setup' did not work
|
44
|
+
module ActionController::TestCase::Behavior
|
45
|
+
def process_with_routes(*args)
|
46
|
+
@routes = ROUTES
|
47
|
+
process_without_routes(*args)
|
48
|
+
end
|
49
|
+
alias_method_chain :process, :routes
|
50
|
+
end
|
51
|
+
|
52
|
+
class ActionController::Base
|
53
|
+
self.view_paths = 'test/views'
|
54
|
+
|
55
|
+
def self._routes
|
56
|
+
ROUTES
|
57
|
+
end
|
58
|
+
|
59
|
+
def url_for(*args)
|
60
|
+
'xxx'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
else
|
64
|
+
require 'action_controller/test_process'
|
65
|
+
|
66
|
+
ActionController::Routing::Routes.reload rescue nil
|
67
|
+
ActionController::Base.cache_store = :memory_store
|
68
|
+
|
69
|
+
class ActionController::Base
|
70
|
+
before_filter :set_view_paths
|
71
|
+
|
72
|
+
def set_view_paths
|
73
|
+
@template.view_paths = 'test/views'
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= image_tag params[:image], :alt => '' %>
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'test/test_helper'
|
2
|
+
|
3
|
+
class VirtualAssetPathTestController < ActionController::Base
|
4
|
+
def index
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
#noinspection RubyArgCount
|
9
|
+
class VirtualAssetPathTest < ActionController::TestCase
|
10
|
+
def setup
|
11
|
+
VirtualAssetPath.method = nil
|
12
|
+
VirtualAssetPath.style = nil
|
13
|
+
ENV["RAILS_ASSET_ID"] = nil
|
14
|
+
@controller = VirtualAssetPathTestController.new
|
15
|
+
ActionView::Base.send :class_variable_set, '@@asset_timestamps_cache', {}
|
16
|
+
ActionView::Base.send :class_variable_set, '@@cache_asset_timestamps', true
|
17
|
+
end
|
18
|
+
|
19
|
+
test "adds folder to found assets" do
|
20
|
+
get :index, :image => '/bar/foo.jpg'
|
21
|
+
@response.body.strip.should == "<img alt=\"\" src=\"/asset-vd41d8cd/bar/foo.jpg\" />"
|
22
|
+
end
|
23
|
+
|
24
|
+
test "does not add virtual path to unknown assets" do
|
25
|
+
get :index, :image => '/bar/not_found.jpg'
|
26
|
+
@response.body.strip.should == "<img alt=\"\" src=\"/bar/not_found.jpg\" />"
|
27
|
+
end
|
28
|
+
|
29
|
+
test "adds RAILS_ASSET_ID to found assets" do
|
30
|
+
ENV["RAILS_ASSET_ID"] = 'abc'
|
31
|
+
get :index, :image => '/bar/foo.jpg'
|
32
|
+
@response.body.strip.should == "<img alt=\"\" src=\"/asset-vabc/bar/foo.jpg\" />"
|
33
|
+
end
|
34
|
+
|
35
|
+
test "caches found asset ids" do
|
36
|
+
file = 'test/public/cached.jpg'
|
37
|
+
hash = '0cc175b'
|
38
|
+
|
39
|
+
begin
|
40
|
+
File.open(file,'w'){|f| f.write 'a' }
|
41
|
+
get :index, :image => '/cached.jpg'
|
42
|
+
@response.body.strip.should == "<img alt=\"\" src=\"/asset-v#{hash}/cached.jpg\" />"
|
43
|
+
|
44
|
+
File.open(file,'w'){|f| f.write 'b' }
|
45
|
+
get :index, :image => '/cached.jpg'
|
46
|
+
@response.body.strip.should == "<img alt=\"\" src=\"/asset-v#{hash}/cached.jpg\" />"
|
47
|
+
ensure
|
48
|
+
`rm #{file}`
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
test "caches with mtime" do
|
53
|
+
VirtualAssetPath.method = :mtime
|
54
|
+
get :index, :image => '/bar/foo.jpg'
|
55
|
+
@response.body.strip.should == "<img alt=\"\" src=\"/asset-v#{File.mtime('test/public/bar/foo.jpg').to_i}/bar/foo.jpg\" />"
|
56
|
+
end
|
57
|
+
|
58
|
+
test "has a version" do
|
59
|
+
VirtualAssetPath::VERSION.should =~ /^\d+\.\d+\.\d+$/
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{virtual_asset_path}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Michael Grosser"]
|
12
|
+
s.date = %q{2011-06-25}
|
13
|
+
s.email = %q{michael@grosser.it}
|
14
|
+
s.files = [
|
15
|
+
"Gemfile",
|
16
|
+
"Gemfile.lock",
|
17
|
+
"Rakefile",
|
18
|
+
"Readme.md",
|
19
|
+
"VERSION",
|
20
|
+
"init.rb",
|
21
|
+
"lib/virtual_asset_path.rb",
|
22
|
+
"test/public/bar/foo.jpg",
|
23
|
+
"test/test_helper.rb",
|
24
|
+
"test/views/virtual_asset_path_test/index.erb",
|
25
|
+
"test/virtual_asset_path_test.rb",
|
26
|
+
"virtual_asset_path.gemspec"
|
27
|
+
]
|
28
|
+
s.homepage = %q{http://github.com/grosser/virtual_asset_path}
|
29
|
+
s.require_paths = ["lib"]
|
30
|
+
s.rubygems_version = %q{1.6.2}
|
31
|
+
s.summary = %q{Instantly expired, cacheable assets, without query params}
|
32
|
+
|
33
|
+
if s.respond_to? :specification_version then
|
34
|
+
s.specification_version = 3
|
35
|
+
|
36
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
37
|
+
else
|
38
|
+
end
|
39
|
+
else
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
metadata
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: virtual_asset_path
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Michael Grosser
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-06-25 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description:
|
23
|
+
email: michael@grosser.it
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files: []
|
29
|
+
|
30
|
+
files:
|
31
|
+
- Gemfile
|
32
|
+
- Gemfile.lock
|
33
|
+
- Rakefile
|
34
|
+
- Readme.md
|
35
|
+
- VERSION
|
36
|
+
- init.rb
|
37
|
+
- lib/virtual_asset_path.rb
|
38
|
+
- test/public/bar/foo.jpg
|
39
|
+
- test/test_helper.rb
|
40
|
+
- test/views/virtual_asset_path_test/index.erb
|
41
|
+
- test/virtual_asset_path_test.rb
|
42
|
+
- virtual_asset_path.gemspec
|
43
|
+
has_rdoc: true
|
44
|
+
homepage: http://github.com/grosser/virtual_asset_path
|
45
|
+
licenses: []
|
46
|
+
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options: []
|
49
|
+
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 3
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
hash: 3
|
67
|
+
segments:
|
68
|
+
- 0
|
69
|
+
version: "0"
|
70
|
+
requirements: []
|
71
|
+
|
72
|
+
rubyforge_project:
|
73
|
+
rubygems_version: 1.6.2
|
74
|
+
signing_key:
|
75
|
+
specification_version: 3
|
76
|
+
summary: Instantly expired, cacheable assets, without query params
|
77
|
+
test_files: []
|
78
|
+
|