render_component 1.0.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 +4 -0
- data/README +37 -0
- data/Rakefile +22 -0
- data/init.rb +3 -0
- data/lib/components.rb +140 -0
- data/lib/render_component.rb +1 -0
- data/render_component.gemspec +19 -0
- data/test/abstract_unit.rb +7 -0
- data/test/components_test.rb +156 -0
- metadata +92 -0
data/Gemfile
ADDED
data/README
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
Components allow you to call other actions for their rendered response while executing another action. You can either delegate
|
2
|
+
the entire response rendering or you can mix a partial response in with your other content.
|
3
|
+
|
4
|
+
class WeblogController < ActionController::Base
|
5
|
+
# Performs a method and then lets hello_world output its render
|
6
|
+
def delegate_action
|
7
|
+
do_other_stuff_before_hello_world
|
8
|
+
render_component :controller => "greeter", :action => "hello_world", :params => { :person => "david" }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class GreeterController < ActionController::Base
|
13
|
+
def hello_world
|
14
|
+
render :text => "#{params[:person]} says, Hello World!"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
The same can be done in a view to do a partial rendering:
|
19
|
+
|
20
|
+
Let's see a greeting:
|
21
|
+
<%= render_component :controller => "greeter", :action => "hello_world" %>
|
22
|
+
|
23
|
+
It is also possible to specify the controller as a class constant, bypassing the inflector
|
24
|
+
code to compute the controller class at runtime:
|
25
|
+
|
26
|
+
<%= render_component :controller => GreeterController, :action => "hello_world" %>
|
27
|
+
|
28
|
+
== When to use components
|
29
|
+
|
30
|
+
Components should be used with care. They're significantly slower than simply splitting reusable parts into partials and
|
31
|
+
conceptually more complicated. Don't use components as a way of separating concerns inside a single application. Instead,
|
32
|
+
reserve components to those rare cases where you truly have reusable view and controller elements that can be employed
|
33
|
+
across many applications at once.
|
34
|
+
|
35
|
+
So to repeat: Components are a special-purpose approach that can often be replaced with better use of partials and filters.
|
36
|
+
|
37
|
+
Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
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 components plugin.'
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'lib'
|
11
|
+
t.pattern = 'test/**/*_test.rb'
|
12
|
+
t.verbose = true
|
13
|
+
end
|
14
|
+
|
15
|
+
desc 'Generate documentation for the components plugin.'
|
16
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
17
|
+
rdoc.rdoc_dir = 'rdoc'
|
18
|
+
rdoc.title = 'Components'
|
19
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
20
|
+
rdoc.rdoc_files.include('README')
|
21
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
22
|
+
end
|
data/init.rb
ADDED
data/lib/components.rb
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
module Components
|
2
|
+
def self.included(base) #:nodoc:
|
3
|
+
base.class_eval do
|
4
|
+
include InstanceMethods
|
5
|
+
extend ClassMethods
|
6
|
+
ActionView::Base.send(:include, HelperMethods)
|
7
|
+
|
8
|
+
# If this controller was instantiated to process a component request,
|
9
|
+
# +parent_controller+ points to the instantiator of this controller.
|
10
|
+
attr_accessor :parent_controller
|
11
|
+
|
12
|
+
alias_method_chain :session, :render_component
|
13
|
+
alias_method_chain :flash, :render_component
|
14
|
+
alias_method :component_request?, :parent_controller
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
# Track parent controller to identify component requests
|
20
|
+
def process_with_components(request, action, parent_controller = nil) #:nodoc:
|
21
|
+
controller = new
|
22
|
+
controller.parent_controller = parent_controller
|
23
|
+
controller.dispatch(action, request)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module HelperMethods
|
28
|
+
def render_component(options)
|
29
|
+
controller.send(:render_component_into_view, options)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module InstanceMethods
|
34
|
+
|
35
|
+
protected
|
36
|
+
# Renders the component specified as the response for the current method
|
37
|
+
def render_component(options) #:doc:
|
38
|
+
component_logging(options) do
|
39
|
+
response = component_response(options, true)[2]
|
40
|
+
if response.redirect_url
|
41
|
+
redirect_to response.redirect_url
|
42
|
+
else
|
43
|
+
render :text => response.body, :status => response.status
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns the component response as a string
|
49
|
+
def render_component_into_view(options) #:doc:
|
50
|
+
component_logging(options) do
|
51
|
+
response = component_response(options, false)[2]
|
52
|
+
if redirected = response.redirect_url
|
53
|
+
if redirected =~ %r{://}
|
54
|
+
location = URI.parse(redirected)
|
55
|
+
redirected = location.query ? "#{location.path}?#{location.query}" : location.path
|
56
|
+
end
|
57
|
+
render_component_into_view(Rails.application.routes.recognize_path(redirected, { :method => nil }))
|
58
|
+
else
|
59
|
+
response.body.html_safe
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
def flash_with_render_component(refresh = false) #:nodoc:
|
66
|
+
if @component_flash.nil? || refresh
|
67
|
+
@component_flash =
|
68
|
+
if defined?(@parent_controller)
|
69
|
+
@parent_controller.flash
|
70
|
+
else
|
71
|
+
session['flash'] ||= ActionDispatch::Flash::FlashHash.new
|
72
|
+
end
|
73
|
+
end
|
74
|
+
@component_flash
|
75
|
+
end
|
76
|
+
|
77
|
+
def session_with_render_component
|
78
|
+
#if defined?(@parent_controller)
|
79
|
+
if component_request?
|
80
|
+
@parent_controller.session
|
81
|
+
else
|
82
|
+
@_request.session
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
def component_response(options, reuse_response)
|
88
|
+
klass = component_class(options)
|
89
|
+
component_request = request_for_component(klass.controller_path, options)
|
90
|
+
# needed ???
|
91
|
+
#if reuse_response
|
92
|
+
#component_request.env["action_controller.instance"].instance_variable_set :@_response, request.env["action_controller.instance"].instance_variable_get(:@_response)
|
93
|
+
#end
|
94
|
+
klass.process_with_components(component_request, options[:action], self)
|
95
|
+
end
|
96
|
+
|
97
|
+
# determine the controller class for the component request
|
98
|
+
def component_class(options)
|
99
|
+
if controller = options[:controller]
|
100
|
+
controller.is_a?(Class) ? controller : "#{controller.to_s.camelize}Controller".constantize
|
101
|
+
else
|
102
|
+
self.class
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Create a new request object based on the current request.
|
107
|
+
# NOT IMPLEMENTED FOR RAILS 3 SO FAR: The new request inherits the session from the current request,
|
108
|
+
# bypassing any session options set for the component controller's class
|
109
|
+
def request_for_component(controller_path, options)
|
110
|
+
if options.is_a? Hash
|
111
|
+
old_style_params = options.delete(:params)
|
112
|
+
options.merge!(old_style_params) unless old_style_params.nil?
|
113
|
+
|
114
|
+
request_params = options.symbolize_keys
|
115
|
+
request_env = {}
|
116
|
+
|
117
|
+
request.env.select {|key, value| key == key.upcase || key == 'rack.input'}.each {|item| request_env[item[0]] = item[1]}
|
118
|
+
|
119
|
+
request_env['REQUEST_URI'] = url_for(options)
|
120
|
+
request_env["PATH_INFO"] = url_for(options.merge(:only_path => true))
|
121
|
+
request_env["action_dispatch.request.symbolized_path_parameters"] = request_params
|
122
|
+
request_env["action_dispatch.request.parameters"] = request_params.with_indifferent_access
|
123
|
+
ActionDispatch::Request.new(request_env)
|
124
|
+
else
|
125
|
+
request
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def component_logging(options)
|
130
|
+
if logger
|
131
|
+
logger.info "Start rendering component (#{options.inspect}): "
|
132
|
+
result = yield
|
133
|
+
logger.info "\n\nEnd of component rendering"
|
134
|
+
result
|
135
|
+
else
|
136
|
+
yield
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'components'
|
@@ -0,0 +1,19 @@
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "render_component"
|
5
|
+
s.version = "1.0.0"
|
6
|
+
s.platform = Gem::Platform::RUBY
|
7
|
+
s.authors = ["David Heinemeier Hansson"]
|
8
|
+
s.email = ["david@loudthinking.com"]
|
9
|
+
s.homepage = "https://rubygems.org/gems/render_component"
|
10
|
+
s.summary = %q{Rails plugin to render other actions for their rendered response}
|
11
|
+
s.description = %q{Components allow you to call other actions for their rendered response while executing another action}
|
12
|
+
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
|
18
|
+
s.add_dependency('railties', '~>3.0.0')
|
19
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/abstract_unit'
|
2
|
+
|
3
|
+
class CallersController < ActionController::Base
|
4
|
+
def calling_from_controller
|
5
|
+
render_component(:controller => "callees", :action => "being_called")
|
6
|
+
end
|
7
|
+
|
8
|
+
def calling_from_controller_with_params
|
9
|
+
render_component(:controller => "callees", :action => "being_called", :params => { "name" => "David" })
|
10
|
+
end
|
11
|
+
|
12
|
+
def calling_from_controller_with_session
|
13
|
+
session['name'] = 'Bernd'
|
14
|
+
render_component(:controller => "callees", :action => "being_called")
|
15
|
+
end
|
16
|
+
|
17
|
+
def calling_from_controller_with_different_status_code
|
18
|
+
render_component(:controller => "callees", :action => "blowing_up")
|
19
|
+
end
|
20
|
+
|
21
|
+
def calling_from_template
|
22
|
+
render :inline => "Ring, ring: <%= render_component(:controller => 'callees', :action => 'being_called') %>"
|
23
|
+
end
|
24
|
+
|
25
|
+
def internal_caller
|
26
|
+
render :inline => "Are you there? <%= render_component(:action => 'internal_callee') %>"
|
27
|
+
end
|
28
|
+
|
29
|
+
def internal_callee
|
30
|
+
render :text => "Yes, ma'am"
|
31
|
+
end
|
32
|
+
|
33
|
+
def set_flash
|
34
|
+
render_component(:controller => "callees", :action => "set_flash")
|
35
|
+
end
|
36
|
+
|
37
|
+
def use_flash
|
38
|
+
render_component(:controller => "callees", :action => "use_flash")
|
39
|
+
end
|
40
|
+
|
41
|
+
def calling_redirected
|
42
|
+
render_component(:controller => "callees", :action => "redirected")
|
43
|
+
end
|
44
|
+
|
45
|
+
def calling_redirected_as_string
|
46
|
+
render :inline => "<%= render_component(:controller => 'callees', :action => 'redirected') %>"
|
47
|
+
end
|
48
|
+
|
49
|
+
def rescue_action(e) raise end
|
50
|
+
end
|
51
|
+
|
52
|
+
class CalleesController < ActionController::Base
|
53
|
+
def being_called
|
54
|
+
render :text => "#{params[:name] || session[:name] || "Lady"} of the House, speaking"
|
55
|
+
end
|
56
|
+
|
57
|
+
def blowing_up
|
58
|
+
render :text => "It's game over, man, just game over, man!", :status => 500
|
59
|
+
end
|
60
|
+
|
61
|
+
def set_flash
|
62
|
+
flash[:notice] = 'My stoney baby'
|
63
|
+
render :text => 'flash is set'
|
64
|
+
end
|
65
|
+
|
66
|
+
def use_flash
|
67
|
+
render :text => flash[:notice] || 'no flash'
|
68
|
+
end
|
69
|
+
|
70
|
+
def redirected
|
71
|
+
redirect_to :controller => "callees", :action => "being_called"
|
72
|
+
end
|
73
|
+
|
74
|
+
def rescue_action(e) raise end
|
75
|
+
end
|
76
|
+
|
77
|
+
class ComponentsTest < ActionController::IntegrationTest #ActionController::TestCase
|
78
|
+
|
79
|
+
def setup
|
80
|
+
@routes.draw do
|
81
|
+
match 'callers/:action', :to => 'callers'
|
82
|
+
match 'callees/:action', :to => 'callees'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_calling_from_controller
|
87
|
+
get '/callers/calling_from_controller'
|
88
|
+
assert_equal "Lady of the House, speaking", @response.body
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_calling_from_controller_with_params
|
92
|
+
get '/callers/calling_from_controller_with_params'
|
93
|
+
assert_equal "David of the House, speaking", @response.body
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_calling_from_controller_with_different_status_code
|
97
|
+
get '/callers/calling_from_controller_with_different_status_code'
|
98
|
+
assert_equal 500, @response.response_code
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_calling_from_template
|
102
|
+
get '/callers/calling_from_template'
|
103
|
+
assert_equal "Ring, ring: Lady of the House, speaking", @response.body
|
104
|
+
end
|
105
|
+
|
106
|
+
def test_etag_is_set_for_parent_template_when_calling_from_template
|
107
|
+
get '/callers/calling_from_template'
|
108
|
+
expected_etag = etag_for("Ring, ring: Lady of the House, speaking")
|
109
|
+
assert_equal expected_etag, @response.headers['ETag']
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_internal_calling
|
113
|
+
get '/callers/internal_caller'
|
114
|
+
assert_equal "Are you there? Yes, ma'am", @response.body
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_flash
|
118
|
+
get '/callers/set_flash'
|
119
|
+
assert_equal 'My stoney baby', flash[:notice]
|
120
|
+
get '/callers/use_flash'
|
121
|
+
assert_equal 'My stoney baby', @response.body
|
122
|
+
get '/callers/use_flash'
|
123
|
+
assert_equal 'no flash', @response.body
|
124
|
+
end
|
125
|
+
|
126
|
+
def test_component_redirect_redirects
|
127
|
+
get '/callers/calling_redirected'
|
128
|
+
assert_redirected_to :controller=>"callees", :action => "being_called"
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
def test_component_multiple_redirect_redirects
|
133
|
+
test_component_redirect_redirects
|
134
|
+
test_internal_calling
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
def test_component_as_string_redirect_renders_redirected_action
|
139
|
+
get '/callers/calling_redirected_as_string'
|
140
|
+
|
141
|
+
assert_equal "Lady of the House, speaking", @response.body
|
142
|
+
end
|
143
|
+
|
144
|
+
def test_calling_from_controller_with_session
|
145
|
+
get '/callers/calling_from_controller_with_session'
|
146
|
+
assert_equal "Bernd of the House, speaking", @response.body
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
|
151
|
+
|
152
|
+
protected
|
153
|
+
def etag_for(text)
|
154
|
+
%("#{Digest::MD5.hexdigest(text)}")
|
155
|
+
end
|
156
|
+
end
|
metadata
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: render_component
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- David Heinemeier Hansson
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-11-16 00:00:00 -05:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: railties
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 7
|
30
|
+
segments:
|
31
|
+
- 3
|
32
|
+
- 0
|
33
|
+
- 0
|
34
|
+
version: 3.0.0
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
description: Components allow you to call other actions for their rendered response while executing another action
|
38
|
+
email:
|
39
|
+
- david@loudthinking.com
|
40
|
+
executables: []
|
41
|
+
|
42
|
+
extensions: []
|
43
|
+
|
44
|
+
extra_rdoc_files: []
|
45
|
+
|
46
|
+
files:
|
47
|
+
- Gemfile
|
48
|
+
- README
|
49
|
+
- Rakefile
|
50
|
+
- init.rb
|
51
|
+
- lib/components.rb
|
52
|
+
- lib/render_component.rb
|
53
|
+
- render_component.gemspec
|
54
|
+
- test/abstract_unit.rb
|
55
|
+
- test/components_test.rb
|
56
|
+
has_rdoc: true
|
57
|
+
homepage: https://rubygems.org/gems/render_component
|
58
|
+
licenses: []
|
59
|
+
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
hash: 3
|
71
|
+
segments:
|
72
|
+
- 0
|
73
|
+
version: "0"
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
hash: 3
|
80
|
+
segments:
|
81
|
+
- 0
|
82
|
+
version: "0"
|
83
|
+
requirements: []
|
84
|
+
|
85
|
+
rubyforge_project:
|
86
|
+
rubygems_version: 1.3.7
|
87
|
+
signing_key:
|
88
|
+
specification_version: 3
|
89
|
+
summary: Rails plugin to render other actions for their rendered response
|
90
|
+
test_files:
|
91
|
+
- test/abstract_unit.rb
|
92
|
+
- test/components_test.rb
|