Skip to content
Browse files

Merge branch 'master' of git://github.com/rails/rails

  • Loading branch information...
2 parents edd2f21 + da7f042 commit 9d54f8994d09db5435d6c234430ae13333928fb9 @dmitry dmitry committed Sep 15, 2011
Showing with 2,362 additions and 825 deletions.
  1. +7 −0 Gemfile
  2. +1 −1 README.rdoc
  3. +1 −1 actionmailer/CHANGELOG
  4. +12 −0 actionpack/CHANGELOG
  5. +1 −1 actionpack/actionpack.gemspec
  6. +1 −1 actionpack/lib/action_controller/metal/request_forgery_protection.rb
  7. +1 −1 actionpack/lib/action_dispatch/http/url.rb
  8. +6 −6 actionpack/lib/action_dispatch/routing/mapper.rb
  9. +0 −60 actionpack/lib/action_dispatch/routing/route.rb
  10. +71 −33 actionpack/lib/action_dispatch/routing/route_set.rb
  11. +11 −9 actionpack/lib/action_view/asset_paths.rb
  12. +2 −2 actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb
  13. +1 −1 actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb
  14. +1 −1 actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
  15. +2 −2 actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
  16. +7 −1 actionpack/lib/sprockets/assets.rake
  17. +15 −13 actionpack/lib/sprockets/helpers/rails_helper.rb
  18. +4 −2 actionpack/lib/sprockets/railtie.rb
  19. +16 −0 actionpack/test/controller/request_forgery_protection_test.rb
  20. +1 −1 actionpack/test/controller/resources_test.rb
  21. +1 −0 actionpack/test/dispatch/mapper_test.rb
  22. +2 −9 actionpack/test/lib/controller/fake_models.rb
  23. +1 −2 actionpack/test/template/form_helper_test.rb
  24. +35 −16 actionpack/test/template/sprockets_helper_test.rb
  25. +1 −1 activemodel/CHANGELOG
  26. +52 −42 activemodel/lib/active_model/attribute_methods.rb
  27. +17 −14 activemodel/lib/active_model/errors.rb
  28. +102 −6 activemodel/test/cases/attribute_methods_test.rb
  29. +29 −2 activemodel/test/cases/errors_test.rb
  30. +6 −4 activerecord/CHANGELOG
  31. +1 −1 activerecord/lib/active_record/associations.rb
  32. +56 −28 activerecord/lib/active_record/attribute_methods.rb
  33. +0 −2 activerecord/lib/active_record/attribute_methods/read.rb
  34. +11 −3 activerecord/lib/active_record/attribute_methods/write.rb
  35. +4 −1 activerecord/lib/active_record/autosave_association.rb
  36. +2 −2 activerecord/lib/active_record/base.rb
  37. +1 −1 activerecord/lib/active_record/persistence.rb
  38. +7 −5 activerecord/lib/active_record/railties/databases.rake
  39. +1 −2 activerecord/lib/active_record/relation.rb
  40. +36 −0 activerecord/lib/active_record/relation/query_methods.rb
  41. +3 −1 activerecord/lib/active_record/timestamp.rb
  42. +1 −0 activerecord/test/cases/attribute_methods/read_test.rb
  43. +34 −27 activerecord/test/cases/attribute_methods_test.rb
  44. +3 −10 activerecord/test/cases/base_test.rb
  45. +11 −6 activerecord/test/cases/persistence_test.rb
  46. +7 −13 activerecord/test/cases/relations_test.rb
  47. +7 −6 activerecord/test/cases/serialization_test.rb
  48. +2 −0 activerecord/test/cases/session_store/session_test.rb
  49. +7 −6 activerecord/test/models/contact.rb
  50. +5 −4 activerecord/test/models/topic.rb
  51. +7 −1 activerecord/test/schema/schema.rb
  52. +1 −1 activeresource/CHANGELOG
  53. +9 −1 activeresource/lib/active_resource/base.rb
  54. +2 −2 activeresource/test/cases/base/load_test.rb
  55. +27 −3 activeresource/test/cases/base_test.rb
  56. +1 −1 activesupport/CHANGELOG
  57. +1 −1 activesupport/lib/active_support/time_with_zone.rb
  58. +0 −7 bin/rails
  59. +1 −1 rails.gemspec
  60. +1 −1 railties/CHANGELOG
  61. +7 −2 railties/Rakefile
  62. +6 −1 railties/bin/rails
  63. +32 −0 railties/guides/code/getting_started/Gemfile
  64. +261 −0 railties/guides/code/getting_started/README
  65. +7 −0 railties/guides/code/getting_started/Rakefile
  66. BIN railties/guides/code/getting_started/app/assets/images/rails.png
  67. +9 −0 railties/guides/code/getting_started/app/assets/javascripts/application.js
  68. +3 −0 railties/guides/code/getting_started/app/assets/javascripts/comments.js.coffee
  69. +3 −0 railties/guides/code/getting_started/app/assets/javascripts/home.js.coffee
  70. +3 −0 railties/guides/code/getting_started/app/assets/javascripts/posts.js.coffee
  71. +7 −0 railties/guides/code/getting_started/app/assets/stylesheets/application.css
  72. +3 −0 railties/guides/code/getting_started/app/assets/stylesheets/comments.css.scss
  73. +3 −0 railties/guides/code/getting_started/app/assets/stylesheets/home.css.scss
  74. +3 −0 railties/guides/code/getting_started/app/assets/stylesheets/posts.css.scss
  75. +56 −0 railties/guides/code/getting_started/app/assets/stylesheets/scaffolds.css.scss
  76. +3 −0 railties/guides/code/getting_started/app/controllers/application_controller.rb
  77. +16 −0 railties/guides/code/getting_started/app/controllers/comments_controller.rb
  78. +5 −0 railties/guides/code/getting_started/app/controllers/home_controller.rb
  79. +84 −0 railties/guides/code/getting_started/app/controllers/posts_controller.rb
  80. +2 −0 railties/guides/code/getting_started/app/helpers/application_helper.rb
  81. +2 −0 railties/guides/code/getting_started/app/helpers/comments_helper.rb
  82. +2 −0 railties/guides/code/getting_started/app/helpers/home_helper.rb
  83. +5 −0 railties/guides/code/getting_started/app/helpers/posts_helper.rb
  84. 0 railties/guides/code/getting_started/app/mailers/.gitkeep
  85. 0 railties/guides/code/getting_started/app/models/.gitkeep
  86. +3 −0 railties/guides/code/getting_started/app/models/comment.rb
  87. +11 −0 railties/guides/code/getting_started/app/models/post.rb
  88. +3 −0 railties/guides/code/getting_started/app/models/tag.rb
  89. +15 −0 railties/guides/code/getting_started/app/views/comments/_comment.html.erb
  90. +13 −0 railties/guides/code/getting_started/app/views/comments/_form.html.erb
  91. +2 −0 railties/guides/code/getting_started/app/views/home/index.html.erb
  92. +14 −0 railties/guides/code/getting_started/app/views/layouts/application.html.erb
  93. +32 −0 railties/guides/code/getting_started/app/views/posts/_form.html.erb
  94. +6 −0 railties/guides/code/getting_started/app/views/posts/edit.html.erb
  95. +27 −0 railties/guides/code/getting_started/app/views/posts/index.html.erb
  96. +5 −0 railties/guides/code/getting_started/app/views/posts/new.html.erb
  97. +31 −0 railties/guides/code/getting_started/app/views/posts/show.html.erb
  98. +12 −0 railties/guides/code/getting_started/app/views/tags/_form.html.erb
  99. +4 −0 railties/guides/code/getting_started/config.ru
  100. +48 −0 railties/guides/code/getting_started/config/application.rb
  101. +6 −0 railties/guides/code/getting_started/config/boot.rb
  102. +25 −0 railties/guides/code/getting_started/config/database.yml
  103. +5 −0 railties/guides/code/getting_started/config/environment.rb
  104. +30 −0 railties/guides/code/getting_started/config/environments/development.rb
  105. +60 −0 railties/guides/code/getting_started/config/environments/production.rb
  106. +42 −0 railties/guides/code/getting_started/config/environments/test.rb
  107. +7 −0 railties/guides/code/getting_started/config/initializers/backtrace_silencers.rb
  108. +10 −0 railties/guides/code/getting_started/config/initializers/inflections.rb
  109. +5 −0 railties/guides/code/getting_started/config/initializers/mime_types.rb
  110. +7 −0 railties/guides/code/getting_started/config/initializers/secret_token.rb
  111. +8 −0 railties/guides/code/getting_started/config/initializers/session_store.rb
  112. +14 −0 railties/guides/code/getting_started/config/initializers/wrap_parameters.rb
  113. +5 −0 railties/guides/code/getting_started/config/locales/en.yml
  114. +64 −0 railties/guides/code/getting_started/config/routes.rb
  115. +11 −0 railties/guides/code/getting_started/db/migrate/20110901012504_create_posts.rb
  116. +12 −0 railties/guides/code/getting_started/db/migrate/20110901012815_create_comments.rb
  117. +11 −0 railties/guides/code/getting_started/db/migrate/20110901013701_create_tags.rb
  118. +43 −0 railties/guides/code/getting_started/db/schema.rb
  119. +7 −0 railties/guides/code/getting_started/db/seeds.rb
  120. +2 −0 railties/guides/code/getting_started/doc/README_FOR_APP
  121. 0 railties/guides/code/getting_started/lib/assets/.gitkeep
  122. 0 railties/guides/code/getting_started/lib/tasks/.gitkeep
  123. +26 −0 railties/guides/code/getting_started/public/404.html
  124. +26 −0 railties/guides/code/getting_started/public/422.html
  125. +26 −0 railties/guides/code/getting_started/public/500.html
  126. 0 railties/guides/code/getting_started/public/favicon.ico
  127. +5 −0 railties/guides/code/getting_started/public/robots.txt
  128. +6 −0 railties/guides/code/getting_started/script/rails
  129. 0 railties/guides/code/getting_started/test/fixtures/.gitkeep
  130. +11 −0 railties/guides/code/getting_started/test/fixtures/comments.yml
  131. +11 −0 railties/guides/code/getting_started/test/fixtures/posts.yml
  132. +9 −0 railties/guides/code/getting_started/test/fixtures/tags.yml
  133. 0 railties/guides/code/getting_started/test/functional/.gitkeep
  134. +7 −0 railties/guides/code/getting_started/test/functional/comments_controller_test.rb
  135. +9 −0 railties/guides/code/getting_started/test/functional/home_controller_test.rb
  136. +49 −0 railties/guides/code/getting_started/test/functional/posts_controller_test.rb
  137. 0 railties/guides/code/getting_started/test/integration/.gitkeep
  138. +12 −0 railties/guides/code/getting_started/test/performance/browsing_test.rb
  139. +13 −0 railties/guides/code/getting_started/test/test_helper.rb
  140. 0 railties/guides/code/getting_started/test/unit/.gitkeep
  141. +7 −0 railties/guides/code/getting_started/test/unit/comment_test.rb
  142. +4 −0 railties/guides/code/getting_started/test/unit/helpers/comments_helper_test.rb
  143. +4 −0 railties/guides/code/getting_started/test/unit/helpers/home_helper_test.rb
  144. +4 −0 railties/guides/code/getting_started/test/unit/helpers/posts_helper_test.rb
  145. +7 −0 railties/guides/code/getting_started/test/unit/post_test.rb
  146. +7 −0 railties/guides/code/getting_started/test/unit/tag_test.rb
  147. 0 railties/guides/code/getting_started/vendor/assets/stylesheets/.gitkeep
  148. 0 railties/guides/code/getting_started/vendor/plugins/.gitkeep
  149. +0 −6 railties/guides/source/action_controller_overview.textile
  150. +0 −5 railties/guides/source/action_mailer_basics.textile
  151. +0 −7 railties/guides/source/action_view_overview.textile
  152. +0 −5 railties/guides/source/active_model_basics.textile
  153. +12 −19 railties/guides/source/active_record_querying.textile
  154. +0 −11 railties/guides/source/active_record_validations_callbacks.textile
  155. +0 −4 railties/guides/source/active_resource_basics.textile
  156. +1 −6 railties/guides/source/active_support_core_extensions.textile
  157. +0 −4 railties/guides/source/api_documentation_guidelines.textile
  158. +21 −7 railties/guides/source/asset_pipeline.textile
  159. +4 −13 railties/guides/source/association_basics.textile
  160. +0 −11 railties/guides/source/caching_with_rails.textile
  161. +0 −8 railties/guides/source/configuring.textile
  162. +0 −70 railties/guides/source/contribute.textile
  163. +9 −17 railties/guides/source/contributing_to_ruby_on_rails.textile
  164. +0 −7 railties/guides/source/debugging_rails_applications.textile
  165. +0 −10 railties/guides/source/form_helpers.textile
  166. +0 −11 railties/guides/source/generators.textile
  167. +13 −31 railties/guides/source/getting_started.textile
  168. +0 −1 railties/guides/source/index.html.erb
  169. +1 −1 railties/guides/source/initialization.textile
  170. +5 −1 railties/guides/source/layout.html.erb
  171. +0 −12 railties/guides/source/layouts_and_rendering.textile
  172. +0 −6 railties/guides/source/migrations.textile
  173. +0 −6 railties/guides/source/performance_testing.textile
  174. +0 −8 railties/guides/source/plugins.textile
  175. +0 −4 railties/guides/source/rails_application_templates.textile
  176. +1 −6 railties/guides/source/rails_on_rack.textile
  177. +0 −9 railties/guides/source/routing.textile
  178. +0 −5 railties/guides/source/ruby_on_rails_guides_guidelines.textile
  179. +0 −4 railties/guides/source/security.textile
  180. +0 −8 railties/guides/source/testing.textile
  181. +2 −1 railties/lib/rails/application/configuration.rb
  182. +43 −0 railties/lib/rails/application/route_inspector.rb
  183. +2 −0 railties/lib/rails/generators/app_base.rb
  184. +1 −0 railties/lib/rails/generators/rails/app/app_generator.rb
  185. +3 −31 railties/lib/rails/tasks/routes.rake
  186. +72 −1 railties/test/application/assets_test.rb
  187. +52 −95 railties/test/application/rake_test.rb
  188. +97 −0 railties/test/application/route_inspect_test.rb
  189. +1 −1 railties/test/isolation/abstract_unit.rb
  190. +1 −1 railties/test/railties/generators_test.rb
