diff options
22 files changed, 125 insertions, 295 deletions
@@ -14,6 +14,7 @@ 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 "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/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 78ecf177be..1f4f3ac0da 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -118,7 +118,15 @@ module ActionDispatch # :nodoc: def body=(body) @blank = true if body == EMPTY - @body = body.respond_to?(:each) ? body : [body] + + # Explicitly check for strings. This is *wrong* theoretically + # but if we don't check this, the performance on string bodies + # is bad on Ruby 1.8 (because strings responds to each then). + @body = if body.respond_to?(:to_str) || !body.respond_to?(:each) + [body] + else + body + end end def body_parts 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/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/sprockets/railtie.rb b/actionpack/lib/sprockets/railtie.rb index 2f7f95c44d..9c10decd60 100644 --- a/actionpack/lib/sprockets/railtie.rb +++ b/actionpack/lib/sprockets/railtie.rb @@ -33,9 +33,10 @@ module Sprockets app.assets = asset_environment(app) - # FIXME: Temp hack for extending Sprockets::Context so ActiveSupport.on_load(:action_view) do - ::Sprockets::Context.send :include, ::ActionView::Helpers::SprocketsHelper + app.assets.context.instance_eval do + include ::ActionView::Helpers::SprocketsHelper + end end app.routes.append do @@ -56,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
\ No newline at end of file +end 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..5ff81aa023 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 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 1270a1292c..2c05ff21f9 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -432,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 diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 3f70d5d0f7..e2b9a5d0d9 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 @@ -462,42 +465,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 @@ -554,10 +558,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]) @@ -935,10 +935,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 clause that's distinct on the columns we want but includes - # all the required columns for the ORDER BY to work properly. - sql = "DISTINCT ON (#{columns}) #{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 a9dc756bbb..e1518f9a0f 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -178,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 diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 359f9d8a66..45a7000cce 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -56,7 +56,7 @@ 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 diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index aae257a0e7..a3d4b7f45a 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -246,6 +246,8 @@ module ActiveRecord orders = relation.order_values values = @klass.connection.distinct("#{@klass.connection.quote_table_name table_name}.#{primary_key}", orders) + relation = relation.dup + ids_array = relation.select(values).collect {|row| row[primary_key]} ids_array.empty? ? raise(ThrowResult) : table[primary_key].in(ids_array) end 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/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/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/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/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index 0cee7deb72..141d9fd15c 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -8,6 +8,7 @@ source 'http://rubygems.org' <%= "gem 'json'\n" if RUBY_VERSION < "1.9.2" -%> gem 'sass', '~> 3.1.0.alpha' gem 'coffee-script' +gem 'uglifier' # Use unicorn as the web server # gem 'unicorn' 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..b00f10c545 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..12921f47b6 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 @@ -93,7 +93,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" diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 3ef06c7f25..58febfd9c7 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 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 |