publicious 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Justin French
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.textile ADDED
@@ -0,0 +1,82 @@
1
+ h1. Publicious
2
+
3
+ It's a plugin for plugins. It allows plugin engine[http://guides.rubyonrails.org/2_3_release_notes.html] developers to supply image, javascript and stylesheet assets in a public directory inside their plugin that "just work" without having to copy those assets over to the app's main public directory with rake tasks or installation scripts, which is stupid.
4
+
5
+ In short, it responds to "missing" asset URLs (like /stylesheets/foo.css, /images/foo.png, /javascripts/foo.js) when the web server can't find the file in the public directory, and tries to find a matching file in your plugins and pass that back to the browser through Rails.
6
+
7
+
8
+ h2. Example
9
+
10
+ Let's assume you have the following plugins installed:
11
+
12
+ * RAILS_ROOT/vendor/plugins/plugin_123
13
+ * RAILS_ROOT/vendor/gems/plugin_456
14
+
15
+ Rails would establish the following view paths:
16
+
17
+ * RAILS_ROOT/app/views
18
+ * RAILS_ROOT/vendor/plugins/plugin_123/app/views
19
+ * RAILS_ROOT/vendor/gems/plugin_456/app/views
20
+
21
+ PluginAssets takes these "view paths" and builds the same thing for "public paths" (public directories inside plugins), like this:
22
+
23
+ * RAILS_ROOT/vendor/plugins/plugin_123/public
24
+ * RAILS_ROOT/vendor/gems/plugin_456/public
25
+
26
+ Given a request for "/stylesheets/foo/bah.css", the web server would first look for "bah.css" in "RAILS_ROOT/public/foo/bah.css". If that resource doesn't exist, the request would usually be passed through to Rails, which will try to route the request to a controller action and eventually respond with a 404 (file not found).
27
+
28
+ Instead, this plugin intercepts the requests for these missing assets with a piece of middleware, searching through the installed plugins. In this case, it would search for "bah.css" in the following locations:
29
+
30
+ * RAILS_ROOT/vendor/plugins/plugin_123/public/stylesheets/foo/bah.css
31
+ * RAILS_ROOT/vendor/plugins/plugin_456/public/stylesheets/foo/bah.css
32
+
33
+ As soon as a matching file is found, it's passed back to the browser with the correct mime type. If none of the plugins have such a file, the request is passed onto Rails, which will handle the 404 like it usually does.
34
+
35
+ It works for plugins and gem plugins. It works for the following URLs:
36
+
37
+ * @/images/*@
38
+ * @/stylesheets/*@
39
+ * @/javascripts/*@
40
+
41
+ One day I'll make it configurable for other assets, but in the meantime it's easy to hack/fork.
42
+
43
+
44
+ h2. Installation
45
+
46
+ The gem is hosted on gemcutter, so *if you haven't already*, add it as a gem source:
47
+
48
+ <pre>
49
+ sudo gem sources -a http://gemcutter.org/
50
+ </pre>
51
+
52
+ Then install the Publicious gem:
53
+
54
+ <pre>
55
+ sudo gem install publicious
56
+ </pre>
57
+
58
+
59
+
60
+ h2. Caveats
61
+
62
+ Publicious leverages the view_paths array used by Rails plugins that supply their own views. Rails plugins and gems are only added to the view_paths array automatically if there's an app/views directory inside the plugin. If you're supplying public assets, chances are pretty good you're supplying views too, so that probably isn't a big deal.
63
+
64
+
65
+ h2. Isn't it inefficient?
66
+
67
+ Yes, it's probably inefficient to go looking through the filesystem and pass the asset back through a Rails request over and over, but this problem should be solved with caching and appropriate response headers, not by copying files around through rake tasks and installer scripts.
68
+
69
+
70
+ h2. There's Still Plenty To Do!
71
+
72
+ I wrote this on the train this morning as a proof of concept, so there's a long list:
73
+
74
+ * It should be a gem plugin, so that other gem plugins can list it as a dependency
75
+ * I haven't tackled caching yet (probably need to do both page caching and header response caching)
76
+
77
+ I actually want to see this in Rails core, so help me make it awesome!
78
+
79
+
80
+ h2. Etc
81
+
82
+ Publicious is Copyright (c) 2009 Justin French, released under the MIT license. Your feedback, forkings and contributions are greatly welcomed. Many thanks to Daniel Neighman (hassox) for his help converting Publicious to Rack middleware rather than a regular Rails routing and controller stack.
data/Rakefile ADDED
@@ -0,0 +1,33 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the Publicious plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.pattern = 'test/*_test.rb'
11
+ end
12
+
13
+ require 'jeweler'
14
+ Jeweler::Tasks.new do |gemspec|
15
+ gemspec.name = "publicious"
16
+ gemspec.summary = "A Rails gem plugin for plugins to serve images, javascripts and stylesheets from thier own public directory"
17
+ gemspec.description = "A Rails gem plugin for plugins to serve images, javascripts and stylesheets from thier own public directory"
18
+ gemspec.email = "justin@indent.com.au"
19
+ gemspec.homepage = "http://github.com/justinfrench/publicious"
20
+ gemspec.authors = ["Justin French", "Daniel Neighman"]
21
+ end
22
+
23
+ desc 'Generate documentation for the Publicious plugin.'
24
+ Rake::RDocTask.new(:rdoc) do |rdoc|
25
+ rdoc.rdoc_dir = 'rdoc'
26
+ rdoc.title = 'Publicious'
27
+ rdoc.options << '--line-numbers' << '--inline-source'
28
+ rdoc.rdoc_files.include('README.textile')
29
+ rdoc.rdoc_files.include('MIT-LICENSE')
30
+ rdoc.rdoc_files.include('app/**/*.rb')
31
+ rdoc.rdoc_files.include('test/*.rb')
32
+ rdoc.rdoc_files.include('rails/init.rb')
33
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,49 @@
1
+ class PubliciousMetal
2
+
3
+ def self.call(env)
4
+ request = Rack::Request.new(env)
5
+ if request.path_info =~ %r{^\/(#{allowed_dirs.join("|")})}
6
+ file_name = nil
7
+ path = nil
8
+
9
+ public_paths.detect do |pub_path|
10
+ path = pub_path
11
+ fp = File.join(pub_path, request.path_info)
12
+ file_name = fp if File.file?(fp)
13
+ end
14
+
15
+ return respond_not_found! unless file_name
16
+
17
+ # Make sure pricks aren't ../../config/database.yml ing us
18
+ respond_not_found! unless file_name.gsub(%r[^#{path}], "") == request.path_info
19
+
20
+ Rack::Response.new(
21
+ File.open(file_name),
22
+ 200,'Content-Type' => content_type_for_file(file_name)
23
+ ).finish
24
+ else
25
+ respond_not_found!
26
+ end
27
+ end
28
+
29
+ def self.respond_not_found!
30
+ Rack::Response.new("Not Found", 404).finish
31
+ end
32
+
33
+ def self.allowed_dirs
34
+ %w(stylesheets javascripts images)
35
+ end
36
+
37
+ def self.content_type_for_file(name)
38
+ file_name = File.basename(name).split(".").last.to_s
39
+ Mime::Type.lookup_by_extension(file_name).to_s
40
+ end
41
+
42
+ def self.public_paths
43
+ ActionController::Base.view_paths.map do |vp|
44
+ full_path = File.expand_path(vp.to_s, RAILS_ROOT)
45
+ full_path.sub("app/views", "public") if full_path =~ /vendor/
46
+ end.compact
47
+ end
48
+
49
+ end
data/lib/publicious.rb ADDED
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), "../rails/init")
data/rails/init.rb ADDED
@@ -0,0 +1,3 @@
1
+ Mime::Type.register "image/jpeg", :jpg
2
+ Mime::Type.register "image/gif", :gif
3
+ Mime::Type.register "image/png", :png
data/test/css_test.rb ADDED
@@ -0,0 +1,31 @@
1
+ require File.join(File.dirname(__FILE__), "test_helper")
2
+
3
+ class CssTest < Test::Unit::TestCase
4
+
5
+ include TestHelper
6
+
7
+ def setup
8
+ super
9
+
10
+ setup_plugin :my_plugin
11
+
12
+ @path = 'foo/bah.css'
13
+ @filename = "/tmp/vendor/my_plugin/public/stylesheets/#{@path}"
14
+ @filecontents = "hello from stylesheet"
15
+ FileUtils.mkdir(File.dirname(@filename))
16
+ File.open(@filename, 'w') do |file|
17
+ file << @filecontents
18
+ end
19
+
20
+ get "/stylesheets/#{@path}"
21
+ end
22
+
23
+ def test_responds_ok
24
+ assert last_response.ok?
25
+ end
26
+
27
+ def test_responds_with_css_content_type
28
+ assert_equal "text/css", last_response.content_type
29
+ end
30
+
31
+ end
@@ -0,0 +1,62 @@
1
+ require File.join(File.dirname(__FILE__), "test_helper")
2
+
3
+ class ImageTest < Test::Unit::TestCase
4
+
5
+ include TestHelper
6
+
7
+ def setup
8
+ super
9
+
10
+ setup_plugin :my_plugin
11
+
12
+ @gif_path = 'foo/bah.gif'
13
+ @jpg_path = 'foo/bah.jpg'
14
+ @png_path = 'foo/bah.png'
15
+
16
+ @gif_filename = "/tmp/vendor/my_plugin/public/images/#{@gif_path}"
17
+ @jpg_filename = "/tmp/vendor/my_plugin/public/images/#{@jpg_path}"
18
+ @png_filename = "/tmp/vendor/my_plugin/public/images/#{@png_path}"
19
+
20
+ @gif_filecontents = "hello from GIF"
21
+ @jpg_filecontents = "hello from JPG"
22
+ @png_filecontents = "hello from PNG"
23
+
24
+ FileUtils.mkdir(File.dirname(@jpg_filename))
25
+
26
+ File.open(@jpg_filename, 'w') do |file|
27
+ file << @jpg_filecontents
28
+ end
29
+
30
+ File.open(@gif_filename, 'w') do |file|
31
+ file << @gif_filecontents
32
+ end
33
+
34
+ File.open(@png_filename, 'w') do |file|
35
+ file << @png_filecontents
36
+ end
37
+ end
38
+
39
+ def test_responds_ok
40
+ get "/images/#{@gif_path}"
41
+ assert last_response.ok?
42
+
43
+ get "/images/#{@jpg_path}"
44
+ assert last_response.ok?
45
+
46
+ get "/images/#{@png_path}"
47
+ assert last_response.ok?
48
+
49
+ end
50
+
51
+ def test_responds_with_css_content_type
52
+ get "/images/#{@gif_path}"
53
+ assert_equal "image/gif", last_response.content_type
54
+
55
+ get "/images/#{@png_path}"
56
+ assert_equal "image/png", last_response.content_type
57
+
58
+ get "/images/#{@jpg_path}"
59
+ assert_equal "image/jpeg", last_response.content_type
60
+ end
61
+
62
+ end
@@ -0,0 +1,31 @@
1
+ require File.join(File.dirname(__FILE__), "test_helper")
2
+
3
+ class JavascriptTest < Test::Unit::TestCase
4
+
5
+ include TestHelper
6
+
7
+ def setup
8
+ super
9
+
10
+ setup_plugin :my_plugin
11
+
12
+ @path = 'foo/bah.js'
13
+ @filename = "/tmp/vendor/my_plugin/public/javascripts/#{@path}"
14
+ @filecontents = "hello from javascript"
15
+ FileUtils.mkdir(File.dirname(@filename))
16
+ File.open(@filename, 'w') do |file|
17
+ file << @filecontents
18
+ end
19
+
20
+ get "/javascripts/#{@path}"
21
+ end
22
+
23
+ def test_responds_ok
24
+ assert last_response.ok?
25
+ end
26
+
27
+ def test_responds_with_css_content_type
28
+ assert_equal "text/javascript", last_response.content_type
29
+ end
30
+
31
+ end
@@ -0,0 +1,54 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class MultipleMatchesTest < Test::Unit::TestCase
4
+
5
+ include TestHelper
6
+
7
+ def setup
8
+ super
9
+
10
+ setup_plugin :no_file_here
11
+ setup_plugin :my_plugin
12
+ setup_plugin :your_plugin
13
+
14
+ @path = 'foo/bah.css'
15
+
16
+ @filename_1 = "/tmp/vendor/my_plugin/public/stylesheets/#{@path}"
17
+ @filecontents_1 = "hello from my stylesheet"
18
+ FileUtils.mkdir(File.dirname(@filename_1))
19
+ File.open(@filename_1, 'w') do |file|
20
+ file << @filecontents_1
21
+ end
22
+
23
+ @filename_2 = "/tmp/vendor/your_plugin/public/stylesheets/#{@path}"
24
+ @filecontents_2 = "hello from your stylesheet"
25
+ FileUtils.mkdir(File.dirname(@filename_2))
26
+ File.open(@filename_2, 'w') do |file|
27
+ file << @filecontents_2
28
+ end
29
+
30
+ get "/stylesheets/#{@path}"
31
+ end
32
+
33
+ def test_should_respond_with_success
34
+ assert last_response.ok?
35
+ end
36
+
37
+ def test_view_paths_should_contain_three_items
38
+ assert_equal 4, ActionController::Base.view_paths.size
39
+ end
40
+
41
+ def test_public_paths_should_contain_two_items
42
+ assert_equal 3, app.public_paths.size # one less that view_paths
43
+ end
44
+
45
+ def test_public_paths_should_contain_the_plugins_public_dir
46
+ assert_equal "/tmp/vendor/my_plugin/public", app.public_paths.second
47
+ end
48
+
49
+ def test_should_respond_with_first_file_contents
50
+ assert_equal File.read(@filename_1), last_response.body
51
+ assert_equal @filecontents_1, last_response.body
52
+ end
53
+
54
+ end
@@ -0,0 +1,12 @@
1
+ require File.join(File.dirname(__FILE__), "test_helper")
2
+
3
+ class NoMatchTest < Test::Unit::TestCase
4
+
5
+ include TestHelper
6
+
7
+ def test_responds_with_not_found
8
+ get "/stylesheets/not_here.css"
9
+ assert last_response.not_found?
10
+ end
11
+
12
+ end
@@ -0,0 +1,44 @@
1
+ require File.join(File.dirname(__FILE__), "test_helper")
2
+
3
+ class SingleMatchTest < Test::Unit::TestCase
4
+
5
+ include TestHelper
6
+
7
+ def setup
8
+ super
9
+
10
+ setup_plugin :my_plugin
11
+
12
+ @path = 'foo/bah.css'
13
+ @filename = "/tmp/vendor/my_plugin/public/stylesheets/#{@path}"
14
+ @filecontents = "hello from stylesheet"
15
+ FileUtils.mkdir(File.dirname(@filename))
16
+ File.open(@filename, 'w') do |file|
17
+ file << @filecontents
18
+ end
19
+
20
+ get "/stylesheets/#{@path}"
21
+ end
22
+
23
+ def test_should_respond_with_success
24
+ assert last_response.ok?
25
+ end
26
+
27
+ def test_view_paths_should_contain_three_items
28
+ assert_equal 2, ActionController::Base.view_paths.size
29
+ end
30
+
31
+ def test_public_paths_should_contain_two_items
32
+ assert_equal 1, app.public_paths.size # one less that view_paths
33
+ end
34
+
35
+ def test_public_paths_should_contain_the_plugins_public_dir
36
+ assert_equal "/tmp/vendor/my_plugin/public", app.public_paths.first
37
+ end
38
+
39
+ def test_should_respond_with_first_file_contents
40
+ assert_equal File.read(@filename), last_response.body
41
+ assert_equal @filecontents, last_response.body
42
+ end
43
+
44
+ end
@@ -0,0 +1,62 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require "rack/test"
4
+ require "action_controller"
5
+ require File.join(File.dirname(__FILE__), "../app/metal/publicious_metal")
6
+ require File.join(File.dirname(__FILE__), "../rails/init")
7
+
8
+ RAILS_ROOT = "/tmp"
9
+
10
+ module TestHelper
11
+ include Rack::Test::Methods
12
+
13
+ def app
14
+ PubliciousMetal
15
+ end
16
+
17
+ def setup_vendor_dir
18
+ unless @vendor_dir
19
+ @vendor_dir = "/tmp/vendor"
20
+ FileUtils.mkdir(@vendor_dir)
21
+ end
22
+ end
23
+
24
+ def teardown_vendor_dir
25
+ if @vendor_dir
26
+ begin
27
+ FileUtils.rm_r(@vendor_dir)
28
+ rescue StandardError => e
29
+ puts "Error while removing #{@vendor_dir}"
30
+ end
31
+ end
32
+ end
33
+
34
+ def setup_standard_view_paths
35
+ ActionController::Base.view_paths = ['/tmp/app/views']
36
+ end
37
+
38
+ def setup
39
+ setup_vendor_dir
40
+ setup_standard_view_paths
41
+ end
42
+
43
+ def teardown
44
+ teardown_vendor_dir
45
+ setup_standard_view_paths
46
+ end
47
+
48
+ def setup_plugin(*plugin_names)
49
+ plugin_names.each do |plugin_name|
50
+ plugin_name = plugin_name.to_s
51
+
52
+ ActionController::Base.view_paths << File.join(@vendor_dir, plugin_name, 'app', 'views')
53
+
54
+ FileUtils.mkdir(File.join(@vendor_dir, plugin_name))
55
+ FileUtils.mkdir(File.join(@vendor_dir, plugin_name, 'public'))
56
+ FileUtils.mkdir(File.join(@vendor_dir, plugin_name, 'public', 'stylesheets'))
57
+ FileUtils.mkdir(File.join(@vendor_dir, plugin_name, 'public', 'images'))
58
+ FileUtils.mkdir(File.join(@vendor_dir, plugin_name, 'public', 'javascripts'))
59
+ end
60
+ end
61
+
62
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: publicious
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Justin French
8
+ - Daniel Neighman
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-11-23 00:00:00 +11:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description: A Rails gem plugin for plugins to serve images, javascripts and stylesheets from thier own public directory
18
+ email: justin@indent.com.au
19
+ executables: []
20
+
21
+ extensions: []
22
+
23
+ extra_rdoc_files:
24
+ - README.textile
25
+ files:
26
+ - MIT-LICENSE
27
+ - README.textile
28
+ - Rakefile
29
+ - VERSION
30
+ - app/metal/publicious_metal.rb
31
+ - lib/publicious.rb
32
+ - rails/init.rb
33
+ - test/css_test.rb
34
+ - test/image_test.rb
35
+ - test/javascript_test.rb
36
+ - test/multiple_match_test.rb
37
+ - test/no_match_test.rb
38
+ - test/single_match_test.rb
39
+ - test/test_helper.rb
40
+ has_rdoc: true
41
+ homepage: http://github.com/justinfrench/publicious
42
+ licenses: []
43
+
44
+ post_install_message:
45
+ rdoc_options:
46
+ - --charset=UTF-8
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ requirements: []
62
+
63
+ rubyforge_project:
64
+ rubygems_version: 1.3.5
65
+ signing_key:
66
+ specification_version: 3
67
+ summary: A Rails gem plugin for plugins to serve images, javascripts and stylesheets from thier own public directory
68
+ test_files:
69
+ - test/css_test.rb
70
+ - test/image_test.rb
71
+ - test/javascript_test.rb
72
+ - test/multiple_match_test.rb
73
+ - test/no_match_test.rb
74
+ - test/single_match_test.rb
75
+ - test/test_helper.rb