View
7 Gemfile
@@ -8,6 +8,13 @@ end
gem "bcrypt-ruby", "~> 3.0.0"
gem "jquery-rails"
+
+if ENV['JOURNEY']
+ gem "journey", :path => ENV['JOURNEY']
+else
+ gem "journey", :git => "git://github.com/rails/journey"
+end
+
# This needs to be with require false to avoid
# it being automatically loaded by sprockets
gem "uglifier", ">= 1.0.3", :require => false
View
2 README.rdoc
@@ -67,7 +67,7 @@ We encourage you to contribute to Ruby on Rails! Please check out the {Contribut
guide}[http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html] for guidelines about how
to proceed. {Join us}[http://contributors.rubyonrails.org]!
-== Travis Build Status {<img src="https://secure.travis-ci.org/rails/rails.png"/>}[https://secure.travis-ci.org/rails/rails.png]
+== Travis Build Status {<img src="https://secure.travis-ci.org/rails/rails.png"/>}[http://travis-ci.org/rails/rails]
== License
View
2 actionmailer/CHANGELOG
@@ -1,4 +1,4 @@
-*Rails 3.1.0 (unreleased)*
+*Rails 3.1.0 (August 30, 2011)*
* No changes
View
12 actionpack/CHANGELOG
@@ -1,5 +1,7 @@
*Rails 3.2.0 (unreleased)*
+* Changed log level of warning for missing CSRF token from :debug to :warn. [Mike Dillon]
+
* content_tag_for and div_for can now take the collection of records. It will also yield the record as the first argument if you set a receiving argument in your block [Prem Sichanugrist]
So instead of having to do this:
@@ -46,6 +48,16 @@
*Rails 3.1.1 (unreleased)*
+* Allow asset tag helper methods to accept :digest => false option in order to completely avoid the digest generation.
+Useful for linking assets from static html files or from emails when the user
+could probably look at an older html email with an older asset. [Santiago Pastorino]
+
+* Don't mount Sprockets server at config.assets.prefix if config.assets.compile is false. [Mark J. Titorenko]
+
+* Set relative url root in assets when controller isn't available for Sprockets (eg. Sass files using asset_path). Fixes #2435 [Guillermo Iguaran]
+
+* Fix basic auth credential generation to not make newlines. GH #2882
+
* Fixed the behavior of asset pipeline when config.assets.digest and config.assets.compile are false and requested asset isn't precompiled.
Before the requested asset were compiled anyway ignoring that the config.assets.compile flag is false. [Guillermo Iguaran]
View
2 actionpack/actionpack.gemspec
@@ -23,7 +23,7 @@ Gem::Specification.new do |s|
s.add_dependency('i18n', '~> 0.6')
s.add_dependency('rack', '~> 1.3.2')
s.add_dependency('rack-test', '~> 0.6.1')
- s.add_dependency('rack-mount', '~> 0.8.2')
+ s.add_dependency('journey', '~> 1.0.0')
s.add_dependency('sprockets', '~> 2.0.0')
s.add_dependency('erubis', '~> 2.7.0')
View
2 actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -74,7 +74,7 @@ def protect_from_forgery(options = {})
# The actual before_filter that is used. Modify this to change how you handle unverified requests.
def verify_authenticity_token
unless verified_request?
- logger.debug "WARNING: Can't verify CSRF token authenticity" if logger
+ logger.warn "WARNING: Can't verify CSRF token authenticity" if logger
handle_unverified_request
end
end
View
2 actionpack/lib/action_dispatch/http/url.rb
@@ -45,7 +45,7 @@ def url_for(options = {})
rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
rewritten_url << "?#{params.to_query}" unless params.empty?
- rewritten_url << "##{Rack::Mount::Utils.escape_uri(options[:anchor].to_param.to_s)}" if options[:anchor]
+ rewritten_url << "##{Journey::Router::Utils.escape_uri(options[:anchor].to_param.to_s)}" if options[:anchor]
rewritten_url
end
View
12 actionpack/lib/action_dispatch/routing/mapper.rb
@@ -213,8 +213,8 @@ def request_method_condition
end
def segment_keys
- @segment_keys ||= Rack::Mount::RegexpWithNamedGroups.new(
- Rack::Mount::Strexp.compile(@path, requirements, SEPARATORS)
+ @segment_keys ||= Journey::Path::Pattern.new(
+ Journey::Router::Strexp.compile(@path, requirements, SEPARATORS)
).names
end
@@ -235,7 +235,7 @@ def default_action
# (:locale) becomes (/:locale) instead of /(:locale). Except
# for root cases, where the latter is the correct one.
def self.normalize_path(path)
- path = Rack::Mount::Utils.normalize_path(path)
+ path = Journey::Router::Utils.normalize_path(path)
path.gsub!(%r{/(\(+)/?}, '\1/') unless path =~ %r{^/\(+[^/]+\)$}
path
end
@@ -1465,9 +1465,9 @@ def name_for_action(as, action) #:nodoc:
end
module Shorthand #:nodoc:
- def match(*args)
- if args.size == 1 && args.last.is_a?(Hash)
- options = args.pop
+ def match(path, *rest)
+ if rest.empty? && Hash === path
+ options = path
path, to = options.find { |name, value| name.is_a?(String) }
options.merge!(:to => to).delete(path)
super(path, options)
View
60 actionpack/lib/action_dispatch/routing/route.rb
@@ -1,60 +0,0 @@
-module ActionDispatch
- module Routing
- class Route #:nodoc:
- attr_reader :app, :conditions, :defaults, :name
- attr_reader :path, :requirements, :set
-
- def initialize(set, app, conditions, requirements, defaults, name, anchor)
- @set = set
- @app = app
- @defaults = defaults
- @name = name
-
- # FIXME: we should not be doing this much work in a constructor.
-
- @requirements = requirements.merge(defaults)
- @requirements.delete(:controller) if @requirements[:controller].is_a?(Regexp)
- @requirements.delete_if { |k, v|
- v == Regexp.compile("[^#{SEPARATORS.join}]+")
- }
-
- if path = conditions[:path_info]
- @path = path
- conditions[:path_info] = ::Rack::Mount::Strexp.compile(path, requirements, SEPARATORS, anchor)
- end
-
- @verbs = conditions[:request_method] || []
-
- @conditions = conditions.dup
-
- # Rack-Mount requires that :request_method be a regular expression.
- # :request_method represents the HTTP verb that matches this route.
- #
- # Here we munge values before they get sent on to rack-mount.
- @conditions[:request_method] = %r[^#{verb}$] unless @verbs.empty?
- @conditions[:path_info] = Rack::Mount::RegexpWithNamedGroups.new(@conditions[:path_info]) if @conditions[:path_info]
- @conditions.delete_if{ |k,v| k != :path_info && !valid_condition?(k) }
- @requirements.delete_if{ |k,v| !valid_condition?(k) }
- end
-
- def verb
- @verbs.join '|'
- end
-
- def segment_keys
- @segment_keys ||= conditions[:path_info].names.compact.map { |key| key.to_sym }
- end
-
- def to_s
- @to_s ||= begin
- "%-6s %-40s %s" % [(verb || :any).to_s.upcase, path, requirements.inspect]
- end
- end
-
- private
- def valid_condition?(method)
- segment_keys.include?(method) || set.valid_conditions.include?(method)
- end
- end
- end
-end
View
104 actionpack/lib/action_dispatch/routing/route_set.rb
@@ -1,4 +1,4 @@
-require 'rack/mount'
+require 'journey/router'
require 'forwardable'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/to_query'
@@ -205,29 +205,42 @@ def #{selector}(*args)
end
end
- attr_accessor :set, :routes, :named_routes, :default_scope
+ attr_accessor :formatter, :set, :named_routes, :default_scope, :router
attr_accessor :disable_clear_and_finalize, :resources_path_names
attr_accessor :default_url_options, :request_class, :valid_conditions
+ alias :routes :set
+
def self.default_resources_path_names
{ :new => 'new', :edit => 'edit' }
end
def initialize(request_class = ActionDispatch::Request)
- self.routes = []
self.named_routes = NamedRouteCollection.new
self.resources_path_names = self.class.default_resources_path_names.dup
self.default_url_options = {}
self.request_class = request_class
- self.valid_conditions = request_class.public_instance_methods.map { |m| m.to_sym }
+ @valid_conditions = {}
+
+ request_class.public_instance_methods.each { |m|
+ @valid_conditions[m.to_sym] = true
+ }
+ @valid_conditions[:controller] = true
+ @valid_conditions[:action] = true
+
self.valid_conditions.delete(:id)
- self.valid_conditions.push(:controller, :action)
- @append = []
- @prepend = []
+ @append = []
+ @prepend = []
@disable_clear_and_finalize = false
- clear!
+ @finalized = false
+
+ @set = Journey::Routes.new
+ @router = Journey::Router.new(@set, {
+ :parameters_key => PARAMETERS_KEY,
+ :request_class => request_class})
+ @formatter = Journey::Formatter.new @set
end
def draw(&block)
@@ -263,17 +276,13 @@ def finalize!
return if @finalized
@append.each { |blk| eval_block(blk) }
@finalized = true
- @set.freeze
end
def clear!
@finalized = false
- routes.clear
named_routes.clear
- @set = ::Rack::Mount::RouteSet.new(
- :parameters_key => PARAMETERS_KEY,
- :request_class => request_class
- )
+ set.clear
+ formatter.clear
@prepend.each { |blk| eval_block(blk) }
end
@@ -341,26 +350,55 @@ def empty?
def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil, anchor = true)
raise ArgumentError, "Invalid route name: '#{name}'" unless name.blank? || name.to_s.match(/^[_a-z]\w*$/i)
- route = Route.new(self, app, conditions, requirements, defaults, name, anchor)
- @set.add_route(route.app, route.conditions, route.defaults, route.name)
+
+ path = build_path(conditions.delete(:path_info), requirements, SEPARATORS, anchor)
+ conditions = build_conditions(conditions, valid_conditions, path.names.map { |x| x.to_sym })
+
+ route = @set.add_route(app, path, conditions, defaults, name)
named_routes[name] = route if name
- routes << route
route
end
+ def build_path(path, requirements, separators, anchor)
+ strexp = Journey::Router::Strexp.new(
+ path,
+ requirements,
+ SEPARATORS,
+ anchor)
+
+ Journey::Path::Pattern.new(strexp)
+ end
+ private :build_path
+
+ def build_conditions(current_conditions, req_predicates, path_values)
+ conditions = current_conditions.dup
+
+ verbs = conditions[:request_method] || []
+
+ # Rack-Mount requires that :request_method be a regular expression.
+ # :request_method represents the HTTP verb that matches this route.
+ #
+ # Here we munge values before they get sent on to rack-mount.
+ unless verbs.empty?
+ conditions[:request_method] = %r[^#{verbs.join('|')}$]
+ end
+ conditions.delete_if { |k,v| !(req_predicates.include?(k) || path_values.include?(k)) }
+
+ conditions
+ end
+ private :build_conditions
+
class Generator #:nodoc:
- PARAMETERIZE = {
- :parameterize => lambda do |name, value|
- if name == :controller
- value
- elsif value.is_a?(Array)
- value.map { |v| Rack::Mount::Utils.escape_uri(v.to_param) }.join('/')
- else
- return nil unless param = value.to_param
- param.split('/').map { |v| Rack::Mount::Utils.escape_uri(v) }.join("/")
- end
+ PARAMETERIZE = lambda do |name, value|
+ if name == :controller
+ value
+ elsif value.is_a?(Array)
+ value.map { |v| Journey::Router::Utils.escape_uri(v.to_param) }.join('/')
+ else
+ return nil unless param = value.to_param
+ param.split('/').map { |v| Journey::Router::Utils.escape_uri(v) }.join("/")
end
- }
+ end
attr_reader :options, :recall, :set, :named_route
@@ -450,14 +488,14 @@ def handle_nil_action!
end
def generate
- path, params = @set.set.generate(:path_info, named_route, options, recall, PARAMETERIZE)
+ path, params = @set.formatter.generate(:path_info, named_route, options, recall, PARAMETERIZE)
raise_routing_error unless path
return [path, params.keys] if @extras
[path, params]
- rescue Rack::Mount::RoutingError
+ rescue Journey::Router::RoutingError
raise_routing_error
end
@@ -529,12 +567,12 @@ def url_for(options)
def call(env)
finalize!
- @set.call(env)
+ @router.call(env)
end
def recognize_path(path, environment = {})
method = (environment[:method] || "GET").to_s.upcase
- path = Rack::Mount::Utils.normalize_path(path) unless path =~ %r{://}
+ path = Journey::Router::Utils.normalize_path(path) unless path =~ %r{://}
begin
env = Rack::MockRequest.env_for(path, {:method => method})
@@ -543,7 +581,7 @@ def recognize_path(path, environment = {})
end
req = @request_class.new(env)
- @set.recognize(req) do |route, matches, params|
+ @router.recognize(req) do |route, matches, params|
params.each do |key, value|
if value.is_a?(String)
value = value.dup.force_encoding(Encoding::BINARY) if value.encoding_aware?
View
20 actionpack/lib/action_view/asset_paths.rb
@@ -21,14 +21,15 @@ def initialize(config, controller = nil)
# When :relative (default), the protocol will be determined by the client using current protocol
# When :request, the protocol will be the request protocol
# Otherwise, the protocol is used (E.g. :http, :https, etc)
- def compute_public_path(source, dir, ext = nil, include_host = true, protocol = nil)
+ def compute_public_path(source, dir, options = {})
source = source.to_s
return source if is_uri?(source)
- source = rewrite_extension(source, dir, ext) if ext
- source = rewrite_asset_path(source, dir)
- source = rewrite_relative_url_root(source, relative_url_root) if has_request?
- source = rewrite_host_and_protocol(source, protocol) if include_host
+ options[:include_host] ||= true
+ source = rewrite_extension(source, dir, options[:ext]) if options[:ext]
+ source = rewrite_asset_path(source, dir, options)
+ source = rewrite_relative_url_root(source, relative_url_root)
+ source = rewrite_host_and_protocol(source, options[:protocol]) if options[:include_host]
source
end
@@ -119,10 +120,11 @@ def compute_asset_host(source)
end
def relative_url_root
- config = controller.config if controller.respond_to?(:config)
- config ||= config.action_controller if config.action_controller.present?
- config ||= config
- config.relative_url_root
+ if config.action_controller.present?
+ config.action_controller.relative_url_root
+ else
+ config.relative_url_root
+ end
end
def asset_host_config
View
4 actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb
@@ -60,8 +60,8 @@ def include_tag(*sources)
private
- def path_to_asset(source, include_host = true, protocol = nil)
- asset_paths.compute_public_path(source, asset_name.to_s.pluralize, extension, include_host, protocol)
+ def path_to_asset(source, options = {})
+ asset_paths.compute_public_path(source, asset_name.to_s.pluralize, options.merge(:ext => extension))
end
def path_to_asset_source(source)
View
2 actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb
@@ -41,7 +41,7 @@ def rewrite_extension(source, dir, ext)
# Break out the asset path rewrite in case plugins wish to put the asset id
# someplace other than the query string.
- def rewrite_asset_path(source, dir)
+ def rewrite_asset_path(source, dir, options = nil)
source = "/#{dir}/#{source}" unless source[0] == ?/
path = config.asset_path
View
2 actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
@@ -83,7 +83,7 @@ def register_javascript_expansion(expansions)
# javascript_path "http://www.example.com/js/xmlhr" # => http://www.example.com/js/xmlhr
# javascript_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js
def javascript_path(source)
- asset_paths.compute_public_path(source, 'javascripts', 'js')
+ asset_paths.compute_public_path(source, 'javascripts', :ext => 'js')
end
alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
View
4 actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
@@ -17,7 +17,7 @@ def extension
def asset_tag(source, options)
# We force the :request protocol here to avoid a double-download bug in IE7 and IE8
- tag("link", { "rel" => "stylesheet", "type" => Mime::CSS, "media" => "screen", "href" => ERB::Util.html_escape(path_to_asset(source, true, :request)) }.merge(options), false, false)
+ tag("link", { "rel" => "stylesheet", "type" => Mime::CSS, "media" => "screen", "href" => ERB::Util.html_escape(path_to_asset(source, :protocol => :request)) }.merge(options), false, false)
end
def custom_dir
@@ -61,7 +61,7 @@ def register_stylesheet_expansion(expansions)
# stylesheet_path "http://www.example.com/css/style" # => http://www.example.com/css/style
# stylesheet_path "http://www.example.com/css/style.css" # => http://www.example.com/css/style.css
def stylesheet_path(source)
- asset_paths.compute_public_path(source, 'stylesheets', 'css', true, :request)
+ asset_paths.compute_public_path(source, 'stylesheets', :ext => 'css', :protocol => :request)
end
alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route
View
8 actionpack/lib/sprockets/assets.rake
@@ -9,13 +9,17 @@ namespace :assets do
Kernel.exec $0, *ARGV
else
Rake::Task["environment"].invoke
+ Rake::Task["tmp:cache:clear"].invoke
# Ensure that action view is loaded and the appropriate sprockets hooks get executed
ActionView::Base
# Always compile files
Rails.application.config.assets.compile = true
+ # Always ignore asset host
+ Rails.application.config.action_controller.asset_host = nil
+
config = Rails.application.config
env = Rails.application.assets
target = Pathname.new(File.join(Rails.public_path, config.assets.prefix))
@@ -26,6 +30,8 @@ namespace :assets do
env.each_logical_path do |logical_path|
if path.is_a?(Regexp)
next unless path.match(logical_path)
+ elsif path.is_a?(Proc)
+ next unless path.call(logical_path)
else
next unless File.fnmatch(path.to_s, logical_path)
end
@@ -42,7 +48,7 @@ namespace :assets do
end
end
- File.open("#{manifest_path}/manifest.yml", 'w') do |f|
+ File.open("#{manifest_path}/manifest.yml", 'wb') do |f|
YAML.dump(manifest, f)
end
end
View
28 actionpack/lib/sprockets/helpers/rails_helper.rb
@@ -25,38 +25,40 @@ def javascript_include_tag(*sources)
options = sources.extract_options!
debug = options.key?(:debug) ? options.delete(:debug) : debug_assets?
body = options.key?(:body) ? options.delete(:body) : false
+ digest = options.key?(:digest) ? options.delete(:digest) : digest_assets?
sources.collect do |source|
if debug && asset = asset_paths.asset_for(source, 'js')
asset.to_a.map { |dep|
- super(dep.to_s, { :src => asset_path(dep, 'js', true) }.merge!(options))
+ super(dep.to_s, { :src => asset_path(dep, :ext => 'js', :body => true, :digest => digest) }.merge!(options))
}
else
- super(source.to_s, { :src => asset_path(source, 'js', body) }.merge!(options))
+ super(source.to_s, { :src => asset_path(source, :ext => 'js', :body => body, :digest => digest) }.merge!(options))
end
end.join("\n").html_safe
end
def stylesheet_link_tag(*sources)
options = sources.extract_options!
- debug = options.key?(:debug) ? options.delete(:debug) : debug_assets?
- body = options.key?(:body) ? options.delete(:body) : false
+ debug = options.key?(:debug) ? options.delete(:debug) : debug_assets?
+ body = options.key?(:body) ? options.delete(:body) : false
+ digest = options.key?(:digest) ? options.delete(:digest) : digest_assets?
sources.collect do |source|
if debug && asset = asset_paths.asset_for(source, 'css')
asset.to_a.map { |dep|
- super(dep.to_s, { :href => asset_path(dep, 'css', true, :request) }.merge!(options))
+ super(dep.to_s, { :href => asset_path(dep, :ext => 'css', :body => true, :protocol => :request, :digest => digest) }.merge!(options))
}
else
- super(source.to_s, { :href => asset_path(source, 'css', body, :request) }.merge!(options))
+ super(source.to_s, { :href => asset_path(source, :ext => 'css', :body => body, :protocol => :request, :digest => digest) }.merge!(options))
end
end.join("\n").html_safe
end
- def asset_path(source, default_ext = nil, body = false, protocol = nil)
+ def asset_path(source, options = {})
source = source.logical_path if source.respond_to?(:logical_path)
- path = asset_paths.compute_public_path(source, 'assets', default_ext, true, protocol)
- body ? "#{path}?body=1" : path
+ path = asset_paths.compute_public_path(source, 'assets', options.merge(:body => true))
+ options[:body] ? "#{path}?body=1" : path
end
private
@@ -100,8 +102,8 @@ class AssetPaths < ::ActionView::AssetPaths #:nodoc:
class AssetNotPrecompiledError < StandardError; end
- def compute_public_path(source, dir, ext=nil, include_host=true, protocol=nil)
- super(source, asset_prefix, ext, include_host, protocol)
+ def compute_public_path(source, dir, options = {})
+ super(source, asset_prefix, options)
end
# Return the filesystem path for the source
@@ -131,11 +133,11 @@ def digest_for(logical_path)
end
end
- def rewrite_asset_path(source, dir)
+ def rewrite_asset_path(source, dir, options = {})
if source[0] == ?/
source
else
- source = digest_for(source)
+ source = digest_for(source) unless options[:digest] == false
source = File.join(dir, source)
source = "/#{source}" unless source =~ /^\//
source
View
6 actionpack/lib/sprockets/railtie.rb
@@ -67,8 +67,10 @@ class Railtie < ::Rails::Railtie
end
end
- app.routes.prepend do
- mount app.assets => config.assets.prefix
+ if config.assets.compile
+ app.routes.prepend do
+ mount app.assets => config.assets.prefix
+ end
end
if config.assets.digest
View
16 actionpack/test/controller/request_forgery_protection_test.rb
@@ -1,6 +1,7 @@
require 'abstract_unit'
require 'digest/sha1'
require 'active_support/core_ext/string/strip'
+require "active_support/log_subscriber/test_helper"
# common controller actions
module RequestForgeryProtectionActions
@@ -157,6 +158,21 @@ def test_should_allow_put_with_token_in_header
assert_not_blocked { put :index }
end
+ def test_should_warn_on_missing_csrf_token
+ old_logger = ActionController::Base.logger
+ logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
+ ActionController::Base.logger = logger
+
+ begin
+ assert_blocked { post :index }
+
+ assert_equal 1, logger.logged(:warn).size
+ assert_match(/CSRF token authenticity/, logger.logged(:warn).last)
+ ensure
+ ActionController::Base.logger = old_logger
+ end
+ end
+
def assert_blocked
session[:something_like_user_id] = 1
yield
View
2 actionpack/test/controller/resources_test.rb
@@ -532,7 +532,7 @@ def test_restful_routes_dont_generate_duplicates
routes.each do |route|
routes.each do |r|
next if route === r # skip the comparison instance
- assert_not_equal route.conditions, r.conditions
+ assert_not_equal [route.conditions, route.path.spec.to_s], [r.conditions, r.path.spec.to_s]
end
end
end
View
1 actionpack/test/dispatch/mapper_test.rb
@@ -5,6 +5,7 @@ module Routing
class MapperTest < ActiveSupport::TestCase
class FakeSet
attr_reader :routes
+ alias :set :routes
def initialize
@routes = []
View
11 actionpack/test/lib/controller/fake_models.rb
@@ -48,26 +48,19 @@ class Store < Question
end
end
-class Post < Struct.new(:title, :author_name, :body, :secret, :written_on, :cost)
+class Post < Struct.new(:title, :author_name, :body, :secret, :persisted, :written_on, :cost)
extend ActiveModel::Naming
include ActiveModel::Conversion
extend ActiveModel::Translation
alias_method :secret?, :secret
+ alias_method :persisted?, :persisted
def initialize(*args)
super
@persisted = false
end
- def persisted=(boolean)
- @persisted = boolean
- end
-
- def persisted?
- @persisted
- end
-
attr_accessor :author
def author_attributes=(attributes); end
View
3 actionpack/test/template/form_helper_test.rb
@@ -698,8 +698,7 @@ def test_form_for_with_isolated_namespaced_model
expected = whole_form("/posts/44", "edit_post_44" , "edit_post", :method => "put") do
"<input name='post[title]' size='30' type='text' id='post_title' value='And his name will be forty and four.' />" +
- "<input name='commit' type='submit' value='Edit post' />" +
- "</form>"
+ "<input name='commit' type='submit' value='Edit post' />"
end
assert_dom_equal expected, output_buffer
View
51 actionpack/test/template/sprockets_helper_test.rb
@@ -41,6 +41,10 @@ def url_for(*args)
test "asset_path" do
assert_match %r{/assets/logo-[0-9a-f]+.png},
asset_path("logo.png")
+ assert_match %r{/assets/logo-[0-9a-f]+.png},
+ asset_path("logo.png", :digest => true)
+ assert_match %r{/assets/logo.png},
+ asset_path("logo.png", :digest => false)
end
test "asset_path with root relative assets" do
@@ -124,27 +128,38 @@ def url_for(*args)
asset_path("/images/logo.gif")
end
+ test "asset path with relative url root when controller isn't present but relative_url_root is" do
+ @controller = nil
+ @config.action_controller.relative_url_root = "/collaboration/hieraki"
+ assert_equal "/collaboration/hieraki/images/logo.gif",
+ asset_path("/images/logo.gif")
+ end
+
test "javascript path" do
assert_match %r{/assets/application-[0-9a-f]+.js},
- asset_path(:application, "js")
+ asset_path(:application, :ext => "js")
assert_match %r{/assets/xmlhr-[0-9a-f]+.js},
- asset_path("xmlhr", "js")
+ asset_path("xmlhr", :ext => "js")
assert_match %r{/assets/dir/xmlhr-[0-9a-f]+.js},
- asset_path("dir/xmlhr.js", "js")
+ asset_path("dir/xmlhr.js", :ext => "js")
assert_equal "/dir/xmlhr.js",
- asset_path("/dir/xmlhr", "js")
+ asset_path("/dir/xmlhr", :ext => "js")
assert_equal "http://www.example.com/js/xmlhr",
- asset_path("http://www.example.com/js/xmlhr", "js")
+ asset_path("http://www.example.com/js/xmlhr", :ext => "js")
assert_equal "http://www.example.com/js/xmlhr.js",
- asset_path("http://www.example.com/js/xmlhr.js", "js")
+ asset_path("http://www.example.com/js/xmlhr.js", :ext => "js")
end
test "javascript include tag" do
assert_match %r{<script src="/assets/application-[0-9a-f]+.js" type="text/javascript"></script>},
javascript_include_tag(:application)
+ assert_match %r{<script src="/assets/application-[0-9a-f]+.js" type="text/javascript"></script>},
+ javascript_include_tag(:application, :digest => true)
+ assert_match %r{<script src="/assets/application.js" type="text/javascript"></script>},
+ javascript_include_tag(:application, :digest => false)
assert_match %r{<script src="/assets/xmlhr-[0-9a-f]+.js" type="text/javascript"></script>},
javascript_include_tag("xmlhr")
@@ -166,21 +181,25 @@ def url_for(*args)
end
test "stylesheet path" do
- assert_match %r{/assets/application-[0-9a-f]+.css}, asset_path(:application, "css")
+ assert_match %r{/assets/application-[0-9a-f]+.css}, asset_path(:application, :ext => "css")
- assert_match %r{/assets/style-[0-9a-f]+.css}, asset_path("style", "css")
- assert_match %r{/assets/dir/style-[0-9a-f]+.css}, asset_path("dir/style.css", "css")
- assert_equal "/dir/style.css", asset_path("/dir/style.css", "css")
+ assert_match %r{/assets/style-[0-9a-f]+.css}, asset_path("style", :ext => "css")
+ assert_match %r{/assets/dir/style-[0-9a-f]+.css}, asset_path("dir/style.css", :ext => "css")
+ assert_equal "/dir/style.css", asset_path("/dir/style.css", :ext => "css")
assert_equal "http://www.example.com/css/style",
- asset_path("http://www.example.com/css/style", "css")
+ asset_path("http://www.example.com/css/style", :ext => "css")
assert_equal "http://www.example.com/css/style.css",
- asset_path("http://www.example.com/css/style.css", "css")
+ asset_path("http://www.example.com/css/style.css", :ext => "css")
end
test "stylesheet link tag" do
assert_match %r{<link href="/assets/application-[0-9a-f]+.css" media="screen" rel="stylesheet" type="text/css" />},
stylesheet_link_tag(:application)
+ assert_match %r{<link href="/assets/application-[0-9a-f]+.css" media="screen" rel="stylesheet" type="text/css" />},
+ stylesheet_link_tag(:application, :digest => true)
+ assert_match %r{<link href="/assets/application.css" media="screen" rel="stylesheet" type="text/css" />},
+ stylesheet_link_tag(:application, :digest => false)
assert_match %r{<link href="/assets/style-[0-9a-f]+.css" media="screen" rel="stylesheet" type="text/css" />},
stylesheet_link_tag("style")
@@ -211,25 +230,25 @@ def url_for(*args)
test "alternate asset prefix" do
stubs(:asset_prefix).returns("/themes/test")
- assert_match %r{/themes/test/style-[0-9a-f]+.css}, asset_path("style", "css")
+ assert_match %r{/themes/test/style-[0-9a-f]+.css}, asset_path("style", :ext => "css")
end
test "alternate asset environment" do
assets = Sprockets::Environment.new
assets.append_path(FIXTURES.join("sprockets/alternate/stylesheets"))
stubs(:asset_environment).returns(assets)
- assert_match %r{/assets/style-[0-9a-f]+.css}, asset_path("style", "css")
+ assert_match %r{/assets/style-[0-9a-f]+.css}, asset_path("style", :ext => "css")
end
test "alternate hash based on environment" do
assets = Sprockets::Environment.new
assets.version = 'development'
assets.append_path(FIXTURES.join("sprockets/alternate/stylesheets"))
stubs(:asset_environment).returns(assets)
- dev_path = asset_path("style", "css")
+ dev_path = asset_path("style", :ext => "css")
assets.version = 'production'
- prod_path = asset_path("style", "css")
+ prod_path = asset_path("style", :ext => "css")
assert_not_equal prod_path, dev_path
end
View
2 activemodel/CHANGELOG
@@ -4,7 +4,7 @@
* Provide mass_assignment_sanitizer as an easy API to replace the sanitizer behavior. Also support both :logger (default) and :strict sanitizer behavior [Bogdan Gusiev]
-*Rails 3.1.0 (unreleased)*
+*Rails 3.1.0 (August 30, 2011)*
* Alternate I18n namespace lookup is no longer supported.
Instead of "activerecord.models.admins.post", do "activerecord.models.admins/post" instead [José Valim]
View
94 activemodel/lib/active_model/attribute_methods.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/class/attribute'
+require 'active_support/deprecation'
module ActiveModel
class MissingAttributeError < NoMethodError
@@ -60,7 +61,7 @@ module AttributeMethods
included do
class_attribute :attribute_method_matchers, :instance_writer => false
- self.attribute_method_matchers = []
+ self.attribute_method_matchers = [ClassMethods::AttributeMethodMatcher.new]
end
module ClassMethods
@@ -284,33 +285,25 @@ def define_attribute_methods(attr_names)
def define_attribute_method(attr_name)
attribute_method_matchers.each do |matcher|
- unless instance_method_already_implemented?(matcher.method_name(attr_name))
- generate_method = "define_method_#{matcher.prefix}attribute#{matcher.suffix}"
+ method_name = matcher.method_name(attr_name)
+
+ unless instance_method_already_implemented?(method_name)
+ generate_method = "define_method_#{matcher.method_missing_target}"
if respond_to?(generate_method)
send(generate_method, attr_name)
else
- method_name = matcher.method_name(attr_name)
+ if method_name =~ COMPILABLE_REGEXP
+ defn = "def #{method_name}(*args)"
+ else
+ defn = "define_method(:'#{method_name}') do |*args|"
+ end
generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
- if method_defined?('#{method_name}')
- undef :'#{method_name}'
+ #{defn}
+ send(:#{matcher.method_missing_target}, '#{attr_name}', *args)
end
RUBY
-
- if method_name.to_s =~ COMPILABLE_REGEXP
- generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
- def #{method_name}(*args)
- send(:#{matcher.method_missing_target}, '#{attr_name}', *args)
- end
- RUBY
- else
- generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
- define_method('#{method_name}') do |*args|
- send('#{matcher.method_missing_target}', '#{attr_name}', *args)
- end
- RUBY
- end
end
end
end
@@ -336,7 +329,7 @@ def generated_attribute_methods #:nodoc:
protected
def instance_method_already_implemented?(method_name)
- method_defined?(method_name)
+ generated_attribute_methods.method_defined?(method_name)
end
private
@@ -357,19 +350,32 @@ def attribute_method_matcher(method_name)
if attribute_method_matchers_cache.key?(method_name)
attribute_method_matchers_cache[method_name]
else
+ # Must try to match prefixes/suffixes first, or else the matcher with no prefix/suffix
+ # will match every time.
+ matchers = attribute_method_matchers.partition(&:plain?).reverse.flatten(1)
match = nil
- attribute_method_matchers.detect { |method| match = method.match(method_name) }
+ matchers.detect { |method| match = method.match(method_name) }
attribute_method_matchers_cache[method_name] = match
end
end
class AttributeMethodMatcher
attr_reader :prefix, :suffix, :method_missing_target
- AttributeMethodMatch = Struct.new(:target, :attr_name)
+ AttributeMethodMatch = Struct.new(:target, :attr_name, :method_name)
def initialize(options = {})
options.symbolize_keys!
+
+ if options[:prefix] == '' || options[:suffix] == ''
+ ActiveSupport::Deprecation.warn(
+ "Specifying an empty prefix/suffix for an attribute method is no longer " \
+ "necessary. If the un-prefixed/suffixed version of the method has not been " \
+ "defined when `define_attribute_methods` is called, it will be defined " \
+ "automatically."
+ )
+ end
+
@prefix, @suffix = options[:prefix] || '', options[:suffix] || ''
@regex = /^(#{Regexp.escape(@prefix)})(.+?)(#{Regexp.escape(@suffix)})$/
@method_missing_target = "#{@prefix}attribute#{@suffix}"
@@ -378,7 +384,7 @@ def initialize(options = {})
def match(method_name)
if @regex =~ method_name
- AttributeMethodMatch.new(method_missing_target, $2)
+ AttributeMethodMatch.new(method_missing_target, $2, method_name)
else
nil
end
@@ -387,6 +393,10 @@ def match(method_name)
def method_name(attr_name)
@method_name % attr_name
end
+
+ def plain?
+ prefix.empty? && suffix.empty?
+ end
end
end
@@ -401,13 +411,21 @@ def method_name(attr_name)
# It's also possible to instantiate related objects, so a Client class
# belonging to the clients table with a +master_id+ foreign key can
# instantiate master through Client#master.
- def method_missing(method_id, *args, &block)
- method_name = method_id.to_s
- if match = match_attribute_method?(method_name)
- guard_private_attribute_method!(method_name, args)
- return __send__(match.target, match.attr_name, *args, &block)
+ def method_missing(method, *args, &block)
+ if respond_to_without_attributes?(method, true)
+ super
+ else
+ match = match_attribute_method?(method.to_s)
+ match ? attribute_missing(match, *args, &block) : super
end
- super
+ end
+
+ # attribute_missing is like method_missing, but for attributes. When method_missing is
+ # called we check to see if there is a matching attribute method. If so, we call
+ # attribute_missing to dispatch the attribute. This method can be overloaded to
+ # customise the behaviour.
+ def attribute_missing(match, *args, &block)
+ __send__(match.target, match.attr_name, *args, &block)
end
# A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
@@ -416,15 +434,14 @@ def method_missing(method_id, *args, &block)
alias :respond_to_without_attributes? :respond_to?
def respond_to?(method, include_private_methods = false)
if super
- return true
+ true
elsif !include_private_methods && super(method, true)
# If we're here then we haven't found among non-private methods
# but found among all methods. Which means that the given method is private.
- return false
- elsif match_attribute_method?(method.to_s)
- return true
+ false
+ else
+ !match_attribute_method?(method.to_s).nil?
end
- super
end
protected
@@ -440,13 +457,6 @@ def match_attribute_method?(method_name)
match && attribute_method?(match.attr_name) ? match : nil
end
- # prevent method_missing from calling private methods with #send
- def guard_private_attribute_method!(method_name, args)
- if self.class.private_method_defined?(method_name)
- raise NoMethodError.new("Attempt to call private method `#{method_name}'", method_name, args)
- end
- end
-
def missing_attribute(attr_name, stack)
raise ActiveModel::MissingAttributeError, "missing attribute: #{attr_name}", stack
end
View
31 activemodel/lib/active_model/errors.rb
@@ -180,6 +180,7 @@ def empty?
all? { |k, v| v && v.empty? }
end
alias_method :blank?, :empty?
+
# Returns an xml formatted representation of the Errors hash.
#
# p.errors.add(:name, "can't be blank")
@@ -254,20 +255,22 @@ def add_on_blank(attributes, options = {})
# company.errors.full_messages # =>
# ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Email can't be blank"]
def full_messages
- map { |attribute, message|
- if attribute == :base
- message
- else
- attr_name = attribute.to_s.gsub('.', '_').humanize
- attr_name = @base.class.human_attribute_name(attribute, :default => attr_name)
-
- I18n.t(:"errors.format", {
- :default => "%{attribute} %{message}",
- :attribute => attr_name,
- :message => message
- })
- end
- }
+ map { |attribute, message| full_message(attribute, message) }
+ end
+
+ # Returns a full message for a given attribute.
+ #
+ # company.errors.full_message(:name, "is invalid") # =>
+ # "Name is invalid"
+ def full_message(attribute, message)
+ return message if attribute == :base
+ attr_name = attribute.to_s.gsub('.', '_').humanize
+ attr_name = @base.class.human_attribute_name(attribute, :default => attr_name)
+ I18n.t(:"errors.format", {
+ :default => "%{attribute} %{message}",
+ :attribute => attr_name,
+ :message => message
+ })
end
# Translates an error message in its default scope
View
108 activemodel/test/cases/attribute_methods_test.rb
@@ -3,8 +3,6 @@
class ModelWithAttributes
include ActiveModel::AttributeMethods
- attribute_method_suffix ''
-
class << self
define_method(:bar) do
'original bar'
@@ -24,14 +22,31 @@ def attribute(name)
class ModelWithAttributes2
include ActiveModel::AttributeMethods
+ attr_accessor :attributes
+
attribute_method_suffix '_test'
+
+private
+ def attribute(name)
+ attributes[name.to_s]
+ end
+
+ alias attribute_test attribute
+
+ def private_method
+ "<3 <3"
+ end
+
+protected
+
+ def protected_method
+ "O_o O_o"
+ end
end
class ModelWithAttributesWithSpaces
include ActiveModel::AttributeMethods
- attribute_method_suffix ''
-
def attributes
{ :'foo bar' => 'value of foo bar'}
end
@@ -45,8 +60,6 @@ def attribute(name)
class ModelWithWeirdNamesAttributes
include ActiveModel::AttributeMethods
- attribute_method_suffix ''
-
class << self
define_method(:'c?d') do
'original c?d'
@@ -76,6 +89,29 @@ class AttributeMethodsTest < ActiveModel::TestCase
assert_equal "value of foo", ModelWithAttributes.new.foo
end
+ test '#define_attribute_method does not generate attribute method if already defined in attribute module' do
+ klass = Class.new(ModelWithAttributes)
+ klass.generated_attribute_methods.module_eval do
+ def foo
+ '<3'
+ end
+ end
+ klass.define_attribute_method(:foo)
+
+ assert_equal '<3', klass.new.foo
+ end
+
+ test '#define_attribute_method generates a method that is already defined on the host' do
+ klass = Class.new(ModelWithAttributes) do
+ def foo
+ super
+ end
+ end
+ klass.define_attribute_method(:foo)
+
+ assert_equal 'value of foo', klass.new.foo
+ end
+
test '#define_attribute_method generates attribute method with invalid identifier characters' do
ModelWithWeirdNamesAttributes.define_attribute_method(:'a?b')
@@ -129,4 +165,64 @@ class AttributeMethodsTest < ActiveModel::TestCase
assert !ModelWithAttributes.new.respond_to?(:foo)
assert_raises(NoMethodError) { ModelWithAttributes.new.foo }
end
+
+ test 'acessing a suffixed attribute' do
+ m = ModelWithAttributes2.new
+ m.attributes = { 'foo' => 'bar' }
+
+ assert_equal 'bar', m.foo
+ assert_equal 'bar', m.foo_test
+ end
+
+ test 'explicitly specifying an empty prefix/suffix is deprecated' do
+ klass = Class.new(ModelWithAttributes)
+
+ assert_deprecated { klass.attribute_method_suffix '' }
+ assert_deprecated { klass.attribute_method_prefix '' }
+
+ klass.define_attribute_methods([:foo])
+
+ assert_equal 'value of foo', klass.new.foo
+ end
+
+ test 'should not interfere with method_missing if the attr has a private/protected method' do
+ m = ModelWithAttributes2.new
+ m.attributes = { 'private_method' => '<3', 'protected_method' => 'O_o' }
+
+ # dispatches to the *method*, not the attribute
+ assert_equal '<3 <3', m.send(:private_method)
+ assert_equal 'O_o O_o', m.send(:protected_method)
+
+ # sees that a method is already defined, so doesn't intervene
+ assert_raises(NoMethodError) { m.private_method }
+ assert_raises(NoMethodError) { m.protected_method }
+ end
+
+ test 'should not interfere with respond_to? if the attribute has a private/protected method' do
+ m = ModelWithAttributes2.new
+ m.attributes = { 'private_method' => '<3', 'protected_method' => 'O_o' }
+
+ assert !m.respond_to?(:private_method)
+ assert m.respond_to?(:private_method, true)
+
+ # This is messed up, but it's how Ruby works at the moment. Apparently it will be changed
+ # in the future.
+ assert m.respond_to?(:protected_method)
+ assert m.respond_to?(:protected_method, true)
+ end
+
+ test 'should use attribute_missing to dispatch a missing attribute' do
+ m = ModelWithAttributes2.new
+ m.attributes = { 'foo' => 'bar' }
+
+ def m.attribute_missing(match, *args, &block)
+ match
+ end
+
+ match = m.foo_test
+
+ assert_equal 'foo', match.attr_name
+ assert_equal 'attribute_test', match.target
+ assert_equal 'foo_test', match.method_name
+ end
end
View
31 activemodel/test/cases/errors_test.rb
@@ -52,7 +52,6 @@ def test_has_key?
person.validate!
assert_equal ["name can not be nil"], person.errors.full_messages
assert_equal ["can not be nil"], person.errors[:name]
-
end
test 'should be able to assign error' do
@@ -78,12 +77,40 @@ def test_has_key?
person.errors.add(:name, "can not be blank")
person.errors.add(:name, "can not be nil")
assert_equal ["name can not be blank", "name can not be nil"], person.errors.to_a
-
end
test 'to_hash should return an ordered hash' do
person = Person.new
person.errors.add(:name, "can not be blank")
assert_instance_of ActiveSupport::OrderedHash, person.errors.to_hash
end
+
+ test 'full_messages should return an array of error messages, with the attribute name included' do
+ person = Person.new
+ person.errors.add(:name, "can not be blank")
+ person.errors.add(:name, "can not be nil")
+ assert_equal ["name can not be blank", "name can not be nil"], person.errors.to_a
+ end
+
+ test 'full_message should return the given message if attribute equals :base' do
+ person = Person.new
+ assert_equal "press the button", person.errors.full_message(:base, "press the button")
+ end
+
+ test 'full_message should return the given message with the attribute name included' do
+ person = Person.new
+ assert_equal "name can not be blank", person.errors.full_message(:name, "can not be blank")
+ end
+
+ test 'should return a JSON hash representation of the errors' do
+ person = Person.new
+ person.errors.add(:name, "can not be blank")
+ person.errors.add(:name, "can not be nil")
+ person.errors.add(:email, "is invalid")
+ hash = person.errors.as_json
+ assert_equal ["can not be blank", "can not be nil"], hash[:name]
+ assert_equal ["is invalid"], hash[:email]
+ end
+
end
+
View
10 activerecord/CHANGELOG
@@ -4,10 +4,12 @@ Wed Sep 7 15:25:02 2011 Aaron Patterson <aaron@tenderlovemaking.com>
keys are per process id.
* lib/active_record/connection_adapters/sqlite_adapter.rb: ditto
-* Add first_or_create, first_or_create!, first_or_build and first_or_new methods to Active Record. This is a better approach over the old find_or_create_by dynamic methods because it's clearer which arguments are used to find the record and which are used to create it:
+* Add first_or_create, first_or_create!, first_or_initialize methods to Active Record. This is a
+ better approach over the old find_or_create_by dynamic methods because it's clearer which
+ arguments are used to find the record and which are used to create it:
+
+ User.where(:first_name => "Scarlett").first_or_create!(:last_name => "Johansson")
- User.where(:first_name => "Scarlett").first_or_create!(:last_name => "Johansson", :hot => true)
-
[Andrés Mejía]
* Support bulk change_table in mysql2 adapter, as well as the mysql one. [Jon Leighton]
@@ -34,7 +36,7 @@ a URI that specifies the connection configuration. For example:
[Prem Sichanugrist]
-*Rails 3.1.0 (unreleased)*
+*Rails 3.1.0 (August 30, 2011)*
* Add a proxy_association method to association proxies, which can be called by association
extensions to access information about the association. This replaces proxy_owner etc with
View
2 activerecord/lib/active_record/associations.rb
@@ -1329,7 +1329,7 @@ def has_one(name, options = {})
#
# [:class_name]
# Specify the class name of the association. Use it only if that name can't be inferred
- # from the association name. So <tt>has_one :author</tt> will by default be linked to the Author class, but
+ # from the association name. So <tt>belongs_to :author</tt> will by default be linked to the Author class, but
# if the real class name is Person, you'll have to specify it with this option.
# [:conditions]
# Specify the conditions that the associated object must meet in order to be included as a +WHERE+
View
84 activerecord/lib/active_record/attribute_methods.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/enumerable'
+require 'active_support/deprecation'
module ActiveRecord
# = Active Record Attribute Methods
@@ -11,56 +12,83 @@ module ClassMethods
# accessors, mutators and query methods.
def define_attribute_methods
return if attribute_methods_generated?
- super(column_names)
- @attribute_methods_generated = true
+
+ if base_class == self
+ super(column_names)
+ @attribute_methods_generated = true
+ else
+ base_class.define_attribute_methods
+ end
end
def attribute_methods_generated?
- @attribute_methods_generated ||= false
+ if base_class == self
+ @attribute_methods_generated ||= false
+ else
+ base_class.attribute_methods_generated?
+ end
end
def undefine_attribute_methods(*args)
- super
- @attribute_methods_generated = false
+ if base_class == self
+ super
+ @attribute_methods_generated = false
+ else
+ base_class.undefine_attribute_methods(*args)
+ end
end
- # Checks whether the method is defined in the model or any of its subclasses
- # that also derive from Active Record. Raises DangerousAttributeError if the
- # method is defined by Active Record though.
def instance_method_already_implemented?(method_name)
- method_name = method_name.to_s
- index = ancestors.index(ActiveRecord::Base) || ancestors.length
- @_defined_class_methods ||= ancestors.first(index).map { |m|
- m.instance_methods(false) | m.private_instance_methods(false)
- }.flatten.map {|m| m.to_s }.to_set
+ if dangerous_attribute_method?(method_name)
+ raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord"
+ end
- @@_defined_activerecord_methods ||= defined_activerecord_methods
- raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" if @@_defined_activerecord_methods.include?(method_name)
- @_defined_class_methods.include?(method_name)
+ super
end
- def defined_activerecord_methods
+ # A method name is 'dangerous' if it is already defined by Active Record, but
+ # not by any ancestors. (So 'puts' is not dangerous but 'save' is.)
+ def dangerous_attribute_method?(method_name)
active_record = ActiveRecord::Base
- super_klass = ActiveRecord::Base.superclass
- methods = (active_record.instance_methods - super_klass.instance_methods) +
- (active_record.private_instance_methods - super_klass.private_instance_methods)
- methods.map {|m| m.to_s }.to_set
+ superclass = ActiveRecord::Base.superclass
+
+ (active_record.method_defined?(method_name) ||
+ active_record.private_method_defined?(method_name)) &&
+ !superclass.method_defined?(method_name) &&
+ !superclass.private_method_defined?(method_name)
end
end
- def method_missing(method_id, *args, &block)
- # If we haven't generated any methods yet, generate them, then
- # see if we've created the method we're looking for.
- if !self.class.attribute_methods_generated?
+ # If we haven't generated any methods yet, generate them, then
+ # see if we've created the method we're looking for.
+ def method_missing(method, *args, &block)
+ unless self.class.attribute_methods_generated?
self.class.define_attribute_methods
- method_name = method_id.to_s
- guard_private_attribute_method!(method_name, args)
- send(method_id, *args, &block)
+
+ if respond_to_without_attributes?(method)
+ send(method, *args, &block)
+ else
+ super
+ end
else
super
end
end
+ def attribute_missing(match, *args, &block)
+ if self.class.columns_hash[match.attr_name]
+ ActiveSupport::Deprecation.warn(
+ "The method `#{match.method_name}', matching the attribute `#{match.attr_name}' has " \
+ "dispatched through method_missing. This shouldn't happen, because `#{match.attr_name}' " \
+ "is a column of the table. If this error has happened through normal usage of Active " \
+ "Record (rather than through your own code or external libraries), please report it as " \
+ "a bug."
+ )
+ end
+
+ super
+ end
+
def respond_to?(name, include_private = false)
self.class.define_attribute_methods unless self.class.attribute_methods_generated?
super
View
2 activerecord/lib/active_record/attribute_methods/read.rb
@@ -6,8 +6,6 @@ module Read
ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
included do
- attribute_method_suffix ""
-
cattr_accessor :attribute_types_cached_by_default, :instance_writer => false
self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
View
14 activerecord/lib/active_record/attribute_methods/write.rb
@@ -17,19 +17,27 @@ def define_method_attribute=(attr_name)
write_attribute(attr_name, new_value)
end
end
+
+ if attr_name == primary_key && attr_name != "id"
+ generated_attribute_methods.module_eval("alias :id= :'#{primary_key}='")
+ end
end
end
# Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings
# for fixnum and float columns are turned into +nil+.
def write_attribute(attr_name, value)
attr_name = attr_name.to_s
- attr_name = self.class.primary_key if attr_name == 'id'
+ attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
@attributes_cache.delete(attr_name)
- if (column = column_for_attribute(attr_name)) && column.number?
+ column = column_for_attribute(attr_name)
+
+ if column && column.number?
@attributes[attr_name] = convert_number_column_value(value)
- else
+ elsif column || @attributes.has_key?(attr_name)
@attributes[attr_name] = value
+ else
+ raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"
end
end
alias_method :raw_write_attribute, :write_attribute
View
5 activerecord/lib/active_record/autosave_association.rb
@@ -370,7 +370,10 @@ def save_has_one_association(reflection)
else
key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
if autosave != false && (new_record? || record.new_record? || record[reflection.foreign_key] != key || autosave)
- record[reflection.foreign_key] = key
+ unless reflection.through_reflection
+ record[reflection.foreign_key] = key
+ end
+
saved = record.save(:validate => !autosave)
raise ActiveRecord::Rollback if !saved && autosave
saved
View
4 activerecord/lib/active_record/base.rb
@@ -442,7 +442,7 @@ class Base
class << self # Class methods
delegate :find, :first, :first!, :last, :last!, :all, :exists?, :any?, :many?, :to => :scoped
- delegate :first_or_create, :first_or_create!, :first_or_new, :first_or_build, :to => :scoped
+ delegate :first_or_create, :first_or_create!, :first_or_initialize, :to => :scoped
delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to => :scoped
delegate :find_each, :find_in_batches, :to => :scoped
delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped
@@ -1332,7 +1332,7 @@ def compute_type(type_name)
# Returns the class descending directly from ActiveRecord::Base or an
# abstract class, if any, in the inheritance hierarchy.
def class_of_active_record_descendant(klass)
- if klass.superclass == Base || klass.superclass.abstract_class?
+ if klass == Base || klass.superclass == Base || klass.superclass.abstract_class?
klass
elsif klass.superclass.nil?
raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
View
2 activerecord/lib/active_record/persistence.rb
@@ -314,7 +314,7 @@ def create
new_id = self.class.unscoped.insert attributes_values
- self.id ||= new_id
+ self.id ||= new_id if self.class.primary_key
IdentityMap.add(self) if IdentityMap.enabled?
@new_record = false
View
12 activerecord/lib/active_record/railties/databases.rake
@@ -203,11 +203,13 @@ db_namespace = namespace :db do
end
db_list = ActiveRecord::Base.connection.select_values("SELECT version FROM #{ActiveRecord::Migrator.schema_migrations_table_name}")
file_list = []
- Dir.foreach(File.join(Rails.root, 'db', 'migrate')) do |file|
- # only files matching "20091231235959_some_name.rb" pattern
- if match_data = /^(\d{14})_(.+)\.rb$/.match(file)
- status = db_list.delete(match_data[1]) ? 'up' : 'down'
- file_list << [status, match_data[1], match_data[2].humanize]
+ ActiveRecord::Migrator.migrations_paths.each do |path|
+ Dir.foreach(path) do |file|
+ # only files matching "20091231235959_some_name.rb" pattern
+ if match_data = /^(\d{14})_(.+)\.rb$/.match(file)