diff options
56 files changed, 807 insertions, 258 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 2a9afed35b..3fc3e06160 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,5 +1,21 @@ ## Rails 4.0.0 (unreleased) ## +* Fixed `ActionView::Helpers::CaptureHelper#content_for` regression when trying to use it in + a boolean statement. + Fixes #9360. + + *Nikolay Shebanov* + +* `format: true` does not override existing format constraints. + Fixes #9466. + + Example: + + # This will force the .json extension. + get '/json_only', to: ok, format: true, constraints: { format: /json/ } + + *Yves Senn* + * Skip valid encoding checks for non-String parameters that come from the matched route's defaults. Fixes #9435. diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb index 063302e0f2..6fda085681 100644 --- a/actionpack/lib/action_dispatch/journey/route.rb +++ b/actionpack/lib/action_dispatch/journey/route.rb @@ -71,6 +71,10 @@ module ActionDispatch Visitors::Formatter.new(path_options).accept(path.spec) end + def optimized_path + Visitors::OptimizedPath.new.accept(path.spec) + end + def optional_parts path.optional_names.map { |n| n.to_sym } end diff --git a/actionpack/lib/action_dispatch/journey/visitors.rb b/actionpack/lib/action_dispatch/journey/visitors.rb index 46bd58c178..2964d80d9f 100644 --- a/actionpack/lib/action_dispatch/journey/visitors.rb +++ b/actionpack/lib/action_dispatch/journey/visitors.rb @@ -74,6 +74,14 @@ module ActionDispatch end end + class OptimizedPath < String # :nodoc: + private + + def visit_GROUP(node) + "" + end + end + # Used for formatting urls (url_for) class Formatter < Visitor # :nodoc: attr_reader :options, :consumed diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index dba9ccbfa5..4fbe156a72 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -113,14 +113,6 @@ module ActionDispatch @options.merge!(default_controller_and_action) end - def normalize_format! - if options[:format] == true - options[:format] = /.+/ - elsif options[:format] == false - options.delete(:format) - end - end - def normalize_requirements! constraints.each do |key, requirement| next unless segment_keys.include?(key) || key == :controller @@ -129,7 +121,7 @@ module ActionDispatch end if options[:format] == true - @requirements[:format] = /.+/ + @requirements[:format] ||= /.+/ elsif Regexp === options[:format] @requirements[:format] = options[:format] elsif String === options[:format] diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index ca31b5e02e..619dd22ec1 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -165,7 +165,7 @@ module ActionDispatch super @path_parts = @route.required_parts @arg_size = @path_parts.size - @string_route = string_route(route) + @string_route = @route.optimized_path end def call(t, args) @@ -180,14 +180,6 @@ module ActionDispatch private - def string_route(route) - string_route = route.ast.to_s.dup - while string_route.gsub!(/\([^\)]*\)/, "") - true - end - string_route - end - def optimized_helper(args) path = @string_route.dup klass = Journey::Router::Utils diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb index 4ec860d69a..1bad82159a 100644 --- a/actionpack/lib/action_view/helpers/capture_helper.rb +++ b/actionpack/lib/action_view/helpers/capture_helper.rb @@ -156,7 +156,7 @@ module ActionView end nil else - @view_flow.get(name) + @view_flow.get(name).presence end end diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 4775324b43..2bf7056ff7 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -3198,6 +3198,7 @@ class TestOptimizedNamedRoutes < ActionDispatch::IntegrationTest app.draw do ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] } get '/foo' => ok, as: :foo + get '/post(/:action(/:id))' => ok, as: :posts end end @@ -3215,6 +3216,11 @@ class TestOptimizedNamedRoutes < ActionDispatch::IntegrationTest test 'named route called on included module' do assert_equal '/foo', foo_path end + + test 'nested optional segments are removed' do + assert_equal '/post', Routes.url_helpers.posts_path + assert_equal '/post', posts_path + end end class TestNamedRouteUrlHelpers < ActionDispatch::IntegrationTest @@ -3407,6 +3413,8 @@ class TestFormatConstraints < ActionDispatch::IntegrationTest get '/string', to: ok, constraints: { format: 'json' } get '/regexp', to: ok, constraints: { format: /json/ } + get '/json_only', to: ok, format: true, constraints: { format: /json/ } + get '/xml_only', to: ok, format: 'xml' end end @@ -3434,6 +3442,28 @@ class TestFormatConstraints < ActionDispatch::IntegrationTest get 'http://www.example.com/regexp.html' assert_response :not_found end + + def test_enforce_with_format_true_with_constraint + get 'http://www.example.com/json_only.json' + assert_response :success + + get 'http://www.example.com/json_only.html' + assert_response :not_found + + get 'http://www.example.com/json_only' + assert_response :not_found + end + + def test_enforce_with_string + get 'http://www.example.com/xml_only.xml' + assert_response :success + + get 'http://www.example.com/xml_only' + assert_response :success + + get 'http://www.example.com/xml_only.json' + assert_response :not_found + end end class TestRouteDefaults < ActionDispatch::IntegrationTest diff --git a/actionpack/test/template/capture_helper_test.rb b/actionpack/test/template/capture_helper_test.rb index 234ac3252d..938f1c3e54 100644 --- a/actionpack/test/template/capture_helper_test.rb +++ b/actionpack/test/template/capture_helper_test.rb @@ -137,6 +137,10 @@ class CaptureHelperTest < ActionView::TestCase assert_equal 'bar', content_for(:title) end + def test_content_for_returns_nil_when_content_missing + assert_equal nil, content_for(:some_missing_key) + end + def test_content_for_question_mark assert ! content_for?(:title) content_for :title, 'title' diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 4a5a2d17af..2eaf388095 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,48 @@ ## Rails 4.0.0 (unreleased) ## +* Postgresql timestamp with time zone (timestamptz) datatype now returns a + ActiveSupport::TimeWithZone instance instead of a string + + *Troy Kruthoff* + +* The `#append` method for collection associations behaves like`<<`. + `#prepend` is not defined and `<<` or `#append` should be used. + Fixes #7364. + + *Yves Senn* + +* Added support for creating a table via Rails migration generator. + For example, + + rails g migration create_books title:string content:text + + will generate a migration that creates a table called books with + the listed attributes, without creating a model. + + *Sammy Larbi* + +* Fix bug that raises the wrong exception when the exception handled by PostgreSQL adapter + doesn't respond to `#result`. + Fixes #8617. + + *kennyj* + +* Support PostgreSQL specific column types when using `change_table`. + Fixes #9480. + + Example: + + change_table :authors do |t| + t.hstore :books + t.json :metadata + end + + *Yves Senn* + +* Revert 408227d9c5ed7d, 'quote numeric'. This introduced some regressions. + + *Steve Klabnik* + * Fix calculation of `db_runtime` property in `ActiveRecord::Railties::ControllerRuntime#cleanup_view_runtime`. Previously, after raising `ActionView::MissingTemplate`, `db_runtime` was @@ -1527,4 +1570,5 @@ *Aaron Patterson* + Please check [3-2-stable](https://github.com/rails/rails/blob/3-2-stable/activerecord/CHANGELOG.md) for previous changes. diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index e93e700c93..543204abac 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -83,9 +83,9 @@ module ActiveRecord # # #<Pet id: 3, name: "Choo-Choo"> # # ] # - # 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 + # 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: # # person.pets.select(:name).first.person_id @@ -924,7 +924,7 @@ module ActiveRecord alias_method :to_a, :to_ary # Adds one or more +records+ to the collection by setting their foreign keys - # to the association‘s primary key. Returns +self+, so several appends may be + # to the association's primary key. Returns +self+, so several appends may be # chained together. # # class Person < ActiveRecord::Base @@ -947,6 +947,11 @@ module ActiveRecord proxy_association.concat(records) && self end alias_method :push, :<< + alias_method :append, :<< + + def prepend(*args) + raise NoMethodError, "prepend on association is not defined. Please use << or append" + end # Equivalent to +delete_all+. The difference is that returns +self+, instead # of an array with the deleted objects, so methods can be chained. See diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index aec4654eee..d18b9c991f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -25,19 +25,13 @@ module ActiveRecord when true, false if column && column.type == :integer value ? '1' : '0' - elsif column && [:text, :string, :binary].include?(column.type) - value ? "'1'" : "'0'" else value ? quoted_true : quoted_false end # BigDecimals need to be put in a non-normalized form and quoted. when nil then "NULL" - when Numeric, ActiveSupport::Duration - value = BigDecimal === value ? value.to_s('F') : value.to_s - if column && ![:integer, :float, :decimal].include?(column.type) - value = "'#{value}'" - end - value + when BigDecimal then value.to_s('F') + when Numeric, ActiveSupport::Duration then value.to_s when Date, Time then "'#{quoted_date(value)}'" when Symbol then "'#{quote_string(value.to_s)}'" when Class then "'#{value.to_s}'" diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index f758e19a4f..42206de8fc 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -44,8 +44,8 @@ module ActiveRecord # Represents the schema of an SQL table in an abstract way. This class # provides methods for manipulating the schema representation. # - # Inside migration files, the +t+ object in +create_table+ and - # +change_table+ is actually of this type: + # Inside migration files, the +t+ object in +create_table+ + # is actually of this type: # # class SomeMigration < ActiveRecord::Migration # def up @@ -489,7 +489,7 @@ module ActiveRecord args.each do |name| @base.add_column(@table_name, name, column_type, options) end - end + end end private diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 9bae880024..0cce8c7596 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -156,7 +156,7 @@ module ActiveRecord # # See also TableDefinition#column for details on how to create columns. def create_table(table_name, options = {}) - td = table_definition + td = create_table_definition td.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false yield td if block_given? @@ -298,10 +298,10 @@ module ActiveRecord def change_table(table_name, options = {}) if supports_bulk_alter? && options[:bulk] recorder = ActiveRecord::Migration::CommandRecorder.new(self) - yield Table.new(table_name, recorder) + yield update_table_definition(table_name, recorder) bulk_change_table(table_name, recorder.commands) else - yield Table.new(table_name, self) + yield update_table_definition(table_name, self) end end @@ -727,9 +727,13 @@ module ActiveRecord end private - def table_definition + def create_table_definition TableDefinition.new(self) end + + def update_table_definition(table_name, base) + Table.new(table_name, base) + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 5480204511..9826b18053 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -212,6 +212,8 @@ module ActiveRecord if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary) s = column.class.string_to_binary(value).unpack("H*")[0] "x'#{s}'" + elsif value.kind_of?(BigDecimal) + value.to_s("F") else super end diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index 2c683fc3ac..8bad7d0cf5 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -38,7 +38,7 @@ module ActiveRecord private def resolve_string_connection(spec) # :nodoc: hash = configurations.fetch(spec) do |k| - self.class.connection_url_to_hash(k) + connection_url_to_hash(k) end raise(AdapterNotSpecified, "#{spec} database is not configured") unless hash @@ -65,7 +65,7 @@ module ActiveRecord ConnectionSpecification.new(spec, adapter_method) end - def self.connection_url_to_hash(url) # :nodoc: + def connection_url_to_hash(url) # :nodoc: config = URI.parse url adapter = config.scheme adapter = "postgresql" if adapter == "postgres" diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb index e09319890a..68f2f2ca7b 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -317,10 +317,6 @@ module ActiveRecord alias_type 'macaddr', 'text' alias_type 'uuid', 'text' - # FIXME: I don't think this is correct. We should probably be returning a parsed date, - # but the tests pass with a string returned. - register_type 'timestamptz', OID::Identity.new - register_type 'money', OID::Money.new register_type 'bytea', OID::Bytea.new register_type 'bool', OID::Boolean.new @@ -329,6 +325,7 @@ module ActiveRecord alias_type 'float8', 'float4' register_type 'timestamp', OID::Timestamp.new + register_type 'timestamptz', OID::Timestamp.new register_type 'date', OID::Date.new register_type 'time', OID::Time.new diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 2bb2557efd..c91e1b3fb9 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -263,7 +263,7 @@ module ActiveRecord attr_accessor :array end - class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition + module ColumnMethods def xml(*args) options = args.extract_options! column(args[0], 'xml', options) @@ -325,6 +325,10 @@ module ActiveRecord def json(name, options = {}) column(name, 'json', options) end + end + + class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition + include ColumnMethods def column(name, type = nil, options = {}) super @@ -344,6 +348,10 @@ module ActiveRecord end end + class Table < ActiveRecord::ConnectionAdapters::Table + include ColumnMethods + end + ADAPTER_NAME = 'PostgreSQL' NATIVE_DATABASE_TYPES = { @@ -667,6 +675,8 @@ module ActiveRecord UNIQUE_VIOLATION = "23505" def translate_exception(exception, message) + return exception unless exception.respond_to?(:result) + case exception.result.try(:error_field, PGresult::PG_DIAG_SQLSTATE) when UNIQUE_VIOLATION RecordNotUnique.new(message, exception) @@ -884,9 +894,13 @@ module ActiveRecord $1.strip if $1 end - def table_definition + def create_table_definition TableDefinition.new(self) end + + def update_table_definition(table_name, base) + Table.new(table_name, base) + end end end end diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 64eac3aca7..13f3bf7085 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -152,7 +152,7 @@ module ActiveRecord # and then establishes the connection. initializer "active_record.initialize_database" do |app| ActiveSupport.on_load(:active_record) do - self.configurations = app.config.database_configuration + self.configurations = app.config.database_configuration || {} establish_connection end end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index f36af7182f..d92e268109 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -2,7 +2,7 @@ require 'active_record' db_namespace = namespace :db do task :load_config do - ActiveRecord::Base.configurations = Rails.application.config.database_configuration + ActiveRecord::Base.configurations = Rails.application.config.database_configuration || {} ActiveRecord::Migrator.migrations_paths = Rails.application.paths['db/migrate'].to_a if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH) diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 5cd015eba7..bd783a94cf 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -98,11 +98,6 @@ module ActiveRecord when Class # FIXME: I think we need to deprecate this behavior attribute.eq(value.name) - when Integer, ActiveSupport::Duration - # Arel treats integers as literals, but they should be quoted when compared with strings - table = attribute.relation - column = table.engine.connection.schema_cache.columns_hash(table.name)[attribute.name.to_s] - attribute.eq(Arel::Nodes::SqlLiteral.new(table.engine.connection.quote(value, column))) else attribute.eq(value) end diff --git a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb index 5f1dbe36d6..b967bb6e0f 100644 --- a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb +++ b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb @@ -8,13 +8,14 @@ module ActiveRecord def create_migration_file set_local_assigns! validate_file_name! - migration_template "migration.rb", "db/migrate/#{file_name}.rb" + migration_template @migration_template, "db/migrate/#{file_name}.rb" end protected attr_reader :migration_action, :join_tables def set_local_assigns! + @migration_template = "migration.rb" case file_name when /^(add|remove)_.*_(?:to|from)_(.*)/ @migration_action = $1 @@ -26,6 +27,9 @@ module ActiveRecord set_index_names end + when /^create_(.+)/ + @table_name = $1.pluralize + @migration_template = "create_table_migration.rb" end end @@ -44,7 +48,10 @@ module ActiveRecord end private - + def attributes_with_index + attributes.select { |a| !a.reference? && a.has_index? } + end + def validate_file_name! unless file_name =~ /^[_a-z0-9]+$/ raise IllegalMigrationNameError.new(file_name) diff --git a/activerecord/lib/rails/generators/active_record/model/templates/migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb index 3a3cf86d73..3a3cf86d73 100644 --- a/activerecord/lib/rails/generators/active_record/model/templates/migration.rb +++ b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb diff --git a/activerecord/lib/rails/generators/active_record/model/model_generator.rb b/activerecord/lib/rails/generators/active_record/model/model_generator.rb index 5f36181694..40e134e626 100644 --- a/activerecord/lib/rails/generators/active_record/model/model_generator.rb +++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb @@ -15,7 +15,7 @@ module ActiveRecord def create_migration_file return unless options[:migration] && options[:parent].nil? attributes.each { |a| a.attr_options.delete(:index) if a.reference? && !a.has_index? } if options[:indexes] == false - migration_template "migration.rb", "db/migrate/create_#{table_name}.rb" + migration_template "../../migration/templates/create_table_migration.rb", "db/migrate/create_#{table_name}.rb" end def create_model_file diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb index 33c796191e..1e6ae85a25 100644 --- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb +++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb @@ -573,6 +573,7 @@ _SQL @first_timestamp_with_zone = PostgresqlTimestampWithZone.find(1) assert_equal Time.utc(2010,1,1, 11,0,0), @first_timestamp_with_zone.time + assert_instance_of Time, @first_timestamp_with_zone.time ensure ActiveRecord::Base.default_timezone = old_default_tz ActiveRecord::Base.time_zone_aware_attributes = old_tz @@ -590,6 +591,7 @@ _SQL @first_timestamp_with_zone = PostgresqlTimestampWithZone.find(1) assert_equal Time.utc(2010,1,1, 11,0,0), @first_timestamp_with_zone.time + assert_instance_of Time, @first_timestamp_with_zone.time ensure ActiveRecord::Base.default_timezone = old_default_tz ActiveRecord::Base.time_zone_aware_attributes = old_tz diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb index 6640f9b497..ad98d7c8ce 100644 --- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb +++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb @@ -65,6 +65,21 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase assert_equal :hstore, @column.type end + def test_change_table_supports_hstore + @connection.transaction do + @connection.change_table('hstores') do |t| + t.hstore 'users', default: '' + end + Hstore.reset_column_information + column = Hstore.columns.find { |c| c.name == 'users' } + assert_equal :hstore, column.type + + raise ActiveRecord::Rollback # reset the schema change + end + ensure + Hstore.reset_column_information + end + def test_type_cast_hstore assert @column diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb index d64037eec0..6fc08ae4f0 100644 --- a/activerecord/test/cases/adapters/postgresql/json_test.rb +++ b/activerecord/test/cases/adapters/postgresql/json_test.rb @@ -31,6 +31,21 @@ class PostgresqlJSONTest < ActiveRecord::TestCase assert_equal :json, @column.type end + def test_change_table_supports_json + @connection.transaction do + @connection.change_table('json_data_type') do |t| + t.json 'users', default: '{}' + end + JsonDataType.reset_column_information + column = JsonDataType.columns.find { |c| c.name == 'users' } + assert_equal :json, column.type + + raise ActiveRecord::Rollback # reset the schema change + end + ensure + JsonDataType.reset_column_information + end + def test_type_cast_json assert @column diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index 872204c644..05e0f0e192 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -250,6 +250,12 @@ module ActiveRecord assert_equal "DISTINCT posts.title, posts.updater_id AS alias_0", @connection.distinct("posts.title", ["posts.updater_id desc nulls last"]) end + def test_raise_error_when_cannot_translate_exception + assert_raise TypeError do + @connection.send(:log, nil) { @connection.execute(nil) } + end + end + private def insert(ctx, data) binds = data.map { |name, value| diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index d7f25f760e..201fa5d5a9 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -172,6 +172,18 @@ class AssociationProxyTest < ActiveRecord::TestCase assert_equal 1, josh.posts.size end + def test_append_behaves_like_push + josh = Author.new(:name => "Josh") + josh.posts.append Post.new(:title => "New on Edge", :body => "More cool stuff!") + assert josh.posts.loaded? + assert_equal 1, josh.posts.size + end + + def test_prepend_is_not_defined + josh = Author.new(:name => "Josh") + assert_raises(NoMethodError) { josh.posts.prepend Post.new } + end + def test_save_on_parent_does_not_load_target david = developers(:david) @@ -291,7 +303,7 @@ class OverridingAssociationsTest < ActiveRecord::TestCase end def test_requires_symbol_argument - assert_raises ArgumentError do + assert_raises ArgumentError do Class.new(Post) do belongs_to "author" end diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb index 0ad05223d4..3dd11ae89d 100644 --- a/activerecord/test/cases/quoting_test.rb +++ b/activerecord/test/cases/quoting_test.rb @@ -122,35 +122,35 @@ module ActiveRecord def test_quote_float float = 1.2 assert_equal float.to_s, @quoter.quote(float, nil) - assert_equal float.to_s, @quoter.quote(float, FakeColumn.new(:float)) + assert_equal float.to_s, @quoter.quote(float, Object.new) end def test_quote_fixnum fixnum = 1 assert_equal fixnum.to_s, @quoter.quote(fixnum, nil) - assert_equal fixnum.to_s, @quoter.quote(fixnum, FakeColumn.new(:integer)) + assert_equal fixnum.to_s, @quoter.quote(fixnum, Object.new) end def test_quote_bignum bignum = 1 << 100 assert_equal bignum.to_s, @quoter.quote(bignum, nil) - assert_equal bignum.to_s, @quoter.quote(bignum, FakeColumn.new(:integer)) + assert_equal bignum.to_s, @quoter.quote(bignum, Object.new) end def test_quote_bigdecimal bigdec = BigDecimal.new((1 << 100).to_s) assert_equal bigdec.to_s('F'), @quoter.quote(bigdec, nil) - assert_equal bigdec.to_s('F'), @quoter.quote(bigdec, FakeColumn.new(:decimal)) + assert_equal bigdec.to_s('F'), @quoter.quote(bigdec, Object.new) end def test_dates_and_times @quoter.extend(Module.new { def quoted_date(value) 'lol' end }) assert_equal "'lol'", @quoter.quote(Date.today, nil) - assert_equal "'lol'", @quoter.quote(Date.today, FakeColumn.new(:date)) + assert_equal "'lol'", @quoter.quote(Date.today, Object.new) assert_equal "'lol'", @quoter.quote(Time.now, nil) - assert_equal "'lol'", @quoter.quote(Time.now, FakeColumn.new(:time)) + assert_equal "'lol'", @quoter.quote(Time.now, Object.new) assert_equal "'lol'", @quoter.quote(DateTime.now, nil) - assert_equal "'lol'", @quoter.quote(DateTime.now, FakeColumn.new(:datetime)) + assert_equal "'lol'", @quoter.quote(DateTime.now, Object.new) end def test_crazy_object diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb index 53cdf89b1f..c43c7601a2 100644 --- a/activerecord/test/cases/relation/where_test.rb +++ b/activerecord/test/cases/relation/where_test.rb @@ -108,30 +108,5 @@ module ActiveRecord assert_equal 4, Edge.where(blank).order("sink_id").to_a.size end end - - def test_where_with_integer_for_string_column - count = Post.where(:title => 0).count - assert_equal 0, count - end - - def test_where_with_float_for_string_column - count = Post.where(:title => 0.0).count - assert_equal 0, count - end - - def test_where_with_boolean_for_string_column - count = Post.where(:title => false).count - assert_equal 0, count - end - - def test_where_with_decimal_for_string_column - count = Post.where(:title => BigDecimal.new(0)).count - assert_equal 0, count - end - - def test_where_with_duration_for_string_column - count = Post.where(:title => 0.seconds).count - assert_equal 0, count - end end end diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb index 8e6c38706f..7388324a0d 100644 --- a/activerecord/test/cases/relation_scoping_test.rb +++ b/activerecord/test/cases/relation_scoping_test.rb @@ -391,19 +391,19 @@ class DefaultScopingTest < ActiveRecord::TestCase def test_default_scope_with_inheritance wheres = InheritedPoorDeveloperCalledJamis.all.where_values_hash assert_equal "Jamis", wheres[:name] - assert_equal Arel.sql("50000"), wheres[:salary] + assert_equal 50000, wheres[:salary] end def test_default_scope_with_module_includes wheres = ModuleIncludedPoorDeveloperCalledJamis.all.where_values_hash assert_equal "Jamis", wheres[:name] - assert_equal Arel.sql("50000"), wheres[:salary] + assert_equal 50000, wheres[:salary] end def test_default_scope_with_multiple_calls wheres = MultiplePoorDeveloperCalledJamis.all.where_values_hash assert_equal "Jamis", wheres[:name] - assert_equal Arel.sql("50000"), wheres[:salary] + assert_equal 50000, wheres[:salary] end def test_scope_overwrites_default diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index d789b6cb7a..cd9835259a 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -540,8 +540,6 @@ ActiveRecord::Schema.define do create_table :price_estimates, :force => true do |t| t.string :estimate_of_type t.integer :estimate_of_id - t.string :thing_type - t.integer :thing_id t.integer :price end diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index c71525309b..a28310032a 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -20,7 +20,7 @@ Gem::Specification.new do |s| s.rdoc_options.concat ['--encoding', 'UTF-8'] - s.add_dependency 'i18n', '~> 0.6', '>= 0.6.2' + s.add_dependency('i18n', '~> 0.6', '>= 0.6.4') s.add_dependency 'multi_json', '~> 1.3' s.add_dependency 'tzinfo', '~> 0.3.33' s.add_dependency 'minitest', '~> 4.2' diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md index ee6e64b731..b0e52847e1 100644 --- a/guides/CHANGELOG.md +++ b/guides/CHANGELOG.md @@ -1,5 +1,5 @@ ## Rails 4.0.0 (unreleased) ## - +* Change Service pages(404, etc). *Stanislav Sobolev* ## Rails 4.0.0.beta1 (unreleased) ## diff --git a/guides/code/getting_started/public/404.html b/guides/code/getting_started/public/404.html index 3d875c342e..ae7b8649ae 100644 --- a/guides/code/getting_started/public/404.html +++ b/guides/code/getting_started/public/404.html @@ -2,17 +2,48 @@ <html> <head> <title>The page you were looking for doesn't exist (404)</title> - <style> - body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; } - div.dialog { - width: 25em; - padding: 0 4em; - margin: 4em auto 0 auto; - border: 1px solid #ccc; - border-right-color: #999; - border-bottom-color: #999; - } - h1 { font-size: 100%; color: #f00; line-height: 1.5em; } + <style> + body + { + background-color: #efefef; + color: #2E2F30; + text-align: center; + font-family: arial,sans-serif; + } + div.dialog + { + width: 25em; + margin: 4em auto 0 auto; + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #BBB; + border-top: #B00100 solid 4px; + border-top-left-radius: 9px; + border-top-right-radius: 9px; + background-color: white; + padding: 7px 4em 0 4em; + } + h1{ + font-size: 100%; + color: #730E15; + line-height: 1.5em; + } + body>p + { + width: 33em; + margin: 0 auto 1em; + padding: 1em 0; + background-color: #f7f7f7; + border: 1px solid #CCC; + border-right-color: #999; + border-bottom-color: #999; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-top-color: #DADADA; + color: #666; + box-shadow: 0 3px 8px rgba(50,50,50,0.17); + } </style> </head> diff --git a/guides/code/getting_started/public/422.html b/guides/code/getting_started/public/422.html index 3f1bfb3417..b75cc2fd6c 100644 --- a/guides/code/getting_started/public/422.html +++ b/guides/code/getting_started/public/422.html @@ -2,17 +2,48 @@ <html> <head> <title>The change you wanted was rejected (422)</title> - <style> - body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; } - div.dialog { - width: 25em; - padding: 0 4em; - margin: 4em auto 0 auto; - border: 1px solid #ccc; - border-right-color: #999; - border-bottom-color: #999; - } - h1 { font-size: 100%; color: #f00; line-height: 1.5em; } + <style> + body + { + background-color: #efefef; + color: #2E2F30; + text-align: center; + font-family: arial,sans-serif; + } + div.dialog + { + width: 25em; + margin: 4em auto 0 auto; + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #BBB; + border-top: #B00100 solid 4px; + border-top-left-radius: 9px; + border-top-right-radius: 9px; + background-color: white; + padding: 7px 4em 0 4em; + } + h1{ + font-size: 100%; + color: #730E15; + line-height: 1.5em; + } + body>p + { + width: 33em; + margin: 0 auto 1em; + padding: 1em 0; + background-color: #f7f7f7; + border: 1px solid #CCC; + border-right-color: #999; + border-bottom-color: #999; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-top-color: #DADADA; + color: #666; + box-shadow: 0 3px 8px rgba(50,50,50,0.17); + } </style> </head> diff --git a/guides/code/getting_started/public/500.html b/guides/code/getting_started/public/500.html index 012977d3d2..9641851e74 100644 --- a/guides/code/getting_started/public/500.html +++ b/guides/code/getting_started/public/500.html @@ -2,17 +2,48 @@ <html> <head> <title>We're sorry, but something went wrong (500)</title> - <style> - body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; } - div.dialog { - width: 25em; - padding: 0 4em; - margin: 4em auto 0 auto; - border: 1px solid #ccc; - border-right-color: #999; - border-bottom-color: #999; - } - h1 { font-size: 100%; color: #f00; line-height: 1.5em; } + <style> + body + { + background-color: #efefef; + color: #2E2F30; + text-align: center; + font-family: arial,sans-serif; + } + div.dialog + { + width: 25em; + margin: 4em auto 0 auto; + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #BBB; + border-top: #B00100 solid 4px; + border-top-left-radius: 9px; + border-top-right-radius: 9px; + background-color: white; + padding: 7px 4em 0 4em; + } + h1{ + font-size: 100%; + color: #730E15; + line-height: 1.5em; + } + body>p + { + width: 33em; + margin: 0 auto 1em; + padding: 1em 0; + background-color: #f7f7f7; + border: 1px solid #CCC; + border-right-color: #999; + border-bottom-color: #999; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-top-color: #DADADA; + color: #666; + box-shadow: 0 3px 8px rgba(50,50,50,0.17); + } </style> </head> diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index da155628f3..e65f7e5b18 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -6,6 +6,7 @@ In this guide you will learn how controllers work and how they fit into the requ After reading this guide, you will know: * How to follow the flow of a request through a controller. +* How to restrict parameters passed to your controller. * Why and how to store data in the session or cookies. * How to work with filters to execute code during request processing. * How to use Action Controller's built-in HTTP authentication. @@ -170,6 +171,123 @@ These options will be used as a starting point when generating URLs, so it's pos If you define `default_url_options` in `ApplicationController`, as in the example above, it would be used for all URL generation. The method can also be defined in one specific controller, in which case it only affects URLs generated there. +### Strong Parameters + +With strong parameters Action Controller parameters are forbidden to +be used in Active Model mass assignments until they have been +whitelisted. This means you'll have to make a conscious choice about +which attributes to allow for mass updating and thus prevent +accidentally exposing that which shouldn't be exposed. + +In addition, parameters can be marked as required and flow through a +predefined raise/rescue flow to end up as a 400 Bad Request with no +effort. + +```ruby +class PeopleController < ActionController::Base + # This will raise an ActiveModel::ForbiddenAttributes exception + # because it's using mass assignment without an explicit permit + # step. + def create + Person.create(params[:person]) + end + + # This will pass with flying colors as long as there's a person key + # in the parameters, otherwise it'll raise a + # ActionController::MissingParameter exception, which will get + # caught by ActionController::Base and turned into that 400 Bad + # Request reply. + def update + person = current_account.people.find(params[:id]) + person.update_attributes!(person_params) + redirect_to person + end + + private + # Using a private method to encapsulate the permissible parameters + # is just a good pattern since you'll be able to reuse the same + # permit list between create and update. Also, you can specialize + # this method with per-user checking of permissible attributes. + def person_params + params.require(:person).permit(:name, :age) + end +end +``` + +#### Permitted Scalar Values + +Given + +```ruby +params.permit(:id) +``` + +the key `:id` will pass the whitelisting if it appears in `params` and +it has a permitted scalar value associated. Otherwise the key is going +to be filtered out, so arrays, hashes, or any other objects cannot be +injected. + +The permitted scalar types are `String`, `Symbol`, `NilClass`, +`Numeric`, `TrueClass`, `FalseClass`, `Date`, `Time`, `DateTime`, +`StringIO`, `IO`, `ActionDispatch::Http::UploadedFile` and +`Rack::Test::UploadedFile`. + +To declare that the value in `params+ must be an array of permitted +scalar values map the key to an empty array: + +```ruby +params.permit(:id => []) +``` + +To whitelist an entire hash of parameters, the `permit!+ method can be +used + +```ruby +params.require(:log_entry).permit! +``` + +This will mark the `:log_entry` parameters hash and any subhash of it +permitted. Extreme care should be taken when using `permit!` as it +will allow all current and future model attributes to be +mass-assigned. + +#### Nested Parameters + +You can also use permit on nested parameters, like: + +```ruby +params.permit(:name, {:emails => []}, + :friends => [ :name, + { :family => [ :name ], :hobbies => [] }]) +``` + +This declaration whitelists the `name`, `emails` and `friends` +attributes. It is expected that `emails` will be an array of permitted +scalar values and that `friends` will be an array of resources with +specific attributes : they should have a `name` attribute (any +permitted scalar values allowed), a `hobbies` attribute as an array of +permitted scalar values, and a `family` attribute which is restricted +to having a `name` (any permitted scalar values allowed, too). + +#### Outside the Scope of Strong Parameters + +The strong parameter API was designed with the most common use cases +in mind. It is not meant as a silver bullet to handle all your +whitelisting problems. However you can easily mix the API with your +own code to adapt to your situation. + +Imagine a situation where you want to whitelist an attribute +containing a hash with any keys. Using strong parameters you can't +allow a hash with any keys but you can use a simple assignment to get +the job done: + +```ruby +def product_params + params.require(:product).permit(:name).tap do |whitelisted| + whitelisted[:data] = params[:product][:data] + end +end +``` Session ------- diff --git a/guides/source/migrations.md b/guides/source/migrations.md index d738d847e9..89ae564c24 100644 --- a/guides/source/migrations.md +++ b/guides/source/migrations.md @@ -179,6 +179,27 @@ class AddDetailsToProducts < ActiveRecord::Migration end ``` +If the migration name is of the form "CreateXXX" and is +followed by a list of column names and types then a migration creating the table +XXX with the columns listed will be generated. For example: + +```bash +$ rails generate migration CreateProducts name:string part_number:string +``` + +generates + +```ruby +class CreateProducts < ActiveRecord::Migration + def change + create_table :products do |t| + t.string :name + t.string :part_number + end + end +end +``` + As always, what has been generated for you is just a starting point. You can add or remove from it as you see fit by editing the `db/migrate/YYYYMMDDHHMMSS_add_details_to_products.rb` file. diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index 52f5232efc..57945a256b 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -47,6 +47,18 @@ Rails 4.0 no longer supports loading plugins from `vendor/plugins`. You must rep * Rails 4.0 has removed `attr_accessible` and `attr_protected` feature in favor of Strong Parameters. You can use the [Protected Attributes gem](https://github.com/rails/protected_attributes) to a smoothly upgrade path. +* Rails 4.0 requires that scopes use a callable object such as a Proc or lambda: + +```ruby + scope :active, where(active: true) + + # becomes + scope :active, -> { where active: true } +``` + +* Rails 4.0 has deprecated `ActiveRecord::Fixtures` in favor of `ActiveRecord::FixtureSet`. +* Rails 4.0 has deprecated `ActiveRecord::TestCase` in favor of `ActiveSupport::TestCase`. + ### Active Resource Rails 4.0 extracted Active Resource to its own gem. If you still need the feature you can add the [Active Resource gem](https://github.com/rails/activeresource) in your Gemfile. @@ -66,7 +78,16 @@ Rails 4.0 extracted Active Resource to its own gem. If you still need the featur ### Action Pack -* There is an upgrading cookie store `UpgradeSignatureToEncryptionCookieStore` which helps you upgrading apps that use `CookieStore` to the new default `EncryptedCookieStore`. To use this `CookieStore` set `Myapp::Application.config.session_store :upgrade_signature_to_encryption_cookie_store, key: '_myapp_session'` in `config/initializers/session_store.rb`. Additionally, add `Myapp::Application.config.secret_key_base = 'some secret'` in `config/initializers/secret_token.rb`. Do not remove `Myapp::Application.config.secret_token = 'some secret'`. +* Rails 4.0 introduces a new `UpgradeSignatureToEncryptionCookieStore` cookie store. This is useful for upgrading apps using the old default `CookieStore` to the new default `EncryptedCookieStore`. To use this transitional cookie store, you'll want to leave your existing `secret_token` in place, add a new `secret_key_base`, and change your `session_store` like so: + +```ruby + # config/initializers/session_store.rb + Myapp::Application.config.session_store :upgrade_signature_to_encryption_cookie_store, key: 'existing session key' + + # config/initializers/secret_token.rb + Myapp::Application.config.secret_token = 'existing secret token' + Myapp::Application.config.secret_key_base = 'new secret key base' +``` * Rails 4.0 removed the `ActionController::Base.asset_path` option. Use the assets pipeline feature. @@ -74,6 +95,12 @@ Rails 4.0 extracted Active Resource to its own gem. If you still need the featur * Rails 4.0 has removed Action and Page caching from Action Pack. You will need to add the `actionpack-action_caching` gem in order to use `caches_action` and the `actionpack-page_caching` to use `caches_pages` in your controllers. +* Rails 4.0 has removed the XML parameters parser. You will need to add the `actionpack-xml_parser` gem if you require this feature. + +* Rails 4.0 changes the default memcached client from `memcache-client` to `dalli`. To upgrade, simply add `gem 'dalli'` to your `Gemfile`. + +* Rails 4.0 deprecates the `dom_id` and `dom_class` methods. You will need to include the `ActionView::RecordIdentifier` module in controllers requiring this feature. + * Rails 4.0 changed how `assert_generates`, `assert_recognizes`, and `assert_routing` work. Now all these assertions raise `Assertion` instead of `ActionController::RoutingError`. * Rails 4.0 also changed the way unicode character routes are drawn. Now you can draw unicode character routes directly. If you already draw such routes, you must change them, for example: @@ -88,6 +115,19 @@ becomes get 'こんにちは', controller: 'welcome', action: 'index' ``` +* Rails 4.0 requires that routes using `match` must specify the request method. For example: + +```ruby + # Rails 3.x + match "/" => "root#index" + + # becomes + match "/" => "root#index", via: :get + + # or + get "/" => "root#index" +``` + * Rails 4.0 has removed ActionDispatch::BestStandardsSupport middleware, !DOCTYPE html already triggers standards mode per http://msdn.microsoft.com/en-us/library/jj676915(v=vs.85).aspx and ChromeFrame header has been moved to `config.action_dispatch.default_headers` Remember you must also remove any references to the middleware from your application code, for example: @@ -101,6 +141,21 @@ Also check your environment settings for `config.action_dispatch.best_standards_ * In Rails 4.0, precompiling assets no longer automatically copies non-JS/CSS assets from `vendor/assets` and `lib/assets`. Rails application and engine developers should put these assets in `app/assets` or configure `config.assets.precompile`. +* In Rails 4.0, `ActionController::UnknownFormat` is raised when the action doesn't handle the request format. By default, the exception is handled by responding with 406 Not Acceptable, but you can override that now. In Rails 3, 406 Not Acceptable was always returned. No overrides. + +* In Rails 4.0, a generic `ActionDispatch::ParamsParser::ParseError` exception is raised when `ParamsParser` fails to parse request params. You will want to rescue this exception instead of the low-level `MultiJson::DecodeError`, for example. + +* In Rails 4.0, `SCRIPT_NAME` is properly nested when engines are mounted on an app that's served from a URL prefix. You no longer have to set `default_url_options[:script_name]` to work around overwritten URL prefixes. + +* Rails 4.0 deprecated `ActionController::Integration` in favor of `ActionDispatch::Integration`. +* Rails 4.0 deprecated `ActionController::IntegrationTest` in favor of `ActionDispatch::IntegrationTest`. +* Rails 4.0 deprecated `ActionController::PerformanceTest` in favor of `ActionDispatch::PerformanceTest`. +* Rails 4.0 deprecated `ActionController::AbstractRequest` in favor of `ActionDispatch::Request`. +* Rails 4.0 deprecated `ActionController::Request` in favor of `ActionDispatch::Request`. +* Rails 4.0 deprecated `ActionController::AbstractResponse` in favor of `ActionDispatch::Response`. +* Rails 4.0 deprecated `ActionController::Response` in favor of `ActionDispatch::Response`. +* Rails 4.0 deprecated `ActionController::Routing` in favor of `ActionDispatch::Routing`. + ### Active Support Rails 4.0 removes the `j` alias for `ERB::Util#json_escape` since `j` is already used for `ActionView::Helpers::JavaScriptHelper#escape_javascript`. @@ -109,6 +164,18 @@ Rails 4.0 removes the `j` alias for `ERB::Util#json_escape` since `j` is already The order in which helpers from more than one directory are loaded has changed in Rails 4.0. Previously, they were gathered and then sorted alphabetically. After upgrading to Rails 4.0, helpers will preserve the order of loaded directories and will be sorted alphabetically only within each directory. Unless you explicitly use the `helpers_path` parameter, this change will only impact the way of loading helpers from engines. If you rely on the ordering, you should check if correct methods are available after upgrade. If you would like to change the order in which engines are loaded, you can use `config.railties_order=` method. +### Active Record Observer and Action Controller Sweeper + +Active Record Observer and Action Controller Sweeper have been extracted to the `rails-observers` gem. You will need to add the `rails-observers` gem if you require these features. + +### sprockets-rails + +* `assets:precompile:primary` has been removed. Use `assets:precompile` instead. + +### sass-rails + +* `asset_url` with two arguments is deprecated. For example: `asset-url("rails.png", image)` becomes `asset-url("rails.png")` + Upgrading from Rails 3.1 to Rails 3.2 ------------------------------------- diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index f3c07470af..4f7cb8254f 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -2,6 +2,7 @@ ## Rails 4.0.0.beta1 (February 25, 2013) ## +* Change Service pages(404, etc). *Stanislav Sobolev* * Improve `rake stats` for JavaScript and CoffeeScript: ignore block comments and calculates number of functions. diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 5af7de720c..25cc36ff5d 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -305,22 +305,8 @@ module Rails def default_middleware_stack #:nodoc: ActionDispatch::MiddlewareStack.new.tap do |middleware| app = self - if rack_cache = config.action_dispatch.rack_cache - begin - require 'rack/cache' - rescue LoadError => error - error.message << ' Be sure to add rack-cache to your Gemfile' - raise - end - - if rack_cache == true - rack_cache = { - metastore: "rails:/", - entitystore: "rails:/", - verbose: false - } - end + if rack_cache = load_rack_cache require "action_dispatch/http/rack_cache" middleware.use ::Rack::Cache, rack_cache end @@ -337,12 +323,14 @@ module Rails middleware.use ::ActionDispatch::Static, paths["public"].first, config.static_cache_control end - middleware.use ::Rack::Lock unless config.cache_classes + middleware.use ::Rack::Lock unless allow_concurrency? middleware.use ::Rack::Runtime middleware.use ::Rack::MethodOverride middleware.use ::ActionDispatch::RequestId - middleware.use ::Rails::Rack::Logger, config.log_tags # must come after Rack::MethodOverride to properly log overridden methods - middleware.use ::ActionDispatch::ShowExceptions, config.exceptions_app || ActionDispatch::PublicExceptions.new(Rails.public_path) + + # Must come after Rack::MethodOverride to properly log overridden methods + middleware.use ::Rails::Rack::Logger, config.log_tags + middleware.use ::ActionDispatch::ShowExceptions, show_exceptions_app middleware.use ::ActionDispatch::DebugExceptions, app middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies @@ -368,6 +356,40 @@ module Rails end end + def allow_concurrency? + if config.allow_concurrency.nil? + config.cache_classes + else + config.allow_concurrency + end + end + + def load_rack_cache + rack_cache = config.action_dispatch.rack_cache + return unless rack_cache + + begin + require 'rack/cache' + rescue LoadError => error + error.message << ' Be sure to add rack-cache to your Gemfile' + raise + end + + if rack_cache == true + { + metastore: "rails:/", + entitystore: "rails:/", + verbose: false + } + else + rack_cache + end + end + + def show_exceptions_app + config.exceptions_app || ActionDispatch::PublicExceptions.new(Rails.public_path) + end + def build_original_fullpath(env) #:nodoc: path_info = env["PATH_INFO"] query_string = env["QUERY_STRING"] diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 17763b39c5..31fc80e544 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -5,7 +5,7 @@ require 'rails/engine/configuration' module Rails class Application class Configuration < ::Rails::Engine::Configuration - attr_accessor :asset_host, :assets, :autoflush_log, + attr_accessor :allow_concurrency, :asset_host, :assets, :autoflush_log, :cache_classes, :cache_store, :consider_all_requests_local, :console, :eager_load, :exceptions_app, :file_watcher, :filter_parameters, :force_ssl, :helpers_paths, :logger, :log_formatter, :log_tags, @@ -20,6 +20,7 @@ module Rails def initialize(*) super self.encoding = "utf-8" + @allow_concurrency = nil @consider_all_requests_local = false @filter_parameters = [] @filter_redirect = [] @@ -98,14 +99,15 @@ module Rails end # Loads and returns the configuration of the database. - # First, looks at If ENV['DATABASE_URL'] if it's not present it uses the #paths["config/database"] - # The contents of the file are processed via ERB before being sent through YAML::load. def database_configuration - if ENV['DATABASE_URL'] - {Rails.env => ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.connection_url_to_hash(ENV['DATABASE_URL']).stringify_keys} + yaml = paths["config/database"].first + if File.exists?(yaml) + require "erb" + YAML.load ERB.new(IO.read(yaml)).result + elsif ENV['DATABASE_URL'] + nil else - require 'erb' - YAML.load ERB.new(IO.read(paths["config/database"].first)).result + raise "Could not load database configuration. No such file - #{yaml}" end rescue Psych::SyntaxError => e raise "YAML syntax error occurred while parsing #{paths["config/database"].first}. " \ diff --git a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb index e15c963971..daae72270f 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb @@ -7,6 +7,5 @@ </p> <% end -%> - <%%= link_to 'Edit', edit_<%= singular_table_name %>_path(@<%= singular_table_name %>) %> | <%%= link_to 'Back', <%= index_helper %>_path %> diff --git a/railties/lib/rails/generators/rails/app/templates/public/404.html b/railties/lib/rails/generators/rails/app/templates/public/404.html index 3d875c342e..0ee82d3722 100644 --- a/railties/lib/rails/generators/rails/app/templates/public/404.html +++ b/railties/lib/rails/generators/rails/app/templates/public/404.html @@ -2,17 +2,48 @@ <html> <head> <title>The page you were looking for doesn't exist (404)</title> - <style> - body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; } - div.dialog { - width: 25em; - padding: 0 4em; - margin: 4em auto 0 auto; - border: 1px solid #ccc; - border-right-color: #999; - border-bottom-color: #999; - } - h1 { font-size: 100%; color: #f00; line-height: 1.5em; } + <style> + body + { + background-color: #efefef; + color: #2E2F30; + text-align: center; + font-family: arial,sans-serif; + } + div.dialog + { + width: 25em; + margin: 4em auto 0 auto; + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #BBB; + border-top: #B00100 solid 4px; + border-top-left-radius: 9px; + border-top-right-radius: 9px; + background-color: white; + padding: 7px 4em 0 4em; + } + h1{ + font-size: 100%; + color: #730E15; + line-height: 1.5em; + } + body>p + { + width: 33em; + margin: 0 auto 1em; + padding: 1em 0; + background-color: #f7f7f7; + border: 1px solid #CCC; + border-right-color: #999; + border-bottom-color: #999; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-top-color: #DADADA; + color: #666; + box-shadow:0 3px 8px rgba(50,50,50,0.17); + } </style> </head> diff --git a/railties/lib/rails/generators/rails/app/templates/public/422.html b/railties/lib/rails/generators/rails/app/templates/public/422.html index 3f1bfb3417..ed58409a27 100644 --- a/railties/lib/rails/generators/rails/app/templates/public/422.html +++ b/railties/lib/rails/generators/rails/app/templates/public/422.html @@ -2,17 +2,48 @@ <html> <head> <title>The change you wanted was rejected (422)</title> - <style> - body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; } - div.dialog { - width: 25em; - padding: 0 4em; - margin: 4em auto 0 auto; - border: 1px solid #ccc; - border-right-color: #999; - border-bottom-color: #999; - } - h1 { font-size: 100%; color: #f00; line-height: 1.5em; } + <style> + body + { + background-color: #efefef; + color: #2E2F30; + text-align: center; + font-family: arial,sans-serif; + } + div.dialog + { + width: 25em; + margin: 4em auto 0 auto; + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #BBB; + border-top: #B00100 solid 4px; + border-top-left-radius: 9px; + border-top-right-radius: 9px; + background-color: white; + padding: 7px 4em 0 4em; + } + h1{ + font-size: 100%; + color: #730E15; + line-height: 1.5em; + } + body>p + { + width: 33em; + margin: 0 auto 1em; + padding: 1em 0; + background-color: #f7f7f7; + border: 1px solid #CCC; + border-right-color: #999; + border-bottom-color: #999; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-top-color: #DADADA; + color: #666; + box-shadow:0 3px 8px rgba(50,50,50,0.17); + } </style> </head> diff --git a/railties/lib/rails/generators/rails/app/templates/public/500.html b/railties/lib/rails/generators/rails/app/templates/public/500.html index 012977d3d2..9417de0cc0 100644 --- a/railties/lib/rails/generators/rails/app/templates/public/500.html +++ b/railties/lib/rails/generators/rails/app/templates/public/500.html @@ -2,17 +2,48 @@ <html> <head> <title>We're sorry, but something went wrong (500)</title> - <style> - body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; } - div.dialog { - width: 25em; - padding: 0 4em; - margin: 4em auto 0 auto; - border: 1px solid #ccc; - border-right-color: #999; - border-bottom-color: #999; - } - h1 { font-size: 100%; color: #f00; line-height: 1.5em; } + <style> + body + { + background-color: #efefef; + color: #2E2F30; + text-align: center; + font-family: arial,sans-serif; + } + div.dialog + { + width: 25em; + margin: 4em auto 0 auto; + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #BBB; + border-top: #B00100 solid 4px; + border-top-left-radius: 9px; + border-top-right-radius: 9px; + background-color: white; + padding: 7px 4em 0 4em; + } + h1{ + font-size: 100%; + color: #730E15; + line-height: 1.5em; + } + body>p + { + width: 33em; + margin: 0 auto 1em; + padding: 1em 0; + background-color: #f7f7f7; + border: 1px solid #CCC; + border-right-color: #999; + border-bottom-color: #999; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-top-color: #DADADA; + color: #666; + box-shadow:0 3px 8px rgba(50,50,50,0.17); + } </style> </head> 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 af00748037..5fe01d0961 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 @@ -94,6 +94,11 @@ task default: :test end end + def test_dummy_assets + template "rails/javascripts.js", "#{dummy_path}/app/assets/javascripts/application.js", force: true + template "rails/stylesheets.css", "#{dummy_path}/app/assets/stylesheets/application.css", force: true + end + def test_dummy_clean inside dummy_path do remove_file ".gitignore" @@ -112,7 +117,7 @@ task default: :test def stylesheets if mountable? - copy_file "#{app_templates_dir}/app/assets/stylesheets/application.css", + copy_file "rails/stylesheets.css", "app/assets/stylesheets/#{name}/application.css" elsif full? empty_directory_with_keep_file "app/assets/stylesheets/#{name}" @@ -123,8 +128,8 @@ task default: :test return if options.skip_javascript? if mountable? - template "#{app_templates_dir}/app/assets/javascripts/application.js.tt", - "app/assets/javascripts/#{name}/application.js" + template "rails/javascripts.js", + "app/assets/javascripts/#{name}/application.js" elsif full? empty_directory_with_keep_file "app/assets/javascripts/#{name}" end @@ -263,6 +268,7 @@ task default: :test build(:generate_test_dummy) store_application_definition! build(:test_dummy_config) + build(:test_dummy_assets) build(:test_dummy_clean) # ensure that bin/rails has proper dummy_path build(:bin, true) diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec index e956d13d8a..f7c12e67dd 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec +++ b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec @@ -19,9 +19,6 @@ Gem::Specification.new do |s| <% end -%> <%= '# ' if options.dev? || options.edge? -%>s.add_dependency "rails", "~> <%= Rails::VERSION::STRING %>" -<% if engine? && !options[:skip_javascript] -%> - # s.add_dependency "<%= "#{options[:javascript]}-rails" %>" -<% end -%> <% unless options[:skip_active_record] -%> s.add_development_dependency "<%= gem_for_database %>" diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile index a8b5bfaf3f..3f2b78f2fd 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile +++ b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile @@ -2,9 +2,6 @@ source "https://rubygems.org" <% if options[:skip_gemspec] -%> <%= '# ' if options.dev? || options.edge? -%>gem "rails", "~> <%= Rails::VERSION::STRING %>" -<% if engine? && !options[:skip_javascript] -%> -# gem "<%= "#{options[:javascript]}-rails" %>" -<% end -%> <% else -%> # Declare your gem's dependencies in <%= name %>.gemspec. # Bundler will treat runtime dependencies like base dependencies, and @@ -12,11 +9,6 @@ source "https://rubygems.org" gemspec <% end -%> -<% unless options[:javascript] == 'jquery' -%> -# jquery-rails is used by the dummy application -gem "jquery-rails" - -<% end -%> <% if options[:skip_gemspec] -%> group :development do gem "<%= gem_for_database %>" diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/rails/javascripts.js b/railties/lib/rails/generators/rails/plugin_new/templates/rails/javascripts.js new file mode 100644 index 0000000000..084d5d1c49 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/rails/javascripts.js @@ -0,0 +1,13 @@ +// This is a manifest file that'll be compiled into application.js, which will include all the files +// listed below. +// +// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, +// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. +// +// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the +// compiled file. +// +// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD +// GO AFTER THE REQUIRES BELOW. +// +//= require_tree . diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/rails/stylesheets.css b/railties/lib/rails/generators/rails/plugin_new/templates/rails/stylesheets.css new file mode 100644 index 0000000000..3192ec897b --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/rails/stylesheets.css @@ -0,0 +1,13 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, + * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the top of the + * compiled file, but it's generally better to create a new file per style scope. + * + *= require_self + *= require_tree . + */ diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index 73ef2046c0..d8076c7151 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -96,6 +96,12 @@ module ApplicationTests assert !middleware.include?("Rack::Lock") end + test "removes lock if allow concurrency is set" do + add_to_config "config.allow_concurrency = true" + boot! + assert !middleware.include?("Rack::Lock") + end + test "removes static asset server if serve_static_assets is disabled" do add_to_config "config.serve_static_assets = false" boot! diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb index 820b838702..9e711f25bd 100644 --- a/railties/test/application/rake/dbs_test.rb +++ b/railties/test/application/rake/dbs_test.rb @@ -166,19 +166,6 @@ module ApplicationTests require "#{app_path}/config/environment" db_test_load_structure end - - test 'db:test:load_structure with database_url' do - old_rails_env = ENV["RAILS_ENV"] - ENV["RAILS_ENV"] = 'test' - - begin - require "#{app_path}/config/environment" - set_database_url - db_test_load_structure - ensure - ENV["RAILS_ENV"] = old_rails_env - end - end end end end diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb index 62d9d1f06a..d876597944 100644 --- a/railties/test/generators/migration_generator_test.rb +++ b/railties/test/generators/migration_generator_test.rb @@ -172,8 +172,19 @@ class MigrationGeneratorTest < Rails::Generators::TestCase end end - def test_should_create_empty_migrations_if_name_not_start_with_add_or_remove - migration = "create_books" + def test_create_table_migration + run_generator ["create_books", "title:string", "content:text"] + assert_migration "db/migrate/create_books.rb" do |content| + assert_method :change, content do |change| + assert_match(/create_table :books/, change) + assert_match(/ t\.string :title/, change) + assert_match(/ t\.text :content/, change) + end + end + end + + def test_should_create_empty_migrations_if_name_not_start_with_add_or_remove_or_create + migration = "delete_books" run_generator [migration, "title:string", "content:text"] assert_migration "db/migrate/#{migration}.rb" do |content| @@ -182,7 +193,7 @@ class MigrationGeneratorTest < Rails::Generators::TestCase end end end - + def test_properly_identifies_usage_file assert generator_class.send(:usage_path) end diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb index 904c1db57e..34441ef679 100644 --- a/railties/test/generators/plugin_new_generator_test.rb +++ b/railties/test/generators/plugin_new_generator_test.rb @@ -63,13 +63,24 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase assert_no_directory "test/integration/" assert_no_file "test" - assert_no_match(/APP_RAKEFILE/, File.read(File.join(destination_root, "Rakefile"))) + assert_file "Rakefile" do |contents| + assert_no_match(/APP_RAKEFILE/, contents) + end end def test_generating_adds_dummy_app_rake_tasks_without_unit_test_files run_generator [destination_root, "-T", "--mountable", '--dummy-path', 'my_dummy_app'] + assert_file "Rakefile", /APP_RAKEFILE/ + end + + def test_generating_adds_dummy_app_without_javascript_and_assets_deps + run_generator [destination_root] + + assert_file "test/dummy/app/assets/stylesheets/application.css" - assert_match(/APP_RAKEFILE/, File.read(File.join(destination_root, "Rakefile"))) + assert_file "test/dummy/app/assets/javascripts/application.js" do |contents| + assert_no_match(/jquery/, contents) + end end def test_ensure_that_plugin_options_are_not_passed_to_app_generator @@ -112,7 +123,9 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase def test_ensure_that_skip_active_record_option_is_passed_to_app_generator run_generator [destination_root, "--skip_active_record"] assert_no_file "test/dummy/config/database.yml" - assert_no_match(/ActiveRecord/, File.read(File.join(destination_root, "test/test_helper.rb"))) + assert_file "test/test_helper.rb" do |contents| + assert_no_match /ActiveRecord/, contents + end end def test_ensure_that_database_option_is_passed_to_app_generator @@ -134,8 +147,6 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase def test_skipping_javascripts_without_mountable_option run_generator assert_no_file "app/assets/javascripts/bukkits/application.js" - assert_no_file "vendor/assets/javascripts/jquery.js" - assert_no_file "vendor/assets/javascripts/jquery_ujs.js" end def test_javascripts_generation @@ -143,33 +154,9 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase assert_file "app/assets/javascripts/bukkits/application.js" end - def test_jquery_is_the_default_javascript_library - run_generator [destination_root, "--mountable"] - assert_file "app/assets/javascripts/bukkits/application.js" do |contents| - assert_match %r{^//= require jquery}, contents - assert_match %r{^//= require jquery_ujs}, contents - end - assert_file 'bukkits.gemspec' do |contents| - assert_match(/jquery-rails/, contents) - end - end - - def test_other_javascript_libraries - run_generator [destination_root, "--mountable", '-j', 'prototype'] - assert_file "app/assets/javascripts/bukkits/application.js" do |contents| - assert_match %r{^//= require prototype}, contents - assert_match %r{^//= require prototype_ujs}, contents - end - assert_file 'bukkits.gemspec' do |contents| - assert_match(/prototype-rails/, contents) - end - end - def test_skip_javascripts run_generator [destination_root, "--skip-javascript", "--mountable"] assert_no_file "app/assets/javascripts/bukkits/application.js" - assert_no_file "vendor/assets/javascripts/jquery.js" - assert_no_file "vendor/assets/javascripts/jquery_ujs.js" end def test_template_from_dir_pwd @@ -318,16 +305,6 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase assert_no_match('gemspec', contents) assert_match(/gem "rails", "~> #{Rails::VERSION::STRING}"/, contents) assert_match(/group :development do\n gem "sqlite3"\nend/, contents) - assert_match(/# gem "jquery-rails"/, contents) - assert_no_match(/# jquery-rails is used by the dummy application\ngem "jquery-rails"/, contents) - end - end - - def test_skipping_gemspec_in_full_mode_with_javascript_option - run_generator [destination_root, "--skip-gemspec", "--full", "--javascript=prototype"] - assert_file "Gemfile" do |contents| - assert_match(/# gem "prototype-rails"/, contents) - assert_match(/# jquery-rails is used by the dummy application\ngem "jquery-rails"/, contents) end end |