actionpack 0.9.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (100) hide show
  1. data/CHANGELOG +604 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README +418 -0
  4. data/RUNNING_UNIT_TESTS +14 -0
  5. data/examples/.htaccess +24 -0
  6. data/examples/address_book/index.rhtml +33 -0
  7. data/examples/address_book/layout.rhtml +8 -0
  8. data/examples/address_book_controller.cgi +9 -0
  9. data/examples/address_book_controller.fcgi +6 -0
  10. data/examples/address_book_controller.rb +52 -0
  11. data/examples/address_book_controller.rbx +4 -0
  12. data/examples/benchmark.rb +52 -0
  13. data/examples/benchmark_with_ar.fcgi +89 -0
  14. data/examples/blog_controller.cgi +53 -0
  15. data/examples/debate/index.rhtml +14 -0
  16. data/examples/debate/new_topic.rhtml +22 -0
  17. data/examples/debate/topic.rhtml +32 -0
  18. data/examples/debate_controller.cgi +57 -0
  19. data/install.rb +93 -0
  20. data/lib/action_controller.rb +47 -0
  21. data/lib/action_controller/assertions/action_pack_assertions.rb +166 -0
  22. data/lib/action_controller/assertions/active_record_assertions.rb +65 -0
  23. data/lib/action_controller/base.rb +626 -0
  24. data/lib/action_controller/benchmarking.rb +49 -0
  25. data/lib/action_controller/cgi_ext/cgi_ext.rb +43 -0
  26. data/lib/action_controller/cgi_ext/cgi_methods.rb +91 -0
  27. data/lib/action_controller/cgi_process.rb +123 -0
  28. data/lib/action_controller/filters.rb +279 -0
  29. data/lib/action_controller/flash.rb +65 -0
  30. data/lib/action_controller/layout.rb +143 -0
  31. data/lib/action_controller/request.rb +92 -0
  32. data/lib/action_controller/rescue.rb +94 -0
  33. data/lib/action_controller/response.rb +15 -0
  34. data/lib/action_controller/scaffolding.rb +183 -0
  35. data/lib/action_controller/session/active_record_store.rb +72 -0
  36. data/lib/action_controller/session/drb_server.rb +9 -0
  37. data/lib/action_controller/session/drb_store.rb +31 -0
  38. data/lib/action_controller/support/class_attribute_accessors.rb +57 -0
  39. data/lib/action_controller/support/class_inheritable_attributes.rb +37 -0
  40. data/lib/action_controller/support/clean_logger.rb +10 -0
  41. data/lib/action_controller/support/cookie_performance_fix.rb +121 -0
  42. data/lib/action_controller/support/inflector.rb +70 -0
  43. data/lib/action_controller/templates/rescues/_request_and_response.rhtml +28 -0
  44. data/lib/action_controller/templates/rescues/diagnostics.rhtml +22 -0
  45. data/lib/action_controller/templates/rescues/layout.rhtml +29 -0
  46. data/lib/action_controller/templates/rescues/missing_template.rhtml +2 -0
  47. data/lib/action_controller/templates/rescues/template_error.rhtml +26 -0
  48. data/lib/action_controller/templates/rescues/unknown_action.rhtml +2 -0
  49. data/lib/action_controller/templates/scaffolds/edit.rhtml +6 -0
  50. data/lib/action_controller/templates/scaffolds/layout.rhtml +29 -0
  51. data/lib/action_controller/templates/scaffolds/list.rhtml +24 -0
  52. data/lib/action_controller/templates/scaffolds/new.rhtml +5 -0
  53. data/lib/action_controller/templates/scaffolds/show.rhtml +9 -0
  54. data/lib/action_controller/test_process.rb +194 -0
  55. data/lib/action_controller/url_rewriter.rb +153 -0
  56. data/lib/action_view.rb +40 -0
  57. data/lib/action_view/base.rb +253 -0
  58. data/lib/action_view/helpers/active_record_helper.rb +171 -0
  59. data/lib/action_view/helpers/date_helper.rb +223 -0
  60. data/lib/action_view/helpers/debug_helper.rb +17 -0
  61. data/lib/action_view/helpers/form_helper.rb +176 -0
  62. data/lib/action_view/helpers/form_options_helper.rb +169 -0
  63. data/lib/action_view/helpers/tag_helper.rb +59 -0
  64. data/lib/action_view/helpers/text_helper.rb +129 -0
  65. data/lib/action_view/helpers/url_helper.rb +72 -0
  66. data/lib/action_view/partials.rb +61 -0
  67. data/lib/action_view/template_error.rb +84 -0
  68. data/lib/action_view/vendor/builder.rb +13 -0
  69. data/lib/action_view/vendor/builder/blankslate.rb +21 -0
  70. data/lib/action_view/vendor/builder/xmlbase.rb +143 -0
  71. data/lib/action_view/vendor/builder/xmlevents.rb +63 -0
  72. data/lib/action_view/vendor/builder/xmlmarkup.rb +288 -0
  73. data/rakefile +105 -0
  74. data/test/abstract_unit.rb +9 -0
  75. data/test/controller/action_pack_assertions_test.rb +295 -0
  76. data/test/controller/active_record_assertions_test.rb +118 -0
  77. data/test/controller/cgi_test.rb +142 -0
  78. data/test/controller/cookie_test.rb +38 -0
  79. data/test/controller/filters_test.rb +159 -0
  80. data/test/controller/flash_test.rb +69 -0
  81. data/test/controller/layout_test.rb +49 -0
  82. data/test/controller/redirect_test.rb +44 -0
  83. data/test/controller/render_test.rb +169 -0
  84. data/test/controller/url_test.rb +318 -0
  85. data/test/fixtures/layouts/builder.rxml +3 -0
  86. data/test/fixtures/layouts/standard.rhtml +1 -0
  87. data/test/fixtures/test/_customer.rhtml +1 -0
  88. data/test/fixtures/test/greeting.rhtml +1 -0
  89. data/test/fixtures/test/hello.rxml +4 -0
  90. data/test/fixtures/test/hello_world.rhtml +1 -0
  91. data/test/fixtures/test/hello_xml_world.rxml +11 -0
  92. data/test/fixtures/test/list.rhtml +1 -0
  93. data/test/template/active_record_helper_test.rb +76 -0
  94. data/test/template/date_helper_test.rb +103 -0
  95. data/test/template/form_helper_test.rb +115 -0
  96. data/test/template/form_options_helper_test.rb +174 -0
  97. data/test/template/tag_helper_test.rb +18 -0
  98. data/test/template/text_helper_test.rb +62 -0
  99. data/test/template/url_helper_test.rb +35 -0
  100. metadata +154 -0
