locomotive_plugins 1.0.0.beta7 → 1.0.0.beta8

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/README.md CHANGED
@@ -283,10 +283,16 @@ single site, since each site will have its own database.
283
283
  ### Rack App
284
284
 
285
285
  Plugins can supply a Rack Application to be used for request handling. Do so by
286
- overriding the `rack_app` class method on the plugin class. The plugin object
287
- can also build URLs and paths for that Rack App without knowledge of where it
288
- is mounted. This is important because Locomotive will mount the rack
289
- application on a path based on the plugin's `plugin_id`. Given a string `path`
290
- which is a path relative to the root of the rack app,
291
- `plugin_object.full_path(path)` will give the full absolute path and
292
- `plugin_object.full_url(path)` will give the full URL.
286
+ overriding the `rack_app` class method on the plugin class. The Rack app will
287
+ be given some helper methods:
288
+
289
+ * `plugin_object`: retrieve the plugin object.
290
+ * `full_path(path)`: generate the full url path for `path`. The `path` variable
291
+ is a url path relative to the mountpoint of the Rack app.
292
+ * `full_url(path)`: generate the full url for `path`. The `path` variable is a
293
+ url path relative to the mountpoint of the Rack app.
294
+
295
+ The `full_path` and `full_url` helpers may be used by the Rack app to generate
296
+ full paths and urls without explicit knowledge of the Rack app's mountpoint.
297
+ This is important since Locomotive will mount the app to a path based on its
298
+ `plugin_id`.
@@ -23,7 +23,6 @@ module Locomotive
23
23
  # @param base the plugin class
24
24
  def self.included(base)
25
25
  self.add_liquid_class_methods(base)
26
- self.add_rack_app_helper_methods(base)
27
26
 
28
27
  base.class_eval do
29
28
  extend ActiveModel::Callbacks
@@ -5,86 +5,97 @@ module Locomotive
5
5
  # `rack_app` method.
6
6
  module RackAppHelpers
7
7
 
8
- # @private
9
- #
10
- # Add class methods.
11
- #
12
- # @param base the class which includes this module
13
- def self.included(base)
14
- base.extend(ClassMethods)
15
- end
8
+ # Helper methods to be added to the Rack application.
9
+ module HelperMethods
10
+ attr_accessor :plugin_object
11
+ attr_reader :env
16
12
 
17
- # @api internal
18
- module ClassMethods
19
- # Adds methods from RackAppHelpersClassMethods module.
13
+ # Set the env on the Rack app so that it can be retrieved later. This
14
+ # method will yield, and then set the env back to what it was after the
15
+ # block returns.
20
16
  #
21
- # @param base the plugin class to extend LiquidClassMethods
22
- def add_rack_app_helper_methods(base)
23
- base.extend(RackAppHelpersClassMethods)
17
+ # @param env the Rack environment
18
+ def with_env(env)
19
+ old_env = @env
20
+ @env = env
21
+ yield
22
+ ensure
23
+ @env = old_env
24
24
  end
25
- end
26
25
 
27
- # This module adds class methods for managing the mount point for the
28
- # Rack App. The mount point must be set in order to properly generate
29
- # paths and URLs.
30
- module RackAppHelpersClassMethods
31
- # Set the full URL mountpoint for the Rack app. This will be set by
32
- # Locomotive CMS. The URL must be an HTTP or HTTPS url with no query
33
- # parameters or hash fragments.
26
+ # Generate the full absolute path for the given path based on the
27
+ # mountpoint of this plugin's rack app.
34
28
  #
35
- # @param url [String] the full URL that the Rack app is mounted on
36
- def set_mountpoint(url)
37
- error = lambda do |msg|
38
- raise Error, "Invalid mountpoint: #{msg}"
39
- end
29
+ # @param path [String] the path relative to the mountpoint of the rack
30
+ # app
31
+ # @return the absolute path
32
+ def full_path(path)
33
+ [
34
+ base_uri_object.path.sub(%r{/+$}, ''),
35
+ path.sub(%r{^/+}, '')
36
+ ].join('/')
37
+ end
40
38
 
41
- uri = URI(url)
42
- unless uri.scheme =~ /^https?$/
43
- error.call('only http or https allowed')
44
- end
45
- if uri.fragment
46
- error.call('no hash fragment allowed')
47
- end
48
- if uri.query
49
- error.call('no query string allowed')
50
- end
39
+ # Generate the full URL for the given path based on the mountpoint of
40
+ # this plugin's rack app.
41
+ #
42
+ # @param path [String] the path relative to the mountpoint of the rack
43
+ # app
44
+ # @return the URL
45
+ def full_url(path)
46
+ [
47
+ base_uri_object.to_s.sub(%r{/+$}, ''),
48
+ path.sub(%r{^/+}, '')
49
+ ].join('/')
50
+ end
51
51
 
