diff options
79 files changed, 838 insertions, 544 deletions
@@ -13,7 +13,8 @@ gem "rack-test", :git => "git://github.com/brynary/rack-test.git" gem "sprockets", :git => "git://github.com/sstephenson/sprockets.git" gem "coffee-script" -gem "sass", ">= 3.0" +gem "sass" +gem "uglifier" gem "rake", ">= 0.8.7" gem "mocha", ">= 0.9.8" @@ -26,7 +27,6 @@ end # AS gem "memcache-client", ">= 1.8.5" -gem "fssm", "~> 0.2.5" platforms :mri_18 do gem "system_timer" @@ -52,7 +52,7 @@ platforms :ruby do gem "sqlite3", "~> 1.3.3" group :db do - gem "pg", ">= 0.9.0" + gem "pg", ">= 0.11.0" gem "mysql", ">= 2.8.1" gem "mysql2", :git => "git://github.com/brianmario/mysql2.git" end diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec index a59069cc37..447e25ca8a 100644 --- a/actionmailer/actionmailer.gemspec +++ b/actionmailer/actionmailer.gemspec @@ -18,5 +18,5 @@ Gem::Specification.new do |s| s.requirements << 'none' s.add_dependency('actionpack', version) - s.add_dependency('mail', '~> 2.2.16') + s.add_dependency('mail', '~> 2.3.0') end diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb index ce664bf301..0b076e1ff9 100644 --- a/actionmailer/test/abstract_unit.rb +++ b/actionmailer/test/abstract_unit.rb @@ -23,11 +23,6 @@ if "ruby".encoding_aware? end end -silence_warnings do - # These external dependencies have warnings :/ - require 'mail' -end - lib = File.expand_path("#{File.dirname(__FILE__)}/../lib") $:.unshift(lib) unless $:.include?('lib') || $:.include?(lib) @@ -35,6 +30,11 @@ require 'test/unit' require 'action_mailer' require 'action_mailer/test_case' +silence_warnings do + # These external dependencies have warnings :/ + require 'mail' +end + # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index d3c66800d9..f771737779 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |s| s.add_dependency('activemodel', version) s.add_dependency('rack-cache', '~> 1.0.0') s.add_dependency('builder', '~> 3.0.0') - s.add_dependency('i18n', '~> 0.5.0') + s.add_dependency('i18n', '~> 0.6.0beta1') s.add_dependency('rack', '~> 1.2.1') s.add_dependency('rack-test', '~> 0.5.7') s.add_dependency('rack-mount', '~> 0.7.1') diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index 20f8601a8e..0ff1c0491a 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -112,17 +112,6 @@ module AbstractController default_helper_module! unless anonymous? end - private - # Makes all the (instance) methods in the helper module available to templates - # rendered through this controller. - # - # ==== Parameters - # * <tt>module</tt> - The module to include into the current helper module - # for the class - def add_template_helper(mod) - _helpers.module_eval { include mod } - end - # Returns a list of modules, normalized from the acceptable kinds of # helpers with the following behavior: # @@ -155,6 +144,17 @@ module AbstractController end end + private + # Makes all the (instance) methods in the helper module available to templates + # rendered through this controller. + # + # ==== Parameters + # * <tt>module</tt> - The module to include into the current helper module + # for the class + def add_template_helper(mod) + _helpers.module_eval { include mod } + end + def default_helper_module! module_name = name.sub(/Controller$/, '') module_path = module_name.underscore diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index 91a88ab68a..75757db564 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -76,35 +76,35 @@ module ActionController @helper_proxy ||= ActionView::Base.new.extend(_helpers) end - private - # Overwrite modules_for_helpers to accept :all as argument, which loads - # all helpers in helpers_path. - # - # ==== Parameters - # * <tt>args</tt> - A list of helpers - # - # ==== Returns - # * <tt>array</tt> - A normalized list of modules for the list of helpers provided. - def modules_for_helpers(args) - args += all_application_helpers if args.delete(:all) - super(args) - end + # Overwrite modules_for_helpers to accept :all as argument, which loads + # all helpers in helpers_path. + # + # ==== Parameters + # * <tt>args</tt> - A list of helpers + # + # ==== Returns + # * <tt>array</tt> - A normalized list of modules for the list of helpers provided. + def modules_for_helpers(args) + args += all_application_helpers if args.delete(:all) + super(args) + end - # Extract helper names from files in <tt>app/helpers/**/*_helper.rb</tt> - def all_application_helpers - all_helpers_from_path(helpers_path) + def all_helpers_from_path(path) + helpers = [] + Array.wrap(path).each do |_path| + extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/ + helpers += Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') } end + helpers.sort! + helpers.uniq! + helpers + end - def all_helpers_from_path(path) - helpers = [] - Array.wrap(path).each do |_path| - extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/ - helpers += Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') } - end - helpers.sort! - helpers.uniq! - helpers - end + private + # Extract helper names from files in <tt>app/helpers/**/*_helper.rb</tt> + def all_application_helpers + all_helpers_from_path(helpers_path) + end end end end diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index dbe3206808..b1adf3d2d1 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -50,7 +50,7 @@ module ActionDispatch # Only this middleware cares about RoutingError. So, let's just raise # it here. if headers['X-Cascade'] == 'pass' - raise ActionController::RoutingError, "No route matches #{env['PATH_INFO'].inspect}" + raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}" end rescue Exception => exception raise exception if env['action_dispatch.show_exceptions'] == false diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index b28f6c2297..1d09091dc7 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -275,8 +275,7 @@ module ActionDispatch module MountedHelpers end - def mounted_helpers(name = :main_app) - define_mounted_helper(name) if name + def mounted_helpers MountedHelpers end diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb index 8a04cfa886..e209978fb7 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/response.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb @@ -42,7 +42,7 @@ module ActionDispatch elsif type.is_a?(Symbol) && @response.response_code == Rack::Utils::SYMBOL_TO_STATUS_CODE[type] assert_block("") { true } # to count the assertion else - assert_block(build_message(message, "Expected response to be a <?>, but was <?>", type, @response.response_code)) { false } + assert(false, build_message(message, "Expected response to be a <?>, but was <?>", type, @response.response_code)) end end diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index a7ae6ad0bf..9bc847a1ab 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -57,7 +57,7 @@ module ActionView # +asset_host+ to a proc like this: # # ActionController::Base.asset_host = Proc.new { |source| - # "http://assets#{source.hash % 2 + 1}.example.com" + # "http://assets#{Digest::MD5.hexdigest(source).to_i(16) % 2 + 1}.example.com" # } # image_tag("rails.png") # # => <img alt="Rails" src="http://assets1.example.com/images/rails.png?1230601161" /> @@ -268,7 +268,7 @@ module ActionView # image_path("edit.png") # => "/images/edit.png" # image_path("icons/edit.png") # => "/images/icons/edit.png" # image_path("/icons/edit.png") # => "/icons/edit.png" - # image_path("http://www.railsapplication.com/img/edit.png") # => "http://www.railsapplication.com/img/edit.png" + # image_path("http://www.example.com/img/edit.png") # => "http://www.example.com/img/edit.png" # # If you have images as application resources this method may conflict with their named routes. # The alias +path_to_image+ is provided to avoid that. Rails uses the alias internally, and @@ -291,7 +291,7 @@ module ActionView # video_path("hd.avi") # => /videos/hd.avi # video_path("trailers/hd.avi") # => /videos/trailers/hd.avi # video_path("/trailers/hd.avi") # => /trailers/hd.avi - # video_path("http://www.railsapplication.com/vid/hd.avi") # => http://www.railsapplication.com/vid/hd.avi + # video_path("http://www.example.com/vid/hd.avi") # => http://www.example.com/vid/hd.avi def video_path(source) if config.use_sprockets asset_path(source) @@ -310,7 +310,7 @@ module ActionView # audio_path("horse.wav") # => /audios/horse.wav # audio_path("sounds/horse.wav") # => /audios/sounds/horse.wav # audio_path("/sounds/horse.wav") # => /sounds/horse.wav - # audio_path("http://www.railsapplication.com/sounds/horse.wav") # => http://www.railsapplication.com/sounds/horse.wav + # audio_path("http://www.example.com/sounds/horse.wav") # => http://www.example.com/sounds/horse.wav def audio_path(source) if config.use_sprockets asset_path(source) diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb index 07ff49659a..3d815b5e1f 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb @@ -77,11 +77,11 @@ module ActionView # Used internally by javascript_include_tag to build the script path. # # ==== Examples - # javascript_path "xmlhr" # => /javascripts/xmlhr.js - # javascript_path "dir/xmlhr.js" # => /javascripts/dir/xmlhr.js - # javascript_path "/dir/xmlhr" # => /dir/xmlhr.js - # javascript_path "http://www.railsapplication.com/js/xmlhr" # => http://www.railsapplication.com/js/xmlhr - # javascript_path "http://www.railsapplication.com/js/xmlhr.js" # => http://www.railsapplication.com/js/xmlhr.js + # javascript_path "xmlhr" # => /javascripts/xmlhr.js + # javascript_path "dir/xmlhr.js" # => /javascripts/dir/xmlhr.js + # javascript_path "/dir/xmlhr" # => /dir/xmlhr.js + # 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) if config.use_sprockets asset_path(source, 'js') @@ -123,11 +123,11 @@ module ActionView # # => <script type="text/javascript" src="/javascripts/common.javascript?1284139606"></script> # # <script type="text/javascript" src="/elsewhere/cools.js?1423139606"></script> # - # javascript_include_tag "http://www.railsapplication.com/xmlhr" - # # => <script type="text/javascript" src="http://www.railsapplication.com/xmlhr.js?1284139606"></script> + # javascript_include_tag "http://www.example.com/xmlhr" + # # => <script type="text/javascript" src="http://www.example.com/xmlhr.js?1284139606"></script> # - # javascript_include_tag "http://www.railsapplication.com/xmlhr.js" - # # => <script type="text/javascript" src="http://www.railsapplication.com/xmlhr.js?1284139606"></script> + # javascript_include_tag "http://www.example.com/xmlhr.js" + # # => <script type="text/javascript" src="http://www.example.com/xmlhr.js?1284139606"></script> # # javascript_include_tag :defaults # # => <script type="text/javascript" src="/javascripts/jquery.js?1284139606"></script> diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb index c3dcd410bb..a95eb221be 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb @@ -54,11 +54,11 @@ module ActionView # Used internally by +stylesheet_link_tag+ to build the stylesheet path. # # ==== Examples - # stylesheet_path "style" # => /stylesheets/style.css - # stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css - # stylesheet_path "/dir/style.css" # => /dir/style.css - # stylesheet_path "http://www.railsapplication.com/css/style" # => http://www.railsapplication.com/css/style - # stylesheet_path "http://www.railsapplication.com/css/style.css" # => http://www.railsapplication.com/css/style.css + # stylesheet_path "style" # => /stylesheets/style.css + # stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css + # stylesheet_path "/dir/style.css" # => /dir/style.css + # 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) if config.use_sprockets asset_path(source, 'css') @@ -79,8 +79,8 @@ module ActionView # stylesheet_link_tag "style.css" # => # <link href="/stylesheets/style.css" media="screen" rel="stylesheet" type="text/css" /> # - # stylesheet_link_tag "http://www.railsapplication.com/style.css" # => - # <link href="http://www.railsapplication.com/style.css" media="screen" rel="stylesheet" type="text/css" /> + # stylesheet_link_tag "http://www.example.com/style.css" # => + # <link href="http://www.example.com/style.css" media="screen" rel="stylesheet" type="text/css" /> # # stylesheet_link_tag "style", :media => "all" # => # <link href="/stylesheets/style.css" media="all" rel="stylesheet" type="text/css" /> diff --git a/actionpack/lib/action_view/helpers/record_tag_helper.rb b/actionpack/lib/action_view/helpers/record_tag_helper.rb index 4d300a1469..142a25f118 100644 --- a/actionpack/lib/action_view/helpers/record_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/record_tag_helper.rb @@ -10,7 +10,7 @@ module ActionView # relate to the specified Active Record object. Usage example: # # <%= div_for(@person, :class => "foo") do %> - # <%=h @person.name %> + # <%= @person.name %> # <% end %> # # produces: @@ -25,8 +25,8 @@ module ActionView # that relate to the specified Active Record object. For example: # # <%= content_tag_for(:tr, @person) do %> - # <td><%=h @person.first_name %></td> - # <td><%=h @person.last_name %></td> + # <td><%= @person.first_name %></td> + # <td><%= @person.last_name %></td> # <% end %> # # would produce the following HTML (assuming @person is an instance of diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb index 59e6ce878f..26ebae6546 100644 --- a/actionpack/lib/action_view/helpers/translation_helper.rb +++ b/actionpack/lib/action_view/helpers/translation_helper.rb @@ -5,7 +5,7 @@ module I18n class ExceptionHandler include Module.new { def call(exception, locale, key, options) - exception.is_a?(MissingTranslationData) ? super.html_safe : super + exception.is_a?(MissingTranslation) ? super.html_safe : super end } end @@ -17,8 +17,8 @@ module ActionView module TranslationHelper # Delegates to I18n#translate but also performs three additional functions. # - # First, it'll pass the :rescue_format => :html option to I18n so that any caught - # MissingTranslationData exceptions will be turned into inline spans that + # First, it'll pass the :rescue_format => :html option to I18n so that any + # thrown MissingTranslation messages will be turned into inline spans that # # * have a "translation-missing" class set, # * contain the missing key as a title attribute and diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index 051d3eb049..ffa9a5bb0b 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -68,7 +68,7 @@ module ActionView # # => /books/find # # <%= url_for(:action => 'login', :controller => 'members', :only_path => false, :protocol => 'https') %> - # # => https://www.railsapplication.com/members/login/ + # # => https://www.example.com/members/login/ # # <%= url_for(:action => 'play', :anchor => 'player') %> # # => /messages/play/#player diff --git a/actionpack/lib/sprockets/railtie.rb b/actionpack/lib/sprockets/railtie.rb index ccec65ba95..9c10decd60 100644 --- a/actionpack/lib/sprockets/railtie.rb +++ b/actionpack/lib/sprockets/railtie.rb @@ -57,7 +57,44 @@ module Sprockets env.static_root = File.join(app.root.join("public"), assets.prefix) env.paths.concat assets.paths env.logger = Rails.logger + env.js_compressor = expand_js_compressor(assets.js_compressor) + env.css_compressor = expand_css_compressor(assets.css_compressor) env end + + def expand_js_compressor(sym) + case sym + when :closure + require 'closure-compiler' + Closure::Compiler.new + when :uglifier + require 'uglifier' + Uglifier.new + when :yui + require 'yui/compressor' + YUI::JavaScriptCompressor.new + else + sym + end + end + + def expand_css_compressor(sym) + case sym + when :scss + require 'sass' + compressor = Object.new + def compressor.compress(source) + Sass::Engine.new(source, + :syntax => :scss, :style => :compressed + ).render + end + compressor + when :yui + require 'yui/compressor' + YUI::JavaScriptCompressor.new(:munge => true) + else + sym + end + end end end diff --git a/actionpack/test/controller/view_paths_test.rb b/actionpack/test/controller/view_paths_test.rb index 9280a1c2d3..42356be1ea 100644 --- a/actionpack/test/controller/view_paths_test.rb +++ b/actionpack/test/controller/view_paths_test.rb @@ -131,8 +131,8 @@ class ViewLoadPathsTest < ActionController::TestCase assert_equal "Hello overridden world!", @response.body end - def test_override_view_paths_with_custom_resolver - resolver_class = Class.new(ActionView::PathResolver) do + def test_decorate_view_paths_with_custom_resolver + decorator_class = Class.new(ActionView::PathResolver) do def initialize(path_set) @path_set = path_set end @@ -140,7 +140,7 @@ class ViewLoadPathsTest < ActionController::TestCase def find_all(*args) @path_set.find_all(*args).collect do |template| ::ActionView::Template.new( - "Customized body", + "Decorated body", template.identifier, template.handler, { @@ -152,12 +152,12 @@ class ViewLoadPathsTest < ActionController::TestCase end end - resolver = resolver_class.new(TestController.view_paths) - TestController.view_paths = ActionView::PathSet.new.push(resolver) + decorator = decorator_class.new(TestController.view_paths) + TestController.view_paths = ActionView::PathSet.new.push(decorator) get :hello_world assert_response :success - assert_equal "Customized body", @response.body + assert_equal "Decorated body", @response.body end def test_inheritance diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb index 18f28deee4..b28a058250 100644 --- a/actionpack/test/dispatch/prefix_generation_test.rb +++ b/actionpack/test/dispatch/prefix_generation_test.rb @@ -69,6 +69,7 @@ module TestGenerationPrefix # force draw RailsApplication.routes + RailsApplication.routes.define_mounted_helper(:main_app) class ::InsideEngineGeneratingController < ActionController::Base include BlogEngine.routes.url_helpers diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb index c8d50ebf75..23a7e17e65 100644 --- a/actionpack/test/template/number_helper_test.rb +++ b/actionpack/test/template/number_helper_test.rb @@ -32,6 +32,7 @@ class NumberHelperTest < ActionView::TestCase assert_equal("555-1234", number_to_phone(5551234)) assert_equal("800-555-1212", number_to_phone(8005551212)) assert_equal("(800) 555-1212", number_to_phone(8005551212, {:area_code => true})) + assert_equal("", number_to_phone("", {:area_code => true})) assert_equal("800 555 1212", number_to_phone(8005551212, {:delimiter => " "})) assert_equal("(800) 555-1212 x 123", number_to_phone(8005551212, {:area_code => true, :extension => 123})) assert_equal("800-555-1212", number_to_phone(8005551212, :extension => " ")) diff --git a/actionpack/test/template/sprockets_helper_test.rb b/actionpack/test/template/sprockets_helper_test.rb index d8aef34bfb..8d3be09a4f 100644 --- a/actionpack/test/template/sprockets_helper_test.rb +++ b/actionpack/test/template/sprockets_helper_test.rb @@ -69,10 +69,10 @@ class SprocketsHelperTest < ActionView::TestCase assert_equal "/dir/xmlhr.js", asset_path("/dir/xmlhr", "js") - assert_equal "http://www.railsapplication.com/js/xmlhr", - asset_path("http://www.railsapplication.com/js/xmlhr", "js") - assert_equal "http://www.railsapplication.com/js/xmlhr.js", - asset_path("http://www.railsapplication.com/js/xmlhr.js", "js") + assert_equal "http://www.example.com/js/xmlhr", + asset_path("http://www.example.com/js/xmlhr", "js") + assert_equal "http://www.example.com/js/xmlhr.js", + asset_path("http://www.example.com/js/xmlhr.js", "js") end test "javascript include tag" do @@ -83,8 +83,8 @@ class SprocketsHelperTest < ActionView::TestCase sprockets_javascript_include_tag("xmlhr") assert_equal '<script src="/assets/xmlhr-d41d8cd98f00b204e9800998ecf8427e.js" type="text/javascript"></script>', sprockets_javascript_include_tag("xmlhr.js") - assert_equal '<script src="http://www.railsapplication.com/xmlhr" type="text/javascript"></script>', - sprockets_javascript_include_tag("http://www.railsapplication.com/xmlhr") + assert_equal '<script src="http://www.example.com/xmlhr" type="text/javascript"></script>', + sprockets_javascript_include_tag("http://www.example.com/xmlhr") end test "stylesheet path" do @@ -94,10 +94,10 @@ class SprocketsHelperTest < ActionView::TestCase assert_equal "/assets/dir/style-d41d8cd98f00b204e9800998ecf8427e.css", asset_path("dir/style.css", "css") assert_equal "/dir/style.css", asset_path("/dir/style.css", "css") - assert_equal "http://www.railsapplication.com/css/style", - asset_path("http://www.railsapplication.com/css/style", "css") - assert_equal "http://www.railsapplication.com/css/style.css", - asset_path("http://www.railsapplication.com/css/style.css", "css") + assert_equal "http://www.example.com/css/style", + asset_path("http://www.example.com/css/style", "css") + assert_equal "http://www.example.com/css/style.css", + asset_path("http://www.example.com/css/style.css", "css") end test "stylesheet link tag" do @@ -109,8 +109,8 @@ class SprocketsHelperTest < ActionView::TestCase assert_equal '<link href="/assets/style-d41d8cd98f00b204e9800998ecf8427e.css" media="screen" rel="stylesheet" type="text/css" />', sprockets_stylesheet_link_tag("style.css") - assert_equal '<link href="http://www.railsapplication.com/style.css" media="screen" rel="stylesheet" type="text/css" />', - sprockets_stylesheet_link_tag("http://www.railsapplication.com/style.css") + assert_equal '<link href="http://www.example.com/style.css" media="screen" rel="stylesheet" type="text/css" />', + sprockets_stylesheet_link_tag("http://www.example.com/style.css") assert_equal '<link href="/assets/style-d41d8cd98f00b204e9800998ecf8427e.css" media="all" rel="stylesheet" type="text/css" />', sprockets_stylesheet_link_tag("style", :media => "all") assert_equal '<link href="/assets/style-d41d8cd98f00b204e9800998ecf8427e.css" media="print" rel="stylesheet" type="text/css" />', diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec index 9f80673bb8..ce69c4a201 100644 --- a/activemodel/activemodel.gemspec +++ b/activemodel/activemodel.gemspec @@ -19,6 +19,6 @@ Gem::Specification.new do |s| s.add_dependency('activesupport', version) s.add_dependency('builder', '~> 3.0.0') - s.add_dependency('i18n', '~> 0.5.0') + s.add_dependency('i18n', '~> 0.6.0beta1') s.add_dependency('bcrypt-ruby', '~> 2.1.4') end diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb index be48415739..01eef762fd 100644 --- a/activemodel/lib/active_model/mass_assignment_security.rb +++ b/activemodel/lib/active_model/mass_assignment_security.rb @@ -24,10 +24,7 @@ module ActiveModel # include ActiveModel::MassAssignmentSecurity # # attr_accessible :first_name, :last_name - # - # def self.admin_accessible_attributes - # accessible_attributes + [ :plan_id ] - # end + # attr_accessible :first_name, :last_name, :plan_id, :as => :admin # # def update # ... @@ -38,18 +35,17 @@ module ActiveModel # protected # # def account_params - # sanitize_for_mass_assignment(params[:account]) - # end - # - # def mass_assignment_authorizer - # admin ? admin_accessible_attributes : super + # scope = admin ? :admin : :default + # sanitize_for_mass_assignment(params[:account], scope) # end # # end # module ClassMethods # Attributes named in this macro are protected from mass-assignment - # whenever attributes are sanitized before assignment. + # whenever attributes are sanitized before assignment. A scope for the + # attributes is optional, if no scope is provided then :default is used. + # A scope can be defined by using the :as option. # # Mass-assignment to these attributes will simply be ignored, to assign # to them you can use direct writer methods. This is meant to protect @@ -60,36 +56,58 @@ module ActiveModel # include ActiveModel::MassAssignmentSecurity # # attr_accessor :name, :credit_rating - # attr_protected :credit_rating # - # def attributes=(values) - # sanitize_for_mass_assignment(values).each do |k, v| + # attr_protected :credit_rating, :last_login + # attr_protected :last_login, :as => :admin + # + # def assign_attributes(values, options = {}) + # sanitize_for_mass_assignment(values, options[:as]).each do |k, v| # send("#{k}=", v) # end # end # end # + # When using a :default scope : + # # customer = Customer.new - # customer.attributes = { "name" => "David", "credit_rating" => "Excellent" } + # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :default) # customer.name # => "David" # customer.credit_rating # => nil + # customer.last_login # => nil # # customer.credit_rating = "Average" # customer.credit_rating # => "Average" # + # And using the :admin scope : + # + # customer = Customer.new + # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :admin) + # customer.name # => "David" + # customer.credit_rating # => "Excellent" + # customer.last_login # => nil + # # To start from an all-closed default and enable attributes as needed, # have a look at +attr_accessible+. # # Note that using <tt>Hash#except</tt> or <tt>Hash#slice</tt> in place of +attr_protected+ # to sanitize attributes won't provide sufficient protection. - def attr_protected(*names) - self._protected_attributes = self.protected_attributes + names + def attr_protected(*args) + options = args.extract_options! + scope = options[:as] || :default + + self._protected_attributes = protected_attributes_configs.dup + self._protected_attributes[scope] = self.protected_attributes(scope) + args + self._active_authorizer = self._protected_attributes end # Specifies a white list of model attributes that can be set via # mass-assignment. # + # Like +attr_protected+, a scope for the attributes is optional, + # if no scope is provided then :default is used. A scope can be defined by + # using the :as option. + # # This is the opposite of the +attr_protected+ macro: Mass-assignment # will only set attributes in this list, to assign to the rest of # attributes you can use direct writer methods. This is meant to protect @@ -102,57 +120,90 @@ module ActiveModel # include ActiveModel::MassAssignmentSecurity # # attr_accessor :name, :credit_rating + # # attr_accessible :name + # attr_accessible :name, :credit_rating, :as => :admin # - # def attributes=(values) - # sanitize_for_mass_assignment(values).each do |k, v| + # def assign_attributes(values, options = {}) + # sanitize_for_mass_assignment(values, options[:as]).each do |k, v| # send("#{k}=", v) # end # end # end # + # When using a :default scope : + # # customer = Customer.new - # customer.attributes = { :name => "David", :credit_rating => "Excellent" } + # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :default) # customer.name # => "David" # customer.credit_rating # => nil # # customer.credit_rating = "Average" # customer.credit_rating # => "Average" # + # And using the :admin scope : + # + # customer = Customer.new + # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :admin) + # customer.name # => "David" + # customer.credit_rating # => "Excellent" + # # Note that using <tt>Hash#except</tt> or <tt>Hash#slice</tt> in place of +attr_accessible+ # to sanitize attributes won't provide sufficient protection. - def attr_accessible(*names) - self._accessible_attributes = self.accessible_attributes + names + def attr_accessible(*args) + options = args.extract_options! + scope = options[:as] || :default + + self._accessible_attributes = accessible_attributes_configs.dup + self._accessible_attributes[scope] = self.accessible_attributes(scope) + args + self._active_authorizer = self._accessible_attributes end - def protected_attributes - self._protected_attributes ||= BlackList.new(attributes_protected_by_default).tap do |w| - w.logger = self.logger if self.respond_to?(:logger) - end + def protected_attributes(scope = :default) + protected_attributes_configs[scope] end - def accessible_attributes - self._accessible_attributes ||= WhiteList.new.tap { |w| w.logger = self.logger if self.respond_to?(:logger) } + def accessible_attributes(scope = :default) + accessible_attributes_configs[scope] end - def active_authorizer - self._active_authorizer ||= protected_attributes + def active_authorizers + self._active_authorizer ||= protected_attributes_configs end + alias active_authorizer active_authorizers def attributes_protected_by_default [] end + + private + + def protected_attributes_configs + self._protected_attributes ||= begin + default_black_list = BlackList.new(attributes_protected_by_default).tap do |w| + w.logger = self.logger if self.respond_to?(:logger) + end + Hash.new(default_black_list) + end + end + + def accessible_attributes_configs + self._accessible_attributes ||= begin + default_white_list = WhiteList.new.tap { |w| w.logger = self.logger if self.respond_to?(:logger) } + Hash.new(default_white_list) + end + end end protected - def sanitize_for_mass_assignment(attributes) - mass_assignment_authorizer.sanitize(attributes) + def sanitize_for_mass_assignment(attributes, scope = :default) + mass_assignment_authorizer(scope).sanitize(attributes) end - def mass_assignment_authorizer - self.class.active_authorizer + def mass_assignment_authorizer(scope = :default) + self.class.active_authorizer[scope] end end end diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb index ef36f80bec..3c80d584fe 100644 --- a/activemodel/lib/active_model/observing.rb +++ b/activemodel/lib/active_model/observing.rb @@ -43,7 +43,7 @@ module ActiveModel @observer_instances ||= [] end - # Instantiate the global Active Record observers. + # Instantiate the global observers. def instantiate_observers observers.each { |o| instantiate_observer(o) } end diff --git a/activemodel/test/cases/mass_assignment_security_test.rb b/activemodel/test/cases/mass_assignment_security_test.rb index f84e55e8d9..b22ce874ea 100644 --- a/activemodel/test/cases/mass_assignment_security_test.rb +++ b/activemodel/test/cases/mass_assignment_security_test.rb @@ -10,10 +10,27 @@ class MassAssignmentSecurityTest < ActiveModel::TestCase assert_equal expected, sanitized end + def test_only_moderator_scope_attribute_accessible + user = SpecialUser.new + expected = { "name" => "John Smith", "email" => "john@smith.com" } + sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true), :moderator) + assert_equal expected, sanitized + + sanitized = user.sanitize_for_mass_assignment({ "name" => "John Smith", "email" => "john@smith.com", "admin" => true }) + assert_equal({}, sanitized) + end + def test_attributes_accessible user = Person.new expected = { "name" => "John Smith", "email" => "john@smith.com" } - sanitized = user.sanitize_for_mass_assignment(expected.merge("super_powers" => true)) + sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true)) + assert_equal expected, sanitized + end + + def test_admin_scoped_attributes_accessible + user = Person.new + expected = { "name" => "John Smith", "email" => "john@smith.com", "admin" => true } + sanitized = user.sanitize_for_mass_assignment(expected.merge("super_powers" => true), :admin) assert_equal expected, sanitized end @@ -26,20 +43,30 @@ class MassAssignmentSecurityTest < ActiveModel::TestCase def test_mass_assignment_protection_inheritance assert_blank LoosePerson.accessible_attributes - assert_equal Set.new([ 'credit_rating', 'administrator']), LoosePerson.protected_attributes + assert_equal Set.new(['credit_rating', 'administrator']), LoosePerson.protected_attributes + + assert_blank LoosePerson.accessible_attributes + assert_equal Set.new(['credit_rating']), LoosePerson.protected_attributes(:admin) assert_blank LooseDescendant.accessible_attributes - assert_equal Set.new([ 'credit_rating', 'administrator', 'phone_number']), LooseDescendant.protected_attributes + assert_equal Set.new(['credit_rating', 'administrator', 'phone_number']), LooseDescendant.protected_attributes assert_blank LooseDescendantSecond.accessible_attributes - assert_equal Set.new([ 'credit_rating', 'administrator', 'phone_number', 'name']), LooseDescendantSecond.protected_attributes, + assert_equal Set.new(['credit_rating', 'administrator', 'phone_number', 'name']), LooseDescendantSecond.protected_attributes, 'Running attr_protected twice in one class should merge the protections' assert_blank TightPerson.protected_attributes - TightPerson.attributes_protected_by_default - assert_equal Set.new([ 'name', 'address' ]), TightPerson.accessible_attributes + assert_equal Set.new(['name', 'address']), TightPerson.accessible_attributes + + assert_blank TightPerson.protected_attributes(:admin) - TightPerson.attributes_protected_by_default + assert_equal Set.new(['name', 'address', 'admin']), TightPerson.accessible_attributes(:admin) assert_blank TightDescendant.protected_attributes - TightDescendant.attributes_protected_by_default - assert_equal Set.new([ 'name', 'address', 'phone_number' ]), TightDescendant.accessible_attributes + assert_equal Set.new(['name', 'address', 'phone_number']), TightDescendant.accessible_attributes + + assert_blank TightDescendant.protected_attributes(:admin) - TightDescendant.attributes_protected_by_default + assert_equal Set.new(['name', 'address', 'admin', 'super_powers']), TightDescendant.accessible_attributes(:admin) + end def test_mass_assignment_multiparameter_protector diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb index c455cf57b3..6950c3be1f 100644 --- a/activemodel/test/cases/secure_password_test.rb +++ b/activemodel/test/cases/secure_password_test.rb @@ -45,13 +45,14 @@ class SecurePasswordTest < ActiveModel::TestCase end test "visitor#password_digest should be protected against mass assignment" do - assert Visitor.active_authorizer.kind_of?(ActiveModel::MassAssignmentSecurity::BlackList) - assert Visitor.active_authorizer.include?(:password_digest) + assert Visitor.active_authorizers[:default].kind_of?(ActiveModel::MassAssignmentSecurity::BlackList) + assert Visitor.active_authorizers[:default].include?(:password_digest) end test "Administrator's mass_assignment_authorizer should be WhiteList" do - assert Administrator.active_authorizer.kind_of?(ActiveModel::MassAssignmentSecurity::WhiteList) - assert !Administrator.active_authorizer.include?(:password_digest) - assert Administrator.active_authorizer.include?(:name) + active_authorizer = Administrator.active_authorizers[:default] + assert active_authorizer.kind_of?(ActiveModel::MassAssignmentSecurity::WhiteList) + assert !active_authorizer.include?(:password_digest) + assert active_authorizer.include?(:name) end end diff --git a/activemodel/test/models/mass_assignment_specific.rb b/activemodel/test/models/mass_assignment_specific.rb index 2a8fe170c2..53b37369ff 100644 --- a/activemodel/test/models/mass_assignment_specific.rb +++ b/activemodel/test/models/mass_assignment_specific.rb @@ -5,9 +5,17 @@ class User public :sanitize_for_mass_assignment end +class SpecialUser + include ActiveModel::MassAssignmentSecurity + attr_accessible :name, :email, :as => :moderator + + public :sanitize_for_mass_assignment +end + class Person include ActiveModel::MassAssignmentSecurity attr_accessible :name, :email + attr_accessible :name, :email, :admin, :as => :admin public :sanitize_for_mass_assignment end @@ -32,6 +40,7 @@ end class LoosePerson include ActiveModel::MassAssignmentSecurity attr_protected :credit_rating, :administrator + attr_protected :credit_rating, :as => :admin end class LooseDescendant < LoosePerson @@ -46,6 +55,7 @@ end class TightPerson include ActiveModel::MassAssignmentSecurity attr_accessible :name, :address + attr_accessible :name, :address, :admin, :as => :admin def self.attributes_protected_by_default ["mobile_number"] @@ -54,4 +64,5 @@ end class TightDescendant < TightPerson attr_accessible :phone_number + attr_accessible :super_powers, :as => :admin end
\ No newline at end of file diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 9a01d793f9..04c12f86b6 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1621,11 +1621,11 @@ end # Allows you to set all the attributes at once by passing in a hash with keys # matching the attribute names (which again matches the column names). # - # If +guard_protected_attributes+ is true (the default), then sensitive - # attributes can be protected from this form of mass-assignment by using - # the +attr_protected+ macro. Or you can alternatively specify which - # attributes *can* be accessed with the +attr_accessible+ macro. Then all the - # attributes not included in that won't be allowed to be mass-assigned. + # If any attributes are protected by either +attr_protected+ or + # +attr_accessible+ then only settable attributes will be assigned. + # + # The +guard_protected_attributes+ argument is now deprecated, use + # the +assign_attributes+ method if you want to bypass mass-assignment security. # # class User < ActiveRecord::Base # attr_protected :is_admin @@ -1635,15 +1635,59 @@ end # user.attributes = { :username => 'Phusion', :is_admin => true } # user.username # => "Phusion" # user.is_admin? # => false + def attributes=(new_attributes, guard_protected_attributes = nil) + unless guard_protected_attributes.nil? + message = "the use of 'guard_protected_attributes' will be removed from the next major release of rails, " + + "if you want to bypass mass-assignment security then look into using assign_attributes" + ActiveSupport::Deprecation.warn(message) + end + + return unless new_attributes.is_a?(Hash) + + guard_protected_attributes ||= true + if guard_protected_attributes + assign_attributes(new_attributes) + else + assign_attributes(new_attributes, :without_protection => true) + end + end + + # Allows you to set all the attributes for a particular mass-assignment + # security scope by passing in a hash of attributes with keys matching + # the attribute names (which again matches the column names) and the scope + # name using the :as option. # - # user.send(:attributes=, { :username => 'Phusion', :is_admin => true }, false) + # To bypass mass-assignment security you can use the :without_protection => true + # option. + # + # class User < ActiveRecord::Base + # attr_accessible :name + # attr_accessible :name, :is_admin, :as => :admin + # end + # + # user = User.new + # user.assign_attributes({ :name => 'Josh', :is_admin => true }) + # user.name # => "Josh" + # user.is_admin? # => false + # + # user = User.new + # user.assign_attributes({ :name => 'Josh', :is_admin => true }, :as => :admin) + # user.name # => "Josh" # user.is_admin? # => true - def attributes=(new_attributes, guard_protected_attributes = true) - return unless new_attributes.is_a?(Hash) + # + # user = User.new + # user.assign_attributes({ :name => 'Josh', :is_admin => true }, :without_protection => true) + # user.name # => "Josh" + # user.is_admin? # => true + def assign_attributes(new_attributes, options = {}) attributes = new_attributes.stringify_keys + scope = options[:as] || :default multi_parameter_attributes = [] - attributes = sanitize_for_mass_assignment(attributes) if guard_protected_attributes + + unless options[:without_protection] + attributes = sanitize_for_mass_assignment(attributes, scope) + end attributes.each do |k, v| if k.include?("(") diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index 6d9b5c7b32..70da9d5f1e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -55,6 +55,13 @@ module ActiveRecord def exec_query(sql, name = 'SQL', binds = []) end + # Executes insert +sql+ statement in the context of this connection using + # +binds+ as the bind substitutes. +name+ is the logged along with + # the executed +sql+ statement. + def exec_insert(sql, name, binds) + exec_query(sql, name, binds) + end + # Returns the last auto-generated ID from the affected table. # # +id_value+ will be returned unless the value is nil, in @@ -280,10 +287,6 @@ module ActiveRecord execute "INSERT INTO #{quote_table_name(table_name)} (#{key_list.join(', ')}) VALUES (#{value_list.join(', ')})", 'Fixture Insert' end - def null_insert_value - Arel.sql 'DEFAULT' - end - def empty_insert_statement_value "VALUES(DEFAULT)" end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index d24cce0a3c..468a2b106b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -223,7 +223,9 @@ module ActiveRecord rescue Exception => e message = "#{e.class.name}: #{e.message}: #{sql}" @logger.debug message if @logger - raise translate_exception(e, message) + exception = translate_exception(e, message) + exception.set_backtrace e.backtrace + raise exception end def translate_exception(e, message) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index c2e75acb9a..2c05ff21f9 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -208,16 +208,18 @@ module ActiveRecord true end - # Returns +true+ when the connection adapter supports prepared statement - # caching, otherwise returns +false+ + # Returns +true+, since this connection adapter supports prepared statement + # caching. def supports_statement_cache? true end + # Returns true. def supports_migrations? #:nodoc: true end + # Returns true. def supports_primary_key? #:nodoc: true end @@ -308,6 +310,8 @@ module ActiveRecord connect end + # Disconnects from the database if already connected. Otherwise, this + # method does nothing. def disconnect! @connection.close rescue nil end @@ -330,6 +334,7 @@ module ActiveRecord rows end + # Clears the prepared statements cache. def clear_cache! @statements.values.each do |cache| cache[:stmt].close @@ -427,10 +432,6 @@ module ActiveRecord end end - def exec_insert(sql, name, binds) - exec_query(sql, name, binds) - end - def last_inserted_id(result) @connection.insert_id end @@ -558,6 +559,10 @@ module ActiveRecord end end + # Drops a MySQL database. + # + # Example: + # drop_database 'sebastian_development' def drop_database(name) #:nodoc: execute "DROP DATABASE IF EXISTS `#{name}`" end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 4e6d9ae087..0c2afc180b 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -1,6 +1,9 @@ require 'active_record/connection_adapters/abstract_adapter' require 'active_support/core_ext/kernel/requires' require 'active_support/core_ext/object/blank' + +# Make sure we're using pg high enough for PGResult#values +gem 'pg', '~> 0.11' require 'pg' module ActiveRecord @@ -220,8 +223,8 @@ module ActiveRecord ADAPTER_NAME end - # Returns +true+ when the connection adapter supports prepared statement - # caching, otherwise returns +false+ + # Returns +true+, since this connection adapter supports prepared statement + # caching. def supports_statement_cache? true end @@ -246,6 +249,7 @@ module ActiveRecord @local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"] end + # Clears the prepared statements cache. def clear_cache! @statements.each_value do |value| @connection.query "DEALLOCATE #{value}" @@ -284,7 +288,8 @@ module ActiveRecord super end - # Close the connection. + # Disconnects from the database if already connected. Otherwise, this + # method does nothing. def disconnect! clear_cache! @connection.close rescue nil @@ -469,42 +474,43 @@ module ActiveRecord # create a 2D array representing the result set def result_as_array(res) #:nodoc: # check if we have any binary column and if they need escaping - unescape_col = [] - res.nfields.times do |j| - unescape_col << res.ftype(j) + ftypes = Array.new(res.nfields) do |i| + [i, res.ftype(i)] end - ary = [] - res.ntuples.times do |i| - ary << [] - res.nfields.times do |j| - data = res.getvalue(i,j) - case unescape_col[j] - - # unescape string passed BYTEA field (OID == 17) - when BYTEA_COLUMN_TYPE_OID - data = unescape_bytea(data) if String === data - - # If this is a money type column and there are any currency symbols, - # then strip them off. Indeed it would be prettier to do this in - # PostgreSQLColumn.string_to_decimal but would break form input - # fields that call value_before_type_cast. - when MONEY_COLUMN_TYPE_OID - # Because money output is formatted according to the locale, there are two - # cases to consider (note the decimal separators): - # (1) $12,345,678.12 - # (2) $12.345.678,12 - case data - when /^-?\D+[\d,]+\.\d{2}$/ # (1) - data.gsub!(/[^-\d.]/, '') - when /^-?\D+[\d.]+,\d{2}$/ # (2) - data.gsub!(/[^-\d,]/, '').sub!(/,/, '.') - end + rows = res.values + return rows unless ftypes.any? { |_, x| + x == BYTEA_COLUMN_TYPE_OID || x == MONEY_COLUMN_TYPE_OID + } + + typehash = ftypes.group_by { |_, type| type } + binaries = typehash[BYTEA_COLUMN_TYPE_OID] || [] + monies = typehash[MONEY_COLUMN_TYPE_OID] || [] + + rows.each do |row| + # unescape string passed BYTEA field (OID == 17) + binaries.each do |index, _| + row[index] = unescape_bytea(row[index]) + end + + # If this is a money type column and there are any currency symbols, + # then strip them off. Indeed it would be prettier to do this in + # PostgreSQLColumn.string_to_decimal but would break form input + # fields that call value_before_type_cast. + monies.each do |index, _| + data = row[index] + # Because money output is formatted according to the locale, there are two + # cases to consider (note the decimal separators): + # (1) $12,345,678.12 + # (2) $12.345.678,12 + case data + when /^-?\D+[\d,]+\.\d{2}$/ # (1) + data.gsub!(/[^-\d.]/, '') + when /^-?\D+[\d.]+,\d{2}$/ # (2) + data.gsub!(/[^-\d,]/, '').sub!(/,/, '.') end - ary[i] << data end end - return ary end @@ -561,10 +567,6 @@ module ActiveRecord end end - def exec_insert(sql, name, binds) - exec_query(sql, name, binds) - end - def sql_for_insert(sql, pk, id_value, sequence_name, binds) unless pk _, table = extract_schema_and_table(sql.split(" ", 4)[2]) @@ -650,7 +652,7 @@ module ActiveRecord execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}" end - # Drops a PostgreSQL database + # Drops a PostgreSQL database. # # Example: # drop_database 'matt_development' @@ -942,10 +944,7 @@ module ActiveRecord order_columns.delete_if { |c| c.blank? } order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" } - # Return a DISTINCT ON() clause that's distinct on the columns we want but includes - # all the required columns for the ORDER BY to work properly. - sql = "DISTINCT #{columns}, " - sql << order_columns * ', ' + "DISTINCT #{columns}, #{order_columns * ', '}" end protected diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 9e7f874f4b..ed5006dcec 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -66,16 +66,18 @@ module ActiveRecord sqlite_version >= '3.6.8' end - # Returns +true+ when the connection adapter supports prepared statement - # caching, otherwise returns +false+ + # Returns true, since this connection adapter supports prepared statement + # caching. def supports_statement_cache? true end + # Returns true. def supports_migrations? #:nodoc: true end + # Returns true. def supports_primary_key? #:nodoc: true end @@ -88,12 +90,15 @@ module ActiveRecord sqlite_version >= '3.1.6' end + # Disconnects from the database if already connected. Otherwise, this + # method does nothing. def disconnect! super clear_cache! @connection.close rescue nil end + # Clears the prepared statements cache. def clear_cache! @statements.clear end @@ -173,10 +178,6 @@ module ActiveRecord end end - def exec_insert(sql, name, binds) - exec_query(sql, name, binds) - end - def last_inserted_id(result) @connection.last_insert_row_id end @@ -345,10 +346,6 @@ module ActiveRecord alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s}) end - def null_insert_value - Arel.sql 'NULL' - end - def empty_insert_statement_value "VALUES(NULL)" end diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index cace6f0cc0..d38588519b 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -50,6 +50,9 @@ module ActiveRecord initializer "active_record.set_configs" do |app| ActiveSupport.on_load(:active_record) do + if app.config.active_record.delete(:whitelist_attributes) + attr_accessible(nil) + end app.config.active_record.each do |k,v| send "#{k}=", v end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 6b3c38cb58..a49f940e5b 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -70,7 +70,13 @@ db_namespace = namespace :db do @charset = ENV['CHARSET'] || 'utf8' @collation = ENV['COLLATION'] || 'utf8_unicode_ci' creation_options = {:charset => (config['charset'] || @charset), :collation => (config['collation'] || @collation)} - error_class = config['adapter'] =~ /mysql2/ ? Mysql2::Error : Mysql::Error + if config['adapter'] =~ /jdbc/ + #FIXME After Jdbcmysql gives this class + require 'active_record/railties/jdbcmysql_error' + error_class = ArJdbcMySQL::Error + else + error_class = config['adapter'] =~ /mysql2/ ? Mysql2::Error : Mysql::Error + end access_denied_error = 1045 begin ActiveRecord::Base.establish_connection(config.merge('database' => nil)) diff --git a/activerecord/lib/active_record/railties/jdbcmysql_error.rb b/activerecord/lib/active_record/railties/jdbcmysql_error.rb new file mode 100644 index 0000000000..6b9af2a0cb --- /dev/null +++ b/activerecord/lib/active_record/railties/jdbcmysql_error.rb @@ -0,0 +1,16 @@ +#FIXME Remove if ArJdbcMysql will give. +module ArJdbcMySQL + class Error < StandardError + attr_accessor :error_number, :sql_state + + def initialize msg + super + @error_number = nil + @sql_state = nil + end + + # Mysql gem compatibility + alias_method :errno, :error_number + alias_method :error, :message + end +end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 359f9d8a66..8e5f66ec1d 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -56,11 +56,11 @@ module ActiveRecord end substitutes.each_with_index do |tuple, i| - tuple[1] = conn.substitute_at(tuple.first, i) + tuple[1] = conn.substitute_at(binds[i][0], i) end if values.empty? # empty insert - im.values = im.create_values [connection.null_insert_value], [] + im.values = Arel.sql(connection.empty_insert_statement_value) else im.insert substitutes end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index a3d4b7f45a..57c9921ea8 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -279,8 +279,8 @@ module ActiveRecord unless record record = @klass.new do |r| - r.send(:attributes=, protected_attributes_for_create, true) unless protected_attributes_for_create.empty? - r.send(:attributes=, unprotected_attributes_for_create, false) unless unprotected_attributes_for_create.empty? + r.assign_attributes(protected_attributes_for_create) + r.assign_attributes(unprotected_attributes_for_create, :without_protection => true) end yield(record) if block_given? record.save if match.instantiator == :create diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index d1225a9ed9..4db4105389 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -56,8 +56,9 @@ module ActiveRecord column = klass.columns_hash[attribute.to_s] value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s if column.text? - if !options[:case_sensitive] && column.text? - relation = table[attribute].matches(value) + if !options[:case_sensitive] && value && column.text? + # will use SQL LOWER function before comparison + relation = table[attribute].lower.eq(table.lower(value)) else value = klass.connection.case_sensitive_modifier(value) relation = table[attribute].eq(value) diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 40c82f2fb8..9bc7910fc6 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -170,6 +170,16 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_equal [comment], category.posts[0].comments end end + + def test_associations_loaded_for_all_records + post = Post.create!(:title => 'foo', :body => "I like cars!") + comment = SpecialComment.create!(:body => 'Come on!', :post => post) + first_category = Category.create! :name => 'First!', :posts => [post] + second_category = Category.create! :name => 'Second!', :posts => [post] + + categories = Category.where(:id => [first_category.id, second_category.id]).includes(:posts => :special_comments) + assert_equal categories.map { |category| category.posts.first.special_comments.loaded? }, [true, true] + end def test_finding_with_includes_on_has_many_association_with_same_include_includes_only_once author_id = authors(:david).id diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 815ff7b825..5ee3b2d776 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -18,7 +18,7 @@ require 'models/comment' require 'models/minimalistic' require 'models/warehouse_thing' require 'models/parrot' -require 'models/loose_person' +require 'models/person' require 'models/edge' require 'models/joke' require 'rexml/document' @@ -489,6 +489,12 @@ class BasicsTest < ActiveRecord::TestCase assert_equal 'value2', weird.send('a$b') end + def test_attributes_guard_protected_attributes_is_deprecated + attributes = { "title" => "An amazing title" } + topic = Topic.new + assert_deprecated { topic.send(:attributes=, attributes, false) } + end + def test_multiparameter_attributes_on_date attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "24" } topic = Topic.find(1) diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb index 025ec1d3fa..43016df479 100644 --- a/activerecord/test/cases/mass_assignment_security_test.rb +++ b/activerecord/test/cases/mass_assignment_security_test.rb @@ -3,6 +3,7 @@ require 'models/company' require 'models/subscriber' require 'models/keyboard' require 'models/task' +require 'models/person' class MassAssignmentSecurityTest < ActiveRecord::TestCase @@ -30,6 +31,66 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase end end + def test_assign_attributes_uses_default_scope_when_no_scope_is_provided + p = LoosePerson.new + p.assign_attributes(attributes_hash) + + assert_equal nil, p.id + assert_equal 'Josh', p.first_name + assert_equal 'male', p.gender + assert_equal nil, p.comments + end + + def test_assign_attributes_skips_mass_assignment_security_protection_when_without_protection_is_used + p = LoosePerson.new + p.assign_attributes(attributes_hash, :without_protection => true) + + assert_equal 5, p.id + assert_equal 'Josh', p.first_name + assert_equal 'male', p.gender + assert_equal 'rides a sweet bike', p.comments + end + + def test_assign_attributes_with_default_scope_and_attr_protected_attributes + p = LoosePerson.new + p.assign_attributes(attributes_hash, :as => :default) + + assert_equal nil, p.id + assert_equal 'Josh', p.first_name + assert_equal 'male', p.gender + assert_equal nil, p.comments + end + + def test_assign_attributes_with_admin_scope_and_attr_protected_attributes + p = LoosePerson.new + p.assign_attributes(attributes_hash, :as => :admin) + + assert_equal nil, p.id + assert_equal 'Josh', p.first_name + assert_equal 'male', p.gender + assert_equal 'rides a sweet bike', p.comments + end + + def test_assign_attributes_with_default_scope_and_attr_accessible_attributes + p = TightPerson.new + p.assign_attributes(attributes_hash, :as => :default) + + assert_equal nil, p.id + assert_equal 'Josh', p.first_name + assert_equal 'male', p.gender + assert_equal nil, p.comments + end + + def test_assign_attributes_with_admin_scope_and_attr_accessible_attributes + p = TightPerson.new + p.assign_attributes(attributes_hash, :as => :admin) + + assert_equal nil, p.id + assert_equal 'Josh', p.first_name + assert_equal 'male', p.gender + assert_equal 'rides a sweet bike', p.comments + end + def test_protection_against_class_attribute_writers [:logger, :configurations, :primary_key_prefix_type, :table_name_prefix, :table_name_suffix, :pluralize_table_names, :default_timezone, :schema_format, :lock_optimistically, :record_timestamps].each do |method| @@ -40,4 +101,14 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase end end + private + + def attributes_hash + { + :id => 5, + :first_name => 'Josh', + :gender => 'male', + :comments => 'rides a sweet bike' + } + end end
\ No newline at end of file diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 9aa13f04cd..3683e3430c 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -12,7 +12,7 @@ require 'models/minimalistic' require 'models/warehouse_thing' require 'models/parrot' require 'models/minivan' -require 'models/loose_person' +require 'models/person' require 'rexml/document' require 'active_support/core_ext/exception' diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb index b4f3dd034c..0f1b3667cc 100644 --- a/activerecord/test/cases/validations/uniqueness_validation_test.rb +++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb @@ -162,6 +162,32 @@ class UniquenessValidationTest < ActiveRecord::TestCase end end + def test_validate_case_sensitive_uniqueness_with_special_sql_like_chars + Topic.validates_uniqueness_of(:title, :case_sensitive => true) + + t = Topic.new("title" => "I'm unique!") + assert t.save, "Should save t as unique" + + t2 = Topic.new("title" => "I'm %") + assert t2.save, "Should save t2 as unique" + + t3 = Topic.new("title" => "I'm uniqu_!") + assert t3.save, "Should save t3 as unique" + end + + def test_validate_case_insensitive_uniqueness_with_special_sql_like_chars + Topic.validates_uniqueness_of(:title, :case_sensitive => false) + + t = Topic.new("title" => "I'm unique!") + assert t.save, "Should save t as unique" + + t2 = Topic.new("title" => "I'm %") + assert t2.save, "Should save t2 as unique" + + t3 = Topic.new("title" => "I'm uniqu_!") + assert t3.save, "Should save t3 as unique" + end + def test_validate_case_sensitive_uniqueness Topic.validates_uniqueness_of(:title, :case_sensitive => true, :allow_nil => true) diff --git a/activerecord/test/models/loose_person.rb b/activerecord/test/models/loose_person.rb deleted file mode 100644 index 256c281d0d..0000000000 --- a/activerecord/test/models/loose_person.rb +++ /dev/null @@ -1,24 +0,0 @@ -class LoosePerson < ActiveRecord::Base - self.table_name = 'people' - self.abstract_class = true - - attr_protected :credit_rating, :administrator -end - -class LooseDescendant < LoosePerson - attr_protected :phone_number -end - -class LooseDescendantSecond< LoosePerson - attr_protected :phone_number - attr_protected :name -end - -class TightPerson < ActiveRecord::Base - self.table_name = 'people' - attr_accessible :name, :address -end - -class TightDescendant < TightPerson - attr_accessible :phone_number -end
\ No newline at end of file diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb index ad59d12672..9c4794902d 100644 --- a/activerecord/test/models/person.rb +++ b/activerecord/test/models/person.rb @@ -48,3 +48,22 @@ class PersonWithDependentNullifyJobs < ActiveRecord::Base has_many :references, :foreign_key => :person_id has_many :jobs, :source => :job, :through => :references, :dependent => :nullify end + + +class LoosePerson < ActiveRecord::Base + self.table_name = 'people' + self.abstract_class = true + + attr_protected :comments + attr_protected :as => :admin +end + +class LooseDescendant < LoosePerson; end + +class TightPerson < ActiveRecord::Base + self.table_name = 'people' + attr_accessible :first_name, :gender + attr_accessible :first_name, :gender, :comments, :as => :admin +end + +class TightDescendant < TightPerson; end
\ No newline at end of file diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index 6b662ac660..6b87774978 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -42,7 +42,6 @@ module ActiveSupport autoload :DescendantsTracker autoload :FileUpdateChecker - autoload :FileWatcher autoload :LogSubscriber autoload :Notifications diff --git a/activesupport/lib/active_support/core_ext/object/inclusion.rb b/activesupport/lib/active_support/core_ext/object/inclusion.rb index 51cfc62f2b..b5671f66d0 100644 --- a/activesupport/lib/active_support/core_ext/object/inclusion.rb +++ b/activesupport/lib/active_support/core_ext/object/inclusion.rb @@ -1,11 +1,11 @@ class Object # Returns true if this object is included in the argument. Argument must be - # any object which respond to +#include?+. Usage: + # any object which responds to +#include?+. Usage: # # characters = ["Konata", "Kagami", "Tsukasa"] # "Konata".in?(characters) # => true # - # This will throw an ArgumentError if the supplied argument doesnt not respond + # This will throw an ArgumentError if the argument doesn't respond # to +#include?+. def in?(another_object) another_object.include?(self) diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb index aedf5c8c82..e77a9da0ec 100644 --- a/activesupport/lib/active_support/core_ext/object/try.rb +++ b/activesupport/lib/active_support/core_ext/object/try.rb @@ -35,7 +35,7 @@ class Object end class NilClass - # Instances of +NilClass+ return always +nil+. + # Calling +try+ on +nil+ always returns +nil+. # It becomes specially helpful when navigating through associations that may return +nil+. # # === Examples diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb index ce0775a690..45b9dda5ca 100644 --- a/activesupport/lib/active_support/deprecation.rb +++ b/activesupport/lib/active_support/deprecation.rb @@ -9,7 +9,7 @@ module ActiveSupport # The version the deprecated behavior will be removed, by default. attr_accessor :deprecation_horizon end - self.deprecation_horizon = '3.1' + self.deprecation_horizon = '3.2' # By default, warnings are not silenced and debugging is off. self.silenced = false diff --git a/activesupport/test/file_watcher_test.rb b/activesupport/test/file_watcher_test.rb deleted file mode 100644 index 7b4d4be24f..0000000000 --- a/activesupport/test/file_watcher_test.rb +++ /dev/null @@ -1,233 +0,0 @@ -require 'abstract_unit' -require 'fssm' -require "fileutils" -require "timeout" - - -class FileWatcherTest < ActiveSupport::TestCase - class DumbBackend < ActiveSupport::FileWatcher::Backend - end - - def setup - @watcher = ActiveSupport::FileWatcher.new - - # In real life, the backend would take the path and use it to observe the file - # system. In our case, we will manually trigger the events for unit testing, - # so we can pass any path. - @backend = DumbBackend.new("RAILS_WOOT", @watcher) - - @payload = [] - @watcher.watch %r{^app/assets/.*\.scss$} do |pay| - pay.each do |status, files| - files.sort! - end - @payload << pay - end - end - - def test_use_triple_equals - fw = ActiveSupport::FileWatcher.new - called = [] - fw.watch("some_arbitrary_file.rb") do |file| - called << "omg" - end - fw.trigger(%w{ some_arbitrary_file.rb }) - assert_equal ['omg'], called - end - - def test_one_change - @backend.trigger("app/assets/main.scss" => :changed) - assert_equal({:changed => ["app/assets/main.scss"]}, @payload.first) - end - - def test_multiple_changes - @backend.trigger("app/assets/main.scss" => :changed, "app/assets/javascripts/foo.coffee" => :changed) - assert_equal([{:changed => ["app/assets/main.scss"]}], @payload) - end - - def test_multiple_changes_match - @backend.trigger("app/assets/main.scss" => :changed, "app/assets/print.scss" => :changed, "app/assets/javascripts/foo.coffee" => :changed) - assert_equal([{:changed => ["app/assets/main.scss", "app/assets/print.scss"]}], @payload) - end - - def test_multiple_state_changes - @backend.trigger("app/assets/main.scss" => :created, "app/assets/print.scss" => :changed) - assert_equal([{:changed => ["app/assets/print.scss"], :created => ["app/assets/main.scss"]}], @payload) - end - - def test_more_blocks - payload = [] - @watcher.watch %r{^config/routes\.rb$} do |pay| - payload << pay - end - - @backend.trigger "config/routes.rb" => :changed - assert_equal [:changed => ["config/routes.rb"]], payload - assert_equal [], @payload - end - - def test_overlapping_watchers - payload = [] - @watcher.watch %r{^app/assets/main\.scss$} do |pay| - payload << pay - end - - @backend.trigger "app/assets/print.scss" => :changed, "app/assets/main.scss" => :changed - assert_equal [:changed => ["app/assets/main.scss"]], payload - assert_equal [:changed => ["app/assets/main.scss", "app/assets/print.scss"]], @payload - end -end - -module FSSM::Backends - class Polling - def initialize_with_low_latency(options={}) - initialize_without_low_latency(options.merge(:latency => 0.1)) - end - alias_method_chain :initialize, :low_latency - end -end - -class FSSMFileWatcherTest < ActiveSupport::TestCase - class FSSMBackend < ActiveSupport::FileWatcher::Backend - def initialize(path, watcher) - super - - monitor = FSSM::Monitor.new - monitor.path(path, '**/*') do |p| - p.update { |base, relative| trigger relative => :changed } - p.delete { |base, relative| trigger relative => :deleted } - p.create { |base, relative| trigger relative => :created } - end - - @thread = Thread.new do - monitor.run - end - end - - def stop - @thread.kill - end - end - - def setup - Thread.abort_on_exception = true - - @payload = [] - @triggered = false - - @watcher = ActiveSupport::FileWatcher.new - - @path = path = File.expand_path("../tmp", __FILE__) - FileUtils.rm_rf path - - create "app/assets/main.scss", true - create "app/assets/javascripts/foo.coffee", true - create "app/assets/print.scss", true - create "app/assets/videos.scss", true - - @backend = FSSMBackend.new(path, @watcher) - - @watcher.watch %r{^app/assets/.*\.scss$} do |pay| - pay.each do |status, files| - files.sort! - end - @payload << pay - trigger - end - end - - def teardown - @backend.stop - Thread.abort_on_exception = false - end - - def create(path, past = false) - wait(past) do - path = File.join(@path, path) - FileUtils.mkdir_p(File.dirname(path)) - - FileUtils.touch(path) - File.utime(Time.now - 100, Time.now - 100, path) if past - end - end - - def change(path) - wait do - FileUtils.touch(File.join(@path, path)) - end - end - - def delete(path) - wait do - FileUtils.rm(File.join(@path, path)) - end - end - - def wait(past = false) - yield - return if past - - begin - Timeout.timeout(1) do - sleep 0.05 until @triggered - end - rescue Timeout::Error - end - - @triggered = false - end - - def trigger - @triggered = true - end - - def test_one_change - change "app/assets/main.scss" - assert_equal({:changed => ["app/assets/main.scss"]}, @payload.first) - end - - def test_multiple_changes - change "app/assets/main.scss" - change "app/assets/javascripts/foo.coffee" - assert_equal([{:changed => ["app/assets/main.scss"]}], @payload) - end - - def test_multiple_changes_match - change "app/assets/main.scss" - change "app/assets/print.scss" - change "app/assets/javascripts/foo.coffee" - assert_equal([{:changed => ["app/assets/main.scss"]}, {:changed => ["app/assets/print.scss"]}], @payload) - end - - def test_multiple_state_changes - create "app/assets/new.scss" - change "app/assets/print.scss" - delete "app/assets/videos.scss" - assert_equal([{:created => ["app/assets/new.scss"]}, {:changed => ["app/assets/print.scss"]}, {:deleted => ["app/assets/videos.scss"]}], @payload) - end - - def test_more_blocks - payload = [] - @watcher.watch %r{^config/routes\.rb$} do |pay| - payload << pay - trigger - end - - create "config/routes.rb" - assert_equal [{:created => ["config/routes.rb"]}], payload - assert_equal [], @payload - end - - def test_overlapping_watchers - payload = [] - @watcher.watch %r{^app/assets/main\.scss$} do |pay| - payload << pay - trigger - end - - change "app/assets/main.scss" - change "app/assets/print.scss" - assert_equal [{:changed => ["app/assets/main.scss"]}], payload - assert_equal [{:changed => ["app/assets/main.scss"]}, {:changed => ["app/assets/print.scss"]}], @payload - end -end diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile index 7cdffe4c2e..2f0a51e868 100644 --- a/railties/guides/source/active_record_querying.textile +++ b/railties/guides/source/active_record_querying.textile @@ -418,7 +418,7 @@ SELECT viewable_by, locked FROM clients Be careful because this also means you're initializing a model object with only the fields that you've selected. If you attempt to access a field that is not in the initialized record you'll receive: <shell> -ActiveRecord::MissingAttributeError: missing attribute: <attribute> +ActiveModel::MissingAttributeError: missing attribute: <attribute> </shell> Where +<attribute>+ is the attribute you asked for. The +id+ method will not raise the +ActiveRecord::MissingAttributeError+, so just be careful when working with associations because they need the +id+ method to function properly. diff --git a/railties/guides/source/active_record_validations_callbacks.textile b/railties/guides/source/active_record_validations_callbacks.textile index 9aab4b6694..19bd4ad0f1 100644 --- a/railties/guides/source/active_record_validations_callbacks.textile +++ b/railties/guides/source/active_record_validations_callbacks.textile @@ -897,8 +897,9 @@ The macro-style class methods can also receive a block. Consider using this styl class User < ActiveRecord::Base validates_presence_of :login, :email - before_create {|user| user.name = user.login.capitalize - if user.name.blank?} + before_create do |user| + user.name = user.login.capitalize if user.name.blank? + end end </ruby> diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index b7f842a0d0..f89c83e4cd 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -1266,6 +1266,15 @@ WARNING: The option +:separator+ can't be a regexp. NOTE: Defined in +active_support/core_ext/string/filters.rb+. +h4. +inquiry+ + +The <tt>inquiry</tt> method converts a string into a +StringInquirer+ object making equality checks prettier. + +<ruby> +"production".inquiry.production? # => true +"active".inquiry.inactive? # => false +</ruby> + h4. Key-based Interpolation In Ruby 1.9 the <tt>%</tt> string operator supports key-based interpolation, both formatted and unformatted: @@ -1997,6 +2006,11 @@ Similarly, +from+ returns the tail from the element at the passed index on: The methods +second+, +third+, +fourth+, and +fifth+ return the corresponding element (+first+ is built-in). Thanks to social wisdom and positive constructiveness all around, +forty_two+ is also available. +<ruby> +%w(a b c d).third # => c +%w(a b c d).fifth # => nil +</ruby> + NOTE: Defined in +active_support/core_ext/array/access.rb+. h4. Random Access @@ -2092,7 +2106,7 @@ h5. +to_xml+ The method +to_xml+ returns a string containing an XML representation of its receiver: <ruby> -Contributor.all(:limit => 2, :order => 'rank ASC').to_xml +Contributor.limit(2).order(:rank).to_xml # => # <?xml version="1.0" encoding="UTF-8"?> # <contributors type="array"> @@ -2167,7 +2181,7 @@ The name of children nodes is by default the name of the root node singularized. The default XML builder is a fresh instance of <tt>Builder::XmlMarkup</tt>. You can configure your own builder via the <tt>:builder</tt> option. The method also accepts options like <tt>:dasherize</tt> and friends, they are forwarded to the builder: <ruby> -Contributor.all(:limit => 2, :order => 'rank ASC').to_xml(:skip_types => true) +Contributor.limit(2).order(:rank).to_xml(:skip_types => true) # => # <?xml version="1.0" encoding="UTF-8"?> # <contributors> @@ -3407,11 +3421,11 @@ h4. +silence+ Silences every log level lesser to the specified one for the duration of the given block. Log level orders are: debug, info, error and fatal. <ruby> - logger = Logger.new("log/development.log") - logger.silence(Logger::INFO) do - logger.debug("In space, no one can hear you scream.") - logger.info("Scream all you want, small mailman!") - end +logger = Logger.new("log/development.log") +logger.silence(Logger::INFO) do + logger.debug("In space, no one can hear you scream.") + logger.info("Scream all you want, small mailman!") +end </ruby> h4. +datetime_format=+ @@ -3419,17 +3433,17 @@ h4. +datetime_format=+ Modifies the datetime format output by the formatter class associated with this logger. If the formatter class does not have a +datetime_format+ method then this is ignored. <ruby> - class Logger::FormatWithTime < Logger::Formatter - cattr_accessor(:datetime_format) { "%Y%m%d%H%m%S" } +class Logger::FormatWithTime < Logger::Formatter + cattr_accessor(:datetime_format) { "%Y%m%d%H%m%S" } - def self.call(severity, timestamp, progname, msg) - "#{timestamp.strftime(datetime_format)} -- #{String === msg ? msg : msg.inspect}\n" - end + def self.call(severity, timestamp, progname, msg) + "#{timestamp.strftime(datetime_format)} -- #{String === msg ? msg : msg.inspect}\n" end +end - logger = Logger.new("log/development.log") - logger.formatter = Logger::FormatWithTime - logger.info("<- is the current time") +logger = Logger.new("log/development.log") +logger.formatter = Logger::FormatWithTime +logger.info("<- is the current time") </ruby> NOTE: Defined in +active_support/core_ext/logger.rb+. diff --git a/railties/guides/source/configuring.textile b/railties/guides/source/configuring.textile index 53460b8c36..d7069b31fc 100644 --- a/railties/guides/source/configuring.textile +++ b/railties/guides/source/configuring.textile @@ -229,6 +229,8 @@ h4. Configuring Active Record * +config.active_record.lock_optimistically+ controls whether ActiveRecord will use optimistic locking. By default this is +true+. +* +config.active_record.whitelist_attributes+ will create an empty whitelist of attributes available for mass-assignment security for all models in your app. + The MySQL adapter adds one additional configuration option: * +ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans+ controls whether ActiveRecord will consider all +tinyint(1)+ columns in a MySQL database to be booleans. By default this is +true+. diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile index 95b877aecf..43c08165dc 100644 --- a/railties/guides/source/routing.textile +++ b/railties/guides/source/routing.textile @@ -557,7 +557,7 @@ match '*a/foo/*b' => 'test#index' would match +zoo/woo/foo/bar/baz+ with +params[:a]+ equals +"zoo/woo"+, and +params[:b]+ equals +"bar/baz"+. -NOTE: Starting from Rails 3.1, wildcard route will always matching the optional format segment by default. For example if you have this route: +NOTE: Starting from Rails 3.1, wildcard routes will always match the optional format segment by default. For example if you have this route: <ruby> map '*pages' => 'pages#show' diff --git a/railties/guides/source/security.textile b/railties/guides/source/security.textile index f4c1bde5b1..f87ffdb20d 100644 --- a/railties/guides/source/security.textile +++ b/railties/guides/source/security.textile @@ -418,10 +418,17 @@ To avoid this, Rails provides two class methods in your Active Record class to c attr_protected :admin </ruby> ++attr_protected+ also optionally takes a scope option using :as which allows you to define multiple mass-assignment groupings. If no scope is defined then attributes will be added to the default group. + +<ruby> +attr_protected :last_login, :as => :admin +</ruby> + A much better way, because it follows the whitelist-principle, is the +attr_accessible+ method. It is the exact opposite of +attr_protected+, because _(highlight)it takes a list of attributes that will be accessible_. All other attributes will be protected. This way you won't forget to protect attributes when adding new ones in the course of development. Here is an example: <ruby> attr_accessible :name +attr_accessible :name, :is_admin, :as => :admin </ruby> If you want to set a protected attribute, you will to have to assign it individually: @@ -434,13 +441,31 @@ params[:user] # => {:name => "ow3ned", :admin => true} @user.admin # => true </ruby> -A more paranoid technique to protect your whole project would be to enforce that all models whitelist their accessible attributes. This can be easily achieved with a very simple initializer: +When assigning attributes in Active Record using +new+, +attributes=+, or +update_attributes+ the :default scope will be used. To assign attributes using different scopes you should use +assign_attributes+ which accepts an optional :as options parameter. If no :as option is provided then the :default scope will be used. You can also bypass mass-assignment security by using the +:without_protection+ option. Here is an example: + +<ruby> +@user = User.new + +@user.assign_attributes({ :name => 'Josh', :is_admin => true }) +@user.name # => Josh +@user.is_admin # => false + +@user.assign_attributes({ :name => 'Josh', :is_admin => true }, :as => :admin) +@user.name # => Josh +@user.is_admin # => true + +@user.assign_attributes({ :name => 'Josh', :is_admin => true }, :without_protection => true) +@user.name # => Josh +@user.is_admin # => true +</ruby> + +A more paranoid technique to protect your whole project would be to enforce that all models define their accessible attributes. This can be easily achieved with a very simple application config option of: <ruby> -ActiveRecord::Base.send(:attr_accessible, nil) +config.active_record.whitelist_attributes = true </ruby> -This will create an empty whitelist of attributes available for mass assignment for all models in your app. As such, your models will need to explicitly whitelist accessible parameters by using an +attr_accessible+ declaration. This technique is best applied at the start of a new project. However, for an existing project with a thorough set of functional tests, it should be straightforward and relatively quick to insert this initializer, run your tests, and expose each attribute (via +attr_accessible+) as dictated by your failing tests. +This will create an empty whitelist of attributes available for mass-assignment for all models in your app. As such, your models will need to explicitly whitelist or blacklist accessible parameters by using an +attr_accessible+ or +attr_protected+ declaration. This technique is best applied at the start of a new project. However, for an existing project with a thorough set of functional tests, it should be straightforward and relatively quick to use this application config option; run your tests, and expose each attribute (via +attr_accessible+ or +attr_protected+) as dictated by your failing tests. h3. User Management diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index bd8c314da6..f818313955 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -35,6 +35,9 @@ module Rails @assets.paths = [] @assets.precompile = [ /\w+\.(?!js|css)$/, "application.js", "application.css" ] @assets.prefix = "/assets" + + @assets.js_compressor = nil + @assets.css_compressor = nil end def compiled_asset_path diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb index bf865ce466..028c8814c4 100644 --- a/railties/lib/rails/application/finisher.rb +++ b/railties/lib/rails/application/finisher.rb @@ -41,6 +41,10 @@ module Rails ActionDispatch::Reloader.prepare! end + initializer :define_main_app_helper do |app| + app.routes.define_mounted_helper(:main_app) + end + initializer :eager_load! do if config.cache_classes && !$rails_rake_task ActiveSupport.run_load_hooks(:before_eager_load, self) diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 87385814f7..6c1064c609 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -286,6 +286,27 @@ module Rails # # This code will use <tt>my_engine.user_path(@user)</tt> to generate the proper route. # + # == Isolated engine's helpers + # + # Sometimes you may want to isolate engine, but use helpers that are defined for it. + # If you want to share just a few specific helpers you can add them to application's + # helpers in ApplicationController: + # + # class ApplicationController < ActionController::Base + # helper MyEngine::SharedEngineHelper + # end + # + # If you want to include all of the engine's helpers, you can use #helpers method on egine's + # instance: + # + # class ApplicationController < ActionController::Base + # helper MyEngine::Engine.helpers + # end + # + # It will include all of the helpers from engine's directory. Take into account that this does + # not include helpers defined in controllers with helper_method or other similar solutions, + # only helpers defined in helpers directory will be included. + # # == Migrations & seed data # # Engines can have their own migrations. The default path for migrations is exactly the same @@ -384,6 +405,24 @@ module Rails @railties ||= self.class::Railties.new(config) end + def helpers + @helpers ||= begin + helpers = Module.new + + helpers_paths = if config.respond_to?(:helpers_paths) + config.helpers_paths + else + paths["app/helpers"].existent + end + + all = ActionController::Base.all_helpers_from_path(helpers_paths) + ActionController::Base.modules_for_helpers(all).each do |mod| + helpers.send(:include, mod) + end + helpers + end + end + def app @app ||= begin config.middleware = config.middleware.merge_into(default_middleware_stack) diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 481fa95068..46a2a3f154 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -10,6 +10,8 @@ module Rails module Generators class AppBase < Base DATABASES = %w( mysql oracle postgresql sqlite3 frontbase ibm_db ) + JDBC_DATABASES = %w( jdbcmysql jdbcsqlite3 ) + DATABASES.concat(JDBC_DATABASES) JAVASCRIPTS = %w( jquery prototype ) attr_accessor :rails_template @@ -156,12 +158,14 @@ module Rails end def gem_for_database - # %w( mysql oracle postgresql sqlite3 frontbase ibm_db ) + # %w( mysql oracle postgresql sqlite3 frontbase ibm_db jdbcmysql jdbcsqlite3) case options[:database] when "oracle" then "ruby-oci8" when "postgresql" then "pg" when "frontbase" then "ruby-frontbase" when "mysql" then "mysql2" + when "jdbcmysql" then "activerecord-jdbcmysql-adapter" + when "jdbcsqlite3" then "activerecord-jdbcsqlite3-adapter" else options[:database] end end diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index 0cee7deb72..9f2346028a 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -6,8 +6,9 @@ source 'http://rubygems.org' # Asset template engines <%= "gem 'json'\n" if RUBY_VERSION < "1.9.2" -%> -gem 'sass', '~> 3.1.0.alpha' +gem 'sass' gem 'coffee-script' +# gem 'uglifier' # Use unicorn as the web server # gem 'unicorn' diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml new file mode 100644 index 0000000000..ca807c9f3f --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml @@ -0,0 +1,30 @@ +# MySQL. Versions 4.1 and 5.0 are recommended. +# +# Install the MySQL driver: +# gem install activerecord-jdbcmysql-adapter +# +# And be sure to use new-style password hashing: +# http://dev.mysql.com/doc/refman/5.0/en/old-client.html +development: + adapter: jdbcmysql + database: <%= app_name %>_development + username: root + password: + host: localhost + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + adapter: jdbcmysql + database: <%= app_name %>_test + username: root + password: + host: localhost + +production: + adapter: jdbcmysql + database: <%= app_name %>_production + username: root + password: + host: localhost diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml new file mode 100644 index 0000000000..30776b3b4e --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml @@ -0,0 +1,17 @@ +# SQLite version 3.x +# gem 'activerecord-jdbcsqlite3-adapter' + +development: + adapter: jdbcsqlite3 + database: db/development.sqlite3 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + adapter: jdbcsqlite3 + database: db/test.sqlite3 + +production: + adapter: jdbcsqlite3 + database: db/production.sqlite3 diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt index 80951cf73a..ce28e41b91 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt @@ -15,6 +15,10 @@ # (comment out if your front-end server doesn't support this) config.action_dispatch.x_sendfile_header = "X-Sendfile" # Use 'X-Accel-Redirect' for nginx + # Compress both stylesheets and JavaScripts + # config.assets.js_compressor = :uglifier + config.assets.css_compressor = :scss + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. # config.force_ssl = true diff --git a/railties/lib/rails/generators/rails/app/templates/db/seeds.rb b/railties/lib/rails/generators/rails/app/templates/db/seeds.rb.tt index 9a2efa68a7..9a2efa68a7 100644 --- a/railties/lib/rails/generators/rails/app/templates/db/seeds.rb +++ b/railties/lib/rails/generators/rails/app/templates/db/seeds.rb.tt diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb index 81563f81d3..126aadb88d 100644 --- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb +++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb @@ -13,11 +13,13 @@ module Rails directory "app" template "#{app_templates_dir}/app/views/layouts/application.html.erb.tt", "app/views/layouts/#{name}/application.html.erb" + empty_directory_with_gitkeep "app/assets/images" elsif full? empty_directory_with_gitkeep "app/models" empty_directory_with_gitkeep "app/controllers" empty_directory_with_gitkeep "app/views" empty_directory_with_gitkeep "app/helpers" + empty_directory_with_gitkeep "app/assets/images" end end @@ -93,7 +95,7 @@ task :default => :test remove_file "doc" remove_file "Gemfile" remove_file "lib/tasks" - remove_file "public/images/rails.png" + remove_file "app/assets/images/rails.png" remove_file "public/index.html" remove_file "public/robots.txt" remove_file "README" @@ -199,6 +201,10 @@ task :default => :test build(:javascripts) end + def create_images_directory + build(:images) + end + def create_script_files build(:script) end diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/test/integration/navigation_test.rb b/railties/lib/rails/generators/rails/plugin_new/templates/test/integration/navigation_test.rb index dd4d2da4eb..824caecb24 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/test/integration/navigation_test.rb +++ b/railties/lib/rails/generators/rails/plugin_new/templates/test/integration/navigation_test.rb @@ -5,9 +5,8 @@ class NavigationTest < ActionDispatch::IntegrationTest fixtures :all <% end -%> - # Replace this with your real tests. - test "the truth" do - assert true - end + # test "the truth" do + # assert true + # end end diff --git a/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb index 11a73ebad7..0bc5fd8ca2 100644 --- a/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb +++ b/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb @@ -3,10 +3,9 @@ require 'test_helper' <% module_namespacing do -%> class <%= class_name %>ControllerTest < ActionController::TestCase <% if actions.empty? -%> - # Replace this with your real tests. - test "the truth" do - assert true - end + # test "the truth" do + # assert true + # end <% else -%> <% for action in actions -%> test "should get <%= action %>" do diff --git a/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb b/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb index de0823749c..e7a06e4a73 100644 --- a/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb +++ b/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb @@ -3,8 +3,7 @@ require 'test_helper' class <%= class_name %>Test < ActionDispatch::IntegrationTest fixtures :all - # Replace this with your real tests. - test "the truth" do - assert true - end + # test "the truth" do + # assert true + # end end diff --git a/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb index b62c7fd279..c05102290c 100644 --- a/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb +++ b/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb @@ -13,10 +13,9 @@ class <%= class_name %>Test < ActionMailer::TestCase <% end -%> <% if actions.blank? -%> - # replace this with your real tests - test "the truth" do - assert true - end + # test "the truth" do + # assert true + # end <% end -%> end <% end -%> diff --git a/railties/lib/rails/generators/test_unit/model/templates/unit_test.rb b/railties/lib/rails/generators/test_unit/model/templates/unit_test.rb index 6f79879838..c9bc7d5b90 100644 --- a/railties/lib/rails/generators/test_unit/model/templates/unit_test.rb +++ b/railties/lib/rails/generators/test_unit/model/templates/unit_test.rb @@ -2,9 +2,8 @@ require 'test_helper' <% module_namespacing do -%> class <%= class_name %>Test < ActiveSupport::TestCase - # Replace this with your real tests. - test "the truth" do - assert true - end + # test "the truth" do + # assert true + # end end <% end -%> diff --git a/railties/lib/rails/generators/test_unit/observer/templates/unit_test.rb b/railties/lib/rails/generators/test_unit/observer/templates/unit_test.rb index cd116f5ce9..28aa23626a 100644 --- a/railties/lib/rails/generators/test_unit/observer/templates/unit_test.rb +++ b/railties/lib/rails/generators/test_unit/observer/templates/unit_test.rb @@ -2,9 +2,8 @@ require 'test_helper' <% module_namespacing do -%> class <%= class_name %>ObserverTest < ActiveSupport::TestCase - # Replace this with your real tests. - test "the truth" do - assert true - end + # test "the truth" do + # assert true + # end end <% end -%> diff --git a/railties/lib/rails/generators/test_unit/plugin/templates/%file_name%_test.rb.tt b/railties/lib/rails/generators/test_unit/plugin/templates/%file_name%_test.rb.tt index 3e0bc29d3a..0cbae1120e 100644 --- a/railties/lib/rails/generators/test_unit/plugin/templates/%file_name%_test.rb.tt +++ b/railties/lib/rails/generators/test_unit/plugin/templates/%file_name%_test.rb.tt @@ -1,8 +1,7 @@ require 'test_helper' class <%= class_name %>Test < ActiveSupport::TestCase - # Replace this with your real tests. - test "the truth" do - assert true - end + # test "the truth" do + # assert true + # end end diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index 62697b1bf9..ab3eb4c9e7 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -258,6 +258,18 @@ module ApplicationTests assert_equal res, last_response.body # value should be unchanged end + test "sets all Active Record models to whitelist all attributes by default" do + add_to_config <<-RUBY + config.active_record.whitelist_attributes = true + RUBY + + require "#{app_path}/config/environment" + + assert_equal ActiveModel::MassAssignmentSecurity::WhiteList, + ActiveRecord::Base.active_authorizers[:default].class + assert_equal [""], ActiveRecord::Base.active_authorizers[:default].to_a + end + test "registers interceptors with ActionMailer" do add_to_config <<-RUBY config.action_mailer.interceptors = MyMailInterceptor diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 3ef06c7f25..839f75d963 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -23,7 +23,7 @@ DEFAULT_APP_FILES = %w( lib lib/tasks log - public/images + app/assets/images script/rails test/fixtures test/functional @@ -131,6 +131,18 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "Gemfile", /^gem\s+["']mysql2["']$/ end + def test_config_jdbcmysql_database + run_generator([destination_root, "-d", "jdbcmysql"]) + assert_file "config/database.yml", /jdbcmysql/ + assert_file "Gemfile", /^gem\s+["']activerecord-jdbcmysql-adapter["']$/ + end + + def test_config_jdbcsqlite3_database + run_generator([destination_root, "-d", "jdbcsqlite3"]) + assert_file "config/database.yml", /jdbcsqlite3/ + assert_file "Gemfile", /^gem\s+["']activerecord-jdbcsqlite3-adapter["']$/ + end + def test_generator_if_skip_active_record_is_given run_generator [destination_root, "--skip-active-record"] assert_no_file "config/database.yml" diff --git a/railties/test/generators/assets_generator_test.rb b/railties/test/generators/assets_generator_test.rb index e99f0f092a..375632e5bc 100644 --- a/railties/test/generators/assets_generator_test.rb +++ b/railties/test/generators/assets_generator_test.rb @@ -1,7 +1,7 @@ require 'generators/generators_test_helper' require 'rails/generators/rails/assets/assets_generator' -# FOXME: Silence the 'Could not find task "using_coffee?"' message in tests due to the public stub +# FIXME: Silence the 'Could not find task "using_coffee?"' message in tests due to the public stub class AssetsGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper arguments %w(posts) diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb index d20335ad95..fb956a8335 100644 --- a/railties/test/generators/plugin_new_generator_test.rb +++ b/railties/test/generators/plugin_new_generator_test.rb @@ -140,13 +140,14 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase run_generator [destination_root, "--full", "--skip_active_record"] FileUtils.cd destination_root `bundle install` - assert_match(/2 tests, 2 assertions, 0 failures, 0 errors/, `bundle exec rake test`) + assert_match(/1 tests, 1 assertions, 0 failures, 0 errors/, `bundle exec rake test`) end def test_creating_engine_in_full_mode run_generator [destination_root, "--full"] assert_file "app/assets/javascripts" assert_file "app/assets/stylesheets" + assert_file "app/assets/images" assert_file "app/models" assert_file "app/controllers" assert_file "app/views" @@ -162,6 +163,9 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase def test_create_mountable_application_with_mountable_option run_generator [destination_root, "--mountable"] + assert_file "app/assets/javascripts" + assert_file "app/assets/stylesheets" + assert_file "app/assets/images" assert_file "config/routes.rb", /Bukkits::Engine.routes.draw do/ assert_file "lib/bukkits/engine.rb", /isolate_namespace Bukkits/ assert_file "test/dummy/config/routes.rb", /mount Bukkits::Engine => "\/bukkits"/ diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 7605984684..b3cf9ad449 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -584,6 +584,51 @@ module RailtiesTest assert_equal Bukkits::Engine.instance, Rails::Engine.find(engine_path) end + test "gather isolated engine's helpers in Engine#helpers" do + @plugin.write "lib/bukkits.rb", <<-RUBY + module Bukkits + class Engine < ::Rails::Engine + isolate_namespace Bukkits + end + end + RUBY + + app_file "app/helpers/some_helper.rb", <<-RUBY + module SomeHelper + def foo + 'foo' + end + end + RUBY + + @plugin.write "app/helpers/bukkits/engine_helper.rb", <<-RUBY + module Bukkits + module EngineHelper + def bar + 'bar' + end + end + end + RUBY + + @plugin.write "app/helpers/engine_helper.rb", <<-RUBY + module EngineHelper + def baz + 'baz' + end + end + RUBY + + add_to_config("config.action_dispatch.show_exceptions = false") + + boot_rails + require "#{rails_root}/config/environment" + + methods = Bukkits::Engine.helpers.public_instance_methods.sort + expected = ["bar", "baz"] + assert_equal expected, methods + end + private def app Rails.application diff --git a/railties/test/railties/shared_tests.rb b/railties/test/railties/shared_tests.rb index b2b18938ae..e975950b85 100644 --- a/railties/test/railties/shared_tests.rb +++ b/railties/test/railties/shared_tests.rb @@ -11,7 +11,7 @@ module RailtiesTest end def test_serving_sprockets_assets - @plugin.write "app/assets/javascripts/engine.js.coffee", "square = (x) -> x * x" + @plugin.write "app/assets/javascripts/engine.js.erb", "<%= :alert %>();" boot_rails require 'rack/test' @@ -19,7 +19,7 @@ module RailtiesTest extend Rack::Test::Methods get "/assets/engine.js" - assert_match "square = function(x) {", last_response.body + assert_match "alert();", last_response.body end def test_copying_migrations |