@@ -0,0 +1,65 @@
1
+ module ActionController #:nodoc:
2
+ # The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed
3
+ # to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create action
4
+ # that sets <tt>flash["notice"] = "Succesfully created"</tt> before redirecting to a display action that can then expose
5
+ # the flash to its template. Actually, that exposure is automatically done. Example:
6
+ #
7
+ # class WeblogController < ActionController::Base
8
+ # def create
9
+ # # save post
10
+ # flash["notice"] = "Succesfully created post"
11
+ # redirect_to :action => "display", :params => { "id" => post.id }
12
+ # end
13
+ #
14
+ # def display
15
+ # # doesn't need to assign the flash notice to the template, that's done automatically
16
+ # end
17
+ # end
18
+ #
19
+ # display.rhtml
20
+ # <% if @flash["notice"] %><div class="notice"><%= @flash["notice"] %></div><% end %>
21
+ #
22
+ # This example just places a string in the flash, but you can put any object in there. And of course, you can put as many
23
+ # as you like at a time too. Just remember: They'll be gone by the time the next action has been performed.
24
+ module Flash
25
+ def self.append_features(base) #:nodoc:
26
+ super
27
+ base.before_filter(:fire_flash)
28
+ base.after_filter(:clear_flash)
29
+ end
30
+
31
+ protected
32
+ # Access the contents of the flash. Use <tt>flash["notice"]</tt> to read a notice you put there or
33
+ # <tt>flash["notice"] = "hello"</tt> to put a new one.
34
+ def flash #:doc:
35
+ if @session["flash"].nil?
36
+ @session["flash"] = {}
37
+ @session["flashes"] ||= 0
38
+ end
39
+ @session["flash"]
40
+ end
41
+
42
+ # Can be called by any action that would like to keep the current content of the flash around for one more action.
43
+ def keep_flash #:doc:
44
+ @session["flashes"] = 0
45
+ end
46
+
47
+ private
48
+ # Records that the contents of @session["flash"] was flashed to the action
49
+ def fire_flash
50
+ if @session["flash"]
51
+ @session["flashes"] += 1 unless @session["flash"].empty?
52
+ @assigns["flash"] = @session["flash"]
53
+ else
54
+ @assigns["flash"] = {}
55
+ end
56
+ end
57
+
58
+ def clear_flash
59
+ if @session["flash"] && (@session["flashes"].nil? || @session["flashes"] >= 1)
60
+ @session["flash"] = {}
61
+ @session["flashes"] = 0
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,143 @@
1
+ module ActionController #:nodoc:
2
+ module Layout #:nodoc:
3
+ def self.append_features(base)
4
+ super
5
+ base.extend(ClassMethods)
6
+ base.class_eval do
7
+ alias_method :render_without_layout, :render
8
+ alias_method :render, :render_with_layout
9
+ end
10
+ end
11
+
12
+ # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in
13
+ # repeated setups. The inclusion pattern has pages that look like this:
14
+ #
15
+ # <%= render "shared/header" %>
16
+ # Hello World
17
+ # <%= render "shared/footer" %>
18
+ #
19
+ # This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose
20
+ # and if you ever want to change the structure of these two includes, you'll have to change all the templates.
21
+ #
22
+ # With layouts, you can flip it around and have the common structure know where to insert changing content. This means
23
+ # that the header and footer is only mentioned in one place, like this:
24
+ #
25
+ # <!-- The header part of this layout -->
26
+ # <%= @content_for_layout %>
27
+ # <!-- The footer part of this layout -->
28
+ #
29
+ # And then you have content pages that look like this:
30
+ #
31
+ # hello world
32
+ #
33
+ # Not a word about common structures. At rendering time, the content page is computed and then inserted in the layout,
34
+ # like this:
35
+ #
36
+ # <!-- The header part of this layout -->
37
+ # hello world
38
+ # <!-- The footer part of this layout -->
39
+ #
40
+ # == Accessing shared variables
41
+ #
42
+ # Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with
43
+ # references that won't materialize before rendering time:
44
+ #
45
+ # <h1><%= @page_title %></h1>
46
+ # <%= @content_for_layout %>
47
+ #
48
+ # ...and content pages that fulfill these references _at_ rendering time:
49
+ #
50
+ # <% @page_title = "Welcome" %>
51
+ # Off-world colonies offers you a chance to start a new life
52
+ #
53
+ # The result after rendering is:
54
+ #
55
+ # <h1>Welcome</h1>
56
+ # Off-world colonies offers you a chance to start a new life
57
+ #
58
+ # == Inheritance for layouts
59
+ #
60
+ # Layouts are shared downwards in the inheritance hierarchy, but not upwards. Examples:
61
+ #
62
+ # class BankController < ActionController::Base
63
+ # layout "layouts/bank_standard"
64
+ #
65
+ # class InformationController < BankController
66
+ #
67
+ # class VaultController < BankController
68
+ # layout :access_level_layout
69
+ #
70
+ # class EmployeeController < BankController
71
+ # layout nil
72
+ #
73
+ # The InformationController uses "layouts/bank_standard" inherited from the BankController, the VaultController overwrites
74
+ # and picks the layout dynamically, and the EmployeeController doesn't want to use a layout at all.
75
+ #
76
+ # == Types of layouts
77
+ #
78
+ # Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes
79
+ # you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can
80
+ # be done either by specifying a method reference as a symbol or using an inline method (as a proc).
81
+ #
82
+ # The method reference is the preferred approach to variable layouts and is used like this:
83
+ #
84
+ # class WeblogController < ActionController::Base
85
+ # layout :writers_and_readers
86
+ #
87
+ # def index
88
+ # # fetching posts
89
+ # end
90
+ #
91
+ # private
92
+ # def writers_and_readers
93
+ # logged_in? ? "writer_layout" : "reader_layout"
94
+ # end
95
+ #
96
+ # Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing
97
+ # is logged in or not.
98
+ #
99
+ # If you want to use an inline method, such as a proc, do something like this:
100
+ #
101
+ # class WeblogController < ActionController::Base
102
+ # layout proc{ |controller| controller.logged_in? ? "writer_layout" : "reader_layout" }
103
+ #
104
+ # Of course, the most common way of specifying a layout is still just as a plain template path:
105
+ #
106
+ # class WeblogController < ActionController::Base
107
+ # layout "layouts/weblog_standard"
108
+ module ClassMethods
109
+ # If a layout is specified, all actions rendered through render and render_action will have their result assigned
110
+ # to <tt>@content_for_layout</tt>, which can then be used by the layout to insert their contents with
111
+ # <tt><%= @content_for_layout %></tt>. This layout can itself depend on instance variables assigned during action
112
+ # performance and have access to them as any normal template would.
113
+ def layout(template_name)
114
+ write_inheritable_attribute "layout", template_name
115
+ end
116
+ end
117
+
118
+ # Returns the name of the active layout. If the layout was specified as a method reference (through a symbol), this method
119
+ # is called and the return value is used. Likewise if the layout was specified as an inline method (through a proc or method
120
+ # object). If the layout was defined without a directory, layouts is assumed. So <tt>layout "weblog/standard"</tt> will return
121
+ # weblog/standard, but <tt>layout "standard"</tt> will return layouts/standard.
122
+ def active_layout(passed_layout = nil)
123
+ layout = passed_layout || self.class.read_inheritable_attribute("layout")
124
+ active_layout = case layout
125
+ when Symbol then send(layout)
126
+ when Proc then layout.call(self)
127
+ when String then layout
128
+ end
129
+ active_layout.include?("/") ? active_layout : "layouts/#{active_layout}" if active_layout
130
+ end
131
+
132
+ def render_with_layout(template_name = "#{controller_name}/#{action_name}", status = nil, layout = nil) #:nodoc:
133
+ if layout || active_layout
134
+ add_variables_to_assigns
135
+ logger.info("Rendering #{template_name} within #{layout || active_layout}") unless logger.nil?
136
+ @content_for_layout = @template.render_file(template_name, true)
137
+ render_without_layout(layout || self.active_layout, status)
138
+ else
139
+ render_without_layout(template_name, status)
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,92 @@
1
+ module ActionController
2
+ class AbstractRequest #:nodoc:
3
+ # Returns both GET and POST parameters in a single hash.
4
+ def parameters
5
+ @parameters ||= request_parameters.update(query_parameters)
6
+ end
7
+
8
+ def method
9
+ env['REQUEST_METHOD'].downcase.intern
10
+ end
11
+
12
+ def get?
13
+ method == :get
14
+ end
15
+
16
+ def post?
17
+ method == :post
18
+ end
19
+
20
+ def put?
21
+ method == :put
22
+ end
23
+
24
+ def delete?
25
+ method == :delete
26
+ end
27
+
28
+ # Determine originating IP address. REMOTE_ADDR is the standard
29
+ # but will fail if the user is behind a proxy. HTTP_CLIENT_IP and/or
30
+ # HTTP_X_FORWARDED_FOR are set by proxies so check for these before
31
+ # falling back to REMOTE_ADDR. HTTP_X_FORWARDED_FOR may be a comma-
32
+ # delimited list in the case of multiple chained proxies; the first is
33
+ # the originating IP.
34
+ def remote_ip
35
+ if env['HTTP_CLIENT_IP']
36
+ env['HTTP_CLIENT_IP']
37
+ elsif env['HTTP_X_FORWARDED_FOR']
38
+ env['HTTP_X_FORWARDED_FOR'].split(',').reject { |ip|
39
+ ip =~ /^unknown$|^(10|172\.16|192\.168)\./i
40
+ }.first.strip
41
+ else
42
+ env['REMOTE_ADDR']
43
+ end
44
+ end
45
+
46
+ def request_uri
47
+ env["REQUEST_URI"]
48
+ end
49
+
50
+ def protocol
51
+ port == 443 ? "https://" : "http://"
52
+ end
53
+
54
+ def path
55
+ request_uri ? request_uri.split("?").first : ""
56
+ end
57
+
58
+ def port
59
+ env["SERVER_PORT"].to_i
60
+ end
61
+
62
+ def host_with_port
63
+ if (protocol == "http://" && port == 80) || (protocol == "https://" && port == 443)
64
+ host
65
+ else
66
+ host + ":#{port}"
67
+ end
68
+ end
69
+
70
+ # Must be implemented in the concrete request
71
+ def query_parameters
72
+ end
73
+
74
+ def request_parameters
75
+ end
76
+
77
+ def env
78
+ end
79
+
80
+ def host
81
+ end
82
+
83
+ def cookies
84
+ end
85
+
86
+ def session
87
+ end
88
+
89
+ def reset_session
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,94 @@
1
+ module ActionController #:nodoc:
2
+ # Actions that fail to perform as expected throw exceptions. These exceptions can either be rescued for the public view
3
+ # (with a nice user-friendly explanation) or for the developers view (with tons of debugging information). The developers view
4
+ # is already implemented by the Action Controller, but the public view should be tailored to your specific application. So too
5
+ # could the decision on whether something is a public or a developer request.
6
+ #
7
+ # You can tailor the rescuing behavior and appearance by overwriting the following two stub methods.
8
+ module Rescue
9
+ def self.append_features(base) #:nodoc:
10
+ super
11
+ base.class_eval do
12
+ alias_method :perform_action_without_rescue, :perform_action
13
+ alias_method :perform_action, :perform_action_with_rescue
14
+ end
15
+ end
16
+
17
+ protected
18
+ # Exception handler called when the performance of an action raises an exception.
19
+ def rescue_action(exception)
20
+ log_error(exception) unless logger.nil?
21
+
22
+ if consider_all_requests_local || local_request?
23
+ rescue_action_locally(exception)
24
+ else
25
+ rescue_action_in_public(exception)
26
+ end
27
+ end
28
+
29
+ # Overwrite to implement custom logging of errors. By default logs as fatal.
30
+ def log_error(exception) #:doc:
31
+ if ActionView::TemplateError === exception
32
+ logger.fatal(exception.to_s)
33
+ else
34
+ logger.fatal(
35
+ "\n\n#{exception.class} (#{exception.message}):\n " +
36
+ clean_backtrace(exception).join("\n ") +
37
+ "\n\n"
38
+ )
39
+ end
40
+ end
41
+
42
+ # Overwrite to implement public exception handling (for requests answering false to <tt>local_request?</tt>).
43
+ def rescue_action_in_public(exception) #:doc:
44
+ render_text "<html><body><h1>Application error (Rails)</h1></body></html>"
45
+ end
46
+
47
+ # Overwrite to expand the meaning of a local request in order to show local rescues on other occurances than
48
+ # the remote IP being 127.0.0.1. For example, this could include the IP of the developer machine when debugging
49
+ # remotely.
50
+ def local_request? #:doc:
51
+ @request.remote_addr == "127.0.0.1"
52
+ end
53
+
54
+ # Renders a detailed diagnostics screen on action exceptions.
55
+ def rescue_action_locally(exception)
56
+ @exception = exception
57
+ @rescues_path = File.dirname(__FILE__) + "/templates/rescues/"
58
+ add_variables_to_assigns
59
+ @contents = @template.render_file(template_path_for_local_rescue(exception), false)
60
+
61
+ @headers["Content-Type"] = "text/html"
62
+ render_file(rescues_path("layout"), "500 Internal Error")
63
+ end
64
+
65
+ private
66
+ def perform_action_with_rescue #:nodoc:
67
+ begin
68
+ perform_action_without_rescue
69
+ rescue Exception => exception
70
+ rescue_action(exception)
71
+ end
72
+ end
73
+
74
+ def rescues_path(template_name)
75
+ File.dirname(__FILE__) + "/templates/rescues/#{template_name}.rhtml"
76
+ end
77
+
78
+ def template_path_for_local_rescue(exception)
79
+ rescues_path(
80
+ case exception
81
+ when MissingTemplate then "missing_template"
82
+ when UnknownAction then "unknown_action"
83
+ when ActionView::TemplateError then "template_error"
84
+ else "diagnostics"
85
+ end
86
+ )
87
+ end
88
+
89
+ def clean_backtrace(exception)
90
+ base_dir = File.expand_path(File.dirname(__FILE__) + "/../../../../")
91
+ exception.backtrace.collect { |line| line.gsub(base_dir, "").gsub("/public/../config/environments/../../", "").gsub("/public/../", "") }
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,15 @@
1
+ module ActionController
2
+ class AbstractResponse #:nodoc:
3
+ DEFAULT_HEADERS = { "Cache-Control" => "no-cache", "cookie" => [] }
4
+ attr_accessor :body, :headers, :session, :cookies, :assigns, :template, :redirected_to, :redirected_to_method_params
5
+
6
+ def initialize
7
+ @body, @headers, @session, @assigns = "", DEFAULT_HEADERS.dup, [], []
8
+ end
9
+
10
+ def redirect(to_url)
11
+ @headers["Status"] = "302 Moved"
12
+ @headers["location"] = to_url
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,183 @@
1
+ module ActionController
2
+ module Scaffolding # :nodoc:
3
+ def self.append_features(base)
4
+ super
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ # Scaffolding is a way to quickly put an Active Record class online by providing a series of standardized actions
9
+ # for listing, showing, creating, updating, and destroying objects of the class. These standardized actions come
10
+ # with both controller logic and default templates that through introspection already know which fields to display
11
+ # and which input types to use. Example:
12
+ #
13
+ # class WeblogController < ActionController::Base
14
+ # scaffold :entry
15
+ # end
16
+ #
17
+ # This tiny piece of code will add all of the following methods to the controller:
18
+ #
19
+ # class WeblogController < ActionController::Base
20
+ # def index
21
+ # list
22
+ # end
23
+ #
24
+ # def list
25
+ # @entries = Entry.find_all
26
+ # render_scaffold "list"
27
+ # end
28
+ #
29
+ # def show
30
+ # @entry = Entry.find(@params["id"])
31
+ # render_scaffold
32
+ # end
33
+ #
34
+ # def destroy
35
+ # Entry.find(@params["id"]).destroy
36
+ # redirect_to :action => "list"
37
+ # end
38
+ #
39
+ # def new
40
+ # @entry = Entry.new
41
+ # render_scaffold
42
+ # end
43
+ #
44
+ # def create
45
+ # @entry = Entry.new(@params["entry"])
46
+ # if @entry.save
47
+ # flash["notice"] = "Entry was succesfully created"
48
+ # redirect_to :action => "list"
49
+ # else
50
+ # render "entry/new"
51
+ # end
52
+ # end
53
+ #
54
+ # def edit
55
+ # @entry = Entry.find(@params["id"])
56
+ # render_scaffold
57
+ # end
58
+ #
59
+ # def update
60
+ # @entry = Entry.find(@params["entry"]["id"])
61
+ # @entry.attributes = @params["entry"]
62
+ #
63
+ # if @entry.save
64
+ # flash["notice"] = "Entry was succesfully updated"
65
+ # redirect_to :action => "show/" + @entry.id.to_s
66
+ # else
67
+ # render "entry/edit"
68
+ # end
69
+ # end
70
+ # end
71
+ #
72
+ # The <tt>render_scaffold</tt> method will first check to see if you've made your own template (like "weblog/show.rhtml" for
73
+ # the show action) and if not, then render the generic template for that action. This gives you the possibility of using the
74
+ # scaffold while you're building your specific application. Start out with a totally generic setup, then replace one template
75
+ # and one action at a time while relying on the rest of the scaffolded templates and actions.
76
+ module ClassMethods
77
+ # Adds a swath of generic CRUD actions to the controller. The +model_id+ is automatically converted into a class name unless
78
+ # one is specifically provide through <tt>options[:class_name]</tt>. So <tt>scaffold :post</tt> would use Post as the class
79
+ # and @post/@posts for the instance variables.
80
+ #
81
+ # It's possible to use more than one scaffold in a single controller by specifying <tt>options[:suffix] = true</tt>. This will
82
+ # make <tt>scaffold :post, :suffix => true</tt> use method names like list_post, show_post, and create_post
83
+ # instead of just list, show, and post. If suffix is used, then no index method is added.
84
+ def scaffold(model_id, options = {})
85
+ validate_options([ :class_name, :suffix ], options.keys)
86
+
87
+ require "#{model_id.id2name}" rescue logger.warn "Couldn't auto-require #{model_id.id2name}.rb" unless logger.nil?
88
+
89
+ singular_name = model_id.id2name
90
+ class_name = options[:class_name] || Inflector.camelize(singular_name)
91
+ plural_name = Inflector.pluralize(singular_name)
92
+ suffix = options[:suffix] ? "_#{singular_name}" : ""
93
+
94
+ unless options[:suffix]
95
+ module_eval <<-"end_eval", __FILE__, __LINE__
96
+ def index
97
+ list
98
+ end
99
+ end_eval
100
+ end
101
+
102
+ module_eval <<-"end_eval", __FILE__, __LINE__
103
+ def list#{suffix}
104
+ @#{plural_name} = #{class_name}.find_all
105
+ render#{suffix}_scaffold "list#{suffix}"
106
+ end
107
+
108
+ def show#{suffix}
109
+ @#{singular_name} = #{class_name}.find(@params["id"])
110
+ render#{suffix}_scaffold
111
+ end
112
+
113
+ def destroy#{suffix}
114
+ #{class_name}.find(@params["id"]).destroy
115
+ redirect_to :action => "list#{suffix}"
116
+ end
117
+
118
+ def new#{suffix}
119
+ @#{singular_name} = #{class_name}.new
120
+ render#{suffix}_scaffold
121
+ end
122
+
123
+ def create#{suffix}
124
+ @#{singular_name} = #{class_name}.new(@params["#{singular_name}"])
125
+ if @#{singular_name}.save
126
+ flash["notice"] = "#{class_name} was succesfully created"
127
+ redirect_to :action => "list#{suffix}"
128
+ else
129
+ render "#{singular_name}/new#{suffix}"
130
+ end
131
+ end
132
+
133
+ def edit#{suffix}
134
+ @#{singular_name} = #{class_name}.find(@params["id"])
135
+ render#{suffix}_scaffold
136
+ end
137
+
138
+ def update#{suffix}
139
+ @#{singular_name} = #{class_name}.find(@params["#{singular_name}"]["id"])
140
+ @#{singular_name}.attributes = @params["#{singular_name}"]
141
+
142
+ if @#{singular_name}.save
143
+ flash["notice"] = "#{class_name} was succesfully updated"
144
+ redirect_to :action => "show#{suffix}/" + @#{singular_name}.id.to_s
145
+ else
146
+ render "#{singular_name}/edit#{suffix}"
147
+ end
148
+ end
149
+
150
+ private
151
+ def render#{suffix}_scaffold(action = caller_method_name(caller))
152
+ if template_exists?("\#{controller_name}/\#{action}")
153
+ render_action(action)
154
+ else
155
+ @scaffold_class = #{class_name}
156
+ @scaffold_singular_name, @scaffold_plural_name = "#{singular_name}", "#{plural_name}"
157
+ @scaffold_suffix = "#{suffix}"
158
+ add_instance_variables_to_assigns
159
+
160
+ @content_for_layout = @template.render_file(scaffold_path(action.sub(/#{suffix}$/, "")), false)
161
+ self.active_layout ? render_file(self.active_layout, "200 OK", true) : render_file(scaffold_path("layout"))
162
+ end
163
+ end
164
+
165
+ def scaffold_path(template_name)
166
+ File.dirname(__FILE__) + "/templates/scaffolds/" + template_name + ".rhtml"
167
+ end
168
+
169
+ def caller_method_name(caller)
170
+ caller.first.scan(/`(.*)'/).first.first # ' ruby-mode
171
+ end
172
+ end_eval
173
+ end
174
+
175
+ private
176
+ # Raises an exception if an invalid option has been specified to prevent misspellings from slipping through
177
+ def validate_options(valid_option_keys, supplied_option_keys)
178
+ unknown_option_keys = supplied_option_keys - valid_option_keys
179
+ raise(ActionController::ActionControllerError, "Unknown options: #{unknown_option_keys}") unless unknown_option_keys.empty?
180
+ end
181
+ end
182
+ end
183
+ end