diff options
67 files changed, 410 insertions, 279 deletions
diff --git a/.gitignore b/.gitignore index 7236e395a2..bc96284375 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # Don't put *.swp, *.bak, etc here; those belong in a global ~/.gitignore. -# Check out http://help.github.com/ignore-files/ for how to set that up. +# Check out https://help.github.com/articles/ignoring-files for how to set that up. debug.log .Gemfile @@ -36,7 +36,7 @@ task :smoke do end desc "Install gems for all projects." -task :install => :gem do +task :install => :build do version = File.read("RAILS_VERSION").strip (PROJECTS - ["railties"]).each do |project| puts "INSTALLING #{project}" @@ -78,7 +78,7 @@ task :update_versions do end # -# We have a webhook configured in Github that gets invoked after pushes. +# We have a webhook configured in GitHub that gets invoked after pushes. # This hook triggers the following tasks: # # * updates the local checkout diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index ffafa7412d..6f5027dc23 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,5 @@ -* No changes. +* Fix an issue where partials with a number in the filename weren't being digested for cache dependencies. + + *Bryan Ricker* Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/actionpack/CHANGELOG.md) for previous changes. diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb index 505f3b4e61..754249cbc8 100644 --- a/actionpack/lib/action_controller/metal/url_for.rb +++ b/actionpack/lib/action_controller/metal/url_for.rb @@ -32,7 +32,8 @@ module ActionController if (same_origin = _routes.equal?(env["action_dispatch.routes"])) || (script_name = env["ROUTES_#{_routes.object_id}_SCRIPT_NAME"]) || - (original_script_name = env['SCRIPT_NAME']) + (original_script_name = env['ORIGINAL_SCRIPT_NAME']) + @_url_options.dup.tap do |options| if original_script_name options[:original_script_name] = original_script_name diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 41a286f0ed..e5f1ad63c2 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -558,7 +558,7 @@ module ActionController parameters ||= {} controller_class_name = @controller.class.anonymous? ? "anonymous" : - @controller.class.name.underscore.sub(/_controller$/, '') + @controller.class.controller_path @request.assign_parameters(@routes, controller_class_name, action.to_s, parameters) diff --git a/actionpack/lib/action_view/dependency_tracker.rb b/actionpack/lib/action_view/dependency_tracker.rb index a2a555dfcb..45d17be605 100644 --- a/actionpack/lib/action_view/dependency_tracker.rb +++ b/actionpack/lib/action_view/dependency_tracker.rb @@ -39,7 +39,7 @@ module ActionView render\s* # render, followed by optional whitespace \(? # start an optional parenthesis for the render call (partial:|:partial\s+=>)?\s* # naming the partial, used with collection -- 1st capture - ([@a-z"'][@a-z_\/\."']+) # the template name itself -- 2nd capture + ([@a-z"'][@\w\/\."']+) # the template name itself -- 2nd capture /x def self.call(name, template) diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index 693b6bdfcc..3a6f449eb8 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -155,7 +155,7 @@ module ActionView # ==== Examples # # favicon_link_tag '/myicon.ico' - # # => <link href="/assets/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" /> + # # => <link href="/assets/myicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" /> # # Mobile Safari looks for a different <link> tag, pointing to an image that # will be used if you add the page to the home screen of an iPod Touch, iPhone, or iPad. diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index 719c9c09b5..ad26505086 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -515,7 +515,6 @@ module ActionView divider = options[:divider] else prompt = options - options = {} message = "Passing the prompt to grouped_options_for_select as an argument is deprecated. " \ "Please use an options hash like `{ prompt: #{prompt.inspect} }`." ActiveSupport::Deprecation.warn message diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb index d61cc0f304..f9d5b97fe3 100644 --- a/actionpack/lib/action_view/lookup_context.rb +++ b/actionpack/lib/action_view/lookup_context.rb @@ -1,5 +1,6 @@ require 'thread_safe' require 'active_support/core_ext/module/remove_method' +require 'active_support/core_ext/module/attribute_accessors' module ActionView # = Action View Lookup Context diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index 946db1df79..ebbc1c79d6 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -138,7 +138,7 @@ module ActionView # we use a bang in this instrumentation because you don't want to # consume this in production. This is only slow if it's being listened to. def render(view, locals, buffer=nil, &block) - ActiveSupport::Notifications.instrument("!render_template.action_view", virtual_path: @virtual_path, identifier: @identifier) do + instrument("!render_template") do compile!(view) view.send(method_name, locals, buffer, &block) end @@ -245,7 +245,9 @@ module ActionView mod = view.singleton_class end - compile(view, mod) + instrument("!compile_template") do + compile(view, mod) + end # Just discard the source if we have a virtual path. This # means we can get the template back. @@ -335,5 +337,10 @@ module ActionView def identifier_method_name #:nodoc: inspect.gsub(/[^a-z_]/, '_') end + + def instrument(action, &block) + payload = { virtual_path: @virtual_path, identifier: @identifier } + ActiveSupport::Notifications.instrument("#{action}.action_view", payload, &block) + end end end diff --git a/actionpack/test/fixtures/digestor/messages/show.html.erb b/actionpack/test/fixtures/digestor/messages/show.html.erb index 51b3b61e8e..130e67109e 100644 --- a/actionpack/test/fixtures/digestor/messages/show.html.erb +++ b/actionpack/test/fixtures/digestor/messages/show.html.erb @@ -7,7 +7,8 @@ <%= render @message.history.events %> <%# render "something_missing" %> +<%# render "something_missing_1" %> <% # Template Dependency: messages/form -%>
\ No newline at end of file +%> diff --git a/actionpack/test/template/digestor_test.rb b/actionpack/test/template/digestor_test.rb index e29cecabc0..06735c30d3 100644 --- a/actionpack/test/template/digestor_test.rb +++ b/actionpack/test/template/digestor_test.rb @@ -83,6 +83,12 @@ class TemplateDigestorTest < ActionView::TestCase end end + def test_logging_of_missing_template_ending_with_number + assert_logged "Couldn't find template for digesting: messages/something_missing_1.html" do + digest("messages/show") + end + end + def test_nested_template_directory assert_digest_difference("messages/show") do change_template("messages/actions/_move") diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index c5b94763ff..341e85c59e 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,52 @@ +* Confirm a record has not already been destroyed before decrementing counter cache. + + *Ben Tucker* + +* Fixed a bug in `ActiveRecord#sanitize_sql_hash_for_conditions` in which + `self.class` is an argument to `PredicateBuilder#build_from_hash` + causing `PredicateBuilder` to call non-existant method + `Class#reflect_on_association`. + + *Zach Ohlgren* + +* While removing index if column option is missing then raise IrreversibleMigration exception. + + Following code should raise `IrreversibleMigration`. But the code was + failing since options is an array and not a hash. + + def change + change_table :users do |t| + t.remove_index [:name, :email] + end + end + + Fix was to check if the options is a Hash before operating on it. + + Fixes #10419. + + *Neeraj Singh* + +* Do not overwrite manually built records during one-to-one nested attribute assignment + + For one-to-one nested associations, if you build the new (in-memory) + child object yourself before assignment, then the NestedAttributes + module will not overwrite it, e.g.: + + class Member < ActiveRecord::Base + has_one :avatar + accepts_nested_attributes_for :avatar + + def avatar + super || build_avatar(width: 200) + end + end + + member = Member.new + member.avatar_attributes = {icon: 'sad'} + member.avatar.width # => 200 + + *Olek Janiszewski* + * fixes bug introduced by #3329. Now, when autosaving associations, deletions happen before inserts and saves. This prevents a 'duplicate unique value' database error that would occur if a record being created had @@ -42,9 +91,9 @@ * Abort a rake task when missing db/structure.sql like `db:schema:load` task. *kennyj* - + * rake:db:test:prepare falls back to original environment after execution. - + *Slava Markevich* Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/activerecord/CHANGELOG.md) for previous changes. diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index db0553ea76..710babe024 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -164,6 +164,13 @@ module ActiveRecord @reflection = @owner.class.reflect_on_association(reflection_name) end + def initialize_attributes(record) #:nodoc: + skip_assign = [reflection.foreign_key, reflection.type].compact + attributes = create_scope.except(*(record.changed - skip_assign)) + record.assign_attributes(attributes) + set_inverse_instance(record) + end + private def find_target? @@ -233,10 +240,7 @@ module ActiveRecord def build_record(attributes) reflection.build_association(attributes) do |record| - skip_assign = [reflection.foreign_key, reflection.type].compact - attributes = create_scope.except(*(record.changed - skip_assign)) - record.assign_attributes(attributes) - set_inverse_instance(record) + initialize_attributes(record) end end end diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb index 543a0247d1..63e9526436 100644 --- a/activerecord/lib/active_record/associations/builder/belongs_to.rb +++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb @@ -34,7 +34,9 @@ module ActiveRecord::Associations::Builder def belongs_to_counter_cache_before_destroy_for_#{name} unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == #{foreign_key.to_sym.inspect} record = #{name} - record.class.decrement_counter(:#{cache_column}, record.id) unless record.nil? + if record && !self.destroyed? + record.class.decrement_counter(:#{cache_column}, record.id) + end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index d9b807bba4..98916b06a5 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -321,6 +321,7 @@ module ActiveRecord result = query(<<-end_sql, 'SCHEMA')[0] SELECT attr.attname, CASE + WHEN pg_get_expr(def.adbin, def.adrelid) !~* 'nextval' THEN NULL WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1) @@ -332,7 +333,7 @@ module ActiveRecord JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1]) WHERE t.oid = '#{quote_table_name(table)}'::regclass AND cons.contype = 'p' - AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval' + AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate' end_sql end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index df729259e9..88b09e7999 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -768,7 +768,7 @@ module ActiveRecord end def exec_cache(sql, binds) - stmt_key = prepare_statement sql + stmt_key = prepare_statement(sql) # Clear the queue @connection.get_last_result diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb index 79c55045ba..9782a48055 100644 --- a/activerecord/lib/active_record/migration/command_recorder.rb +++ b/activerecord/lib/active_record/migration/command_recorder.rb @@ -144,7 +144,10 @@ module ActiveRecord def invert_remove_index(args) table, options = *args - raise ActiveRecord::IrreversibleMigration, "remove_index is only reversible if given a :column option." unless options && options[:column] + + unless options && options.is_a?(Hash) && options[:column] + raise ActiveRecord::IrreversibleMigration, "remove_index is only reversible if given a :column option." + end options = options.dup [:add_index, [table, options.delete(:column), options]] diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index d607f49e2b..021832de46 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -229,6 +229,23 @@ module ActiveRecord # belongs_to :member, inverse_of: :posts # validates_presence_of :member # end + # + # For one-to-one nested associations, if you build the new (in-memory) + # child object yourself before assignment, then this module will not + # overwrite it, e.g.: + # + # class Member < ActiveRecord::Base + # has_one :avatar + # accepts_nested_attributes_for :avatar + # + # def avatar + # super || build_avatar(width: 200) + # end + # end + # + # member = Member.new + # member.avatar_attributes = {icon: 'sad'} + # member.avatar.width # => 200 module ClassMethods REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == '_destroy' || value.blank? } } @@ -356,20 +373,28 @@ module ActiveRecord def assign_nested_attributes_for_one_to_one_association(association_name, attributes) options = self.nested_attributes_options[association_name] attributes = attributes.with_indifferent_access + existing_record = send(association_name) - if (options[:update_only] || !attributes['id'].blank?) && (record = send(association_name)) && - (options[:update_only] || record.id.to_s == attributes['id'].to_s) - assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes) + if (options[:update_only] || !attributes['id'].blank?) && existing_record && + (options[:update_only] || existing_record.id.to_s == attributes['id'].to_s) + assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes) elsif attributes['id'].present? raise_nested_attributes_record_not_found!(association_name, attributes['id']) elsif !reject_new_record?(association_name, attributes) - method = "build_#{association_name}" - if respond_to?(method) - send(method, attributes.except(*UNASSIGNABLE_KEYS)) + assignable_attributes = attributes.except(*UNASSIGNABLE_KEYS) + + if existing_record && existing_record.new_record? + existing_record.assign_attributes(assignable_attributes) + association(association_name).initialize_attributes(existing_record) else - raise ArgumentError, "Cannot build association `#{association_name}'. Are you trying to build a polymorphic one-to-one association?" + method = "build_#{association_name}" + if respond_to?(method) + send(method, assignable_attributes) + else + raise ArgumentError, "Cannot build association `#{association_name}'. Are you trying to build a polymorphic one-to-one association?" + end end end end diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index 3c5b871e99..0ed97b66d6 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -89,7 +89,7 @@ module ActiveRecord attrs = expand_hash_conditions_for_aggregates(attrs) table = Arel::Table.new(table_name, arel_engine).alias(default_table_name) - PredicateBuilder.build_from_hash(self.class, attrs, table).map { |b| + PredicateBuilder.build_from_hash(self, attrs, table).map { |b| connection.visitor.accept b }.join(' AND ') end diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index 10c6d272cd..1181cc739e 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -106,9 +106,12 @@ HEADER end tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}" - if columns.detect { |c| c.name == pk } + pkcol = columns.detect { |c| c.name == pk } + if pkcol if pk != 'id' tbl.print %Q(, primary_key: "#{pk}") + elsif pkcol.sql_type == 'uuid' + tbl.print ", id: :uuid" end else tbl.print ", id: false" diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb index 8c17372286..b5d7ea603e 100644 --- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb +++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb @@ -281,7 +281,6 @@ _SQL tz = ::ActiveRecord::Base.default_timezone assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)..Time.send(tz, 2011, 1, 1, 14, 30, 0), @first_range.ts_range assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 1, 1, 14, 30, 0), @second_range.ts_range - assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)...Float::INFINITY, @third_range.ts_range assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.ts_range) assert_equal nil, @empty_range.ts_range end @@ -290,7 +289,6 @@ _SQL skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? assert_equal Time.parse('2010-01-01 09:30:00 UTC')..Time.parse('2011-01-01 17:30:00 UTC'), @first_range.tstz_range assert_equal Time.parse('2010-01-01 09:30:00 UTC')...Time.parse('2011-01-01 17:30:00 UTC'), @second_range.tstz_range - assert_equal Time.parse('2010-01-01 09:30:00 UTC')...Float::INFINITY, @third_range.tstz_range assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.tstz_range) assert_equal nil, @empty_range.tstz_range end diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb index 04c9065393..b573d48807 100644 --- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb +++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb @@ -50,6 +50,18 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase u.reload assert_not_nil u.other_uuid end + + def test_pk_and_sequence_for_uuid_primary_key + pk, seq = @connection.pk_and_sequence_for('pg_uuids') + assert_equal 'id', pk + assert_equal nil, seq + end + + def test_schema_dumper_for_uuid_primary_key + schema = StringIO.new + ActiveRecord::SchemaDumper.dump(@connection, schema) + assert_match(/\bcreate_table "pg_uuids", id: :uuid\b/, schema.string) + end end class PostgresqlUUIDTestNilDefault < ActiveRecord::TestCase diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index f5316952b8..87af24cbe6 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -414,6 +414,26 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_equal 15, topic.replies.size end + def test_counter_cache_double_destroy + topic = Topic.create :title => "Zoom-zoom-zoom" + + 5.times do + topic.replies.create(:title => "re: zoom", :content => "speedy quick!") + end + + assert_equal 5, topic.reload[:replies_count] + assert_equal 5, topic.replies.size + + reply = topic.replies.first + + reply.destroy + assert_equal 4, topic.reload[:replies_count] + + reply.destroy + assert_equal 4, topic.reload[:replies_count] + assert_equal 4, topic.replies.size + end + def test_custom_counter_cache reply = Reply.create(:title => "re: zoom", :content => "speedy quick!") assert_equal 0, reply[:replies_count] diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb index be59ffc4ab..428145d00b 100644 --- a/activerecord/test/cases/invertible_migration_test.rb +++ b/activerecord/test/cases/invertible_migration_test.rb @@ -58,6 +58,24 @@ module ActiveRecord end end + class RemoveIndexMigration1 < SilentMigration + def self.up + create_table("horses") do |t| + t.column :name, :string + t.column :color, :string + t.index [:name, :color] + end + end + end + + class RemoveIndexMigration2 < SilentMigration + def change + change_table("horses") do |t| + t.remove_index [:name, :color] + end + end + end + class LegacyMigration < ActiveRecord::Migration def self.up create_table("horses") do |t| @@ -104,6 +122,16 @@ module ActiveRecord end end + def test_exception_on_removing_index_without_column_option + RemoveIndexMigration1.new.migrate(:up) + migration = RemoveIndexMigration2.new + migration.migrate(:up) + + assert_raises(IrreversibleMigration) do + migration.migrate(:down) + end + end + def test_migrate_up migration = InvertibleMigration.new migration.migrate(:up) diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index b6e140b912..165b7454b7 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -131,6 +131,20 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase assert_equal 's1', ship.reload.name end + def test_reuse_already_built_new_record + pirate = Pirate.new + ship_built_first = pirate.build_ship + pirate.ship_attributes = { name: 'Ship 1' } + assert_equal ship_built_first.object_id, pirate.ship.object_id + end + + def test_do_not_allow_assigning_foreign_key_when_reusing_existing_new_record + pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?") + pirate.build_ship + pirate.ship_attributes = { name: 'Ship 1', pirate_id: pirate.id + 1 } + assert_equal pirate.id, pirate.ship.pirate_id + end + def test_reject_if_with_a_proc_which_returns_true_always_for_has_many Man.accepts_nested_attributes_for :interests, :reject_if => proc {|attributes| true } man = Man.create(name: "John") diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb index 817897ceac..f061c28e88 100644 --- a/activerecord/test/cases/sanitize_test.rb +++ b/activerecord/test/cases/sanitize_test.rb @@ -5,6 +5,16 @@ class SanitizeTest < ActiveRecord::TestCase def setup end + def test_sanitize_sql_hash_handles_associations + if current_adapter?(:MysqlAdapter, :Mysql2Adapter) + expected_value = "`adorable_animals`.`name` = 'Bambi'" + else + expected_value = "\"adorable_animals\".\"name\" = 'Bambi'" + end + + assert_equal expected_value, Binary.send(:sanitize_sql_hash, {adorable_animals: {name: 'Bambi'}}) + end + def test_sanitize_sql_array_handles_string_interpolation quoted_bambi = ActiveRecord::Base.connection.quote_string("Bambi") assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=%s", "Bambi"]) diff --git a/activesupport/lib/active_support/core_ext/date.rb b/activesupport/lib/active_support/core_ext/date.rb index 5f13f5f70f..465fedda80 100644 --- a/activesupport/lib/active_support/core_ext/date.rb +++ b/activesupport/lib/active_support/core_ext/date.rb @@ -2,5 +2,4 @@ require 'active_support/core_ext/date/acts_like' require 'active_support/core_ext/date/calculations' require 'active_support/core_ext/date/conversions' require 'active_support/core_ext/date/zones' -require 'active_support/core_ext/date/infinite_comparable' diff --git a/activesupport/lib/active_support/core_ext/date/infinite_comparable.rb b/activesupport/lib/active_support/core_ext/date/infinite_comparable.rb deleted file mode 100644 index ca5d793942..0000000000 --- a/activesupport/lib/active_support/core_ext/date/infinite_comparable.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'active_support/core_ext/infinite_comparable' - -class Date - include InfiniteComparable -end diff --git a/activesupport/lib/active_support/core_ext/date_time.rb b/activesupport/lib/active_support/core_ext/date_time.rb index 024af91738..e8a27b9f38 100644 --- a/activesupport/lib/active_support/core_ext/date_time.rb +++ b/activesupport/lib/active_support/core_ext/date_time.rb @@ -2,4 +2,3 @@ require 'active_support/core_ext/date_time/acts_like' require 'active_support/core_ext/date_time/calculations' require 'active_support/core_ext/date_time/conversions' require 'active_support/core_ext/date_time/zones' -require 'active_support/core_ext/date_time/infinite_comparable' diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb index f347f09293..937567440b 100644 --- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb @@ -155,4 +155,11 @@ class DateTime def utc_offset (offset * 86400).to_i end + + # Layers additional behavior on DateTime#<=> so that Time and + # ActiveSupport::TimeWithZone instances can be compared with a DateTime. + def <=>(other) + super other.to_datetime + end + end diff --git a/activesupport/lib/active_support/core_ext/date_time/infinite_comparable.rb b/activesupport/lib/active_support/core_ext/date_time/infinite_comparable.rb deleted file mode 100644 index 8a282b19f2..0000000000 --- a/activesupport/lib/active_support/core_ext/date_time/infinite_comparable.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'active_support/core_ext/infinite_comparable' - -class DateTime - include InfiniteComparable -end diff --git a/activesupport/lib/active_support/core_ext/infinite_comparable.rb b/activesupport/lib/active_support/core_ext/infinite_comparable.rb deleted file mode 100644 index b78b2deaad..0000000000 --- a/activesupport/lib/active_support/core_ext/infinite_comparable.rb +++ /dev/null @@ -1,35 +0,0 @@ -require 'active_support/concern' -require 'active_support/core_ext/module/aliasing' -require 'active_support/core_ext/object/try' - -module InfiniteComparable - extend ActiveSupport::Concern - - included do - alias_method_chain :<=>, :infinity - end - - define_method :'<=>_with_infinity' do |other| - if other.class == self.class - public_send :'<=>_without_infinity', other - else - infinite = try(:infinite?) - other_infinite = other.try(:infinite?) - - # inf <=> inf - if infinite && other_infinite - infinite <=> other_infinite - # not_inf <=> inf - elsif other_infinite - -other_infinite - # inf <=> not_inf - elsif infinite - infinite - else - conversion = "to_#{self.class.name.downcase}" - other = other.public_send(conversion) if other.respond_to?(conversion) - public_send :'<=>_without_infinity', other - end - end - end -end diff --git a/activesupport/lib/active_support/core_ext/numeric.rb b/activesupport/lib/active_support/core_ext/numeric.rb index d5cfc2ece4..a6bc0624be 100644 --- a/activesupport/lib/active_support/core_ext/numeric.rb +++ b/activesupport/lib/active_support/core_ext/numeric.rb @@ -1,4 +1,3 @@ require 'active_support/core_ext/numeric/bytes' require 'active_support/core_ext/numeric/time' require 'active_support/core_ext/numeric/conversions' -require 'active_support/core_ext/numeric/infinite_comparable' diff --git a/activesupport/lib/active_support/core_ext/numeric/infinite_comparable.rb b/activesupport/lib/active_support/core_ext/numeric/infinite_comparable.rb deleted file mode 100644 index b5f1b0487b..0000000000 --- a/activesupport/lib/active_support/core_ext/numeric/infinite_comparable.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'active_support/core_ext/infinite_comparable' - -class Float - include InfiniteComparable -end - -class BigDecimal - include InfiniteComparable -end diff --git a/activesupport/lib/active_support/core_ext/object/to_param.rb b/activesupport/lib/active_support/core_ext/object/to_param.rb index 0d5f3501e5..3b137ce6ae 100644 --- a/activesupport/lib/active_support/core_ext/object/to_param.rb +++ b/activesupport/lib/active_support/core_ext/object/to_param.rb @@ -53,6 +53,6 @@ class Hash def to_param(namespace = nil) collect do |key, value| value.to_query(namespace ? "#{namespace}[#{key}]" : key) - end.sort * '&' + end.sort! * '&' end end diff --git a/activesupport/lib/active_support/core_ext/time.rb b/activesupport/lib/active_support/core_ext/time.rb index af6b589b71..32cffe237d 100644 --- a/activesupport/lib/active_support/core_ext/time.rb +++ b/activesupport/lib/active_support/core_ext/time.rb @@ -3,4 +3,3 @@ require 'active_support/core_ext/time/calculations' require 'active_support/core_ext/time/conversions' require 'active_support/core_ext/time/marshal' require 'active_support/core_ext/time/zones' -require 'active_support/core_ext/time/infinite_comparable' diff --git a/activesupport/lib/active_support/core_ext/time/infinite_comparable.rb b/activesupport/lib/active_support/core_ext/time/infinite_comparable.rb deleted file mode 100644 index 63795885f5..0000000000 --- a/activesupport/lib/active_support/core_ext/time/infinite_comparable.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'active_support/core_ext/infinite_comparable' - -class Time - include InfiniteComparable -end diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb index f3fa96ec6f..b1adf6d396 100644 --- a/activesupport/test/core_ext/date_ext_test.rb +++ b/activesupport/test/core_ext/date_ext_test.rb @@ -357,11 +357,6 @@ class DateExtBehaviorTest < ActiveSupport::TestCase Date.today.freeze.freeze end end - - def test_compare_with_infinity - assert_equal(-1, Date.today <=> Float::INFINITY) - assert_equal(1, Date.today <=> -Float::INFINITY) - end end class DateExtConversionsTest < ActiveSupport::TestCase diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index 7be578599b..3e76b8a9b1 100644 --- a/activesupport/test/core_ext/date_time_ext_test.rb +++ b/activesupport/test/core_ext/date_time_ext_test.rb @@ -335,10 +335,3 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') end end - -class DateTimeExtBehaviorTest < ActiveSupport::TestCase - def test_compare_with_infinity - assert_equal(-1, DateTime.now <=> Float::INFINITY) - assert_equal(1, DateTime.now <=> -Float::INFINITY) - end -end diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb index 3744d50864..f22ae3ccac 100644 --- a/activesupport/test/core_ext/numeric_ext_test.rb +++ b/activesupport/test/core_ext/numeric_ext_test.rb @@ -441,57 +441,3 @@ class NumericExtFormattingTest < ActiveSupport::TestCase assert_equal '1 Million', BigDecimal("1000010").to_s(:human) end end - -class NumericExtBehaviorTest < ActiveSupport::TestCase - def setup - @inf = BigDecimal.new('Infinity') - end - - def test_compare_infinity_with_date - assert_equal(-1, -Float::INFINITY <=> Date.today) - assert_equal(1, Float::INFINITY <=> Date.today) - assert_equal(-1, -@inf <=> Date.today) - assert_equal(1, @inf <=> Date.today) - end - - def test_compare_infinty_with_infinty - assert_equal(-1, -Float::INFINITY <=> Float::INFINITY) - assert_equal(1, Float::INFINITY <=> -Float::INFINITY) - assert_equal(0, Float::INFINITY <=> Float::INFINITY) - assert_equal(0, -Float::INFINITY <=> -Float::INFINITY) - - assert_equal(-1, -Float::INFINITY <=> BigDecimal::INFINITY) - assert_equal(1, Float::INFINITY <=> -BigDecimal::INFINITY) - assert_equal(0, Float::INFINITY <=> BigDecimal::INFINITY) - assert_equal(0, -Float::INFINITY <=> -BigDecimal::INFINITY) - - assert_equal(-1, -BigDecimal::INFINITY <=> Float::INFINITY) - assert_equal(1, BigDecimal::INFINITY <=> -Float::INFINITY) - assert_equal(0, BigDecimal::INFINITY <=> Float::INFINITY) - assert_equal(0, -BigDecimal::INFINITY <=> -Float::INFINITY) - end - - def test_compare_infinity_with_time - assert_equal(-1, -Float::INFINITY <=> Time.now) - assert_equal(1, Float::INFINITY <=> Time.now) - assert_equal(-1, -@inf <=> Time.now) - assert_equal(1, @inf <=> Time.now) - end - - def test_compare_infinity_with_datetime - assert_equal(-1, -Float::INFINITY <=> DateTime.now) - assert_equal(1, Float::INFINITY <=> DateTime.now) - assert_equal(-1, -@inf <=> DateTime.now) - assert_equal(1, @inf <=> DateTime.now) - end - - def test_compare_infinity_with_twz - time_zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - twz = ActiveSupport::TimeWithZone.new(Time.now, time_zone) - - assert_equal(-1, -Float::INFINITY <=> twz) - assert_equal(1, Float::INFINITY <=> twz) - assert_equal(-1, -@inf <=> twz) - assert_equal(1, @inf <=> twz) - end -end diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb index 6e94d5e10d..3e2355ae23 100644 --- a/activesupport/test/core_ext/range_ext_test.rb +++ b/activesupport/test/core_ext/range_ext_test.rb @@ -1,7 +1,6 @@ require 'abstract_unit' require 'active_support/time' require 'active_support/core_ext/range' -require 'active_support/core_ext/numeric' class RangeTest < ActiveSupport::TestCase def test_to_s_from_dates @@ -17,7 +16,6 @@ class RangeTest < ActiveSupport::TestCase def test_date_range assert_instance_of Range, DateTime.new..DateTime.new assert_instance_of Range, DateTime::Infinity.new..DateTime::Infinity.new - assert_instance_of Range, DateTime.new..DateTime::Infinity.new end def test_overlaps_last_inclusive @@ -92,28 +90,4 @@ class RangeTest < ActiveSupport::TestCase time_range_2 = Time.utc(2005, 12, 10, 17, 31)..Time.utc(2005, 12, 10, 18, 00) assert !time_range_1.overlaps?(time_range_2) end - - def test_infinite_bounds - time_zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - - time = Time.now - date = Date.today - datetime = DateTime.now - twz = ActiveSupport::TimeWithZone.new(time, time_zone) - - infinity1 = Float::INFINITY - infinity2 = BigDecimal.new('Infinity') - - [infinity1, infinity2].each do |infinity| - [time, date, datetime, twz].each do |bound| - [time, date, datetime, twz].each do |value| - assert Range.new(bound, infinity).include?(value + 10.years) - assert Range.new(-infinity, bound).include?(value - 10.years) - - assert !Range.new(bound, infinity).include?(value - 10.years) - assert !Range.new(-infinity, bound).include?(value + 10.years) - end - end - end - end end diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index 2864d7a57f..4e53aff00b 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -855,10 +855,3 @@ class TimeExtMarshalingTest < ActiveSupport::TestCase assert_equal Time.local(2004, 2, 29), Time.local(2004, 5, 31).last_quarter end end - -class TimeExtBehaviorTest < ActiveSupport::TestCase - def test_compare_with_infinity - assert_equal(-1, Time.now <=> Float::INFINITY) - assert_equal(1, Time.now <=> -Float::INFINITY) - end -end diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index 98a87ab9e6..3ce3297874 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -1118,13 +1118,3 @@ class TimeWithZoneMethodsForString < ActiveSupport::TestCase Time.zone = old_tz end end - -class TimeWithZoneExtBehaviorTest < ActiveSupport::TestCase - def test_compare_with_infinity - time_zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - twz = ActiveSupport::TimeWithZone.new(Time.now, time_zone) - - assert_equal(-1, twz <=> Float::INFINITY) - assert_equal(1, twz <=> -Float::INFINITY) - end -end diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb index d1454902e5..a2d1d0dc39 100644 --- a/activesupport/test/json/decoding_test.rb +++ b/activesupport/test/json/decoding_test.rb @@ -62,21 +62,25 @@ class TestJSONDecoding < ActiveSupport::TestCase backends.each do |backend| TESTS.each do |json, expected| test "json decodes #{json} with the #{backend} backend" do + prev = ActiveSupport.parse_json_times ActiveSupport.parse_json_times = true silence_warnings do ActiveSupport::JSON.with_backend backend do assert_equal expected, ActiveSupport::JSON.decode(json) end end + ActiveSupport.parse_json_times = prev end end test "json decodes time json with time parsing disabled with the #{backend} backend" do + prev = ActiveSupport.parse_json_times ActiveSupport.parse_json_times = false expected = {"a" => "2007-01-01 01:12:34 Z"} ActiveSupport::JSON.with_backend backend do assert_equal expected, ActiveSupport::JSON.decode(%({"a": "2007-01-01 01:12:34 Z"})) end + ActiveSupport.parse_json_times = prev end end diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index 12ce250eb3..8686dcf929 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -82,6 +82,8 @@ class TestJSONEncoding < ActiveSupport::TestCase constants.grep(/Tests$/).each do |class_tests| define_method("test_#{class_tests[0..-6].underscore}") do begin + prev = ActiveSupport.use_standard_json_time_format + ActiveSupport.escape_html_entities_in_json = class_tests !~ /^Standard/ ActiveSupport.use_standard_json_time_format = class_tests =~ /^Standard/ self.class.const_get(class_tests).each do |pair| @@ -89,7 +91,7 @@ class TestJSONEncoding < ActiveSupport::TestCase end ensure ActiveSupport.escape_html_entities_in_json = false - ActiveSupport.use_standard_json_time_format = false + ActiveSupport.use_standard_json_time_format = prev end end end @@ -172,16 +174,21 @@ class TestJSONEncoding < ActiveSupport::TestCase end def test_time_to_json_includes_local_offset + prev = ActiveSupport.use_standard_json_time_format ActiveSupport.use_standard_json_time_format = true with_env_tz 'US/Eastern' do assert_equal %("2005-02-01T15:15:10-05:00"), ActiveSupport::JSON.encode(Time.local(2005,2,1,15,15,10)) end ensure - ActiveSupport.use_standard_json_time_format = false + ActiveSupport.use_standard_json_time_format = prev end def test_hash_with_time_to_json + prev = ActiveSupport.use_standard_json_time_format + ActiveSupport.use_standard_json_time_format = false assert_equal '{"time":"2009/01/01 00:00:00 +0000"}', { :time => Time.utc(2009) }.to_json + ensure + ActiveSupport.use_standard_json_time_format = prev end def test_nested_hash_with_float @@ -302,12 +309,12 @@ class TestJSONEncoding < ActiveSupport::TestCase assert_equal({"name" => "David", "sub" => { "name" => "David", - "date" => "2010/01/01" }}, JSON.parse(json_custom)) + "date" => "2010-01-01" }}, JSON.parse(json_custom)) assert_equal({"name" => "David", "email" => "sample@example.com"}, JSON.parse(json_strings)) - assert_equal({"name" => "David", "date" => "2010/01/01"}, + assert_equal({"name" => "David", "date" => "2010-01-01"}, JSON.parse(json_string_and_date)) end diff --git a/activesupport/test/message_encryptor_test.rb b/activesupport/test/message_encryptor_test.rb index 06c7e8a1a7..509c453b5c 100644 --- a/activesupport/test/message_encryptor_test.rb +++ b/activesupport/test/message_encryptor_test.rb @@ -56,9 +56,14 @@ class MessageEncryptorTest < ActiveSupport::TestCase end def test_alternative_serialization_method + prev = ActiveSupport.use_standard_json_time_format + ActiveSupport.use_standard_json_time_format = true encryptor = ActiveSupport::MessageEncryptor.new(SecureRandom.hex(64), SecureRandom.hex(64), :serializer => JSONSerializer.new) message = encryptor.encrypt_and_sign({ :foo => 123, 'bar' => Time.utc(2010) }) - assert_equal encryptor.decrypt_and_verify(message), { "foo" => 123, "bar" => "2010-01-01T00:00:00Z" } + exp = { "foo" => 123, "bar" => "2010-01-01T00:00:00Z" } + assert_equal exp, encryptor.decrypt_and_verify(message) + ensure + ActiveSupport.use_standard_json_time_format = prev end private diff --git a/activesupport/test/message_verifier_test.rb b/activesupport/test/message_verifier_test.rb index 5adff41653..a8633f7299 100644 --- a/activesupport/test/message_verifier_test.rb +++ b/activesupport/test/message_verifier_test.rb @@ -45,9 +45,14 @@ class MessageVerifierTest < ActiveSupport::TestCase end def test_alternative_serialization_method + prev = ActiveSupport.use_standard_json_time_format + ActiveSupport.use_standard_json_time_format = true verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!", :serializer => JSONSerializer.new) message = verifier.generate({ :foo => 123, 'bar' => Time.utc(2010) }) - assert_equal verifier.verify(message), { "foo" => 123, "bar" => "2010-01-01T00:00:00Z" } + exp = { "foo" => 123, "bar" => "2010-01-01T00:00:00Z" } + assert_equal exp, verifier.verify(message) + ensure + ActiveSupport.use_standard_json_time_format = prev end def assert_not_verified(message) diff --git a/activesupport/test/xml_mini/nokogirisax_engine_test.rb b/activesupport/test/xml_mini/nokogirisax_engine_test.rb index d4f63f6bd0..84a5c44a87 100644 --- a/activesupport/test/xml_mini/nokogirisax_engine_test.rb +++ b/activesupport/test/xml_mini/nokogirisax_engine_test.rb @@ -56,9 +56,9 @@ class NokogiriSAXEngineTest < ActiveSupport::TestCase end end - def test_setting_nokogiri_as_backend - XmlMini.backend = 'Nokogiri' - assert_equal XmlMini_Nokogiri, XmlMini.backend + def test_setting_nokogirisax_as_backend + XmlMini.backend = 'NokogiriSAX' + assert_equal XmlMini_NokogiriSAX, XmlMini.backend end def test_blank_returns_empty_hash diff --git a/guides/code/getting_started/.gitignore b/guides/code/getting_started/.gitignore index 25a742dff0..6a502e997f 100644 --- a/guides/code/getting_started/.gitignore +++ b/guides/code/getting_started/.gitignore @@ -1,4 +1,4 @@ -# See http://help.github.com/ignore-files/ for more about ignoring files. +# See https://help.github.com/articles/ignoring-files for more about ignoring files. # # If you find yourself ignoring temporary files generated by your text editor # or operating system, you probably want to add a global ignore instead: diff --git a/guides/code/getting_started/Gemfile.lock b/guides/code/getting_started/Gemfile.lock index 823fac5ff7..888a6b30e2 100644 --- a/guides/code/getting_started/Gemfile.lock +++ b/guides/code/getting_started/Gemfile.lock @@ -92,7 +92,7 @@ GEM multi_json (~> 1.0) hike (1.2.1) i18n (0.6.1) - jbuilder (1.0.2) + jbuilder (1.3.0) activesupport (>= 3.0.0) jquery-rails (2.2.0) railties (>= 3.0, < 5.0) diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md index 50a3c6fabc..5f2d994dd2 100644 --- a/guides/source/4_0_release_notes.md +++ b/guides/source/4_0_release_notes.md @@ -59,15 +59,15 @@ Extraction of features to gems In Rails 4.0, several features have been extracted into gems. You can simply add the extracted gems to your `Gemfile` to bring the functionality back. -* Hash-based & Dynamic finder methods ([Github](https://github.com/rails/activerecord-deprecated_finders)) -* Mass assignment protection in Active Record models ([Github](https://github.com/rails/protected_attributes), [Pull Request](https://github.com/rails/rails/pull/7251)) -* ActiveRecord::SessionStore ([Github](https://github.com/rails/activerecord-session_store), [Pull Request](https://github.com/rails/rails/pull/7436)) -* Active Record Observers ([Github](https://github.com/rails/rails-observers), [Commit](https://github.com/rails/rails/commit/39e85b3b90c58449164673909a6f1893cba290b2)) -* Active Resource ([Github](https://github.com/rails/activeresource), [Pull Request](https://github.com/rails/rails/pull/572), [Blog](http://yetimedia.tumblr.com/post/35233051627/activeresource-is-dead-long-live-activeresource)) -* Action Caching ([Github](https://github.com/rails/actionpack-action_caching), [Pull Request](https://github.com/rails/rails/pull/7833)) -* Page Caching ([Github](https://github.com/rails/actionpack-page_caching), [Pull Request](https://github.com/rails/rails/pull/7833)) -* Sprockets ([Github](https://github.com/rails/sprockets-rails)) -* Performance tests ([Github](https://github.com/rails/rails-perftest), [Pull Request](https://github.com/rails/rails/pull/8876)) +* Hash-based & Dynamic finder methods ([GitHub](https://github.com/rails/activerecord-deprecated_finders)) +* Mass assignment protection in Active Record models ([GitHub](https://github.com/rails/protected_attributes), [Pull Request](https://github.com/rails/rails/pull/7251)) +* ActiveRecord::SessionStore ([GitHub](https://github.com/rails/activerecord-session_store), [Pull Request](https://github.com/rails/rails/pull/7436)) +* Active Record Observers ([GitHub](https://github.com/rails/rails-observers), [Commit](https://github.com/rails/rails/commit/39e85b3b90c58449164673909a6f1893cba290b2)) +* Active Resource ([GitHub](https://github.com/rails/activeresource), [Pull Request](https://github.com/rails/rails/pull/572), [Blog](http://yetimedia.tumblr.com/post/35233051627/activeresource-is-dead-long-live-activeresource)) +* Action Caching ([GitHub](https://github.com/rails/actionpack-action_caching), [Pull Request](https://github.com/rails/rails/pull/7833)) +* Page Caching ([GitHub](https://github.com/rails/actionpack-page_caching), [Pull Request](https://github.com/rails/rails/pull/7833)) +* Sprockets ([GitHub](https://github.com/rails/sprockets-rails)) +* Performance tests ([GitHub](https://github.com/rails/rails-perftest), [Pull Request](https://github.com/rails/rails/pull/8876)) Documentation ------------- diff --git a/guides/source/credits.html.erb b/guides/source/credits.html.erb index 34be07ef81..74ae7216e1 100644 --- a/guides/source/credits.html.erb +++ b/guides/source/credits.html.erb @@ -32,7 +32,7 @@ Ruby on Rails Guides: Credits <% end %> <%= author('Oscar Del Ben', 'oscardelben', 'oscardelben.jpg') do %> -Oscar Del Ben is a software engineer at <a href="http://www.wildfireapp.com/">Wildfire</a>. He's a regular open source contributor (<a href="https://github.com/oscardelben">Github account</a>) and tweets regularly at <a href="https://twitter.com/oscardelben">@oscardelben</a>. +Oscar Del Ben is a software engineer at <a href="http://www.wildfireapp.com/">Wildfire</a>. He's a regular open source contributor (<a href="https://github.com/oscardelben">GitHub account</a>) and tweets regularly at <a href="https://twitter.com/oscardelben">@oscardelben</a>. <% end %> <%= author('Frederick Cheung', 'fcheung') do %> diff --git a/guides/source/i18n.md b/guides/source/i18n.md index 3c4c1e2cb3..65a85efa69 100644 --- a/guides/source/i18n.md +++ b/guides/source/i18n.md @@ -417,7 +417,7 @@ So that would give you:  -TIP: Right now you might need to add some more date/time formats in order to make the I18n backend work as expected (at least for the 'pirate' locale). Of course, there's a great chance that somebody already did all the work by **translating Rails' defaults for your locale**. See the [rails-i18n repository at Github](https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale) for an archive of various locale files. When you put such file(s) in `config/locales/` directory, they will automatically be ready for use. +TIP: Right now you might need to add some more date/time formats in order to make the I18n backend work as expected (at least for the 'pirate' locale). Of course, there's a great chance that somebody already did all the work by **translating Rails' defaults for your locale**. See the [rails-i18n repository at GitHub](https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale) for an archive of various locale files. When you put such file(s) in `config/locales/` directory, they will automatically be ready for use. ### Inflection Rules For Other Locales @@ -980,8 +980,8 @@ Resources * [rails-i18n.org](http://rails-i18n.org) - Homepage of the rails-i18n project. You can find lots of useful resources on the [wiki](http://rails-i18n.org/wiki). * [Google group: rails-i18n](http://groups.google.com/group/rails-i18n) - The project's mailing list. -* [Github: rails-i18n](https://github.com/svenfuchs/rails-i18n/tree/master) - Code repository for the rails-i18n project. Most importantly you can find lots of [example translations](https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale) for Rails that should work for your application in most cases. -* [Github: i18n](https://github.com/svenfuchs/i18n/tree/master) - Code repository for the i18n gem. +* [GitHub: rails-i18n](https://github.com/svenfuchs/rails-i18n/tree/master) - Code repository for the rails-i18n project. Most importantly you can find lots of [example translations](https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale) for Rails that should work for your application in most cases. +* [GitHub: i18n](https://github.com/svenfuchs/i18n/tree/master) - Code repository for the i18n gem. * [Lighthouse: rails-i18n](http://i18n.lighthouseapp.com/projects/14948-rails-i18n/overview) - Issue tracker for the rails-i18n project. * [Lighthouse: i18n](http://i18n.lighthouseapp.com/projects/14947-ruby-i18n/overview) - Issue tracker for the i18n gem. diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index e1b98eda55..04cdbec428 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,8 @@ +* Fixes bug with scaffold generator with `--assets=false --resource-route=false`. + Fixes #9525. + + *Arun Agrawal* + * Rails::Railtie no longer forces the Rails::Configurable module on everything that subclassess it. Instead, the methods from Rails::Configurable have been moved to class methods in Railtie and the Railtie has been made abstract. diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 2d5aa9d952..914e4393c4 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -93,6 +93,7 @@ module Rails # dispatches the request to the underlying middleware stack. def call(env) env["ORIGINAL_FULLPATH"] = build_original_fullpath(env) + env["ORIGINAL_SCRIPT_NAME"] = env["SCRIPT_NAME"] super(env) end diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb index 0d1286031c..e8c42b149b 100644 --- a/railties/lib/rails/commands.rb +++ b/railties/lib/rails/commands.rb @@ -66,7 +66,7 @@ when 'console' Rails::Console.start(Rails.application, options) when 'server' - # Change to the application's path if there is no config.ru file in current dir. + # Change to the application's path if there is no config.ru file in current directory. # This allows us to run `rails server` from other directories, but still get # the main config.ru and properly set the tmp directory. Dir.chdir(File.expand_path('../../', APP_PATH)) unless File.exists?(File.expand_path("config.ru")) diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index b2d1be9b51..5877579fc4 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -56,6 +56,7 @@ module Rails def app directory 'app' + keep_file 'app/assets/images' keep_file 'app/mailers' keep_file 'app/models' diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index ace804ffe6..243e7e9da8 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -10,7 +10,7 @@ source 'https://rubygems.org' <%= javascript_gemfile_entry -%> # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder -gem 'jbuilder', '~> 1.0.1' +gem 'jbuilder', '~> 1.2' group :doc do # bundle exec rake doc:rails generates the API under doc/api. diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/images/.keep b/railties/lib/rails/generators/rails/app/templates/app/assets/images/.keep deleted file mode 100644 index e69de29bb2..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/app/assets/images/.keep +++ /dev/null diff --git a/railties/lib/rails/generators/rails/app/templates/gitignore b/railties/lib/rails/generators/rails/app/templates/gitignore index 25a742dff0..6a502e997f 100644 --- a/railties/lib/rails/generators/rails/app/templates/gitignore +++ b/railties/lib/rails/generators/rails/app/templates/gitignore @@ -1,4 +1,4 @@ -# See http://help.github.com/ignore-files/ for more about ignoring files. +# See https://help.github.com/articles/ignoring-files for more about ignoring files. # # If you find yourself ignoring temporary files generated by your text editor # or operating system, you probably want to add a global ignore instead: diff --git a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb index 2a0522e81c..e89789e72b 100644 --- a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb +++ b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb @@ -8,6 +8,8 @@ module Rails class_option :stylesheets, type: :boolean, desc: "Generate Stylesheets" class_option :stylesheet_engine, desc: "Engine for Stylesheets" + class_option :assets, type: :boolean + class_option :resource_route, type: :boolean def handle_skip @options = @options.merge(stylesheets: false) unless options[:assets] diff --git a/railties/lib/rails/tasks/documentation.rake b/railties/lib/rails/tasks/documentation.rake index f89d6b12e1..d45e892424 100644 --- a/railties/lib/rails/tasks/documentation.rake +++ b/railties/lib/rails/tasks/documentation.rake @@ -1,61 +1,72 @@ -require 'rdoc/task' -require 'rails/api/task' +begin + require 'rdoc/task' +rescue LoadError + # Rubinius installs RDoc as a gem, and for this interpreter "rdoc/task" is + # available only if the application bundle includes "rdoc" (normally as a + # dependency of the "sdoc" gem.) + # + # If RDoc is not available it is fine that we do not generate the tasks that + # depend on it. Just be robust to this gotcha and go on. +else + require 'rails/api/task' -# Monkey-patch to remove redoc'ing and clobber descriptions to cut down on rake -T noise -class RDocTaskWithoutDescriptions < RDoc::Task - include ::Rake::DSL + # Monkey-patch to remove redoc'ing and clobber descriptions to cut down on rake -T noise + class RDocTaskWithoutDescriptions < RDoc::Task + include ::Rake::DSL - def define - task rdoc_task_name + def define + task rdoc_task_name - task rerdoc_task_name => [clobber_task_name, rdoc_task_name] + task rerdoc_task_name => [clobber_task_name, rdoc_task_name] - task clobber_task_name do - rm_r rdoc_dir rescue nil - end + task clobber_task_name do + rm_r rdoc_dir rescue nil + end - task :clobber => [clobber_task_name] + task :clobber => [clobber_task_name] - directory @rdoc_dir - task rdoc_task_name => [rdoc_target] - file rdoc_target => @rdoc_files + [Rake.application.rakefile] do - rm_r @rdoc_dir rescue nil - @before_running_rdoc.call if @before_running_rdoc - args = option_list + @rdoc_files - if @external - argstring = args.join(' ') - sh %{ruby -Ivendor vendor/rd #{argstring}} - else - require 'rdoc/rdoc' - RDoc::RDoc.new.document(args) + directory @rdoc_dir + task rdoc_task_name => [rdoc_target] + file rdoc_target => @rdoc_files + [Rake.application.rakefile] do + rm_r @rdoc_dir rescue nil + @before_running_rdoc.call if @before_running_rdoc + args = option_list + @rdoc_files + if @external + argstring = args.join(' ') + sh %{ruby -Ivendor vendor/rd #{argstring}} + else + require 'rdoc/rdoc' + RDoc::RDoc.new.document(args) + end end + self end - self end -end -namespace :doc do - def gem_path(gem_name) - path = $LOAD_PATH.grep(/#{gem_name}[\w.-]*\/lib$/).first - yield File.dirname(path) if path - end + namespace :doc do + def gem_path(gem_name) + path = $LOAD_PATH.grep(/#{gem_name}[\w.-]*\/lib$/).first + yield File.dirname(path) if path + end - RDocTaskWithoutDescriptions.new("app") { |rdoc| - rdoc.rdoc_dir = 'doc/app' - rdoc.template = ENV['template'] if ENV['template'] - rdoc.title = ENV['title'] || "Rails Application Documentation" - rdoc.options << '--line-numbers' - rdoc.options << '--charset' << 'utf-8' - rdoc.rdoc_files.include('README.rdoc') - rdoc.rdoc_files.include('app/**/*.rb') - rdoc.rdoc_files.include('lib/**/*.rb') - } - Rake::Task['doc:app'].comment = "Generate docs for the app -- also available doc:rails, doc:guides (options: TEMPLATE=/rdoc-template.rb, TITLE=\"Custom Title\")" + RDocTaskWithoutDescriptions.new("app") { |rdoc| + rdoc.rdoc_dir = 'doc/app' + rdoc.template = ENV['template'] if ENV['template'] + rdoc.title = ENV['title'] || "Rails Application Documentation" + rdoc.options << '--line-numbers' + rdoc.options << '--charset' << 'utf-8' + rdoc.rdoc_files.include('README.rdoc') + rdoc.rdoc_files.include('app/**/*.rb') + rdoc.rdoc_files.include('lib/**/*.rb') + } + Rake::Task['doc:app'].comment = "Generate docs for the app -- also available doc:rails, doc:guides (options: TEMPLATE=/rdoc-template.rb, TITLE=\"Custom Title\")" - # desc 'Generate documentation for the Rails framework.' - Rails::API::AppTask.new('rails') + # desc 'Generate documentation for the Rails framework.' + Rails::API::AppTask.new('rails') + end +end - # desc "Generate Rails Guides" +namespace :doc do task :guides do rails_gem_dir = Gem::Specification.find_by_name("rails").gem_dir require File.expand_path(File.join(rails_gem_dir, "/guides/rails_guides")) diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index f70c90bffa..0ffb615764 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -260,6 +260,11 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file 'test' end + def test_creation_of_app_assets_images_directory + run_generator + assert_file "app/assets/images" + end + def test_creation_of_vendor_assets_javascripts_directory run_generator assert_file "vendor/assets/javascripts" diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index 25f299118c..d5ad978986 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -239,13 +239,27 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_file "config/routes.rb", /\.routes\.draw do\s*\|map\|\s*$/ end - def test_scaffold_generator_no_assets + def test_scaffold_generator_no_assets_with_switch_no_assets run_generator [ "posts", "--no-assets" ] assert_no_file "app/assets/stylesheets/scaffold.css" assert_no_file "app/assets/javascripts/posts.js" assert_no_file "app/assets/stylesheets/posts.css" end + def test_scaffold_generator_no_assets_with_switch_assets_false + run_generator [ "posts", "--assets=false" ] + assert_no_file "app/assets/stylesheets/scaffold.css" + assert_no_file "app/assets/javascripts/posts.js" + assert_no_file "app/assets/stylesheets/posts.css" + end + + def test_scaffold_generator_no_assets_with_switch_resource_route_false + run_generator [ "posts", "--resource-route=false" ] + assert_file "config/routes.rb" do |route| + assert_no_match(/resources :posts$/, route) + end + end + def test_scaffold_generator_no_stylesheets run_generator [ "posts", "--no-stylesheets" ] assert_no_file "app/assets/stylesheets/scaffold.css" diff --git a/railties/test/rails_info_test.rb b/railties/test/rails_info_test.rb index 5b9088cb64..44a5fd1904 100644 --- a/railties/test/rails_info_test.rb +++ b/railties/test/rails_info_test.rb @@ -37,6 +37,11 @@ class InfoTest < ActiveSupport::TestCase assert_property 'Goodbye', 'World' end + def test_rails_version + assert_property 'Rails version', + File.read(File.realpath('../../../RAILS_VERSION', __FILE__)).chomp + end + def test_framework_version assert_property 'Active Support version', ActiveSupport.version.to_s end diff --git a/railties/test/railties/mounted_engine_test.rb b/railties/test/railties/mounted_engine_test.rb index 80559a6e36..c94937f964 100644 --- a/railties/test/railties/mounted_engine_test.rb +++ b/railties/test/railties/mounted_engine_test.rb @@ -13,6 +13,7 @@ module ApplicationTests @simple_plugin = engine "weblog" @plugin = engine "blog" + @metrics_plugin = engine "metrics" app_file 'config/routes.rb', <<-RUBY AppTemplate::Application.routes.draw do @@ -28,6 +29,7 @@ module ApplicationTests scope "/:user", :user => "anonymous" do mount Blog::Engine => "/blog" end + mount Metrics::Engine => "/metrics" root :to => 'main#index' end RUBY @@ -54,6 +56,34 @@ module ApplicationTests end RUBY + @metrics_plugin.write "lib/metrics.rb", <<-RUBY + module Metrics + class Engine < ::Rails::Engine + isolate_namespace(Metrics) + end + end + RUBY + + @metrics_plugin.write "config/routes.rb", <<-RUBY + Metrics::Engine.routes.draw do + get '/generate_blog_route', to: 'generating#generate_blog_route' + get '/generate_blog_route_in_view', to: 'generating#generate_blog_route_in_view' + end + RUBY + + @metrics_plugin.write "app/controllers/metrics/generating_controller.rb", <<-RUBY + module Metrics + class GeneratingController < ActionController::Base + def generate_blog_route + render text: blog.post_path(1) + end + + def generate_blog_route_in_view + render inline: "<%= blog.post_path(1) -%>" + end + end + end + RUBY @plugin.write "app/models/blog/post.rb", <<-RUBY module Blog @@ -201,6 +231,21 @@ module ApplicationTests get "/somone/blog/application_route_in_view" assert_equal "/", last_response.body + # test generating engine's route from other engine + get "/metrics/generate_blog_route" + assert_equal '/anonymous/blog/posts/1', last_response.body + + get "/metrics/generate_blog_route_in_view" + assert_equal '/anonymous/blog/posts/1', last_response.body + + # test generating engine's route from other engine with default_url_options + get "/metrics/generate_blog_route", {}, 'SCRIPT_NAME' => '/foo' + assert_equal '/foo/anonymous/blog/posts/1', last_response.body + + get "/metrics/generate_blog_route_in_view", {}, 'SCRIPT_NAME' => '/foo' + assert_equal '/foo/anonymous/blog/posts/1', last_response.body + + # test generating application's route from engine with default_url_options get "/someone/blog/generate_application_route", {}, 'SCRIPT_NAME' => '/foo' assert_equal "/foo/", last_response.body |