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 ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in foo.gemspec
4
+ gemspec
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
@@ -0,0 +1,3 @@
1
+ require 'components'
2
+ require 'action_dispatch/middleware/flash'
3
+ ActionController::Base.send :include, Components
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,7 @@
1
+ require 'rubygems'
2
+ ENV["RAILS_ENV"] = "test"
3
+ require File.expand_path('../../../../../config/environment', __FILE__)
4
+ require 'rails/test_help'
5
+
6
+ $: << File.dirname(__FILE__) + "/../lib"
7
+ require File.dirname(__FILE__) + "/../init"
@@ -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