52
- @mountpoint = uri
52
+ protected
53
+
54
+ def base_uri_object
55
+ request_uri = env['REQUEST_URI']
56
+ base_path = env['SCRIPT_NAME']
57
+ base_uri = request_uri.sub(/(#{base_path}).*$/, '\1')
58
+ URI(base_uri)
53
59
  end
60
+ end
54
61
 
55
- # Get the mountpoint for this plugin class's Rack app.
62
+ # Wrapper class around the Rack application returned by the plugin class.
63
+ # Acts as middleware to ensure some setup and teardown when the app is
64
+ # called.
65
+ class RackAppWrapper
66
+ # Initialize with the Rack application to wrap.
56
67
  #
57
- # @return the mountpoint
58
- def mountpoint
59
- @mountpoint
68
+ # @param app the Rack application
69
+ def initialize(app)
70
+ @app = app
60
71
  end
61
- end
62
72
 
63
- # Generate the full absolute path for the given path based on the
64
- # mountpoint of this plugin's rack app.
65
- #
66
- # @param path [String] the path relative to the mountpoint of the rack
67
- # app
68
- # @return the absolute path
69
- def full_path(path)
70
- [ mountpoint.path.sub(%r{/+$}, ''), path.sub(%r{^/+}, '') ].join('/')
73
+ # Call the underlying Rack app with the given environment.
74
+ #
75
+ # @param env the Rack environment
76
+ def call(env)
77
+ @app.with_env(env) do
78
+ @app.call(env)
79
+ end
80
+ end
71
81
  end
72
82
 
73
- # Generate the full URL for the given path based on the mountpoint of
74
- # this plugin's rack app.
83
+ # Adds helper methods to the Rack app and returns another Rack app which
84
+ # wraps it. This method gets the Rack application ready to be called by
85
+ # Locomotive. Locomotive CMS calls this method to get the Rack app rather
86
+ # than calling `rack_app` directly.
75
87
  #
76
- # @param path [String] the path relative to the mountpoint of the rack
77
- # app
78
- # @return the URL
79
- def full_url(path)
80
- [ mountpoint.to_s.sub(%r{/+$}, ''), path.sub(%r{^/+}, '') ].join('/')
81
- end
88
+ # @return the Rack app with helper methods
89
+ def prepared_rack_app
90
+ app = self.class.rack_app
82
91
 
83
- # Get the mountpoint for this plugin's Rack app.
84
- #
85
- # @return the mountpoint
86
- def mountpoint
87
- self.class.mountpoint
92
+ # Extend helper module if needed
93
+ unless app.singleton_class.included_modules.include?(HelperMethods)
94
+ app.extend(HelperMethods)
95
+ end
96
+
97
+ app.plugin_object = self
98
+ RackAppWrapper.new(app)
88
99
  end
89
100
 
90
101
  end
data/lib/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module LocomotivePlugins
2
- VERSION = '1.0.0.beta7'
2
+ VERSION = '1.0.0.beta8'
3
3
  end
@@ -5,60 +5,96 @@ module Locomotive
5
5
  module Plugin
6
6
  describe RackAppHelpers do
7
7
 
8
- before(:all) do
9
- PluginWithRackApp.set_mountpoint('http://www.example.com/my/path')
8
+ let(:config) { {} }
9
+
10
+ let(:plugin) { PluginWithRackApp.new(config) }
11
+
12
+ let(:prepared_app) { plugin.prepared_rack_app }
13
+
14
+ let(:original_app) { plugin.class.rack_app }
15
+
16
+ it 'should add the plugin object to the Rack app' do
17
+ stub_app_call do
18
+ original_app.plugin_object.should == plugin
19
+ end
20
+
21
+ prepared_app.call(default_env)
10
22
  end
11
23
 
12
- before(:each) do
13
- @config = {}
14
- @plugin = PluginWithRackApp.new(@config)
24
+ it 'should add the Rack environment to the Rack app' do
25
+ stub_app_call do
26
+ original_app.env.should == default_env
27
+ end
28
+
29
+ prepared_app.call(default_env)
15
30
  end
16
31
 
17
- it 'should supply an absolute path based on where it is mounted' do
18
- @plugin.full_path('/plugin/path').should == '/my/path/plugin/path'
19
- @plugin.full_path('another//path').should == '/my/path/another//path'
32
+ it 'should add path and url helpers to the Rack app' do
33
+ original_app.respond_to?(:full_path).should be_true
34
+ original_app.respond_to?(:full_url).should be_true
35
+ end
36
+
37
+ it 'should not add the helper methods if they have already been added' do
38
+ NewRackAppClass = Class.new do
39
+ def method_missing(*args)
40
+ end
41
+ end
42
+
43
+ plugin = PluginWithRackApp.new(config)
44
+ rack_app = NewRackAppClass.new
45
+ plugin.class.stubs(:rack_app).returns(rack_app)
46
+
47
+ rack_app.expects(:extend).with(HelperMethods)
48
+ app = plugin.prepared_rack_app
49
+
50
+ plugin = PluginWithRackApp.new(config)
51
+ rack_app = NewRackAppClass.new
52
+ plugin.class.stubs(:rack_app).returns(rack_app)
53
+
54
+ app = plugin.prepared_rack_app
55
+ rack_app.expects(:extend).with(HelperMethods).never
56
+ app = plugin.prepared_rack_app
20
57
  end
21
58
 
22
- it 'should supply a full URL based on where it is mounted' do
23
- @plugin.full_url('/plugin/path').should ==
24
- 'http://www.example.com/my/path/plugin/path'
25
- @plugin.full_url('another//path').should ==
26
- 'http://www.example.com/my/path/another//path'
59
+ it 'should supply an absolute path' do
60
+ stub_app_call do
61
+ original_app.full_path('/plugin/path').should == '/my/path/plugin/path'
62
+ original_app.full_path('another//path').should == '/my/path/another//path'
63
+ end
64
+
65
+ prepared_app.call(default_env)
27
66
  end
28
67
 
29
- it 'should only allow URLs with proper format' do
30
- old_mountpoint = PluginWithRackApp.mountpoint
31
-
32
- bad_mountpoints = [
33
- 'my/path',
34
- 'my.server.com/my/path',
35
- 'ftp://my.server.com',
36
- 'http://my.server.com/my/path?q=value',
37
- 'http://my.server.com/my/path#fragment',
38
- 'https://my.server.com/my/path?q=value',
39
- 'https://my.server.com/my/path#fragment'
40
- ]
41
-
42
- good_mountpoints = [
43
- 'http://my.server.com/my/path',
44
- 'http://my.server.com:3000/my/path',
45
- 'https://my.server.com/my/path',
46
- 'https://my.server.com:3000/my/path'
47
- ]
48
-
49
- bad_mountpoints.each do |mountpoint|
50
- lambda do
51
- PluginWithRackApp.set_mountpoint(mountpoint)
52
- end.should raise_exception(Locomotive::Plugin::Error)
53
- PluginWithRackApp.mountpoint.should == old_mountpoint
68
+ it 'should supply a full url' do
69
+ stub_app_call do
70
+ original_app.full_url('/plugin/path').should ==
71
+ 'http://www.example.com/my/path/plugin/path'
72
+ original_app.full_url('another//path').should ==
73
+ 'http://www.example.com/my/path/another//path'
54
74
  end
55
75
 
56
- good_mountpoints.each do |mountpoint|
57
- lambda do
58
- PluginWithRackApp.set_mountpoint(mountpoint)
59
- end.should_not raise_exception
60
- PluginWithRackApp.mountpoint.to_s.should == mountpoint
76
+ prepared_app.call(default_env)
77
+ end
78
+
79
+ protected
80
+
81
+ def default_env
82
+ {
83
+ 'REQUEST_URI' => 'http://www.example.com/my/path/request/path',
84
+ 'SCRIPT_NAME' => '/my/path'
85
+ }
86
+ end
87
+
88
+ # Sets up an object which expects the method `the_block_has_been_called`
89
+ # to be invoked. This way, if you stub the app call but do not call
90
+ # `prepared_app.call`, the spec will fail.
91
+ def stub_app_call(&block)
92
+ obj = Object.new
93
+ original_app.block = Proc.new do
94
+ obj.the_block_has_been_called
95
+ block.call
61
96
  end
97
+ obj.expects(:the_block_has_been_called).at_least_once
62
98
  end
63
99
 
64
100
  end
@@ -3,7 +3,16 @@ class PluginWithRackApp
3
3
  include Locomotive::Plugin
4
4
 
5
5
  def self.rack_app
6
- Proc.new do
6
+ RackApp
7
+ end
8
+
9
+ class RackApp
10
+ class << self
11
+ attr_accessor :block
12
+ end
13
+
14
+ def self.call(env)
15
+ block.call
7
16
  [200, {'Content-Type' => 'text/html'}, []]
8
17
  end
9
18
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: locomotive_plugins
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.beta7
4
+ version: 1.0.0.beta8
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-24 00:00:00.000000000 Z
12
+ date: 2013-01-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: locomotive_liquid