actionpack 1.9.1 → 1.10.1

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 (123) hide show
  1. data/CHANGELOG +237 -0
  2. data/README +12 -12
  3. data/lib/action_controller.rb +17 -12
  4. data/lib/action_controller/assertions.rb +119 -67
  5. data/lib/action_controller/base.rb +184 -102
  6. data/lib/action_controller/benchmarking.rb +35 -6
  7. data/lib/action_controller/caching.rb +115 -58
  8. data/lib/action_controller/cgi_ext/cgi_methods.rb +54 -21
  9. data/lib/action_controller/cgi_ext/cookie_performance_fix.rb +39 -35
  10. data/lib/action_controller/cgi_ext/raw_post_data_fix.rb +34 -21
  11. data/lib/action_controller/cgi_process.rb +23 -20
  12. data/lib/action_controller/components.rb +11 -2
  13. data/lib/action_controller/dependencies.rb +0 -5
  14. data/lib/action_controller/deprecated_redirects.rb +17 -0
  15. data/lib/action_controller/filters.rb +13 -9
  16. data/lib/action_controller/flash.rb +7 -7
  17. data/lib/action_controller/helpers.rb +1 -14
  18. data/lib/action_controller/layout.rb +40 -29
  19. data/lib/action_controller/macros/auto_complete.rb +52 -0
  20. data/lib/action_controller/macros/in_place_editing.rb +32 -0
  21. data/lib/action_controller/pagination.rb +44 -28
  22. data/lib/action_controller/request.rb +54 -40
  23. data/lib/action_controller/rescue.rb +8 -6
  24. data/lib/action_controller/routing.rb +77 -28
  25. data/lib/action_controller/scaffolding.rb +10 -14
  26. data/lib/action_controller/session/active_record_store.rb +36 -7
  27. data/lib/action_controller/session_management.rb +126 -0
  28. data/lib/action_controller/streaming.rb +14 -5
  29. data/lib/action_controller/templates/rescues/_request_and_response.rhtml +1 -1
  30. data/lib/action_controller/templates/rescues/_trace.rhtml +24 -0
  31. data/lib/action_controller/templates/rescues/diagnostics.rhtml +2 -13
  32. data/lib/action_controller/templates/rescues/template_error.rhtml +4 -2
  33. data/lib/action_controller/templates/scaffolds/list.rhtml +1 -1
  34. data/lib/action_controller/test_process.rb +35 -17
  35. data/lib/action_controller/upload_progress.rb +52 -0
  36. data/lib/action_controller/url_rewriter.rb +21 -16
  37. data/lib/action_controller/vendor/html-scanner/html/document.rb +2 -2
  38. data/lib/action_controller/vendor/html-scanner/html/node.rb +30 -3
  39. data/lib/action_pack/version.rb +9 -0
  40. data/lib/action_view.rb +1 -1
  41. data/lib/action_view/base.rb +204 -60
  42. data/lib/action_view/compiled_templates.rb +70 -0
  43. data/lib/action_view/helpers/active_record_helper.rb +7 -3
  44. data/lib/action_view/helpers/asset_tag_helper.rb +22 -12
  45. data/lib/action_view/helpers/capture_helper.rb +2 -10
  46. data/lib/action_view/helpers/date_helper.rb +21 -13
  47. data/lib/action_view/helpers/form_helper.rb +14 -10
  48. data/lib/action_view/helpers/form_options_helper.rb +4 -4
  49. data/lib/action_view/helpers/form_tag_helper.rb +59 -25
  50. data/lib/action_view/helpers/java_script_macros_helper.rb +188 -0
  51. data/lib/action_view/helpers/javascript_helper.rb +68 -133
  52. data/lib/action_view/helpers/javascripts/controls.js +427 -165
  53. data/lib/action_view/helpers/javascripts/dragdrop.js +256 -277
  54. data/lib/action_view/helpers/javascripts/effects.js +766 -277
  55. data/lib/action_view/helpers/javascripts/prototype.js +906 -218
  56. data/lib/action_view/helpers/javascripts/slider.js +258 -0
  57. data/lib/action_view/helpers/number_helper.rb +4 -3
  58. data/lib/action_view/helpers/pagination_helper.rb +42 -27
  59. data/lib/action_view/helpers/tag_helper.rb +25 -11
  60. data/lib/action_view/helpers/text_helper.rb +119 -13
  61. data/lib/action_view/helpers/upload_progress_helper.rb +2 -2
  62. data/lib/action_view/helpers/url_helper.rb +68 -21
  63. data/lib/action_view/partials.rb +17 -6
  64. data/lib/action_view/template_error.rb +19 -24
  65. data/rakefile +4 -3
  66. data/test/abstract_unit.rb +2 -1
  67. data/test/controller/action_pack_assertions_test.rb +62 -2
  68. data/test/controller/active_record_assertions_test.rb +5 -6
  69. data/test/controller/active_record_store_test.rb +23 -1
  70. data/test/controller/addresses_render_test.rb +4 -0
  71. data/test/controller/{base_tests.rb → base_test.rb} +4 -3
  72. data/test/controller/benchmark_test.rb +36 -0
  73. data/test/controller/caching_filestore.rb +22 -40
  74. data/test/controller/capture_test.rb +10 -1
  75. data/test/controller/cgi_test.rb +145 -23
  76. data/test/controller/components_test.rb +50 -0
  77. data/test/controller/custom_handler_test.rb +3 -3
  78. data/test/controller/fake_controllers.rb +24 -0
  79. data/test/controller/filters_test.rb +6 -6
  80. data/test/controller/flash_test.rb +6 -6
  81. data/test/controller/fragment_store_setting_test.rb +45 -0
  82. data/test/controller/helper_test.rb +1 -3
  83. data/test/controller/new_render_test.rb +119 -7
  84. data/test/controller/redirect_test.rb +11 -1
  85. data/test/controller/render_test.rb +34 -1
  86. data/test/controller/request_test.rb +14 -5
  87. data/test/controller/routing_test.rb +238 -42
  88. data/test/controller/send_file_test.rb +11 -10
  89. data/test/controller/session_management_test.rb +94 -0
  90. data/test/controller/test_test.rb +194 -5
  91. data/test/controller/url_rewriter_test.rb +46 -0
  92. data/test/fixtures/layouts/talk_from_action.rhtml +2 -0
  93. data/test/fixtures/layouts/yield.rhtml +2 -0
  94. data/test/fixtures/multipart/binary_file +0 -0
  95. data/test/fixtures/multipart/large_text_file +10 -0
  96. data/test/fixtures/multipart/mixed_files +0 -0
  97. data/test/fixtures/multipart/single_parameter +5 -0
  98. data/test/fixtures/multipart/text_file +10 -0
  99. data/test/fixtures/test/_customer_greeting.rhtml +1 -0
  100. data/test/fixtures/test/_hash_object.rhtml +1 -0
  101. data/test/fixtures/test/_person.rhtml +2 -0
  102. data/test/fixtures/test/action_talk_to_layout.rhtml +2 -0
  103. data/test/fixtures/test/content_for.rhtml +2 -0
  104. data/test/fixtures/test/potential_conflicts.rhtml +4 -0
  105. data/test/template/active_record_helper_test.rb +15 -8
  106. data/test/template/asset_tag_helper_test.rb +40 -16
  107. data/test/template/compiled_templates_tests.rb +63 -0
  108. data/test/template/date_helper_test.rb +80 -4
  109. data/test/template/form_helper_test.rb +48 -42
  110. data/test/template/form_options_helper_test.rb +40 -40
  111. data/test/template/form_tag_helper_test.rb +21 -15
  112. data/test/template/java_script_macros_helper_test.rb +56 -0
  113. data/test/template/javascript_helper_test.rb +70 -47
  114. data/test/template/number_helper_test.rb +2 -0
  115. data/test/template/tag_helper_test.rb +9 -0
  116. data/test/template/text_helper_test.rb +146 -1
  117. data/test/template/upload_progress_helper_testx.rb +11 -147
  118. data/test/template/url_helper_test.rb +90 -22
  119. data/test/testing_sandbox.rb +26 -0
  120. metadata +37 -7
  121. data/lib/action_controller/auto_complete.rb +0 -47
  122. data/lib/action_controller/deprecated_renders_and_redirects.rb +0 -76
  123. data/lib/action_controller/session.rb +0 -14
