virtual_asset_path 0.1.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 +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
|
+
|