diff options
-rw-r--r-- | .travis.yml | 2 | ||||
-rw-r--r-- | actionview/CHANGELOG.md | 7 | ||||
-rw-r--r-- | actionview/lib/action_view/helpers/tags/label.rb | 68 | ||||
-rw-r--r-- | actionview/test/template/form_helper_test.rb | 9 | ||||
-rw-r--r-- | activemodel/lib/active_model/model.rb | 14 | ||||
-rw-r--r-- | activemodel/test/cases/validations_test.rb | 4 | ||||
-rw-r--r-- | activerecord/CHANGELOG.md | 4 | ||||
-rw-r--r-- | activerecord/lib/active_record/associations.rb | 15 | ||||
-rw-r--r-- | activerecord/lib/active_record/attribute_methods/dirty.rb | 21 | ||||
-rw-r--r-- | activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb | 4 | ||||
-rw-r--r-- | activerecord/lib/active_record/core.rb | 6 | ||||
-rw-r--r-- | activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb | 12 | ||||
-rw-r--r-- | activerecord/test/cases/base_test.rb | 14 | ||||
-rw-r--r-- | activerecord/test/cases/finder_test.rb | 42 | ||||
-rw-r--r-- | activerecord/test/cases/relations_test.rb | 4 | ||||
-rw-r--r-- | activesupport/test/caching_test.rb | 2 | ||||
-rw-r--r-- | activesupport/test/transliterate_test.rb | 2 | ||||
-rw-r--r-- | guides/source/4_2_release_notes.md | 43 |
18 files changed, 192 insertions, 81 deletions
diff --git a/.travis.yml b/.travis.yml index 3851070b41..c9755ae68e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ rvm: - jruby env: global: - - JRUBY_OPTS='-J-XX:+TieredCompilation -J-XX:TieredStopAtLevel=1 -J-Djruby.compile.mode=OFF -J-Djruby.compile.invokedynamic=false -J-Xmx1024M' + - JRUBY_OPTS='-J-XX:+TieredCompilation -J-XX:TieredStopAtLevel=1 -J-Djruby.compile.mode=OFF -J-Djruby.compile.invokedynamic=false -J-Xmx1024M' matrix: - "GEM=railties" - "GEM=ap" diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index 552a902349..396249ac37 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,3 +1,10 @@ +* Provide a `builder` object when using the `label` form helper in block form. + + The new `builder` object responds to `translation`, allowing I18n fallback support + when you want to customize how a particular label is presented. + + *Alex Robbin* + * Add I18n support for input/textarea placeholder text. Placeholder I18n follows the same convention as `label` I18n. diff --git a/actionview/lib/action_view/helpers/tags/label.rb b/actionview/lib/action_view/helpers/tags/label.rb index 39b2f48c39..08a23e497e 100644 --- a/actionview/lib/action_view/helpers/tags/label.rb +++ b/actionview/lib/action_view/helpers/tags/label.rb @@ -2,6 +2,39 @@ module ActionView module Helpers module Tags # :nodoc: class Label < Base # :nodoc: + class LabelBuilder # :nodoc: + attr_reader :object + + def initialize(template_object, object_name, method_name, object, tag_value) + @template_object = template_object + @object_name = object_name + @method_name = method_name + @object = object + @tag_value = tag_value + end + + def translation + method_and_value = @tag_value.present? ? "#{@method_name}.#{@tag_value}" : @method_name + @object_name.gsub!(/\[(.*)_attributes\]\[\d+\]/, '.\1') + + if object.respond_to?(:to_model) + key = object.model_name.i18n_key + i18n_default = ["#{key}.#{method_and_value}".to_sym, ""] + end + + i18n_default ||= "" + content = I18n.t("#{@object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.label").presence + + content ||= if object && object.class.respond_to?(:human_attribute_name) + object.class.human_attribute_name(method_and_value) + end + + content ||= @method_name.humanize + + content + end + end + def initialize(object_name, method_name, template_object, content_or_options = nil, options = nil) options ||= {} @@ -32,33 +65,24 @@ module ActionView options.delete("namespace") options["for"] = name_and_id["id"] unless options.key?("for") - if block_given? - content = @template_object.capture(&block) - else - method_and_value = tag_value.present? ? "#{@method_name}.#{tag_value}" : @method_name - content = if @content.blank? - @object_name.gsub!(/\[(.*)_attributes\]\[\d+\]/, '.\1') - - if object.respond_to?(:to_model) - key = object.model_name.i18n_key - i18n_default = ["#{key}.#{method_and_value}".to_sym, ""] - end - - i18n_default ||= "" - I18n.t("#{@object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.label").presence - else - @content.to_s - end - - content ||= if object && object.class.respond_to?(:human_attribute_name) - object.class.human_attribute_name(method_and_value) - end + builder = LabelBuilder.new(@template_object, @object_name, @method_name, @object, tag_value) - content ||= @method_name.humanize + content = if block_given? + @template_object.capture(builder, &block) + elsif @content.present? + @content.to_s + else + render_component(builder) end label_tag(name_and_id["id"], content, options) end + + private + + def render_component(builder) + builder.translation + end end end end diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb index 6910769183..6f82462425 100644 --- a/actionview/test/template/form_helper_test.rb +++ b/actionview/test/template/form_helper_test.rb @@ -319,6 +319,15 @@ class FormHelperTest < ActionView::TestCase ) end + def test_label_with_block_and_builder + with_locale :label do + assert_dom_equal( + '<label for="post_body"><b>Write entire text here</b></label>', + label(:post, :body) { |b| "<b>#{b.translation}</b>".html_safe } + ) + end + end + def test_label_with_block_in_erb assert_equal( %{<label for="post_message">\n Message\n <input id="post_message" name="post[message]" type="text" />\n</label>}, diff --git a/activemodel/lib/active_model/model.rb b/activemodel/lib/active_model/model.rb index 640024eaa1..d51d6ddcc9 100644 --- a/activemodel/lib/active_model/model.rb +++ b/activemodel/lib/active_model/model.rb @@ -56,13 +56,13 @@ module ActiveModel # refer to the specific modules included in <tt>ActiveModel::Model</tt> # (see below). module Model - def self.included(base) #:nodoc: - base.class_eval do - extend ActiveModel::Naming - extend ActiveModel::Translation - include ActiveModel::Validations - include ActiveModel::Conversion - end + extend ActiveSupport::Concern + include ActiveModel::Validations + include ActiveModel::Conversion + + included do + extend ActiveModel::Naming + extend ActiveModel::Translation end # Initializes a new model with the given +params+. diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb index ba0aacc2a5..d876f73052 100644 --- a/activemodel/test/cases/validations_test.rb +++ b/activemodel/test/cases/validations_test.rb @@ -18,11 +18,11 @@ class ValidationsTest < ActiveModel::TestCase def test_single_field_validation r = Reply.new r.title = "There's no content!" - assert r.invalid?, "A reply without content shouldn't be savable" + assert r.invalid?, "A reply without content should be invalid" assert r.after_validation_performed, "after_validation callback should be called" r.content = "Messa content!" - assert r.valid?, "A reply with content should be savable" + assert r.valid?, "A reply with content should be valid" assert r.after_validation_performed, "after_validation callback should be called" end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 356911d340..b9af8584ae 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,7 @@ +* `default_sequence_name` from the PostgreSQL adapter returns a `String`. + + *Yves Senn* + * Fixed a regression where whitespaces were stripped from DISTINCT queries in PostgreSQL. diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 18d4291599..18da28d480 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1604,6 +1604,21 @@ module ActiveRecord # where("default_category = ?", category.name) # } # + # === Extensions + # + # The +extension+ argument allows you to pass a block into a + # has_and_belongs_to_many association. This is useful for adding new + # finders, creators and other factory-type methods to be used as part of + # the association. + # + # Extension examples: + # has_and_belongs_to_many :contractors do + # def find_or_create_by_name(name) + # first_name, last_name = name.split(" ", 2) + # find_or_create_by(first_name: first_name, last_name: last_name) + # end + # end + # # === Options # # [:class_name] diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 01f126f1b3..2f02738f6d 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -54,7 +54,19 @@ module ActiveRecord end def changed_attributes - super.reverse_merge(attributes_changed_in_place).freeze + # This should only be set by methods which will call changed_attributes + # multiple times when it is known that the computed value cannot change. + if defined?(@cached_changed_attributes) + @cached_changed_attributes + else + super.reverse_merge(attributes_changed_in_place).freeze + end + end + + def changes + cache_changed_attributes do + super + end end private @@ -157,6 +169,13 @@ module ActiveRecord store_original_raw_attribute(attr) end end + + def cache_changed_attributes + @cached_changed_attributes = changed_attributes + yield + ensure + remove_instance_variable(:@cached_changed_attributes) + end 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 323da7b717..767b6b614a 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -281,9 +281,9 @@ module ActiveRecord def default_sequence_name(table_name, pk = nil) #:nodoc: result = serial_sequence(table_name, pk || 'id') return nil unless result - Utils.extract_schema_qualified_name(result) + Utils.extract_schema_qualified_name(result).to_s rescue ActiveRecord::StatementInvalid - PostgreSQL::Name.new(nil, "#{table_name}_#{pk || 'id'}_seq") + PostgreSQL::Name.new(nil, "#{table_name}_#{pk || 'id'}_seq").to_s end def serial_sequence(table, column) diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index d22806fbdf..82b9c79533 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -151,7 +151,7 @@ module ActiveRecord end def find_by(*args) - return super if current_scope || args.length > 1 || reflect_on_all_aggregations.any? + return super if current_scope || !(Hash === args.first) || reflect_on_all_aggregations.any? hash = args.first @@ -177,6 +177,10 @@ module ActiveRecord end end + def find_by!(*args) + find_by(*args) or raise RecordNotFound + end + def initialize_generated_modules super diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index 01b3f350b5..a71c0dfb26 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -134,18 +134,18 @@ module ActiveRecord end def test_default_sequence_name - assert_equal PostgreSQL::Name.new('public', 'accounts_id_seq'), + assert_equal 'public.accounts_id_seq', @connection.default_sequence_name('accounts', 'id') - assert_equal PostgreSQL::Name.new('public', 'accounts_id_seq'), + assert_equal 'public.accounts_id_seq', @connection.default_sequence_name('accounts') end def test_default_sequence_name_bad_table - assert_equal PostgreSQL::Name.new(nil, 'zomg_id_seq'), + assert_equal 'zomg_id_seq', @connection.default_sequence_name('zomg', 'id') - assert_equal PostgreSQL::Name.new(nil, 'zomg_id_seq'), + assert_equal 'zomg_id_seq', @connection.default_sequence_name('zomg') end @@ -153,7 +153,7 @@ module ActiveRecord with_example_table do pk, seq = @connection.pk_and_sequence_for('ex') assert_equal 'id', pk - assert_equal @connection.default_sequence_name('ex', 'id'), seq + assert_equal @connection.default_sequence_name('ex', 'id'), seq.to_s end end @@ -161,7 +161,7 @@ module ActiveRecord with_example_table 'code serial primary key' do pk, seq = @connection.pk_and_sequence_for('ex') assert_equal 'code', pk - assert_equal @connection.default_sequence_name('ex', 'code'), seq + assert_equal @connection.default_sequence_name('ex', 'code'), seq.to_s end end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 4c0b0c868a..fb535e74fc 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1540,20 +1540,6 @@ class BasicsTest < ActiveRecord::TestCase assert_equal "", Company.new.description end - ["find_by", "find_by!"].each do |meth| - test "#{meth} delegates to scoped" do - record = stub - - scope = mock - scope.expects(meth).with(:foo, :bar).returns(record) - - klass = Class.new(ActiveRecord::Base) - klass.stubs(:all => scope) - - assert_equal record, klass.public_send(meth, :foo, :bar) - end - end - test "scoped can take a values hash" do klass = Class.new(ActiveRecord::Base) assert_equal ['foo'], klass.all.merge!(select: 'foo').select_values diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index b42a60fea5..befbec4e1b 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -1027,6 +1027,48 @@ class FinderTest < ActiveRecord::TestCase assert_nothing_raised(ActiveRecord::StatementInvalid) { Topic.offset("3").to_a } end + test "find_by with hash conditions returns the first matching record" do + assert_equal posts(:eager_other), Post.find_by(id: posts(:eager_other).id) + end + + test "find_by with non-hash conditions returns the first matching record" do + assert_equal posts(:eager_other), Post.find_by("id = #{posts(:eager_other).id}") + end + + test "find_by with multi-arg conditions returns the first matching record" do + assert_equal posts(:eager_other), Post.find_by('id = ?', posts(:eager_other).id) + end + + test "find_by returns nil if the record is missing" do + assert_equal nil, Post.find_by("1 = 0") + end + + test "find_by doesn't have implicit ordering" do + assert_sql(/^((?!ORDER).)*$/) { Post.find_by(id: posts(:eager_other).id) } + end + + test "find_by! with hash conditions returns the first matching record" do + assert_equal posts(:eager_other), Post.find_by!(id: posts(:eager_other).id) + end + + test "find_by! with non-hash conditions returns the first matching record" do + assert_equal posts(:eager_other), Post.find_by!("id = #{posts(:eager_other).id}") + end + + test "find_by! with multi-arg conditions returns the first matching record" do + assert_equal posts(:eager_other), Post.find_by!('id = ?', posts(:eager_other).id) + end + + test "find_by! doesn't have implicit ordering" do + assert_sql(/^((?!ORDER).)*$/) { Post.find_by!(id: posts(:eager_other).id) } + end + + test "find_by! raises RecordNotFound if the record is missing" do + assert_raises(ActiveRecord::RecordNotFound) do + Post.find_by!("1 = 0") + end + end + protected def bind(statement, *vars) if vars.first.is_a?(Hash) diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 88df997a2f..cc6a3888e3 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -1570,7 +1570,7 @@ class RelationTest < ActiveRecord::TestCase end test "find_by doesn't have implicit ordering" do - assert_sql(/^((?!ORDER).)*$/) { Post.find_by(author_id: 2) } + assert_sql(/^((?!ORDER).)*$/) { Post.all.find_by(author_id: 2) } end test "find_by! with hash conditions returns the first matching record" do @@ -1586,7 +1586,7 @@ class RelationTest < ActiveRecord::TestCase end test "find_by! doesn't have implicit ordering" do - assert_sql(/^((?!ORDER).)*$/) { Post.find_by!(author_id: 2) } + assert_sql(/^((?!ORDER).)*$/) { Post.all.find_by!(author_id: 2) } end test "find_by! raises RecordNotFound if the record is missing" do diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index 8287e62f4c..5945605f7b 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -939,8 +939,8 @@ class MemCacheStoreTest < ActiveSupport::TestCase def test_read_should_return_a_different_object_id_each_time_it_is_called @cache.write('foo', 'bar') - assert_not_equal @cache.read('foo').object_id, @cache.read('foo').object_id value = @cache.read('foo') + assert_not_equal value.object_id, @cache.read('foo').object_id value << 'bingo' assert_not_equal value, @cache.read('foo') end diff --git a/activesupport/test/transliterate_test.rb b/activesupport/test/transliterate_test.rb index e0f85f4e7c..6833ae68a7 100644 --- a/activesupport/test/transliterate_test.rb +++ b/activesupport/test/transliterate_test.rb @@ -11,7 +11,7 @@ class TransliterateTest < ActiveSupport::TestCase end def test_transliterate_should_approximate_ascii - # create string with range of Unicode"s western characters with + # create string with range of Unicode's western characters with # diacritics, excluding the division and multiplication signs which for # some reason or other are floating in the middle of all the letters. string = (0xC0..0x17E).to_a.reject {|c| [0xD7, 0xF7].include?(c)}.pack("U*") diff --git a/guides/source/4_2_release_notes.md b/guides/source/4_2_release_notes.md index 176cdadabf..5401b34542 100644 --- a/guides/source/4_2_release_notes.md +++ b/guides/source/4_2_release_notes.md @@ -36,11 +36,11 @@ Major Features ### Active Job, Action Mailer #deliver_later Active Job is a new framework in Rails 4.2. It is an adapter layer on top of -queuing systems like Resque, Delayed Job, Sidekiq, and more. You can write your -jobs to Active Job, and it'll run on all these queues with no changes. (It comes -pre-configured with an inline runner.) +queuing systems like [Resque](https://github.com/resque/resque), [Delayed Job](https://github.com/collectiveidea/delayed_job), [Sidekiq](https://github.com/mperham/sidekiq), and more. You can write your +jobs with the Active Job API, and it'll run on all these queues with no changes +(it comes pre-configured with an inline runner). -Building on top of Active Job, Action Mailer now comes with a #deliver_later +Building on top of Active Job, Action Mailer now comes with a `#deliver_later` method, which adds your email to be sent as a job to a queue, so it doesn't bog down the controller or model. @@ -63,16 +63,16 @@ TODO: add some technical details New applications generated from Rails 4.2 now comes with the Web Console gem by default. -Web Console is a set of debugging tools for your Rails application. It comes -with an interactive console for every error page, a `console` view helper and -VT100 compatible terminal. +Web Console is a set of debugging tools for your Rails application. It will add +an interactive console on every error page, a `console` view helper and a VT100 +compatible terminal. The interactive console on the error pages lets you execute code where the exception originated. It's quite handy to introspect the state that led to the error. -The `console` view helper launches an interactive console with the context of -the view right on the page it's invoked on. +The `console` view helper launches an interactive console within the context of +the view where it is invoked. Finally, you can launch a VT100 terminal that runs `rails console`. If you need to create or modify existing test data, you can do that straight from the @@ -184,7 +184,8 @@ Please refer to the [Changelog][railties] for detailed changes. * Introduced an API to register new extensions for `rake notes`. ([Pull Request](https://github.com/rails/rails/pull/14379)) -* Introduced `Rails.gem_version` as a convenience method to return `Gem::Version.new(Rails.version)`. +* Introduced `Rails.gem_version` as a convenience method to return + `Gem::Version.new(Rails.version)`. ([Pull Request](https://github.com/rails/rails/pull/14101)) @@ -265,8 +266,8 @@ Please refer to the [Changelog][action-pack] for detailed changes. * Added HTTP method `MKCALENDAR` from RFC-4791 ([Pull Request](https://github.com/rails/rails/pull/15121)) -* `*_fragment.action_controller` notifications now include the controller and action name - in the payload. +* `*_fragment.action_controller` notifications now include the controller + and action name in the payload. ([Pull Request](https://github.com/rails/rails/pull/14137)) * Segments that are passed into URL helpers are now automatically escaped. @@ -334,7 +335,7 @@ Please refer to the [Changelog][action-mailer] for detailed changes. ### Notable changes * Introduced `deliver_later` which enqueues a job on the application's queue - to deliver the mailer asynchronously. + to deliver emails asynchronously. ([Pull Request](https://github.com/rails/rails/pull/16485)) * Added the `show_previews` configuration option for enabling mailer previews @@ -345,9 +346,7 @@ Please refer to the [Changelog][action-mailer] for detailed changes. Active Record ------------- -Please refer to the -[Changelog](https://github.com/rails/rails/blob/4-2-stable/activerecord/CHANGELOG.md) -for detailed changes. +Please refer to the [Changelog][active-record] for detailed changes. ### Removals @@ -363,7 +362,7 @@ for detailed changes. * Removed unused `:timestamp` type. Transparently alias it to `:datetime` in all cases. Fixes inconsistencies when column types are sent outside of - `ActiveRecord`, such as for XML Serialization. + `ActiveRecord`, such as for XML serialization. ([Pull Request](https://github.com/rails/rails/pull/15184)) ### Deprecations @@ -458,7 +457,7 @@ for detailed changes. * `sqlite3:///some/path` now resolves to the absolute system path `/some/path`. For relative paths, use `sqlite3:some/path` instead. (Previously, `sqlite3:///some/path` resolved to the relative path - `some/path`. This behaviour was deprecated on Rails 4.1.) + `some/path`. This behaviour was deprecated on Rails 4.1). ([Pull Request](https://github.com/rails/rails/pull/14569)) * Introduced `#validate` as an alias for `#valid?`. @@ -490,17 +489,19 @@ Please refer to the [Changelog][active-model] for detailed changes. ### Deprecations -* Deprecated reset_#{attribute} in favor of restore_#{attribute}. +* Deprecated `reset_#{attribute}` in favor of `restore_#{attribute}`. ([Pull Request](https://github.com/rails/rails/pull/16180)) -* Deprecated ActiveModel::Dirty#reset_changes in favor of #clear_changes_information. +* Deprecated `ActiveModel::Dirty#reset_changes` in favor of + `#clear_changes_information`. ([Pull Request](https://github.com/rails/rails/pull/16180)) ### Notable changes * Introduced the `restore_attributes` method in `ActiveModel::Dirty` to restore the changed (dirty) attributes to their previous values. - (Pull Request [1](https://github.com/rails/rails/pull/14861), [2](https://github.com/rails/rails/pull/16180)) + (Pull Request [1](https://github.com/rails/rails/pull/14861), + [2](https://github.com/rails/rails/pull/16180)) * `has_secure_password` no longer disallow blank passwords (i.e. passwords that contains only spaces) by default. |