@@ -24,8 +24,8 @@ module ActionController #:nodoc:
24
24
  protected
25
25
  # Exception handler called when the performance of an action raises an exception.
26
26
  def rescue_action(exception)
27
- log_error(exception) unless logger.nil?
28
- erase_render_results if performed?
27
+ log_error(exception) if logger
28
+ erase_results if performed?
29
29
 
30
30
  if consider_all_requests_local || local_request?
31
31
  rescue_action_locally(exception)
@@ -65,10 +65,12 @@ module ActionController #:nodoc:
65
65
 
66
66
  # Renders a detailed diagnostics screen on action exceptions.
67
67
  def rescue_action_locally(exception)
68
- @exception = exception
69
- @rescues_path = File.dirname(__FILE__) + "/templates/rescues/"
70
68
  add_variables_to_assigns
71
- @contents = @template.render_file(template_path_for_local_rescue(exception), false)
69
+ @template.instance_variable_set("@exception", exception)
70
+ @template.instance_variable_set("@rescues_path", File.dirname(__FILE__) + "/templates/rescues/")
71
+ @template.send(:assign_variables_from_controller)
72
+
73
+ @template.instance_variable_set("@contents", @template.render_file(template_path_for_local_rescue(exception), false))
72
74
 
73
75
  @headers["Content-Type"] = "text/html"
