Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'master' into adequaterecord

* master: (311 commits)
  Add a missing changelog entry for #13981 and #14035
  Revert "Fixed plugin_generator test"
  implements new option :month_format_string for date select helpers [Closes #13618]
  add factory methods for empty alias trackers
  guarantee a list in the alias tracker so we can remove a conditional
  stop exposing table_joins
  make most parameters to the AliasTracker required
  make a singleton for AssociationScope
  pass the association and connection to the scope method
  pass the tracker down the stack and construct it in the scope method
  clean up add_constraints signature
  remove the reflection delegate
  remove klass delegator
  remove railties changes. fixes #14054
  remove chain delegate
  remove scope_chain delegate
  Add verb to sanitization note
  fix path shown in mailer's templates
  updated Travis build status image url
  fix guide active_support_core_extensions. add Note to String#indent [ci skip]
  ...

Conflicts:
	activerecord/lib/active_record/associations/join_dependency.rb
	activerecord/test/cases/associations/association_scope_test.rb
  • Loading branch information...
commit fe42effb11a97cf19777d7b0dba7e1e2dfd3316c 2 parents 5ac2879 + 3e3ed1e
@tenderlove tenderlove authored
Showing with 4,974 additions and 1,660 deletions.
  1. +2 −2 .travis.yml
  2. +1 −6 Gemfile
  3. +1 −2  README.md
  4. +23 −1 actionmailer/CHANGELOG.md
  5. +1 −1  actionmailer/README.rdoc
  6. +35 −8 actionmailer/lib/action_mailer/base.rb
  7. +37 −6 actionmailer/lib/action_mailer/preview.rb
  8. +1 −1  actionmailer/lib/action_mailer/railtie.rb
  9. +56 −3 actionmailer/test/base_test.rb
  10. +20 −9 actionmailer/test/delivery_methods_test.rb
  11. +82 −1 actionpack/CHANGELOG.md
  12. +3 −3 actionpack/RUNNING_UNIT_TESTS.rdoc
  13. +1 −0  actionpack/lib/abstract_controller/rendering.rb
  14. +9 −0 actionpack/lib/action_controller/log_subscriber.rb
  15. +20 −8 actionpack/lib/action_controller/metal/mime_responds.rb
  16. +11 −4 actionpack/lib/action_controller/metal/params_wrapper.rb
  17. +3 −0  actionpack/lib/action_controller/test_case.rb
  18. +4 −6 actionpack/lib/action_dispatch.rb
  19. +5 −4 actionpack/lib/action_dispatch/http/filter_redirect.rb
  20. +4 −2 actionpack/lib/action_dispatch/http/mime_negotiation.rb
  21. +2 −1  actionpack/lib/action_dispatch/http/response.rb
  22. +85 −20 actionpack/lib/action_dispatch/middleware/cookies.rb
  23. +20 −7 actionpack/lib/action_dispatch/middleware/flash.rb
  24. +11 −2 actionpack/lib/action_dispatch/middleware/reloader.rb
  25. +9 −4 actionpack/lib/action_dispatch/request/utils.rb
  26. +3 −3 actionpack/lib/action_dispatch/routing/inspector.rb
  27. +18 −5 actionpack/lib/action_dispatch/routing/mapper.rb
  28. +16 −0 actionpack/test/controller/filters_test.rb
  29. +10 −0 actionpack/test/controller/flash_hash_test.rb
  30. +4 −4 actionpack/test/controller/flash_test.rb
  31. +1 −1  actionpack/test/controller/http_token_authentication_test.rb
  32. +11 −0 actionpack/test/controller/log_subscriber_test.rb
  33. +25 −0 actionpack/test/controller/mime/respond_to_test.rb
  34. +0 −10 actionpack/test/controller/parameters/parameters_require_test.rb
  35. +20 −0 actionpack/test/controller/params_wrapper_test.rb
  36. +8 −0 actionpack/test/controller/required_params_test.rb
  37. +8 −0 actionpack/test/controller/test_case_test.rb
  38. +0 −3  actionpack/test/controller/url_for_test.rb
  39. +147 −5 actionpack/test/dispatch/cookies_test.rb
  40. +1 −1  actionpack/test/dispatch/rack_test.rb
  41. +7 −0 actionpack/test/dispatch/request/json_params_parsing_test.rb
  42. +13 −1 actionpack/test/dispatch/request_test.rb
  43. +18 −0 actionpack/test/dispatch/response_test.rb
  44. +44 −0 actionpack/test/dispatch/routing/inspector_test.rb
  45. +121 −0 actionpack/test/dispatch/routing_test.rb
  46. +7 −2 actionpack/test/dispatch/static_test.rb
  47. +31 −8 actionview/CHANGELOG.md
  48. +2 −3 actionview/lib/action_view.rb
  49. +4 −0 actionview/lib/action_view/base.rb
  50. +5 −2 actionview/lib/action_view/helpers/csrf_helper.rb
  51. +24 −9 actionview/lib/action_view/helpers/date_helper.rb
  52. +4 −4 actionview/lib/action_view/helpers/translation_helper.rb
  53. +5 −0 actionview/lib/action_view/helpers/url_helper.rb
  54. +1 −0  actionview/lib/action_view/lookup_context.rb
  55. +1 −1  actionview/lib/action_view/rendering.rb
  56. +1 −0  actionview/test/fixtures/customers/_customer.xml.erb
  57. +10 −0 actionview/test/template/date_helper_test.rb
  58. +10 −0 actionview/test/template/render_test.rb
  59. +10 −0 actionview/test/template/translation_helper_test.rb
  60. +7 −0 actionview/test/template/url_helper_test.rb
  61. +29 −2 activemodel/CHANGELOG.md
  62. +1 −1  activemodel/lib/active_model/conversion.rb
  63. +4 −4 activemodel/lib/active_model/dirty.rb
  64. +1 −1  activemodel/lib/active_model/errors.rb
  65. +11 −11 activemodel/lib/active_model/secure_password.rb
  66. +11 −9 activemodel/lib/active_model/validations.rb
  67. +1 −1  activemodel/lib/active_model/validations/absence.rb
  68. +2 −4 activemodel/lib/active_model/validations/acceptance.rb
  69. +1 −1  activemodel/lib/active_model/validations/confirmation.rb
  70. +1 −5 activemodel/lib/active_model/validations/exclusion.rb
  71. +1 −5 activemodel/lib/active_model/validations/format.rb
  72. +1 −5 activemodel/lib/active_model/validations/inclusion.rb
  73. +1 −1  activemodel/lib/active_model/validations/numericality.rb
  74. +1 −1  activemodel/lib/active_model/validations/presence.rb
  75. +3 −1 activemodel/lib/active_model/validations/validates.rb
  76. +10 −0 activemodel/test/cases/conversion_test.rb
  77. +27 −0 activemodel/test/cases/dirty_test.rb
  78. +6 −1 activemodel/test/cases/errors_test.rb
  79. +120 −58 activemodel/test/cases/secure_password_test.rb
  80. +3 −3 activemodel/test/cases/validations/absence_validation_test.rb
  81. +2 −2 activemodel/test/cases/validations/acceptance_validation_test.rb
  82. +1 −1  activemodel/test/cases/validations/conditional_validation_test.rb
  83. +2 −2 activemodel/test/cases/validations/confirmation_validation_test.rb
  84. +3 −3 activemodel/test/cases/validations/exclusion_validation_test.rb
  85. +4 −4 activemodel/test/cases/validations/format_validation_test.rb
  86. +1 −1  activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
  87. +2 −2 activemodel/test/cases/validations/i18n_validation_test.rb
  88. +3 −3 activemodel/test/cases/validations/inclusion_validation_test.rb
  89. +2 −2 activemodel/test/cases/validations/length_validation_test.rb
  90. +2 −2 activemodel/test/cases/validations/numericality_validation_test.rb
  91. +3 −3 activemodel/test/cases/validations/presence_validation_test.rb
  92. +3 −3 activemodel/test/cases/validations/validates_test.rb
  93. +14 −3 activemodel/test/cases/validations/validations_context_test.rb
  94. +1 −2  activemodel/test/cases/validations/with_validation_test.rb
  95. +1 −8 activemodel/test/cases/validations_test.rb
  96. +0 −11 activemodel/test/models/oauthed_user.rb
  97. +1 −1  activemodel/test/models/user.rb
  98. +261 −5 activerecord/CHANGELOG.md
  99. +8 −5 activerecord/lib/active_record/associations.rb
  100. +39 −29 activerecord/lib/active_record/associations/alias_tracker.rb
  101. +1 −1  activerecord/lib/active_record/associations/association.rb
  102. +56 −31 activerecord/lib/active_record/associations/association_scope.rb
  103. +6 −0 activerecord/lib/active_record/associations/builder/association.rb
  104. +30 −6 activerecord/lib/active_record/associations/collection_association.rb
  105. +49 −1 activerecord/lib/active_record/associations/collection_proxy.rb
  106. +1 −1  activerecord/lib/active_record/associations/has_many_association.rb
  107. +6 −6 activerecord/lib/active_record/associations/join_dependency.rb
  108. +4 −2 activerecord/lib/active_record/associations/join_dependency/join_association.rb
  109. +0 −36 activerecord/lib/active_record/associations/join_helper.rb
  110. +1 −1  activerecord/lib/active_record/associations/singular_association.rb
  111. +27 −4 activerecord/lib/active_record/attribute_methods.rb
  112. +27 −4 activerecord/lib/active_record/attribute_methods/dirty.rb
  113. +18 −0 activerecord/lib/active_record/attribute_methods/serialization.rb
  114. +1 −1  activerecord/lib/active_record/autosave_association.rb
  115. +1 −1  activerecord/lib/active_record/base.rb
  116. +1 −1  activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
  117. +14 −3 activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
  118. +3 −3 activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
  119. +4 −0 activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
  120. +6 −0 activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
  121. +2 −6 activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
  122. +1 −1  activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
  123. +2 −2 activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
  124. +3 −2 activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb
  125. +2 −2 activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
  126. +13 −0 activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
  127. +6 −9 activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
  128. +2 −2 activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
  129. +17 −23 activerecord/lib/active_record/core.rb
  130. +6 −2 activerecord/lib/active_record/dynamic_matchers.rb
  131. +75 −4 activerecord/lib/active_record/enum.rb
  132. +10 −0 activerecord/lib/active_record/inheritance.rb
  133. +6 −6 activerecord/lib/active_record/migration.rb
  134. +6 −1 activerecord/lib/active_record/migration/command_recorder.rb
  135. +1 −1  activerecord/lib/active_record/persistence.rb
  136. +1 −0  activerecord/lib/active_record/querying.rb
  137. +2 −2 activerecord/lib/active_record/railties/databases.rake
  138. +3 −2 activerecord/lib/active_record/relation.rb
  139. +24 −9 activerecord/lib/active_record/relation/batches.rb
  140. +99 −11 activerecord/lib/active_record/relation/finder_methods.rb
  141. +26 −17 activerecord/lib/active_record/relation/query_methods.rb
  142. +1 −1  activerecord/lib/active_record/result.rb
  143. +1 −2  activerecord/lib/active_record/sanitization.rb
  144. +5 −0 activerecord/lib/active_record/scoping.rb
  145. +6 −0 activerecord/lib/active_record/scoping/named.rb
  146. +2 −2 activerecord/lib/active_record/timestamp.rb
  147. +1 −1  activerecord/lib/active_record/transactions.rb
  148. +23 −0 activerecord/test/cases/adapter_test.rb
  149. +12 −0 activerecord/test/cases/adapters/postgresql/array_test.rb
  150. +42 −0 activerecord/test/cases/adapters/postgresql/composite_test.rb
  151. +29 −19 activerecord/test/cases/adapters/postgresql/connection_test.rb
  152. +114 −82 activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
  153. +12 −0 activerecord/test/cases/adapters/postgresql/schema_test.rb
  154. +1 −1  activerecord/test/cases/adapters/postgresql/timestamp_test.rb
  155. +1 −1  activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
  156. +3 −2 activerecord/test/cases/associations/association_scope_test.rb
  157. +17 −0 activerecord/test/cases/associations/belongs_to_associations_test.rb
  158. +10 −0 activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
  159. +112 −63 activerecord/test/cases/associations/has_many_associations_test.rb
  160. +17 −0 activerecord/test/cases/associations/has_one_associations_test.rb
  161. +9 −0 activerecord/test/cases/associations_test.rb
  162. +60 −12 activerecord/test/cases/autosave_association_test.rb
  163. +3 −1 activerecord/test/cases/base_test.rb
  164. +40 −1 activerecord/test/cases/batches_test.rb
  165. +12 −10 activerecord/test/cases/calculations_test.rb
  166. +2 −2 activerecord/test/cases/column_definition_test.rb
  167. +2 −0  activerecord/test/cases/connection_management_test.rb
  168. +1 −1  activerecord/test/cases/connection_pool_test.rb
  169. +130 −0 activerecord/test/cases/enum_test.rb
  170. +5 −0 activerecord/test/cases/finder_respond_to_test.rb
  171. +115 −10 activerecord/test/cases/finder_test.rb
  172. +6 −2 activerecord/test/cases/fixtures_test.rb
  173. +2 −2 activerecord/test/cases/inheritance_test.rb
  174. +28 −0 activerecord/test/cases/invertible_migration_test.rb
  175. +11 −0 activerecord/test/cases/locking_test.rb
  176. +3 −3 activerecord/test/cases/migration/command_recorder_test.rb
  177. +5 −1 activerecord/test/cases/mixin_test.rb
  178. +2 −2 activerecord/test/cases/persistence_test.rb
  179. +5 −1 activerecord/test/cases/primary_keys_test.rb
  180. +1 −1  activerecord/test/cases/reaper_test.rb
  181. +4 −0 activerecord/test/cases/relation/mutation_test.rb
  182. +49 −12 activerecord/test/cases/relations_test.rb
  183. +10 −2 activerecord/test/cases/result_test.rb
  184. +5 −0 activerecord/test/cases/sanitize_test.rb
  185. +10 −0 activerecord/test/cases/scoping/default_scoping_test.rb
  186. +63 −4 activerecord/test/cases/scoping/named_scoping_test.rb
  187. +24 −0 activerecord/test/cases/store_test.rb
  188. +15 −0 activerecord/test/cases/transaction_callbacks_test.rb
  189. +11 −0 activerecord/test/cases/transactions_test.rb
  190. +1 −1  activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
  191. +8 −0 activerecord/test/fixtures/companies.yml
  192. +7 −0 activerecord/test/fixtures/topics.yml
  193. +1 −0  activerecord/test/models/book.rb
  194. +2 −0  activerecord/test/models/electron.rb
  195. +0 −2  activerecord/test/models/mixed_case_monkey.rb
  196. +2 −0  activerecord/test/models/molecule.rb
  197. +9 −0 activerecord/test/models/pirate.rb
  198. +8 −0 activerecord/test/models/ship.rb
  199. +1 −0  activerecord/test/schema/schema.rb
  200. +87 −1 activesupport/CHANGELOG.md
  201. +1 −1  activesupport/lib/active_support/cache.rb
  202. +0 −15 activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
  203. +14 −0 activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb
  204. +1 −1  activesupport/lib/active_support/core_ext/enumerable.rb
  205. +2 −1  activesupport/lib/active_support/core_ext/module/attr_internal.rb
  206. +1 −1  activesupport/lib/active_support/core_ext/module/concerning.rb
  207. +4 −4 activesupport/lib/active_support/core_ext/object/json.rb
  208. +7 −3 activesupport/lib/active_support/core_ext/object/to_param.rb
  209. +6 −1 activesupport/lib/active_support/core_ext/object/to_query.rb
  210. +10 −1 activesupport/lib/active_support/dependencies.rb
  211. +6 −0 activesupport/lib/active_support/inflections.rb
  212. +1 −1  activesupport/lib/active_support/inflector/methods.rb
  213. +7 −1 activesupport/lib/active_support/json/encoding.rb
  214. +7 −9 activesupport/lib/active_support/key_generator.rb
  215. +1 −1  activesupport/lib/active_support/message_encryptor.rb
  216. +4 −4 activesupport/lib/active_support/multibyte/unicode.rb
  217. +2 −0  activesupport/lib/active_support/testing/isolation.rb
  218. +65 −11 activesupport/lib/active_support/testing/time_helpers.rb
  219. +8 −7 activesupport/lib/active_support/time_with_zone.rb
  220. +4 −0 activesupport/lib/active_support/values/time_zone.rb
  221. +4 −2 activesupport/lib/active_support/xml_mini.rb
  222. +11 −0 activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb
  223. +0 −12 activesupport/test/core_ext/bigdecimal_test.rb
  224. +4 −21 activesupport/test/core_ext/enumerable_test.rb
  225. +13 −0 activesupport/test/core_ext/object/to_query_test.rb
  226. +4 −0 activesupport/test/core_ext/string_ext_test.rb
  227. +14 −20 activesupport/test/core_ext/time_with_zone_test.rb
  228. +12 −0 activesupport/test/dependencies_test.rb
  229. +66 −11 activesupport/test/json/encoding_test.rb
  230. +16 −0 activesupport/test/test_test.rb
  231. +3 −0  activesupport/test/time_zone_test.rb
  232. +124 −0 activesupport/test/xml_mini_test.rb
  233. BIN  guides/assets/images/getting_started/article_with_comments.png
  234. BIN  guides/assets/images/getting_started/challenge.png
  235. BIN  guides/assets/images/getting_started/confirm_dialog.png
  236. BIN  guides/assets/images/getting_started/forbidden_attributes_for_new_article.png
  237. BIN  guides/assets/images/getting_started/forbidden_attributes_for_new_post.png
  238. BIN  guides/assets/images/getting_started/form_with_errors.png
  239. BIN  guides/assets/images/getting_started/index_action_with_edit_link.png
  240. BIN  guides/assets/images/getting_started/new_article.png
  241. BIN  guides/assets/images/getting_started/new_post.png
  242. BIN  guides/assets/images/getting_started/post_with_comments.png
  243. BIN  guides/assets/images/getting_started/routing_error_no_controller.png
  244. 0  guides/assets/images/getting_started/{show_action_for_posts.png → show_action_for_articles.png}
  245. BIN  guides/assets/images/getting_started/template_is_missing_articles_new.png
  246. BIN  guides/assets/images/getting_started/template_is_missing_posts_new.png
  247. BIN  guides/assets/images/getting_started/undefined_method_post_path.png
  248. BIN  guides/assets/images/getting_started/unknown_action_create_for_articles.png
  249. BIN  guides/assets/images/getting_started/unknown_action_create_for_posts.png
  250. BIN  guides/assets/images/getting_started/unknown_action_new_for_articles.png
  251. BIN  guides/assets/images/getting_started/unknown_action_new_for_posts.png
  252. +1 −1  guides/code/getting_started/Gemfile
  253. +1 −1  guides/code/getting_started/config/environments/production.rb
  254. +2 −0  guides/rails_guides/helpers.rb
  255. +1 −1  guides/source/3_0_release_notes.md
  256. +91 −12 guides/source/4_1_release_notes.md
  257. +38 −2 guides/source/action_controller_overview.md
  258. +5 −5 guides/source/action_mailer_basics.md
  259. +1 −1  guides/source/active_record_querying.md
  260. +11 −9 guides/source/active_record_validations.md
  261. +2 −0  guides/source/active_support_core_extensions.md
  262. +51 −1 guides/source/api_documentation_guidelines.md
  263. +18 −13 guides/source/asset_pipeline.md
  264. +13 −0 guides/source/association_basics.md
  265. +31 −10 guides/source/configuring.md
  266. +7 −6 guides/source/contributing_to_ruby_on_rails.md
  267. +1 −1  guides/source/documents.yaml
  268. +484 −438 guides/source/getting_started.md
  269. +1 −1  guides/source/i18n.md
  270. +2 −2 guides/source/initialization.md
  271. +2 −2 guides/source/layouts_and_rendering.md
  272. +1 −1  guides/source/migrations.md
  273. +1 −1  guides/source/routing.md
  274. +44 −1 guides/source/security.md
  275. +7 −6 guides/source/testing.md
  276. +16 −3 guides/source/upgrading_ruby_on_rails.md
  277. +24 −1 railties/CHANGELOG.md
  278. +1 −1  railties/lib/rails/app_rails_loader.rb
  279. +4 −2 railties/lib/rails/application.rb
  280. +2 −0  railties/lib/rails/application/configuration.rb
  281. +68 −0 railties/lib/rails/generators/actions/create_migration.rb
  282. +11 −89 railties/lib/rails/generators/app_base.rb
  283. +1 −1  railties/lib/rails/generators/erb/mailer/templates/view.html.erb
  284. +1 −1  railties/lib/rails/generators/erb/mailer/templates/view.text.erb
  285. +22 −16 railties/lib/rails/generators/migration.rb
  286. +1 −3 railties/lib/rails/generators/rails/app/app_generator.rb
  287. +3 −0  railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
  288. +6 −1 railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
  289. +3 −0  railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
  290. +3 −0  railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb
  291. +1 −4 railties/lib/rails/generators/rails/plugin/plugin_generator.rb
  292. +4 −0 railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt
  293. +1 −3 railties/lib/rails/generators/resource_helpers.rb
  294. +8 −2 railties/test/app_rails_loader_test.rb
  295. +25 −0 railties/test/application/configuration_test.rb
  296. +31 −0 railties/test/application/rake/migrations_test.rb
  297. +0 −67 railties/test/generators/app_generator_test.rb
  298. +134 −0 railties/test/generators/create_migration_test.rb
  299. +0 −1  railties/test/generators/generator_test.rb
  300. +4 −4 railties/test/generators/mailer_generator_test.rb
View
4 .travis.yml
@@ -6,7 +6,7 @@ rvm:
- 1.9.3
- 2.0.0
- 2.1.0
- - rbx
+ - rbx-2
- jruby
env:
- "GEM=railties"
@@ -17,7 +17,7 @@ env:
- "GEM=ar:postgresql"
matrix:
allow_failures:
- - rvm: rbx
+ - rvm: rbx-2
- rvm: jruby
fast_finish: true
notifications:
View
7 Gemfile
@@ -9,7 +9,7 @@ gem 'mocha', '~> 0.14', require: false
gem 'rack-cache', '~> 1.2'
gem 'bcrypt-ruby', '~> 3.1.2'
-gem 'jquery-rails', '~> 2.2.0'
+gem 'jquery-rails', '~> 3.1.0'
gem 'turbolinks'
gem 'coffee-rails', '~> 4.0.0'
gem 'arel', github: 'rails/arel'
@@ -77,11 +77,6 @@ platforms :jruby do
end
end
-platforms :rbx do
- gem 'psych', '~> 2.0'
- gem 'rubysl', '~> 2.0'
-end
-
# gems that are necessary for ActiveRecord tests with Oracle database
if ENV['ORACLE_ENHANCED']
platforms :ruby do
View
3  README.md
@@ -76,8 +76,7 @@ We encourage you to contribute to Ruby on Rails! Please check out the
## Code Status
-* [![Build Status](https://api.travis-ci.org/rails/rails.png)](https://travis-ci.org/rails/rails)
-* [![Dependencies](https://gemnasium.com/rails/rails.png?travis)](https://gemnasium.com/rails/rails)
+* [![Build Status](https://travis-ci.org/rails/rails.png?branch=master)](https://travis-ci.org/rails/rails)
## License
View
24 actionmailer/CHANGELOG.md
@@ -1,4 +1,26 @@
-* Add mailer previews feature based on 37 Signals mail_view gem
+* Support the use of underscored symbols when registering interceptors and
+ observers like we do elsewhere within Rails.
+
+ *Andrew White*
+
+* Add the ability to intercept emails before previewing in a similar fashion
+ to how emails can be intercepted before delivery.
+
+ Fixes #13622.
+
+ Example:
+
+ class CSSInlineStyler
+ def self.previewing_email(message)
+ # inline CSS styles
+ end
+ end
+
+ ActionMailer::Base.register_preview_interceptor CSSInlineStyler
+
+ *Andrew White*
+
+* Add mailer previews feature based on 37 Signals mail_view gem.
*Andrew White*
View
2  actionmailer/README.rdoc
@@ -74,7 +74,7 @@ Or you can just chain the methods together like:
== Setting defaults
-It is possible to set default values that will be used in every method in your Action Mailer class. To implement this functionality, you just call the public class method <tt>default</tt> which you get for free from <tt>ActionMailer::Base</tt>. This method accepts a Hash as the parameter. You can use any of the headers e-mail messages has, like <tt>:from</tt> as the key. You can also pass in a string as the key, like "Content-Type", but Action Mailer does this out of the box for you, so you won't need to worry about that. Finally, it is also possible to pass in a Proc that will get evaluated when it is needed.
+It is possible to set default values that will be used in every method in your Action Mailer class. To implement this functionality, you just call the public class method <tt>default</tt> which you get for free from <tt>ActionMailer::Base</tt>. This method accepts a Hash as the parameter. You can use any of the headers email messages have, like <tt>:from</tt> as the key. You can also pass in a string as the key, like "Content-Type", but Action Mailer does this out of the box for you, so you won't need to worry about that. Finally, it is also possible to pass in a Proc that will get evaluated when it is needed.
Note that every value you set with this method will get overwritten if you use the same key in your mailer method.
View
43 actionmailer/lib/action_mailer/base.rb
@@ -51,7 +51,7 @@ module ActionMailer
# * <tt>mail</tt> - Allows you to specify email to be sent.
#
# The hash passed to the mail method allows you to specify any header that a <tt>Mail::Message</tt>
- # will accept (any valid Email header including optional fields).
+ # will accept (any valid email header including optional fields).
#
# The mail method, if not passed a block, will inspect your views and send all the views with
# the same name as the method, so the above action would send the +welcome.text.erb+ view
@@ -330,6 +330,21 @@ module ActionMailer
# An overview of all previews is accessible at <tt>http://localhost:3000/rails/mailers</tt>
# on a running development server instance.
#
+ # Previews can also be intercepted in a similar manner as deliveries can be by registering
+ # a preview interceptor that has a <tt>previewing_email</tt> method:
+ #
+ # class CssInlineStyler
+ # def self.previewing_email(message)
+ # # inline CSS styles
+ # end
+ # end
+ #
+ # config.action_mailer.register_preview_interceptor :css_inline_styler
+ #
+ # Note that interceptors need to be registered both with <tt>register_interceptor</tt>
+ # and <tt>register_preview_interceptor</tt> if they should operate on both sending and
+ # previewing emails.
+ #
# = Configuration options
#
# These options are specified on the class level, like
@@ -429,18 +444,30 @@ def register_interceptors(*interceptors)
end
# Register an Observer which will be notified when mail is delivered.
- # Either a class or a string can be passed in as the Observer. If a string is passed in
- # it will be <tt>constantize</tt>d.
+ # Either a class, string or symbol can be passed in as the Observer.
+ # If a string or symbol is passed in it will be camelized and constantized.
def register_observer(observer)
- delivery_observer = (observer.is_a?(String) ? observer.constantize : observer)
+ delivery_observer = case observer
+ when String, Symbol
+ observer.to_s.camelize.constantize
+ else
+ observer
+ end
+
Mail.register_observer(delivery_observer)
end
# Register an Interceptor which will be called before mail is sent.
- # Either a class or a string can be passed in as the Interceptor. If a string is passed in
- # it will be <tt>constantize</tt>d.
+ # Either a class, string or symbol can be passed in as the Interceptor.
+ # If a string or symbol is passed in it will be camelized and constantized.
def register_interceptor(interceptor)
- delivery_interceptor = (interceptor.is_a?(String) ? interceptor.constantize : interceptor)
+ delivery_interceptor = case interceptor
+ when String, Symbol
+ interceptor.to_s.camelize.constantize
+ else
+ interceptor
+ end
+
Mail.register_interceptor(delivery_interceptor)
end
@@ -737,7 +764,7 @@ def mail(headers = {}, &block)
m.charset = charset = headers[:charset]
# Set configure delivery behavior
- wrap_delivery_behavior!(headers.delete(:delivery_method),headers.delete(:delivery_method_options))
+ wrap_delivery_behavior!(headers.delete(:delivery_method), headers.delete(:delivery_method_options))
# Assign all headers except parts_order, content_type and body
assignable = headers.except(:parts_order, :content_type, :body, :template_name, :template_path)
View
43 actionmailer/lib/action_mailer/preview.rb
@@ -9,7 +9,32 @@ module Previews #:nodoc:
#
# config.action_mailer.preview_path = "#{Rails.root}/lib/mailer_previews"
#
- class_attribute :preview_path, instance_writer: false
+ mattr_accessor :preview_path, instance_writer: false
+
+ # :nodoc:
+ mattr_accessor :preview_interceptors, instance_writer: false
+ self.preview_interceptors = []
+
+ # Register one or more Interceptors which will be called before mail is previewed.
+ def register_preview_interceptors(*interceptors)
+ interceptors.flatten.compact.each { |interceptor| register_preview_interceptor(interceptor) }
+ end
+
+ # Register am Interceptor which will be called before mail is previewed.
+ # Either a class or a string can be passed in as the Interceptor. If a
+ # string is passed in it will be <tt>constantize</tt>d.
+ def register_preview_interceptor(interceptor)
+ preview_interceptor = case interceptor
+ when String, Symbol
+ interceptor.to_s.camelize.constantize
+ else
+ interceptor
+ end
+
+ unless preview_interceptors.include?(preview_interceptor)
+ preview_interceptors << preview_interceptor
+ end
+ end
end
end
@@ -23,10 +48,14 @@ def all
descendants
end
- # Returns the mail object for the given email name
+ # Returns the mail object for the given email name. The registered preview
+ # interceptors will be informed so that they can transform the message
+ # as they would if the mail was actually being delivered.
def call(email)
preview = self.new
- preview.public_send(email)
+ message = preview.public_send(email)
+ inform_preview_interceptors(message)
+ message
end
# Returns all of the available email previews
@@ -56,7 +85,7 @@ def preview_name
protected
def load_previews #:nodoc:
- if preview_path?
+ if preview_path
Dir["#{preview_path}/**/*_preview.rb"].each{ |file| require_dependency file }
end
end
@@ -65,8 +94,10 @@ def preview_path #:nodoc:
Base.preview_path
end
- def preview_path? #:nodoc:
- Base.preview_path?
+ def inform_preview_interceptors(message) #:nodoc:
+ Base.preview_interceptors.each do |interceptor|
+ interceptor.previewing_email(message)
+ end
end
end
end
View
2  actionmailer/lib/action_mailer/railtie.rb
@@ -46,7 +46,7 @@ class Railtie < Rails::Railtie # :nodoc:
end
config.after_initialize do
- if ActionMailer::Base.preview_path?
+ if ActionMailer::Base.preview_path
ActiveSupport::Dependencies.autoload_paths << ActionMailer::Base.preview_path
end
end
View
59 actionmailer/test/base_test.rb
@@ -530,6 +530,13 @@ def self.delivered_email(mail)
mail.deliver
end
+ test "you can register an observer using its symbolized underscored name to the mail object that gets informed on email delivery" do
+ ActionMailer::Base.register_observer(:"base_test/my_observer")
+ mail = BaseMailer.welcome
+ MyObserver.expects(:delivered_email).with(mail)
+ mail.deliver
+ end
+
test "you can register multiple observers to the mail object that both get informed on email delivery" do
ActionMailer::Base.register_observers("BaseTest::MyObserver", MySecondObserver)
mail = BaseMailer.welcome
@@ -539,12 +546,18 @@ def self.delivered_email(mail)
end
class MyInterceptor
- def self.delivering_email(mail)
- end
+ def self.delivering_email(mail); end
+ def self.previewing_email(mail); end
end
class MySecondInterceptor
- def self.delivering_email(mail)
+ def self.delivering_email(mail); end
+ def self.previewing_email(mail); end
+ end
+
+ class BaseMailerPreview < ActionMailer::Preview
+ def welcome
+ BaseMailer.welcome
end
end
@@ -562,6 +575,13 @@ def self.delivering_email(mail)
mail.deliver
end
+ test "you can register an interceptor using its symbolized underscored name to the mail object that gets passed the mail object before delivery" do
+ ActionMailer::Base.register_interceptor(:"base_test/my_interceptor")
+ mail = BaseMailer.welcome
+ MyInterceptor.expects(:delivering_email).with(mail)
+ mail.deliver
+ end
+
test "you can register multiple interceptors to the mail object that both get passed the mail object before delivery" do
ActionMailer::Base.register_interceptors("BaseTest::MyInterceptor", MySecondInterceptor)
mail = BaseMailer.welcome
@@ -570,6 +590,39 @@ def self.delivering_email(mail)
mail.deliver
end
+ test "you can register a preview interceptor to the mail object that gets passed the mail object before previewing" do
+ ActionMailer::Base.register_preview_interceptor(MyInterceptor)
+ mail = BaseMailer.welcome
+ BaseMailerPreview.stubs(:welcome).returns(mail)
+ MyInterceptor.expects(:previewing_email).with(mail)
+ BaseMailerPreview.call(:welcome)
+ end
+
+ test "you can register a preview interceptor using its stringified name to the mail object that gets passed the mail object before previewing" do
+ ActionMailer::Base.register_preview_interceptor("BaseTest::MyInterceptor")
+ mail = BaseMailer.welcome
+ BaseMailerPreview.stubs(:welcome).returns(mail)
+ MyInterceptor.expects(:previewing_email).with(mail)
+ BaseMailerPreview.call(:welcome)
+ end
+
+ test "you can register an interceptor using its symbolized underscored name to the mail object that gets passed the mail object before previewing" do
+ ActionMailer::Base.register_preview_interceptor(:"base_test/my_interceptor")
+ mail = BaseMailer.welcome
+ BaseMailerPreview.stubs(:welcome).returns(mail)
+ MyInterceptor.expects(:previewing_email).with(mail)
+ BaseMailerPreview.call(:welcome)
+ end
+
+ test "you can register multiple preview interceptors to the mail object that both get passed the mail object before previewing" do
+ ActionMailer::Base.register_preview_interceptors("BaseTest::MyInterceptor", MySecondInterceptor)
+ mail = BaseMailer.welcome
+ BaseMailerPreview.stubs(:welcome).returns(mail)
+ MyInterceptor.expects(:previewing_email).with(mail)
+ MySecondInterceptor.expects(:previewing_email).with(mail)
+ BaseMailerPreview.call(:welcome)
+ end
+
test "being able to put proc's into the defaults hash and they get evaluated on mail sending" do
mail1 = ProcMailer.welcome['X-Proc-Method']
yesterday = 1.day.ago
View
29 actionmailer/test/delivery_methods_test.rb
@@ -38,8 +38,10 @@ class DefaultsDeliveryMethodsTest < ActiveSupport::TestCase
end
test "default sendmail settings" do
- settings = {location: '/usr/sbin/sendmail',
- arguments: '-i -t'}
+ settings = {
+ location: '/usr/sbin/sendmail',
+ arguments: '-i -t'
+ }
assert_equal settings, ActionMailer::Base.sendmail_settings
end
end
@@ -138,13 +140,15 @@ def teardown
end
test "default delivery options can be overridden per mail instance" do
- settings = { address: "localhost",
- port: 25,
- domain: 'localhost.localdomain',
- user_name: nil,
- password: nil,
- authentication: nil,
- enable_starttls_auto: true }
+ settings = {
+ address: "localhost",
+ port: 25,
+ domain: 'localhost.localdomain',
+ user_name: nil,
+ password: nil,
+ authentication: nil,
+ enable_starttls_auto: true
+ }
assert_equal settings, ActionMailer::Base.smtp_settings
overridden_options = {user_name: "overridden", password: "somethingobtuse"}
mail_instance = DeliveryMailer.welcome(delivery_method_options: overridden_options)
@@ -164,6 +168,13 @@ def teardown
end
end
+ test "undefined delivery methods raises errors" do
+ DeliveryMailer.delivery_method = nil
+ assert_raise RuntimeError do
+ DeliveryMailer.welcome.deliver
+ end
+ end
+
test "does not perform deliveries if requested" do
DeliveryMailer.perform_deliveries = false
DeliveryMailer.deliveries.clear
View
83 actionpack/CHANGELOG.md
@@ -1,3 +1,84 @@
+* Add new config option `config.action_dispatch.cookies_serializer` for
+ specifying a serializer for the signed and encrypted cookie jars.
+
+ The possible values are:
+
+ * `:json` - serialize cookie values with `JSON`
+ * `:marshal` - serialize cookie values with `Marshal`
+ * `:hybrid` - transparently migrate existing `Marshal` cookie values to `JSON`
+
+ For new apps `:json` option is added by default and `:marshal` is used
+ when no option is specified to maintain backwards compatibility.
+
+ *Łukasz Sarnacki*, *Matt Aimonetti*, *Guillermo Iguaran*, *Godfrey Chan*, *Rafael Mendonça França*
+
+* `FlashHash` now behaves like a `HashWithIndifferentAccess`.
+
+ *Guillermo Iguaran*
+
+* Set the `:shallow_path` scope option as each scope is generated rather than
+ waiting until the `shallow` option is set. Also make the behavior of the
+ `:shallow` resource option consistent with the behavior of the `shallow` method.
+
+ Fixes #12498.
+
+ *Andrew White*, *Aleksi Aalto*
+
+* Properly require `action_view` in `AbstractController::Rendering` to prevent
+ uninitialized constant error for `ENCODING_FLAG`.
+
+ *Philipe Fatio*
+
+* Do not discard query parameters that form a hash with the same root key as
+ the `wrapper_key` for a request using `wrap_parameters`.
+
+ *Josh Jordan*
+
+* Ensure that `request.filtered_parameters` is reset between calls to `process`
+ in `ActionController::TestCase`.
+
+ Fixes #13803.
+
+ *Andrew White*
+
+* Fix `rake routes` error when `Rails::Engine` with empty routes is mounted.
+
+ Fixes #13810.
+
+ *Maurizio De Santis*
+
+* Log which keys were affected by deep munge.
+
+ Deep munge solves CVE-2013-0155 security vulnerability, but its
+ behaviour is definately confusing, so now at least information
+ about for which keys values were set to nil is visible in logs.
+
+ *Łukasz Sarnacki*
+
+* Automatically convert dashes to underscores for shorthand routes, e.g:
+
+ get '/our-work/latest'
+
+ When running `rake routes` you will get the following output:
+
+ Prefix Verb URI Pattern Controller#Action
+ our_work_latest GET /our-work/latest(.:format) our_work#latest
+
+ *Mikko Johansson*
+
+* Automatically convert dashes to underscores for url helpers, e.g:
+
+ get '/contact-us' => 'pages#contact'
+ get '/about-us' => 'pages#about_us'
+
+ When running `rake routes` you will get the following output:
+
+ Prefix Verb URI Pattern Controller#Action
+ contact_us GET /contact-us(.:format) pages#contact
+ about_us GET /about-us(.:format) pages#about_us
+
+ *Amr Tamimi*
+
* Fix stream closing when sending file with `ActionController::Live` included.
Fixes #12381
@@ -26,7 +107,7 @@
*Andrew White*
-* Show full route constraints in error message
+* Show full route constraints in error message.
When an optimized helper fails to generate, show the full route constraints
in the error message. Previously it would only show the contraints that were
View
6 actionpack/RUNNING_UNIT_TESTS.rdoc
@@ -1,10 +1,10 @@
== Running with Rake
The easiest way to run the unit tests is through Rake. The default task runs
-the entire test suite for all classes. For more information, checkout the
-full array of rake tasks with "rake -T"
+the entire test suite for all classes. For more information, check out the
+full array of rake tasks with "rake -T".
-Rake can be found at http://rake.rubyforge.org
+Rake can be found at http://rake.rubyforge.org.
== Running by hand
View
1  actionpack/lib/abstract_controller/rendering.rb
@@ -1,5 +1,6 @@
require 'active_support/concern'
require 'active_support/core_ext/class/attribute'
+require 'action_view'
require 'action_view/view_paths'
require 'set'
View
9 actionpack/lib/action_controller/log_subscriber.rb
@@ -53,6 +53,15 @@ def unpermitted_parameters(event)
debug("Unpermitted parameters: #{unpermitted_keys.join(", ")}")
end
+ def deep_munge(event)
+ message = "Value for params[:#{event.payload[:keys].join('][:')}] was set"\
+ "to nil, because it was one of [], [null] or [null, null, ...]."\
+ "Go to http://guides.rubyonrails.org/security.html#unsafe-query-generation"\
+ "for more information."\
+
+ debug(message)
+ end
+
%w(write_fragment read_fragment exist_fragment?
expire_fragment expire_page write_page).each do |method|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
View
28 actionpack/lib/action_controller/metal/mime_responds.rb
@@ -236,6 +236,18 @@ def clear_respond_to
# end
# end
#
+ # You can also set an array of variants:
+ #
+ # request.variant = [:tablet, :phone]
+ #
+ # which will work similarly to formats and MIME types negotiation. If there will be no
+ # :tablet variant declared, :phone variant will be picked:
+ #
+ # respond_to do |format|
+ # format.html.none
+ # format.html.phone # this gets rendered
+ # end
+ #
# Be sure to check the documentation of +respond_with+ and
# <tt>ActionController::MimeResponds.respond_to</tt> for more examples.
def respond_to(*mimes, &block)
@@ -488,7 +500,7 @@ def response
response
else # `format.html{ |variant| variant.phone }` - variant block syntax
variant_collector = VariantCollector.new(@variant)
- response.call(variant_collector) #call format block with variants collector
+ response.call(variant_collector) # call format block with variants collector
variant_collector.variant
end
end
@@ -519,15 +531,15 @@ def method_missing(name, *args, &block)
end
def variant
- key = if @variant.nil?
- :none
- elsif @variants.has_key?(@variant)
- @variant
+ if @variant.nil?
+ @variants[:none] || @variants[:any]
+ elsif (@variants.keys & @variant).any?
+ @variant.each do |v|
+ return @variants[v] if @variants.key?(v)
+ end
else
- :any
+ @variants[:any]
end
-
- @variants[key]
end
end
end
View
15 actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -231,7 +231,12 @@ def inherited(klass)
# by the metal call stack.
def process_action(*args)
if _wrapper_enabled?
- wrapped_hash = _wrap_parameters request.request_parameters
+ if request.parameters[_wrapper_key].present?
+ wrapped_hash = _extract_parameters(request.parameters)
+ else
+ wrapped_hash = _wrap_parameters request.request_parameters
+ end
+
wrapped_keys = request.request_parameters.keys
wrapped_filtered_hash = _wrap_parameters request.filtered_parameters.slice(*wrapped_keys)
@@ -259,14 +264,16 @@ def _wrapper_formats
# Returns the list of parameters which will be selected for wrapped.
def _wrap_parameters(parameters)
- value = if include_only = _wrapper_options.include
+ { _wrapper_key => _extract_parameters(parameters) }
+ end
+
+ def _extract_parameters(parameters)
+ if include_only = _wrapper_options.include
parameters.slice(*include_only)
else
exclude = _wrapper_options.exclude || []
parameters.except(*(exclude + EXCLUDE_PARAMETERS))
end
-
- { _wrapper_key => value }
end
# Checks if we should perform parameters wrapping.
View
3  actionpack/lib/action_controller/test_case.rb
@@ -213,6 +213,9 @@ def assign_parameters(routes, controller_path, action, parameters = {})
# Clear the combined params hash in case it was already referenced.
@env.delete("action_dispatch.request.parameters")
+ # Clear the filter cache variables so they're not stale
+ @filtered_parameters = @filtered_env = @filtered_path = nil
+
params = self.request_parameters.dup
%w(controller action only_path).each do |k|
params.delete(k)
View
10 actionpack/lib/action_dispatch.rb
@@ -74,18 +74,16 @@ module Http
autoload :MimeNegotiation
autoload :Parameters
autoload :ParameterFilter
- autoload :FilterParameters
- autoload :FilterRedirect
autoload :Upload
autoload :UploadedFile, 'action_dispatch/http/upload'
autoload :URL
end
module Session
- autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store'
- autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store'
- autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store'
- autoload :CacheStore, 'action_dispatch/middleware/session/cache_store'
+ autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store'
+ autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store'
+ autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store'
+ autoload :CacheStore, 'action_dispatch/middleware/session/cache_store'
end
mattr_accessor :test_app
View
9 actionpack/lib/action_dispatch/http/filter_redirect.rb
@@ -5,7 +5,8 @@ module FilterRedirect
FILTERED = '[FILTERED]'.freeze # :nodoc:
def filtered_location
- if !location_filter.empty? && location_filter_match?
+ filters = location_filter
+ if !filters.empty? && location_filter_match?(filters)
FILTERED
else
location
@@ -15,15 +16,15 @@ def filtered_location
private
def location_filter
- if request.present?
+ if request
request.env['action_dispatch.redirect_filter'] || []
else
[]
end
end
- def location_filter_match?
- location_filter.any? do |filter|
+ def location_filter_match?(filters)
+ filters.any? do |filter|
if String === filter
location.include?(filter)
elsif Regexp === filter
View
6 actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -68,10 +68,12 @@ def formats
# Sets the \variant for template.
def variant=(variant)
- if variant.is_a? Symbol
+ if variant.is_a?(Symbol)
+ @variant = [variant]
+ elsif variant.is_a?(Array) && variant.any? && variant.all?{ |v| v.is_a?(Symbol) }
@variant = variant
else
- raise ArgumentError, "request.variant must be set to a Symbol, not a #{variant.class}. " \
+ raise ArgumentError, "request.variant must be set to a Symbol or an Array of Symbols, not a #{variant.class}. " \
"For security reasons, never directly set the variant to a user-provided value, " \
"like params[:variant].to_sym. Check user-provided value against a whitelist first, " \
"then set the variant: request.variant = :tablet if params[:variant] == 'tablet'"
View
3  actionpack/lib/action_dispatch/http/response.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/module/attribute_accessors'
+require 'action_dispatch/http/filter_redirect'
require 'monitor'
module ActionDispatch # :nodoc:
@@ -312,7 +313,7 @@ def rack_response(status, header)
header.delete CONTENT_TYPE
[status, header, []]
else
- [status, header, self]
+ [status, header, Rack::BodyProxy.new(self){}]
end
end
end
View
105 actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -23,15 +23,15 @@ def cookie_jar
# # This cookie will be deleted when the user's browser is closed.
# cookies[:user_name] = "david"
#
- # # Assign an array of values to a cookie.
- # cookies[:lat_lon] = [47.68, -122.37]
+ # # Cookie values are String based. Other data types need to be serialized.
+ # cookies[:lat_lon] = JSON.generate([47.68, -122.37])
#
# # Sets a cookie that expires in 1 hour.
# cookies[:login] = { value: "XJ-122", expires: 1.hour.from_now }
#
# # Sets a signed cookie, which prevents users from tampering with its value.
- # # The cookie is signed by your app's <tt>secrets.secret_key_base</tt> value.
- # # It can be read using the signed method <tt>cookies.signed[:name]</tt>
+ # # The cookie is signed by your app's `secrets.secret_key_base` value.
+ # # It can be read using the signed method `cookies.signed[:name]`
# cookies.signed[:user_id] = current_user.id
#
# # Sets a "permanent" cookie (which expires in 20 years from now).
@@ -42,10 +42,10 @@ def cookie_jar
#
# Examples of reading:
#
- # cookies[:user_name] # => "david"
- # cookies.size # => 2
- # cookies[:lat_lon] # => [47.68, -122.37]
- # cookies.signed[:login] # => "XJ-122"
+ # cookies[:user_name] # => "david"
+ # cookies.size # => 2
+ # JSON.parse(cookies[:lat_lon]) # => [47.68, -122.37]
+ # cookies.signed[:login] # => "XJ-122"
#
# Example for deleting:
#
@@ -63,7 +63,7 @@ def cookie_jar
#
# The option symbols for setting cookies are:
#
- # * <tt>:value</tt> - The cookie's value or list of values (as an array).
+ # * <tt>:value</tt> - The cookie's value.
# * <tt>:path</tt> - The path for which this cookie applies. Defaults to the root
# of the application.
# * <tt>:domain</tt> - The domain for which this cookie applies so you can
@@ -89,6 +89,7 @@ class Cookies
ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze
SECRET_TOKEN = "action_dispatch.secret_token".freeze
SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze
+ COOKIES_SERIALIZER = "action_dispatch.cookies_serializer".freeze
# Cookies can typically store 4096 bytes.
MAX_COOKIE_SIZE = 4096
@@ -180,7 +181,7 @@ def initialize(*args)
def verify_and_upgrade_legacy_signed_message(name, signed_message)
@legacy_verifier.verify(signed_message).tap do |value|
- self[name] = value
+ self[name] = { value: value }
end
rescue ActiveSupport::MessageVerifier::InvalidSignature
nil
@@ -210,7 +211,8 @@ def self.options_for_env(env) #:nodoc:
encrypted_signed_cookie_salt: env[ENCRYPTED_SIGNED_COOKIE_SALT] || '',
secret_token: env[SECRET_TOKEN],
secret_key_base: env[SECRET_KEY_BASE],
- upgrade_legacy_signed_cookies: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present?
+ upgrade_legacy_signed_cookies: env[SECRET_TOKEN].present? && env[SECRET_KEY_BASE].present?,
+ serializer: env[COOKIES_SERIALIZER]
}
end
@@ -372,28 +374,89 @@ def []=(name, options)
end
end
+ class JsonSerializer
+ def self.load(value)
+ JSON.parse(value, quirks_mode: true)
+ end
+
+ def self.dump(value)
+ JSON.generate(value, quirks_mode: true)
+ end
+ end
+
+ # Passing the NullSerializer downstream to the Message{Encryptor,Verifier}
+ # allows us to handle the (de)serialization step within the cookie jar,
+ # which gives us the opportunity to detect and migrate legacy cookies.
+ class NullSerializer
+ def self.load(value)
+ value
+ end
+
+ def self.dump(value)
+ value
+ end
+ end
+
+ module SerializedCookieJars
+ MARSHAL_SIGNATURE = "\x04\x08".freeze
+
+ protected
+ def needs_migration?(value)
+ @options[:serializer] == :hybrid && value.start_with?(MARSHAL_SIGNATURE)
+ end
+
+ def serialize(name, value)
+ serializer.dump(value)
+ end
+
+ def deserialize(name, value)
+ if value
+ if needs_migration?(value)
+ Marshal.load(value).tap do |v|
+ self[name] = { value: v }
+ end
+ else
+ serializer.load(value)
+ end
+ end
+ end
+
+ def serializer
+ serializer = @options[:serializer] || :marshal
+ case serializer
+ when :marshal
+ Marshal
+ when :json, :hybrid
+ JsonSerializer
+ else
+ serializer
+ end
+ end
+ end
+
class SignedCookieJar #:nodoc:
include ChainedCookieJars
+ include SerializedCookieJars
def initialize(parent_jar, key_generator, options = {})
@parent_jar = parent_jar
@options = options
secret = key_generator.generate_key(@options[:signed_cookie_salt])
- @verifier = ActiveSupport::MessageVerifier.new(secret)
+ @verifier = ActiveSupport::MessageVerifier.new(secret, serializer: NullSerializer)
end
def [](name)
if signed_message = @parent_jar[name]
- verify(signed_message)
+ deserialize name, verify(signed_message)
end
end
def []=(name, options)
if options.is_a?(Hash)
options.symbolize_keys!
- options[:value] = @verifier.generate(options[:value])
+ options[:value] = @verifier.generate(serialize(name, options[:value]))
else
- options = { :value => @verifier.generate(options) }
+ options = { :value => @verifier.generate(serialize(name, options)) }
end
raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
@@ -417,13 +480,14 @@ class UpgradeLegacySignedCookieJar < SignedCookieJar #:nodoc:
def [](name)
if signed_message = @parent_jar[name]
- verify(signed_message) || verify_and_upgrade_legacy_signed_message(name, signed_message)
+ deserialize(name, verify(signed_message)) || verify_and_upgrade_legacy_signed_message(name, signed_message)
end
end
end
class EncryptedCookieJar #:nodoc:
include ChainedCookieJars
+ include SerializedCookieJars
def initialize(parent_jar, key_generator, options = {})
if ActiveSupport::LegacyKeyGenerator === key_generator
@@ -435,12 +499,12 @@ def initialize(parent_jar, key_generator, options = {})
@options = options
secret = key_generator.generate_key(@options[:encrypted_cookie_salt])
sign_secret = key_generator.generate_key(@options[:encrypted_signed_cookie_salt])
- @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret)
+ @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: NullSerializer)
end
def [](name)
if encrypted_message = @parent_jar[name]
- decrypt_and_verify(encrypted_message)
+ deserialize name, decrypt_and_verify(encrypted_message)
end
end
@@ -450,7 +514,8 @@ def []=(name, options)
else
options = { :value => options }
end
- options[:value] = @encryptor.encrypt_and_sign(options[:value])
+
+ options[:value] = @encryptor.encrypt_and_sign(serialize(name, options[:value]))
raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
@parent_jar[name] = options
@@ -473,7 +538,7 @@ class UpgradeLegacyEncryptedCookieJar < EncryptedCookieJar #:nodoc:
def [](name)
if encrypted_or_signed_message = @parent_jar[name]
- decrypt_and_verify(encrypted_or_signed_message) || verify_and_upgrade_legacy_signed_message(name, encrypted_or_signed_message)
+ deserialize(name, decrypt_and_verify(encrypted_or_signed_message)) || verify_and_upgrade_legacy_signed_message(name, encrypted_or_signed_message)
end
end
end
View
27 actionpack/lib/action_dispatch/middleware/flash.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/hash/keys'
+
module ActionDispatch
class Request < Rack::Request
# Access the contents of the flash. Use <tt>flash["notice"]</tt> to
@@ -50,13 +52,14 @@ def initialize(flash)
end
def []=(k, v)
+ k = k.to_s
@flash[k] = v
@flash.discard(k)
v
end
def [](k)
- @flash[k]
+ @flash[k.to_s]
end
# Convenience accessor for <tt>flash.now[:alert]=</tt>.
@@ -92,8 +95,8 @@ def to_session_value
end
def initialize(flashes = {}, discard = []) #:nodoc:
- @discard = Set.new(discard)
- @flashes = flashes
+ @discard = Set.new(stringify_array(discard))
+ @flashes = flashes.stringify_keys
@now = nil
end
@@ -106,17 +109,18 @@ def initialize_copy(other)
end
def []=(k, v)
+ k = k.to_s
@discard.delete k
@flashes[k] = v
end
def [](k)
- @flashes[k]
+ @flashes[k.to_s]
end
def update(h) #:nodoc:
- @discard.subtract h.keys
- @flashes.update h
+ @discard.subtract stringify_array(h.keys)
+ @flashes.update h.stringify_keys
self
end
@@ -129,6 +133,7 @@ def key?(name)
end
def delete(key)
+ key = key.to_s
@discard.delete key
@flashes.delete key
self
@@ -155,7 +160,7 @@ def each(&block)
def replace(h) #:nodoc:
@discard.clear
- @flashes.replace h
+ @flashes.replace h.stringify_keys
self
end
@@ -186,6 +191,7 @@ def now
# flash.keep # keeps the entire flash
# flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded
def keep(k = nil)
+ k = k.to_s if k
@discard.subtract Array(k || keys)
k ? self[k] : self
end
@@ -195,6 +201,7 @@ def keep(k = nil)
# flash.discard # discard the entire flash at the end of the current action
# flash.discard(:warning) # discard only the "warning" entry at the end of the current action
def discard(k = nil)
+ k = k.to_s if k
@discard.merge Array(k || keys)
k ? self[k] : self
end
@@ -231,6 +238,12 @@ def notice=(message)
def now_is_loaded?
@now
end
+
+ def stringify_array(array)
+ array.map do |item|
+ item.kind_of?(Symbol) ? item.to_s : item
+ end
+ end
end
def initialize(app)
View
13 actionpack/lib/action_dispatch/middleware/reloader.rb
@@ -1,3 +1,5 @@
+require 'active_support/deprecation/reporting'
+
module ActionDispatch
# ActionDispatch::Reloader provides prepare and cleanup callbacks,
# intended to assist with code reloading during development.
@@ -25,19 +27,26 @@ module ActionDispatch
#
class Reloader
include ActiveSupport::Callbacks
+ include ActiveSupport::Deprecation::Reporting
- define_callbacks :prepare, :scope => :name
- define_callbacks :cleanup, :scope => :name
+ define_callbacks :prepare
+ define_callbacks :cleanup
# Add a prepare callback. Prepare callbacks are run before each request, prior
# to ActionDispatch::Callback's before callbacks.
def self.to_prepare(*args, &block)
+ unless block_given?
+ warn "to_prepare without a block is deprecated. Please use a block"
+ end
set_callback(:prepare, *args, &block)
end
# Add a cleanup callback. Cleanup callbacks are run after each request is
# complete (after #close is called on the response body).
def self.to_cleanup(*args, &block)
+ unless block_given?
+ warn "to_cleanup without a block is deprecated. Please use a block"
+ end
set_callback(:cleanup, *args, &block)
end
View
13 actionpack/lib/action_dispatch/request/utils.rb
@@ -7,18 +7,23 @@ class Utils # :nodoc:
class << self
# Remove nils from the params hash
- def deep_munge(hash)
+ def deep_munge(hash, keys = [])
return hash unless perform_deep_munge
hash.each do |k, v|
+ keys << k
case v
when Array
- v.grep(Hash) { |x| deep_munge(x) }
+ v.grep(Hash) { |x| deep_munge(x, keys) }
v.compact!
- hash[k] = nil if v.empty?
+ if v.empty?
+ hash[k] = nil
+ ActiveSupport::Notifications.instrument("deep_munge.action_controller", keys: keys)
+ end
when Hash
- deep_munge(v)
+ deep_munge(v, keys)
end
+ keys.pop
end
hash
View
6 actionpack/lib/action_dispatch/routing/inspector.rb
@@ -194,9 +194,9 @@ def draw_header(routes)
end
def widths(routes)
- [routes.map { |r| r[:name].length }.max,
- routes.map { |r| r[:verb].length }.max,
- routes.map { |r| r[:path].length }.max]
+ [routes.map { |r| r[:name].length }.max || 0,
+ routes.map { |r| r[:verb].length }.max || 0,
+ routes.map { |r| r[:path].length }.max || 0]
end
end
View
23 actionpack/lib/action_dispatch/routing/mapper.rb
@@ -707,6 +707,10 @@ def scope(*args)
options[:path] = args.flatten.join('/') if args.any?
options[:constraints] ||= {}
+ unless shallow?
+ options[:shallow_path] = options[:path] if args.any?
+ end
+
if options[:constraints].is_a?(Hash)
defaults = options[:constraints].select do
|k, v| URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Fixnum))
@@ -1369,7 +1373,7 @@ def namespace(path, options = {})
end
def shallow
- scope(:shallow => true, :shallow_path => @scope[:path]) do
+ scope(:shallow => true) do
yield
end
end
@@ -1410,6 +1414,7 @@ def match(path, *rest)
path_without_format = _path.to_s.sub(/\(\.:format\)$/, '')
if using_match_shorthand?(path_without_format, route_options)
route_options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1')
+ route_options[:to].tr!("-", "_")
end
decomposed_match(_path, route_options)
@@ -1440,8 +1445,8 @@ def add_route(action, options) # :nodoc:
path = path_for_action(action, options.delete(:path))
action = action.to_s.dup
- if action =~ /^[\w\/]+$/
- options[:action] ||= action unless action.include?("/")
+ if action =~ /^[\w\-\/]+$/
+ options[:action] ||= action.tr('-', '_') unless action.include?("/")
else
action = nil
end
@@ -1489,6 +1494,13 @@ def apply_common_behavior_for(method, resources, options, &block) #:nodoc:
return true
end
+ if options.delete(:shallow)
+ shallow do
+ send(method, resources.pop, options, &block)
+ end
+ return true
+ end
+
if resource_scope?
nested { send(method, resources.pop, options, &block) }
return true
@@ -1606,10 +1618,11 @@ def action_path(name, path = nil) #:nodoc:
def prefix_name_for_action(as, action) #:nodoc:
if as
- as.to_s
+ prefix = as
elsif !canonical_action?(action, @scope[:scope_level])
- action.to_s
+ prefix = action
end
+ prefix.to_s.tr('-', '_') if prefix
end
def name_for_action(as, action) #:nodoc:
View
16 actionpack/test/controller/filters_test.rb
@@ -225,6 +225,10 @@ class ConditionalOptionsSkipFilter < ConditionalFilterController
skip_before_filter :clean_up_tmp, if: -> { true }
end
+ class ClassController < ConditionalFilterController
+ before_filter ConditionalClassFilter
+ end
+
class PrependingController < TestController
prepend_before_filter :wonderful_life
# skip_before_filter :fire_flash
@@ -610,6 +614,18 @@ def test_running_conditional_skip_options
assert_equal %w( ensure_login ), assigns["ran_filter"]
end
+ def test_skipping_class_filters
+ test_process(ClassController)
+ assert_equal true, assigns["ran_class_filter"]
+
+ skipping_class_controller = Class.new(ClassController) do
+ skip_before_filter ConditionalClassFilter
+ end
+
+ test_process(skipping_class_controller)
+ assert_nil assigns['ran_class_filter']
+ end
+
def test_running_collection_condition_filters
test_process(ConditionalCollectionFilterController)
assert_equal %w( ensure_login ), assigns["ran_filter"]
View
10 actionpack/test/controller/flash_hash_test.rb
@@ -67,6 +67,16 @@ def test_from_session_value
assert_equal({'flashes' => {'message' => 'Hello'}, 'discard' => %w[message]}, hash.to_session_value)
end
+ def test_from_session_value_on_json_serializer
+ decrypted_data = "{ \"session_id\":\"d98bdf6d129618fc2548c354c161cfb5\", \"flash\":{\"discard\":[], \"flashes\":{\"message\":\"hey you\"}} }"
+ session = ActionDispatch::Cookies::JsonSerializer.load(decrypted_data)
+ hash = Flash::FlashHash.from_session_value(session['flash'])
+
+ assert_equal({'discard' => %w[message], 'flashes' => { 'message' => 'hey you'}}, hash.to_session_value)
+ assert_equal "hey you", hash[:message]
+ assert_equal "hey you", hash["message"]
+ end
+
def test_empty?
assert @hash.empty?
@hash['zomg'] = 'bears'
View
8 actionpack/test/controller/flash_test.rb
@@ -175,13 +175,13 @@ def test_keep_and_discard_return_values
assert_equal(:foo_indeed, flash.discard(:foo)) # valid key passed
assert_nil flash.discard(:unknown) # non existent key passed
- assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.discard().to_hash) # nothing passed
- assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.discard(nil).to_hash) # nothing passed
+ assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.discard().to_hash) # nothing passed
+ assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.discard(nil).to_hash) # nothing passed
assert_equal(:foo_indeed, flash.keep(:foo)) # valid key passed
assert_nil flash.keep(:unknown) # non existent key passed
- assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.keep().to_hash) # nothing passed
- assert_equal({:foo => :foo_indeed, :bar => :bar_indeed}, flash.keep(nil).to_hash) # nothing passed
+ assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.keep().to_hash) # nothing passed
+ assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.keep(nil).to_hash) # nothing passed
end
def test_redirect_to_with_alert
View
2  actionpack/test/controller/http_token_authentication_test.rb
@@ -21,7 +21,7 @@ def show
private
def authenticate
- authenticate_or_request_with_http_token do |token, options|
+ authenticate_or_request_with_http_token do |token, _|
token == 'lifo'
end
end
View
11 actionpack/test/controller/log_subscriber_test.rb
@@ -137,6 +137,17 @@ def test_process_action_with_parameters
assert_equal 'Parameters: {"id"=>"10"}', logs[1]
end
+ def test_multiple_process_with_parameters
+ get :show, :id => '10'
+ get :show, :id => '20'
+
+ wait
+
+ assert_equal 6, logs.size
+ assert_equal 'Parameters: {"id"=>"10"}', logs[1]
+ assert_equal 'Parameters: {"id"=>"20"}', logs[4]
+ end
+
def test_process_action_with_wrapped_parameters
@request.env['CONTENT_TYPE'] = 'application/json'
post :show, :id => '10', :name => 'jose'
View
25 actionpack/test/controller/mime/respond_to_test.rb
@@ -671,6 +671,10 @@ def test_variant_any
end
def test_variant_any_any
+ get :variant_any_any
+ assert_equal "text/html", @response.content_type
+ assert_equal "any", @response.body
+
@request.variant = :phone
get :variant_any_any
assert_equal "text/html", @response.content_type
@@ -740,4 +744,25 @@ def test_format_any_variant_any
assert_equal "text/javascript", @response.content_type
assert_equal "tablet", @response.body
end
+
+ def test_variant_negotiation_inline_syntax
+ @request.variant = [:tablet, :phone]
+ get :variant_inline_syntax_without_block
+ assert_equal "text/html", @response.content_type
+ assert_equal "phone", @response.body
+ end
+
+ def test_variant_negotiation_block_syntax
+ @request.variant = [:tablet, :phone]
+ get :variant_plus_none_for_format
+ assert_equal "text/html", @response.content_type
+ assert_equal "phone", @response.body
+ end
+
+ def test_variant_negotiation_without_block
+ @request.variant = [:tablet, :phone]
+ get :variant_inline_syntax_without_block
+ assert_equal "text/html", @response.content_type
+ assert_equal "phone", @response.body
+ end
end
View
10 actionpack/test/controller/parameters/parameters_require_test.rb
@@ -1,10 +0,0 @@
-require 'abstract_unit'
-require 'action_controller/metal/strong_parameters'
-
-class ParametersRequireTest < ActiveSupport::TestCase
- test "required parameters must be present not merely not nil" do
- assert_raises(ActionController::ParameterMissing) do
- ActionController::Parameters.new(person: {}).require(:person)
- end
- end
-end
View
20 actionpack/test/controller/params_wrapper_test.rb
@@ -188,6 +188,26 @@ def test_not_wrapping_abstract_model
assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu', 'title' => 'Developer' }})
end
end
+
+ def test_preserves_query_string_params
+ with_default_wrapper_options do
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ get :parse, { 'user' => { 'username' => 'nixon' } }
+ assert_parameters(
+ {'user' => { 'username' => 'nixon' } }
+ )
+ end
+ end
+
+ def test_empty_parameter_set
+ with_default_wrapper_options do
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, {}
+ assert_parameters(
+ {'user' => { } }
+ )
+ end
+ end
end
class NamespacedParamsWrapperTest < ActionController::TestCase
View
8 actionpack/test/controller/required_params_test.rb
@@ -25,3 +25,11 @@ class ActionControllerRequiredParamsTest < ActionController::TestCase
assert_response :ok
end
end
+
+class ParametersRequireTest < ActiveSupport::TestCase
+ test "required parameters must be present not merely not nil" do
+ assert_raises(ActionController::ParameterMissing) do
+ ActionController::Parameters.new(person: {}).require(:person)
+ end
+ end
+end
View
8 actionpack/test/controller/test_case_test.rb
@@ -706,6 +706,14 @@ def test_params_reset_after_post_request
assert @request.params[:foo].blank?
end
+ def test_filtered_parameters_reset_between_requests
+ get :no_op, :foo => "bar"
+ assert_equal "bar", @request.filtered_parameters[:foo]
+
+ get :no_op, :foo => "baz"
+ assert_equal "baz", @request.filtered_parameters[:foo]
+ end
+
def test_symbolized_path_params_reset_after_request
get :test_params, :id => "foo"
assert_equal "foo", @request.symbolized_path_parameters[:id]
View
3  actionpack/test/controller/url_for_test.rb
@@ -204,9 +204,6 @@ def test_trailing_slash_with_params
end
def test_relative_url_root_is_respected
- # ROUTES TODO: Tests should not have to pass :relative_url_root directly. This
- # should probably come from routes.
-
add_host!
assert_equal('https://www.basecamphq.com/subdir/c/a/i',
W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https', :script_name => '/subdir')
View
152 actionpack/test/dispatch/cookies_test.rb
@@ -11,6 +11,16 @@
require 'active_support/message_verifier'
class CookiesTest < ActionController::TestCase
+ class CustomSerializer
+ def self.load(value)
+ value.to_s + " and loaded"
+ end
+
+ def self.dump(value)
+ value.to_s + " was dumped"
+ end
+ end
+
class TestController < ActionController::Base
def authenticate
cookies["user_name"] = "david"
@@ -359,9 +369,72 @@ def test_read_permanent_cookie
assert_equal 'Jamie', @controller.send(:cookies).permanent[:user_name]
end
- def test_signed_cookie
+ def test_signed_cookie_using_default_serializer
get :set_signed_cookie
- assert_equal 45, @controller.send(:cookies).signed[:user_id]
+ cookies = @controller.send :cookies
+ assert_not_equal 45, cookies[:user_id]
+ assert_equal 45, cookies.signed[:user_id]
+ end
+
+ def test_signed_cookie_using_marshal_serializer
+ @request.env["action_dispatch.cookies_serializer"] = :marshal
+ get :set_signed_cookie
+ cookies = @controller.send :cookies
+ assert_not_equal 45, cookies[:user_id]
+ assert_equal 45, cookies.signed[:user_id]
+ end
+
+ def test_signed_cookie_using_json_serializer
+ @request.env["action_dispatch.cookies_serializer"] = :json
+ get :set_signed_cookie
+ cookies = @controller.send :cookies
+ assert_not_equal 45, cookies[:user_id]
+ assert_equal 45, cookies.signed[:user_id]
+ end
+
+ def test_signed_cookie_using_custom_serializer
+ @request.env["action_dispatch.cookies_serializer"] = CustomSerializer
+ get :set_signed_cookie
+ assert_not_equal 45, cookies[:user_id]
+ assert_equal '45 was dumped and loaded', cookies.signed[:user_id]
+ end
+
+ def test_signed_cookie_using_hybrid_serializer_can_migrate_marshal_dumped_value_to_json
+ @request.env["action_dispatch.cookies_serializer"] = :hybrid
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ signed_cookie_salt = @request.env["action_dispatch.signed_cookie_salt"]
+ secret = key_generator.generate_key(signed_cookie_salt)
+
+ marshal_value = ActiveSupport::MessageVerifier.new(secret, serializer: Marshal).generate(45)
+ @request.headers["Cookie"] = "user_id=#{marshal_value}"
+
+ get :get_signed_cookie
+
+ cookies = @controller.send :cookies
+ assert_not_equal 45, cookies[:user_id]
+ assert_equal 45, cookies.signed[:user_id]
+
+ verifier = ActiveSupport::MessageVerifier.new(secret, serializer: JSON)
+ assert_equal 45, verifier.verify(@response.cookies['user_id'])
+ end
+
+ def test_signed_cookie_using_hybrid_serializer_can_read_from_json_dumped_value
+ @request.env["action_dispatch.cookies_serializer"] = :hybrid
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ signed_cookie_salt = @request.env["action_dispatch.signed_cookie_salt"]
+ secret = key_generator.generate_key(signed_cookie_salt)
+ json_value = ActiveSupport::MessageVerifier.new(secret, serializer: JSON).generate(45)
+ @request.headers["Cookie"] = "user_id=#{json_value}"
+
+ get :get_signed_cookie
+
+ cookies = @controller.send :cookies
+ assert_not_equal 45, cookies[:user_id]
+ assert_equal 45, cookies.signed[:user_id]
+
+ assert_nil @response.cookies["user_id"]
end
def test_accessing_nonexistant_signed_cookie_should_not_raise_an_invalid_signature
@@ -369,7 +442,18 @@ def test_accessing_nonexistant_signed_cookie_should_not_raise_an_invalid_signatu
assert_nil @controller.send(:cookies).signed[:non_existant_attribute]
end
- def test_encrypted_cookie
+ def test_encrypted_cookie_using_default_serializer
+ get :set_encrypted_cookie
+ cookies = @controller.send :cookies
+ assert_not_equal 'bar', cookies[:foo]
+ assert_raise TypeError do
+ cookies.signed[:foo]
+ end
+ assert_equal 'bar', cookies.encrypted[:foo]
+ end
+
+ def test_encrypted_cookie_using_marshal_serializer
+ @request.env["action_dispatch.cookies_serializer"] = :marshal
get :set_encrypted_cookie
cookies = @controller.send :cookies
assert_not_equal 'bar', cookies[:foo]
@@ -379,6 +463,66 @@ def test_encrypted_cookie
assert_equal 'bar', cookies.encrypted[:foo]
end
+ def test_encrypted_cookie_using_json_serializer
+ @request.env["action_dispatch.cookies_serializer"] = :json
+ get :set_encrypted_cookie
+ cookies = @controller.send :cookies
+ assert_not_equal 'bar', cookies[:foo]
+ assert_raises ::JSON::ParserError do
+ cookies.signed[:foo]
+ end