74
76
  render_file(rescues_path("layout"), response_code_for_rescue(exception))
@@ -93,7 +95,7 @@ module ActionController #:nodoc:
93
95
  callstack.slice!(0) if callstack.first["rescue.rb"]
94
96
  file, line, method = *callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures
95
97
 
96
- message = "Exception at #{file}:#{line}#{" in `#{method}'" if method}."
98
+ message = "Exception at #{file}:#{line}#{" in `#{method}'" if method}." # `� ( for ruby-mode)
97
99
 
98
100
  Breakpoint.handle_breakpoint(context, message, file, line)
99
101
  end
@@ -19,22 +19,30 @@ module ActionController
19
19
  end
20
20
  end
21
21
 
22
- def treat_hash(hash)
22
+ def treat_hash(hash, keys_to_delete = [])
23
23
  k = v = nil
24
24
  hash.each do |k, v|
25
- hash[k] = (v.respond_to? :to_param) ? v.to_param.to_s : v.to_s
25
+ if v then hash[k] = (v.respond_to? :to_param) ? v.to_param.to_s : v.to_s
26
+ else
27
+ hash.delete k
28
+ keys_to_delete << k
29
+ end
26
30
  end
27
31
  hash
28
32
  end
29
- end
30
-
31
- class << self
33
+
32
34
  def test_condition(expression, condition)
33
35
  case condition
34
36
  when String then "(#{expression} == #{condition.inspect})"
35
37
  when Regexp then
36
38
  condition = Regexp.new("^#{condition.source}$") unless /^\^.*\$$/ =~ condition.source
37
39
  "(#{condition.inspect} =~ #{expression})"
40
+ when Array then
41
+ conds = condition.collect do |condition|
42
+ cond = test_condition(expression, condition)
43
+ (cond[0, 1] == '(' && cond[-1, 1] == ')') ? cond : "(#{cond})"
44
+ end
45
+ "(#{conds.join(' || ')})"
38
46
  when true then expression
39
47
  when nil then "! #{expression}"
40
48
  else
@@ -184,7 +192,14 @@ module ActionController
184
192
  g << "controller_result = ::ActionController::Routing::ControllerComponent.traverse_to_controller(#{g.path_name}, #{g.index_name})"
185
193
  g.if('controller_result') do |gp|
186
194
  gp << 'controller_value, segments_to_controller = controller_result'
187
- gp.move_forward('segments_to_controller') {|gpp| yield gpp, :constraint}
195
+ if condition
196
+ gp << "controller_path = #{gp.path_name}[#{gp.index_name},segments_to_controller].join('/')"
197
+ gp.if(Routing.test_condition("controller_path", condition)) do |gpp|
198
+ gpp.move_forward('segments_to_controller') {|gppp| yield gppp, :constraint}
199
+ end
200
+ else
201
+ gp.move_forward('segments_to_controller') {|gpp| yield gpp, :constraint}
202
+ end
188
203
  end
189
204
  end
190
205
 
@@ -253,7 +268,7 @@ module ActionController
253
268
  g.finish(false)
254
269
  end
255
270
 
256
- class Result < ::Array
271
+ class Result < ::Array #:nodoc:
257
272
  def to_s() join '/' end
258
273
  def self.new_escaped(strings)
259
274
  new strings.collect {|str| CGI.unescape str}
@@ -272,6 +287,7 @@ module ActionController
272
287
  defaults, conditions = initialize_hashes options.dup
273
288
  @defaults = defaults.dup
274
289
  configure_components(defaults, conditions)
290
+ add_default_requirements
275
291
  initialize_keys
276
292
  end
277
293
 
@@ -283,9 +299,16 @@ module ActionController
283
299
  generator.before, generator.current, generator.after = [], components.first, (components[1..-1] || [])
284
300
 
285
301
  if known.empty? then generator.go
286
- else generator.if(generator.check_conditions(known)) {|gp| gp.go }
302
+ else
303
+ # Alter the conditions to allow :action => 'index' to also catch :action => nil
304
+ altered_known = known.collect do |k, v|
305
+ if k == :action && v== 'index' then [k, [nil, 'index']]
306
+ else [k, v]
307
+ end
308
+ end
309
+ generator.if(generator.check_conditions(altered_known)) {|gp| gp.go }
287
310
  end
288
-
311
+
289
312
  generator
290
313
  end
291
314
 
@@ -324,7 +347,6 @@ module ActionController
324
347
  end
325
348
 
326
349
  protected
327
-
328
350
  def initialize_components(path)
329
351
  path = path.split('/') if path.is_a? String
330
352
  path.shift if path.first.blank?
@@ -356,6 +378,11 @@ module ActionController
356
378
  component.condition = conditions[component.key] if conditions.key?(component.key)
357
379
  end
358
380
  end
381
+
382
+ def add_default_requirements
383
+ component_keys = components.collect {|c| c.key}
384
+ known[:action] ||= 'index' unless component_keys.include? :action
385
+ end
359
386
  end
360
387
 
361
388
  class RouteSet #:nodoc:
@@ -377,17 +404,15 @@ module ActionController
377
404
  options[:controller] = Routing.controller_relative_to(controller, recall_controller)
378
405
  end
379
406
  options = recall.dup if options.empty? # XXX move to url_rewriter?
380
- Routing.treat_hash(options) # XXX Move inwards (to generated code) or inline?
407
+
408
+ keys_to_delete = []
409
+ Routing.treat_hash(options, keys_to_delete)
410
+
381
411
  merged = recall.merge(options)
412
+ keys_to_delete.each {|key| merged.delete key}
382
413
  expire_on = Routing.expiry_hash(options, recall)
383
414
 
384
- path, keys = generate_path(merged, options, expire_on)
385
-
386
- # Factor out?
387
- extras = {}
388
- k = nil
389
- keys.each {|k| extras[k] = options[k]}
390
- [path, extras]
415
+ generate_path(merged, options, expire_on)
391
416
  end
392
417
 
393
418
  def generate_path(merged, options, expire_on)
@@ -557,16 +582,24 @@ module ActionController
557
582
 
558
583
  def each(&block) @routes.each(&block) end
559
584
 
560
- def method_missing(name, *args)
561
- return super(name, *args) unless (1..2).include?(args.length)
562
-
563
- route = connect(*args)
585
+ # Defines a new named route with the provided name and arguments.
586
+ # This method need only be used when you wish to use a name that a RouteSet instance
587
+ # method exists for, such as categories.
588
+ #
589
+ # For example, map.categories '/categories', :controller => 'categories' will not work
590
+ # due to RouteSet#categories.
591
+ def named_route(name, path, hash = {})
592
+ route = connect(path, hash)
564
593
  NamedRoutes.name_route(route, name)
565
594
  route
566
595
  end
596
+
597
+ def method_missing(name, *args)
598
+ (1..2).include?(args.length) ? named_route(name, *args) : super(name, *args)
599
+ end
567
600
 
568
601
  def extra_keys(options, recall = {})
569
- generate(options.dup, recall).last.keys
602
+ generate(options.dup, recall).last
570
603
  end
571
604
  end
572
605
 
@@ -582,12 +615,27 @@ module ActionController
582
615
  def url_helper_name(name)
583
616
  "#{name}_url"
584
617
  end
585
-
586
- def name_route(route, name)
587
- hash = route.defaults.merge(route.known).symbolize_keys
618
+
619
+ def known_hash_for_route(route)
620
+ hash = route.known.symbolize_keys
621
+ route.defaults.each do |key, value|
622
+ hash[key.to_sym] ||= value if value
623
+ end
588
624
  hash[:controller] = "/#{hash[:controller]}"
589
-
590
- define_method(hash_access_name(name)) { hash }
625
+
626
+ hash
627
+ end
628
+
629
+ def define_hash_access_method(route, name)
630
+ hash = known_hash_for_route(route)
631
+ define_method(hash_access_name(name)) do |*args|
632
+ args.first ? hash.merge(args.first) : hash
633
+ end
634
+ end
635
+
636
+ def name_route(route, name)
637
+ define_hash_access_method(route, name)
638
+
591
639
  module_eval(%{def #{url_helper_name name}(options = {})
592
640
  url_for(#{hash_access_name(name)}.merge(options))
593
641
  end}, "generated/routing/named_routes/#{name}.rb")
@@ -595,6 +643,7 @@ module ActionController
595
643
  protected url_helper_name(name), hash_access_name(name)
596
644
 
597
645
  Helpers << url_helper_name(name).to_sym
646
+ Helpers << hash_access_name(name).to_sym
598
647
  Helpers.uniq!
599
648
  end
600
649
 
@@ -82,7 +82,7 @@ module ActionController
82
82
  # make <tt>scaffold :post, :suffix => true</tt> use method names like list_post, show_post, and create_post
83
83
  # instead of just list, show, and post. If suffix is used, then no index method is added.
84
84
  def scaffold(model_id, options = {})
85
- validate_options([ :class_name, :suffix ], options.keys)
85
+ options.assert_valid_keys(:class_name, :suffix)
86
86
 
87
87
  singular_name = model_id.to_s
88
88
  class_name = options[:class_name] || singular_name.camelize
@@ -146,21 +146,24 @@ module ActionController
146
146
  end
147
147
 
148
148
  private
149
- def render#{suffix}_scaffold(action = caller_method_name(caller))
149
+ def render#{suffix}_scaffold(action=nil)
150
+ action ||= caller_method_name(caller)
151
+ # logger.info ("testing template:" + "\#{self.class.controller_path}/\#{action}") if logger
152
+
150
153
  if template_exists?("\#{self.class.controller_path}/\#{action}")
151
- render(:action => action)
154
+ render_action(action)
152
155
  else
153
156
  @scaffold_class = #{class_name}
154
157
  @scaffold_singular_name, @scaffold_plural_name = "#{singular_name}", "#{plural_name}"
155
158
  @scaffold_suffix = "#{suffix}"
156
159
  add_instance_variables_to_assigns
157
160
 
158
- @content_for_layout = @template.render_file(scaffold_path(action.sub(/#{suffix}$/, "")), false)
161
+ @template.instance_variable_set("@content_for_layout", @template.render_file(scaffold_path(action.sub(/#{suffix}$/, "")), false))
159
162
 
160
163
  if !active_layout.nil?
161
- render :file => active_layout, :use_full_path => true
164
+ render_file(active_layout, nil, true)
162
165
  else
163
- render :file => scaffold_path("layout")
166
+ render_file(scaffold_path("layout"))
164
167
  end
165
168
  end
166
169
  end
@@ -173,14 +176,7 @@ module ActionController
173
176
  caller.first.scan(/`(.*)'/).first.first # ' ruby-mode
174
177
  end
175
178
  end_eval
176
- end
177
-
178
- private
179
- # Raises an exception if an invalid option has been specified to prevent misspellings from slipping through
180
- def validate_options(valid_option_keys, supplied_option_keys)
181
- unknown_option_keys = supplied_option_keys - valid_option_keys
182
- raise(ActionController::ActionControllerError, "Unknown options: #{unknown_option_keys}") unless unknown_option_keys.empty?
183
- end
179
+ end
184
180
  end
185
181
  end
186
182
  end
@@ -12,8 +12,9 @@ class CGI
12
12
  # may be used as the backing store.
13
13
  #
14
14
  # The default assumes a +sessions+ tables with columns +id+ (numeric
15
- # primary key), +session_id+ (text), and +data+ (text). Session data is
16
- # marshaled to +data+. +session_id+ should be indexed for speedy lookups.
15
+ # primary key), +session_id+ (text, or longtext if your session data exceeds 65K),
16
+ # and +data+ (text). Session data is marshaled to +data+. +session_id+ should be
17
+ # indexed for speedy lookups.
17
18
  #
18
19
  # Since the default class is a simple Active Record, you get timestamps
19
20
  # for free if you add +created_at+ and +updated_at+ datetime columns to
@@ -32,13 +33,28 @@ class CGI
32
33
  #
33
34
  # The fast SqlBypass class is a generic SQL session store. You may
34
35
  # use it as a basis for high-performance database-specific stores.
36
+ #
37
+ # If the data you are attempting to write to the +data+ column is larger
38
+ # than the column's size limit, ActionController::SessionOverflowError
39
+ # will be raised.
35
40
  class ActiveRecordStore
36
41
  # The default Active Record class.
37
42
  class Session < ActiveRecord::Base
38
43
  before_save :marshal_data!
44
+ before_save :ensure_data_not_too_big
39
45
  before_update :data_changed?
40
46
 
41
47
  class << self
48
+
49
+ # Don't try to reload ARStore::Session in dev mode.
50
+ def reloadable? #:nodoc:
51
+ false
52
+ end
53
+
54
+ def data_column_size_limit
55
+ columns_hash['data'].limit
56
+ end
57
+
42
58
  # Hook to set up sessid compatibility.
43
59
  def find_by_session_id(session_id)
44
60
  setup_sessid_compatibility!
@@ -54,7 +70,7 @@ class CGI
54
70
  CREATE TABLE #{table_name} (
55
71
  id INTEGER PRIMARY KEY,
56
72
  #{connection.quote_column_name('session_id')} TEXT UNIQUE,
57
- #{connection.quote_column_name('data')} TEXT
73
+ #{connection.quote_column_name('data')} TEXT(255)
58
74
  )
59
75
  end_sql
60
76
  end
@@ -108,6 +124,15 @@ class CGI
108
124
  old_fingerprint, @fingerprint = @fingerprint, self.class.fingerprint(read_attribute('data'))
109
125
  old_fingerprint != @fingerprint
110
126
  end
127
+
128
+ # Ensures that the data about to be stored in the database is not
129
+ # larger than the data storage column. Raises
130
+ # ActionController::SessionOverflowError.
131
+ def ensure_data_not_too_big
132
+ return unless limit = self.class.data_column_size_limit
133
+ raise ActionController::SessionOverflowError, ActionController::SessionOverflowError::DEFAULT_MESSAGE if read_attribute('data').size > limit
134
+ end
135
+
111
136
  end
112
137
 
113
138
  # A barebones session store which duck-types with the default session
@@ -125,9 +150,6 @@ class CGI
125
150
  class SqlBypass
126
151
  # Use the ActiveRecord::Base.connection by default.
127
152
  cattr_accessor :connection
128
- def self.connection
129
- @@connection ||= ActiveRecord::Base.connection
130
- end
131
153
 
132
154
  # The table name defaults to 'sessions'.
133
155
  cattr_accessor :table_name
@@ -142,6 +164,11 @@ class CGI
142
164
  @@data_column = 'data'
143
165
 
144
166
  class << self
167
+
168
+ def connection
169
+ @@connection ||= ActiveRecord::Base.connection
170
+ end
171
+
145
172
  # Look up a session by id and unmarshal its data if found.
146
173
  def find_by_session_id(session_id)
147
174
  if record = @@connection.select_one("SELECT * FROM #{@@table_name} WHERE #{@@session_id_column}=#{@@connection.quote(session_id)}")
@@ -200,6 +227,7 @@ class CGI
200
227
 
201
228
  def save
202
229
  marshaled_data = self.class.marshal(data)
230
+
203
231
  if @new_record
204
232
  @new_record = false
205
233
  @@connection.update <<-end_sql, 'Create session'
@@ -230,6 +258,7 @@ class CGI
230
258
  end_sql
231
259
  end
232
260
  end
261
+
233
262
  end
234
263
 
235
264
  # The class used for session storage. Defaults to
@@ -277,7 +306,7 @@ class CGI
277
306
  @session = nil
278
307
  end
279
308
  end
280
- end
281
309
 
310
+ end
282
311
  end
283
312
  end
@@ -0,0 +1,126 @@
1
+ require 'action_controller/session/drb_store'
2
+ require 'action_controller/session/mem_cache_store'
3
+ if Object.const_defined?(:ActiveRecord)
4
+ require 'action_controller/session/active_record_store'
5
+ end
6
+
7
+ module ActionController #:nodoc:
8
+ module SessionManagement #:nodoc:
9
+ def self.append_features(base)
10
+ super
11
+ base.extend(ClassMethods)
12
+ base.send(:alias_method, :process_without_session_management_support, :process)
13
+ base.send(:alias_method, :process, :process_with_session_management_support)
14
+ base.after_filter(:clear_persistant_model_associations)
15
+ end
16
+
17
+ module ClassMethods
18
+ # Set the session store to be used for keeping the session data between requests. The default is using the
19
+ # file system, but you can also specify one of the other included stores (:active_record_store, :drb_store,
20
+ # :mem_cache_store, or :memory_store) or use your own class.
21
+ def session_store=(store)
22
+ ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:database_manager] =
23
+ store.is_a?(Symbol) ? CGI::Session.const_get(store == :drb_store ? "DRbStore" : store.to_s.camelize) : store
24
+ end
25
+
26
+ # Returns the session store class currently used.
27
+ def session_store
28
+ ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:database_manager]
29
+ end
30
+
31
+ # Returns the hash used to configure the session. Example use:
32
+ #
33
+ # ActionController::Base.session_options[:session_secure] = true # session only available over HTTPS
34
+ def session_options
35
+ ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS
36
+ end
37
+
38
+ # Specify how sessions ought to be managed for a subset of the actions on
39
+ # the controller. Like filters, you can specify <tt>:only</tt> and
40
+ # <tt>:except</tt> clauses to restrict the subset, otherwise options
41
+ # apply to all actions on this controller.
42
+ #
43
+ # The session options are inheritable, as well, so if you specify them in
44
+ # a parent controller, they apply to controllers that extend the parent.
45
+ #
46
+ # Usage:
47
+ #
48
+ # # turn off session management for all actions.
49
+ # session :off
50
+ #
51
+ # # turn off session management for all actions _except_ foo and bar.
52
+ # session :off, :except => %w(foo bar)
53
+ #
54
+ # # turn off session management for only the foo and bar actions.
55
+ # session :off, :only => %w(foo bar)
56
+ #
57
+ # # the session will only work over HTTPS, but only for the foo action
58
+ # session :only => :foo, :session_secure => true
59
+ #
60
+ # # the session will only be disabled for 'foo', and only if it is
61
+ # # requested as a web service
62
+ # session :off, :only => :foo,
63
+ # :if => Proc.new { |req| req.parameters[:ws] }
64
+ #
65
+ # All session options described for ActionController::Base.process_cgi
66
+ # are valid arguments.
67
+ def session(*args)
68
+ options = Hash === args.last ? args.pop : {}
69
+
70
+ options[:disabled] = true if !args.empty?
71
+ options[:only] = [*options[:only]].map { |o| o.to_s } if options[:only]
72
+ options[:except] = [*options[:except]].map { |o| o.to_s } if options[:except]
73
+ if options[:only] && options[:except]
74
+ raise ArgumentError, "only one of either :only or :except are allowed"
75
+ end
76
+
77
+ write_inheritable_array("session_options", [options])
78
+ end
79
+
80
+ def cached_session_options #:nodoc:
81
+ @session_options ||= read_inheritable_attribute("session_options") || []
82
+ end
83
+
84
+ def session_options_for(request, action) #:nodoc:
85
+ if (session_options = cached_session_options).empty?
86
+ {}
87
+ else
88
+ options = {}
89
+
90
+ action = action.to_s
91
+ session_options.each do |opts|
92
+ next if opts[:if] && !opts[:if].call(request)
93
+ if opts[:only] && opts[:only].include?(action)
94
+ options.merge!(opts)
95
+ elsif opts[:except] && !opts[:except].include?(action)
96
+ options.merge!(opts)
97
+ elsif !opts[:only] && !opts[:except]
98
+ options.merge!(opts)
99
+ end
100
+ end
101
+
102
+ if options.empty? then options
103
+ else
104
+ options.delete :only
105
+ options.delete :except
106
+ options.delete :if
107
+ options[:disabled] ? false : options
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ def process_with_session_management_support(request, response, method = :perform_action, *arguments) #:nodoc:
114
+ action = request.parameters["action"] || "index"
115
+ request.session_options = self.class.session_options_for(request, action)
116
+ process_without_session_management_support(request, response, method, *arguments)
117
+ end
118
+
119
+ private
120
+ def clear_persistant_model_associations #:doc:
121
+ if session = @session.instance_variable_get("@data")
122
+ session.each { |key, obj| obj.clear_association_cache if obj.respond_to?(:clear_association_cache) }
123
+ end
124
+ end
125
+ end
126
+ end