diff options
39 files changed, 353 insertions, 119 deletions
@@ -11,7 +11,7 @@ gem 'rack-cache', '~> 1.2' gem 'jquery-rails', '~> 3.1.0' gem 'turbolinks' gem 'coffee-rails', '~> 4.0.0' -gem 'arel', github: 'rails/arel' +gem 'arel', github: 'rails/arel', branch: 'master' gem 'sprockets-rails', github: 'rails/sprockets-rails', branch: '2-1-stable' # require: false so bcrypt is loaded only when has_secure_password is used. diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 15833641bb..fd3f9eb72d 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,11 @@ +* Properly treat the entire IPv6 User Local Address space as private for + purposes of remote IP detection. Also handle uppercase private IPv6 + addresses. + + Fixes #12638. + + *Caleb Spare* + * Fixed an issue with migrating legacy json cookies. Previously, the `VerifyAndUpgradeLegacySignedMessage` assumes all incoming diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb index c1df518b14..cbb066b092 100644 --- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb +++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb @@ -31,7 +31,7 @@ module ActionDispatch TRUSTED_PROXIES = %r{ ^127\.0\.0\.1$ | # localhost IPv4 ^::1$ | # localhost IPv6 - ^fc00: | # private IPv6 range fc00 + ^[fF][cCdD] | # private IPv6 range fc00::/7 ^10\. | # private IPv4 range 10.x.x.x ^172\.(1[6-9]|2[0-9]|3[0-1])\.| # private IPv4 range 172.16.0.0 .. 172.31.255.255 ^192\.168\. # private IPv4 range 192.168.x.x diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 77718a14c1..400956adee 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -340,18 +340,34 @@ module ActionDispatch match '/', { :as => :root, :via => :get }.merge!(options) end - # Matches a url pattern to one or more routes. Any symbols in a pattern - # are interpreted as url query parameters and thus available as +params+ - # in an action: + # Matches a url pattern to one or more routes. + # + # You should not use the `match` method in your router + # without specifying an HTTP method. + # + # If you want to expose your action to both GET and POST, use: # # # sets :controller, :action and :id in params - # match ':controller/:action/:id' + # match ':controller/:action/:id', via: [:get, :post] + # + # Note that +:controller+, +:action+ and +:id+ are interpreted as url + # query parameters and thus available through +params+ in an action. + # + # If you want to expose your action to GET, use `get` in the router: + # + # Instead of: + # + # match ":controller/:action/:id" + # + # Do: + # + # get ":controller/:action/:id" # # Two of these symbols are special, +:controller+ maps to the controller # and +:action+ to the controller's action. A pattern can also map # wildcard segments (globs) to params: # - # match 'songs/*category/:title', to: 'songs#show' + # get 'songs/*category/:title', to: 'songs#show' # # # 'songs/rock/classic/stairway-to-heaven' sets # # params[:category] = 'rock/classic' @@ -364,17 +380,17 @@ module ActionDispatch # When a pattern points to an internal route, the route's +:action+ and # +:controller+ should be set in options or hash shorthand. Examples: # - # match 'photos/:id' => 'photos#show' - # match 'photos/:id', to: 'photos#show' - # match 'photos/:id', controller: 'photos', action: 'show' + # match 'photos/:id' => 'photos#show', via: :get + # match 'photos/:id', to: 'photos#show', via: :get + # match 'photos/:id', controller: 'photos', action: 'show', via: :get # # A pattern can also point to a +Rack+ endpoint i.e. anything that # responds to +call+: # - # match 'photos/:id', to: lambda {|hash| [200, {}, ["Coming soon"]] } - # match 'photos/:id', to: PhotoRackApp + # match 'photos/:id', to: lambda {|hash| [200, {}, ["Coming soon"]] }, via: :get + # match 'photos/:id', to: PhotoRackApp, via: :get # # Yes, controller actions are just rack endpoints - # match 'photos/:id', to: PhotosController.action(:show) + # match 'photos/:id', to: PhotosController.action(:show), via: :get # # Because requesting various HTTP verbs with a single action has security # implications, you must either specify the actions in @@ -397,7 +413,7 @@ module ActionDispatch # [:module] # The namespace for :controller. # - # match 'path', to: 'c#a', module: 'sekret', controller: 'posts' + # match 'path', to: 'c#a', module: 'sekret', controller: 'posts', via: :get # # => Sekret::PostsController # # See <tt>Scoping#namespace</tt> for its scope equivalent. @@ -416,9 +432,9 @@ module ActionDispatch # Points to a +Rack+ endpoint. Can be an object that responds to # +call+ or a string representing a controller's action. # - # match 'path', to: 'controller#action' - # match 'path', to: lambda { |env| [200, {}, ["Success!"]] } - # match 'path', to: RackApp + # match 'path', to: 'controller#action', via: :get + # match 'path', to: lambda { |env| [200, {}, ["Success!"]] }, via: :get + # match 'path', to: RackApp, via: :get # # [:on] # Shorthand for wrapping routes in a specific RESTful context. Valid @@ -443,14 +459,14 @@ module ActionDispatch # other than path can also be specified with any object # that responds to <tt>===</tt> (eg. String, Array, Range, etc.). # - # match 'path/:id', constraints: { id: /[A-Z]\d{5}/ } + # match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }, via: :get # - # match 'json_only', constraints: { format: 'json' } + # match 'json_only', constraints: { format: 'json' }, via: :get # # class Whitelist # def matches?(request) request.remote_ip == '1.2.3.4' end # end - # match 'path', to: 'c#a', constraints: Whitelist.new + # match 'path', to: 'c#a', constraints: Whitelist.new, via: :get # # See <tt>Scoping#constraints</tt> for more examples with its scope # equivalent. @@ -459,7 +475,7 @@ module ActionDispatch # Sets defaults for parameters # # # Sets params[:format] to 'jpg' by default - # match 'path', to: 'c#a', defaults: { format: 'jpg' } + # match 'path', to: 'c#a', defaults: { format: 'jpg' }, via: :get # # See <tt>Scoping#defaults</tt> for its scope equivalent. # @@ -468,7 +484,7 @@ module ActionDispatch # false, the pattern matches any request prefixed with the given path. # # # Matches any request starting with 'path' - # match 'path', to: 'c#a', anchor: false + # match 'path', to: 'c#a', anchor: false, via: :get # # [:format] # Allows you to specify the default value for optional +format+ diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb index cfd33d1f31..b800ee6448 100644 --- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb +++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb @@ -104,7 +104,10 @@ module ActionDispatch recipient = self if record_or_hash_or_array.kind_of?(Array) - record_or_hash_or_array = record_or_hash_or_array.compact + if record_or_hash_or_array.include? nil + raise ArgumentError, "Nil location provided. Can't build URI." + end + record_or_hash_or_array = record_or_hash_or_array.dup if record_or_hash_or_array.first.is_a?(ActionDispatch::Routing::RoutesProxy) recipient = record_or_hash_or_array.shift end @@ -136,7 +139,7 @@ module ActionDispatch url_options = options.except(:action, :routing_type) unless url_options.empty? - args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options + args << url_options end args.collect! { |a| convert_to_model(a) } diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index 6e21b4a258..b48e8ab974 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -152,9 +152,12 @@ class RequestIP < BaseRequestTest request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,::1' assert_equal nil, request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334, fe80:0000:0000:0000:0202:b3ff:fe1e:8329, ::1, fc00::' + request = stub_request 'HTTP_X_FORWARDED_FOR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334, fe80:0000:0000:0000:0202:b3ff:fe1e:8329, ::1, fc00::, fc01::, fdff' assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => 'FE00::, FDFF::' + assert_equal 'FE00::', request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => 'not_ip_address' assert_equal nil, request.remote_ip end diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index 50ca64d536..bedbf78172 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,8 +1,18 @@ +* Change `asset_path` to use File.join to create proper paths: + + https://some.host.com//assets/some.js + + becomes + + https://some.host.com/assets/some.js + + *Peter Schröder* + * Change `favicon_link_tag` default mimetype from `image/vnd.microsoft.icon` to `image/x-icon`. Before: - + #=> favicon_link_tag 'myicon.ico' <link href="/assets/myicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" /> diff --git a/actionview/lib/action_view/helpers/asset_url_helper.rb b/actionview/lib/action_view/helpers/asset_url_helper.rb index c830ab23e3..41997a85b3 100644 --- a/actionview/lib/action_view/helpers/asset_url_helper.rb +++ b/actionview/lib/action_view/helpers/asset_url_helper.rb @@ -134,11 +134,11 @@ module ActionView relative_url_root = defined?(config.relative_url_root) && config.relative_url_root if relative_url_root - source = "#{relative_url_root}#{source}" unless source.starts_with?("#{relative_url_root}/") + source = File.join(relative_url_root, source) unless source.starts_with?("#{relative_url_root}/") end if host = compute_asset_host(source, options) - source = "#{host}#{source}" + source = File.join(host, source) end "#{source}#{tail}" diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb index 8f10eb46ad..10dcb5c28c 100644 --- a/actionview/lib/action_view/helpers/form_tag_helper.rb +++ b/actionview/lib/action_view/helpers/form_tag_helper.rb @@ -83,13 +83,17 @@ module ActionView # * <tt>:multiple</tt> - If set to true the selection will allow multiple choices. # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input. # * <tt>:include_blank</tt> - If set to true, an empty option will be created. - # * <tt>:prompt</tt> - Create a prompt option with blank value and the text asking user to select something + # * <tt>:prompt</tt> - Create a prompt option with blank value and the text asking user to select something. + # * <tt>:selected</tt> - Provide a default selected value. It should be of the exact type as the provided options. # * Any other key creates standard HTML attributes for the tag. # # ==== Examples # select_tag "people", options_from_collection_for_select(@people, "id", "name") # # <select id="people" name="people"><option value="1">David</option></select> # + # select_tag "people", options_from_collection_for_select(@people, "id", "name"), selected: ["1", "David"] + # # <select id="people" name="people"><option value="1" selected="selected">David</option></select> + # # select_tag "people", "<option>David</option>".html_safe # # => <select id="people" name="people"><option>David</option></select> # diff --git a/actionview/test/activerecord/polymorphic_routes_test.rb b/actionview/test/activerecord/polymorphic_routes_test.rb index afb714484b..1ead18518a 100644 --- a/actionview/test/activerecord/polymorphic_routes_test.rb +++ b/actionview/test/activerecord/polymorphic_routes_test.rb @@ -112,6 +112,31 @@ class PolymorphicRoutesTest < ActionController::TestCase end end + def test_with_empty_list + with_test_routes do + assert_raise ArgumentError, "Nil location provided. Can't build URI." do + polymorphic_url([]) + end + end + end + + def test_with_nil_id + with_test_routes do + assert_raise ArgumentError, "Nil location provided. Can't build URI." do + polymorphic_url({ :id => nil }) + end + end + end + + def test_with_nil_in_list + with_test_routes do + assert_raise ArgumentError, "Nil location provided. Can't build URI." do + @series.save + polymorphic_url([nil, @series]) + end + end + end + def test_with_record with_test_routes do @project.save @@ -295,17 +320,17 @@ class PolymorphicRoutesTest < ActionController::TestCase end end - def test_nesting_with_array_containing_nil + def test_nesting_with_array with_test_routes do @project.save - assert_equal "http://example.com/projects/#{@project.id}/bid", polymorphic_url([@project, nil, :bid]) + assert_equal "http://example.com/projects/#{@project.id}/bid", polymorphic_url([@project, :bid]) end end def test_with_array_containing_single_object with_test_routes do @project.save - assert_equal "http://example.com/projects/#{@project.id}", polymorphic_url([nil, @project]) + assert_equal "http://example.com/projects/#{@project.id}", polymorphic_url([@project]) end end @@ -438,7 +463,7 @@ class PolymorphicRoutesTest < ActionController::TestCase def test_with_array_containing_single_irregular_plural_object with_test_routes do @tax.save - assert_equal "http://example.com/taxes/#{@tax.id}", polymorphic_url([nil, @tax]) + assert_equal "http://example.com/taxes/#{@tax.id}", polymorphic_url([@tax]) end end diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb index 651978ed80..18e4277d7a 100644 --- a/actionview/test/template/asset_tag_helper_test.rb +++ b/actionview/test/template/asset_tag_helper_test.rb @@ -309,6 +309,14 @@ class AssetTagHelperTest < ActionView::TestCase AssetPathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end + def test_asset_path_tag_to_not_create_duplicate_slashes + @controller.config.asset_host = "host/" + assert_dom_equal('http://host/foo', asset_path("foo")) + + @controller.config.relative_url_root = '/some/root/' + assert_dom_equal('http://host/some/root/foo', asset_path("foo")) + end + def test_compute_asset_public_path assert_equal "/robots.txt", compute_asset_path("robots.txt") assert_equal "/robots.txt", compute_asset_path("/robots.txt") diff --git a/actionview/test/template/test_test.rb b/actionview/test/template/test_test.rb index 108a674d95..5721ee6c6f 100644 --- a/actionview/test/template/test_test.rb +++ b/actionview/test/template/test_test.rb @@ -37,10 +37,20 @@ class PeopleHelperTest < ActionView::TestCase def test_link_to_person with_test_route_set do - person = mock(:name => "David") - person.class.extend ActiveModel::Naming - expects(:mocha_mock_path).with(person).returns("/people/1") + person = Struct.new(:name) { + extend ActiveModel::Naming + def self.name; 'Mocha::Mock'; end + }.new "David" + + the_model = nil + extend Module.new { + define_method(:mocha_mock_path) { |model, *args| + the_model = model + "/people/1" + } + } assert_equal '<a href="/people/1">David</a>', link_to_person(person) + assert_equal person, the_model end end diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index 0db220ab8f..68cc874ca3 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,6 +1,11 @@ +* Add plural and singular form for length validator's default messages + + *Abd ar-Rahman Hamid* + * Introduce `validate` as an alias for `valid?`. - This is more intuitive when you want to run validations but don't care about the return value. + This is more intuitive when you want to run validations but don't care about + the return value. *Henrik Nyh* diff --git a/activemodel/lib/active_model/locale/en.yml b/activemodel/lib/active_model/locale/en.yml index 540e8132d3..bf07945fe1 100644 --- a/activemodel/lib/active_model/locale/en.yml +++ b/activemodel/lib/active_model/locale/en.yml @@ -14,9 +14,15 @@ en: empty: "can't be empty" blank: "can't be blank" present: "must be blank" - too_long: "is too long (maximum is %{count} characters)" - too_short: "is too short (minimum is %{count} characters)" - wrong_length: "is the wrong length (should be %{count} characters)" + too_long: + one: "is too long (maximum is 1 character)" + other: "is too long (maximum is %{count} characters)" + too_short: + one: "is too short (minimum is 1 character)" + other: "is too short (minimum is %{count} characters)" + wrong_length: + one: "is the wrong length (should be 1 character)" + other: "is the wrong length (should be %{count} characters)" not_a_number: "is not a number" not_an_integer: "must be an integer" greater_than: "must be greater than %{count}" diff --git a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb index 93600c587a..3eeb80a48b 100644 --- a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb +++ b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb @@ -72,28 +72,40 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase end # validates_length_of: generate_message(attr, :too_long, message: custom_message, count: option_value.end) - def test_generate_message_too_long_with_default_message + def test_generate_message_too_long_with_default_message_plural assert_equal "is too long (maximum is 10 characters)", @person.errors.generate_message(:title, :too_long, count: 10) end + def test_generate_message_too_long_with_default_message_singular + assert_equal "is too long (maximum is 1 character)", @person.errors.generate_message(:title, :too_long, count: 1) + end + def test_generate_message_too_long_with_custom_message assert_equal 'custom message 10', @person.errors.generate_message(:title, :too_long, message: 'custom message %{count}', count: 10) end # validates_length_of: generate_message(attr, :too_short, default: custom_message, count: option_value.begin) - def test_generate_message_too_short_with_default_message + def test_generate_message_too_short_with_default_message_plural assert_equal "is too short (minimum is 10 characters)", @person.errors.generate_message(:title, :too_short, count: 10) end + def test_generate_message_too_short_with_default_message_singular + assert_equal "is too short (minimum is 1 character)", @person.errors.generate_message(:title, :too_short, count: 1) + end + def test_generate_message_too_short_with_custom_message assert_equal 'custom message 10', @person.errors.generate_message(:title, :too_short, message: 'custom message %{count}', count: 10) end # validates_length_of: generate_message(attr, :wrong_length, message: custom_message, count: option_value) - def test_generate_message_wrong_length_with_default_message + def test_generate_message_wrong_length_with_default_message_plural assert_equal "is the wrong length (should be 10 characters)", @person.errors.generate_message(:title, :wrong_length, count: 10) end + def test_generate_message_wrong_length_with_default_message_singular + assert_equal "is the wrong length (should be 1 character)", @person.errors.generate_message(:title, :wrong_length, count: 1) + end + def test_generate_message_wrong_length_with_custom_message assert_equal 'custom message 10', @person.errors.generate_message(:title, :wrong_length, message: 'custom message %{count}', count: 10) end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index e8e41192de..1f6ce38569 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,10 +1,29 @@ -* Rails will now pass a custom validation context through to autosave associations +* Rails will now pass a custom validation context through to autosave associations in order to validate child associations with the same context. Fixes #13854. *Eric Chahin*, *Aaron Nelson*, *Kevin Casey* +* Stringify all variable keys of mysql connection configuration. + + When the `sql_mode` variable for mysql adapters is set in the configuration + as a `String`, it was ignored and overwritten by the strict mode option. + + Fixes #14895. + + *Paul Nikitochkin* + +* Ensure SQLite3 statements are closed on errors. + + Fixes #13631. + + *Timur Alperovich* + +* Give ActiveRecord::PredicateBuilder private methods the privacy they deserve. + + *Hector Satre* + * When using a custom `join_table` name on a `habtm`, rails was not saving it on Reflections. This causes a problem when rails loads fixtures, because it uses the reflections to set database with fixtures. diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index a30e5e4579..80cf7572df 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -304,7 +304,7 @@ module ActiveRecord def association_valid?(reflection, record) return true if record.destroyed? || record.marked_for_destruction? - validation_context = self.validation_context unless [:create,:update].include?(self.validation_context) + validation_context = self.validation_context unless [:create, :update].include?(self.validation_context) unless valid = record.valid?(validation_context) if reflection.options[:autosave] record.errors.each do |attribute, message| 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 75c58ac7d9..ebce0c0460 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -765,22 +765,22 @@ module ActiveRecord end def configure_connection - variables = @config[:variables] || {} + variables = @config.fetch(:variables, {}).stringify_keys # By default, MySQL 'where id is null' selects the last inserted id. # Turn this off. http://dev.rubyonrails.org/ticket/6778 - variables[:sql_auto_is_null] = 0 + variables['sql_auto_is_null'] = 0 # Increase timeout so the server doesn't disconnect us. wait_timeout = @config[:wait_timeout] wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum) - variables[:wait_timeout] = self.class.type_cast_config_to_integer(wait_timeout) + variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout) # Make MySQL reject illegal values rather than truncating or blanking them, see # http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_all_tables # If the user has provided another value for sql_mode, don't replace it. - if strict_mode? && !variables.has_key?(:sql_mode) - variables[:sql_mode] = 'STRICT_ALL_TABLES' + if strict_mode? && !variables.has_key?('sql_mode') + variables['sql_mode'] = 'STRICT_ALL_TABLES' end # NAMES does not have an equals sign, see diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index dd4261cec7..2c6186774f 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -296,9 +296,12 @@ module ActiveRecord # Don't cache statements if they are not prepared if without_prepared_statement?(binds) stmt = @connection.prepare(sql) - cols = stmt.columns - records = stmt.to_a - stmt.close + begin + cols = stmt.columns + records = stmt.to_a + ensure + stmt.close + end stmt = records else cache = @statements[sql] ||= { diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 0b56430b34..42c9881b48 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -242,7 +242,7 @@ module ActiveRecord return 0 if relation.limit_value == 0 query_builder = build_count_subquery(relation, column_name, distinct) - bind_values = relation.bind_values + bind_values = query_builder.bind_values + relation.bind_values else column = aggregate_column(column_name) @@ -389,9 +389,11 @@ module ActiveRecord aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias) relation.select_values = [aliased_column] - subquery = relation.arel.as(subquery_alias) + arel = relation.arel + subquery = arel.as(subquery_alias) sm = Arel::SelectManager.new relation.engine + sm.bind_values = arel.bind_values select_value = operation_over_aggregate_column(column_alias, 'count', distinct) sm.project(select_value).from(subquery) end diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 1252af7635..d40f276968 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -113,13 +113,14 @@ module ActiveRecord register_handler(Relation, RelationHandler.new) register_handler(Array, ArrayHandler.new) - private - def self.build(attribute, value) - handler_for(value).call(attribute, value) - end + def self.build(attribute, value) + handler_for(value).call(attribute, value) + end + private_class_method :build - def self.handler_for(object) - @handlers.detect { |klass, _| klass === object }.last - end + def self.handler_for(object) + @handlers.detect { |klass, _| klass === object }.last + end + private_class_method :handler_for end end diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb index 412efa22ff..4c90d06732 100644 --- a/activerecord/test/cases/adapters/mysql/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql/connection_test.rb @@ -151,6 +151,14 @@ class MysqlConnectionTest < ActiveRecord::TestCase end end + def test_mysql_sql_mode_variable_overides_strict_mode + run_without_connection do |orig_connection| + ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { 'sql_mode' => 'ansi' })) + result = ActiveRecord::Base.connection.exec_query 'SELECT @@SESSION.sql_mode' + assert_not_equal [['STRICT_ALL_TABLES']], result.rows + end + end + def test_mysql_set_session_variable_to_default run_without_connection do |orig_connection| ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => :default}})) diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb index 182d9409c7..65f50e77bb 100644 --- a/activerecord/test/cases/adapters/mysql2/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb @@ -77,6 +77,14 @@ class MysqlConnectionTest < ActiveRecord::TestCase end end + def test_mysql_sql_mode_variable_overides_strict_mode + run_without_connection do |orig_connection| + ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { 'sql_mode' => 'ansi' })) + result = ActiveRecord::Base.connection.exec_query 'SELECT @@SESSION.sql_mode' + assert_not_equal [['STRICT_ALL_TABLES']], result.rows + end + end + def test_mysql_set_session_variable_to_default run_without_connection do |orig_connection| ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => :default}})) diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb index ba89487838..e4b69fdf7b 100644 --- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb @@ -100,7 +100,7 @@ module ActiveRecord def quoted_id "'zomg'" end - } + }.new assert_raise(TypeError) { @conn.type_cast(quoted_id_obj, nil) } end end diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index 2630a0f3a4..e55525177f 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -416,6 +416,21 @@ module ActiveRecord assert @conn.respond_to?(:disable_extension) end + def test_statement_closed + db = SQLite3::Database.new(ActiveRecord::Base. + configurations['arunit']['database']) + statement = SQLite3::Statement.new(db, + 'CREATE TABLE statement_test (number integer not null)') + statement.stubs(:step).raises(SQLite3::BusyException, 'busy') + statement.stubs(:columns).once.returns([]) + statement.expects(:close).once + SQLite3::Statement.stubs(:new).returns(statement) + + assert_raises ActiveRecord::StatementInvalid do + @conn.exec_query 'select * from statement_test' + end + end + private def assert_logged logs diff --git a/activerecord/test/cases/associations/callbacks_test.rb b/activerecord/test/cases/associations/callbacks_test.rb index 968f36e92c..5b7e462f64 100644 --- a/activerecord/test/cases/associations/callbacks_test.rb +++ b/activerecord/test/cases/associations/callbacks_test.rb @@ -159,7 +159,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase activerecord.reload assert activerecord.developers_with_callbacks.size == 2 end - log_array = activerecord.developers_with_callbacks.flat_map {|d| ["before_removing#{d.id}","after_removing#{d.id}"]}.sort + activerecord.developers_with_callbacks.flat_map {|d| ["before_removing#{d.id}","after_removing#{d.id}"]}.sort assert activerecord.developers_with_callbacks.clear assert_predicate activerecord.developers_log, :empty? end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 000ea8055a..d748e80695 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -45,12 +45,18 @@ class HasManyAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :categories, :companies, :developers, :projects, :developers_projects, :topics, :authors, :comments, :people, :posts, :readers, :taggings, :cars, :essays, - :categorizations, :jobs + :categorizations, :jobs, :tags, :posts def setup Client.destroyed_client_ids.clear end + def test_sti_subselect_count + tag = Tag.first + len = Post.tagged_with(tag.id).limit(10).size + assert_operator len, :>, 0 + end + def test_anonymous_has_many developer = Class.new(ActiveRecord::Base) { self.table_name = 'developers' diff --git a/activerecord/test/cases/dup_test.rb b/activerecord/test/cases/dup_test.rb index 1e6ccecfab..409d9a82e2 100644 --- a/activerecord/test/cases/dup_test.rb +++ b/activerecord/test/cases/dup_test.rb @@ -1,4 +1,5 @@ require "cases/helper" +require 'models/reply' require 'models/topic' module ActiveRecord @@ -32,6 +33,14 @@ module ActiveRecord assert duped.new_record?, 'topic is new' end + def test_dup_not_destroyed + topic = Topic.first + topic.destroy + + duped = topic.dup + assert_not duped.destroyed? + end + def test_dup_has_no_id topic = Topic.first duped = topic.dup diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 9209672ac5..823146b399 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -234,19 +234,12 @@ class PersistenceTest < ActiveRecord::TestCase end def test_save_with_duping_of_destroyed_object - developer = Developer.create(name: "Kuldeep") + developer = Developer.first developer.destroy new_developer = developer.dup new_developer.save assert new_developer.persisted? - end - - def test_dup_of_destroyed_object_is_not_destroyed - developer = Developer.create(name: "Kuldeep") - developer.destroy - new_developer = developer.dup - new_developer.save - assert_equal new_developer.destroyed?, false + assert_not new_developer.destroyed? end def test_create_many diff --git a/guides/source/_license.html.erb b/guides/source/_license.html.erb index 00b4466f50..d22f016948 100644 --- a/guides/source/_license.html.erb +++ b/guides/source/_license.html.erb @@ -1,2 +1,2 @@ -<p>This work is licensed under a <a href="http://creativecommons.org/licenses/by-sa/3.0/">Creative Commons Attribution-Share Alike 3.0</a> License</p> +<p>This work is licensed under a <a href="https://creativecommons.org/licenses/by-sa/4.0/">Creative Commons Attribution-ShareAlike 4.0 International</a> License</p> <p>"Rails", "Ruby on Rails", and the Rails logo are trademarks of David Heinemeier Hansson. All rights reserved.</p> diff --git a/guides/source/_welcome.html.erb b/guides/source/_welcome.html.erb index 7e39f761f2..9101ed60d4 100644 --- a/guides/source/_welcome.html.erb +++ b/guides/source/_welcome.html.erb @@ -15,11 +15,5 @@ </p> <% end %> <p> - The guides for Rails 4.0.x are available at <a href="http://guides.rubyonrails.org/v4.0.4/">http://guides.rubyonrails.org/v4.0.4/</a>. -</p> -<p> - The guides for Rails 3.2.x are available at <a href="http://guides.rubyonrails.org/v3.2.17/">http://guides.rubyonrails.org/v3.2.17/</a>. -</p> -<p> - The guides for Rails 2.3.x are available at <a href="http://guides.rubyonrails.org/v2.3.11/">http://guides.rubyonrails.org/v2.3.11/</a>. + The guides for earlier releases: <a href="http://guides.rubyonrails.org/v4.1.0/">Rails 4.1.0</a>, <a href="http://guides.rubyonrails.org/v4.0.4/">Rails 4.0.4</a>, <a href="http://guides.rubyonrails.org/v3.2.17/">Rails 3.2.17</a> and <a href="http://guides.rubyonrails.org/v2.3.11/">Rails 2.3.11</a>. </p> diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index 28e1172274..edcf8fa998 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -144,7 +144,7 @@ WARNING: Docrails has a very strict policy: no code can be touched whatsoever, n Contributing to the Rails Code ------------------------------ -### Setting Up a Development Environment ### +### Setting Up a Development Environment To move on from submitting bugs to helping resolve existing issues or contributing your own code to Ruby on Rails, you _must_ be able to run its test suite. In this section of the guide you'll learn how to setup the tests on your own computer. @@ -156,7 +156,7 @@ The easiest and recommended way to get a development environment ready to hack i In case you can't use the Rails development box, see [this other guide](development_dependencies_install.html). -### Clone the Rails Repository ### +### Clone the Rails Repository To be able to contribute code, you need to clone the Rails repository: @@ -173,7 +173,7 @@ $ git checkout -b my_new_branch It doesn't matter much what name you use, because this branch will only exist on your local computer and your personal repository on GitHub. It won't be part of the Rails Git repository. -### Running an Application Against Your Local Branch ### +### Running an Application Against Your Local Branch In case you need a dummy Rails app to test changes, the `--dev` flag of `rails new` generates an application that uses your local branch: @@ -185,9 +185,9 @@ $ bundle exec rails new ~/my-test-app --dev The application generated in `~/my-test-app` runs against your local branch and in particular sees any modifications upon server reboot. -### Write Your Code ### +### Write Your Code -Now get busy and add/edit code. You're on your branch now, so you can write whatever you want (you can check to make sure you're on the right branch with `git branch -a`). But if you're planning to submit your change back for inclusion in Rails, keep a few things in mind: +Now get busy and add/edit code. You're on your branch now, so you can write whatever you want (make sure you're on the right branch with `git branch -a`). But if you're planning to submit your change back for inclusion in Rails, keep a few things in mind: * Get the code right. * Use Rails idioms and helpers. @@ -215,7 +215,8 @@ Rails follows a simple set of coding style conventions: The above are guidelines - please use your best judgment in using them. -### Running Tests ### +### Running Tests + It is not customary in Rails to run the full test suite before pushing changes. The railties test suite in particular takes a long time, and even more if the source code is mounted in `/vagrant` as happens in the recommended @@ -228,20 +229,36 @@ tests are passing, that's enough to propose your contribution. We have unexpected breakages elsewhere. #### Entire Rails: + To run all the tests, do: + ```bash $ cd rails $ bundle exec rake test ``` -#### Particular component of Rails -To run tests only for particular component(ActionPack, ActiveRecord, etc.). For -example, to run `ActionMailer` tests you can: + +#### For a Particular Component + +You can run tests only for a particular component (e.g. Action Pack). For example, +to run Action Mailer tests: ```bash $ cd actionmailer $ bundle exec rake test ``` +#### Running a Single Test + +You can run a single test through ruby. For instance: + +```bash +$ cd actionmailer +$ ruby -w -Itest test/mail_layout_test.rb -n test_explicit_class_layout +``` + +The `-n` option allows you to run a single method instead of the whole +file. + ##### Testing Active Record This is how you run the Active Record test suite only for SQLite3: @@ -275,15 +292,7 @@ $ ARCONN=sqlite3 ruby -Itest test/cases/associations/has_many_associations_test. You can invoke `test_jdbcmysql`, `test_jdbcsqlite3` or `test_jdbcpostgresql` also. See the file `activerecord/RUNNING_UNIT_TESTS.rdoc` for information on running more targeted database tests, or the file `ci/travis.rb` for the test suite run by the continuous integration server. -#### Single Test separately -to run just one test. For example, to run `LayoutMailerTest` you can: - -```bash -$ cd actionmailer -$ ruby -w -Ilib:test test/mail_layout_test.rb -``` - -### Warnings ### +### Warnings The test suite runs with warnings enabled. Ideally, Ruby on Rails should issue no warnings, but there may be a few, as well as some from third-party libraries. Please ignore (or fix!) them, if any, and submit patches that do not issue new warnings. @@ -292,7 +301,8 @@ If you are sure about what you are doing and would like to have a more clear out ```bash $ RUBYOPT=-W0 bundle exec rake test ``` -### Updating the CHANGELOG ### + +### Updating the CHANGELOG The CHANGELOG is an important part of every release. It keeps the list of changes for every Rails version. @@ -317,7 +327,7 @@ A CHANGELOG entry should summarize what was changed and should end with author's Your name can be added directly after the last word if you don't provide any code examples or don't need multiple paragraphs. Otherwise, it's best to make as a new paragraph. -### Sanity Check ### +### Sanity Check You should not be the only person who looks at the code before you submit it. If you know someone else who uses Rails, try asking them if they'll check out @@ -327,7 +337,7 @@ private before you push a patch out publicly is the "smoke test" for a patch: if you can't convince one other developer of the beauty of your code, you’re unlikely to convince the core team either. -### Commit Your Changes ### +### Commit Your Changes When you're happy with the code on your computer, you need to commit the changes to Git: @@ -367,7 +377,7 @@ You can also add bullet points: TIP. Please squash your commits into a single commit when appropriate. This simplifies future cherry picks, and also keeps the git log clean. -### Update Your Branch ### +### Update Your Branch It's pretty likely that other changes to master have happened while you were working. Go get them: @@ -385,7 +395,7 @@ $ git rebase master No conflicts? Tests still pass? Change still seems reasonable to you? Then move on. -### Fork ### +### Fork Navigate to the Rails [GitHub repository](https://github.com/rails/rails) and press "Fork" in the upper right hand corner. @@ -515,14 +525,13 @@ $ git push origin my_pull_request -f You should be able to refresh the pull request on GitHub and see that it has been updated. - -### Older Versions of Ruby on Rails ### +### Older Versions of Ruby on Rails If you want to add a fix to older versions of Ruby on Rails, you'll need to set up and switch to your own local tracking branch. Here is an example to switch to the 3-0-stable branch: ```bash -$ git branch --track 3-0-stable origin/3-0-stable -$ git checkout 3-0-stable +$ git branch --track 4-0-stable origin/4-0-stable +$ git checkout 4-0-stable ``` TIP: You may want to [put your Git branch name in your shell prompt](http://qugstart.com/blog/git-and-svn/add-colored-git-branch-name-to-your-shell-prompt/) to make it easier to remember which version of the code you're working with. diff --git a/guides/source/security.md b/guides/source/security.md index 0d347c9e4b..0f4d4e712b 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -135,8 +135,8 @@ NOTE: _Apart from stealing a user's session id, the attacker may fix a session i This attack focuses on fixing a user's session id known to the attacker, and forcing the user's browser into using this id. It is therefore not necessary for the attacker to steal the session id afterwards. Here is how this attack works: * The attacker creates a valid session id: They load the login page of the web application where they want to fix the session, and take the session id in the cookie from the response (see number 1 and 2 in the image). -* They possibly maintains the session. Expiring sessions, for example every 20 minutes, greatly reduces the time-frame for attack. Therefore they access the web application from time to time in order to keep the session alive. -* Now the attacker will force the user's browser into using this session id (see number 3 in the image). As you may not change a cookie of another domain (because of the same origin policy), the attacker has to run a JavaScript from the domain of the target web application. Injecting the JavaScript code into the application by XSS accomplishes this attack. Here is an example: `<script>document.cookie="_session_id=16d5b78abb28e3d6206b60f22a03c8d9";</script>`. Read more about XSS and injection later on. +* They maintain the session by accessing the web application periodically in order to keep an expiring session alive. +* The attacker forces the user's browser into using this session id (see number 3 in the image). As you may not change a cookie of another domain (because of the same origin policy), the attacker has to run a JavaScript from the domain of the target web application. Injecting the JavaScript code into the application by XSS accomplishes this attack. Here is an example: `<script>document.cookie="_session_id=16d5b78abb28e3d6206b60f22a03c8d9";</script>`. Read more about XSS and injection later on. * The attacker lures the victim to the infected page with the JavaScript code. By viewing the page, the victim's browser will change the session id to the trap session id. * As the new trap session is unused, the web application will require the user to authenticate. * From now on, the victim and the attacker will co-use the web application with the same session: The session became valid and the victim didn't notice the attack. diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 6a31a923a7..480ec32443 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,9 @@ +* Reading name and email from git for plugin gemspec. + + Fixes #9589. + + *Arun Agrawal*, *Abd ar-Rahman Hamidi*, *Roman Shmatov* + * Fix `console` and `generators` blocks defined at different environments. Fixes #14748. diff --git a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb index 10f80abb15..da99e74435 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb @@ -21,13 +21,8 @@ <%%= f.label :password_confirmation %><br> <%%= f.password_field :password_confirmation %> <% else -%> - <%- if attribute.reference? -%> <%%= f.label :<%= attribute.column_name %> %><br> <%%= f.<%= attribute.field_type %> :<%= attribute.column_name %> %> - <%- else -%> - <%%= f.label :<%= attribute.name %> %><br> - <%%= f.<%= attribute.field_type %> :<%= attribute.name %> %> - <%- end -%> <% end -%> </div> <% end -%> diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb index f6f529b80a..584f776c01 100644 --- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb +++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb @@ -288,6 +288,10 @@ task default: :test options[:mountable] end + def skip_git? + options[:skip_git] + end + def with_dummy_app? options[:skip_test_unit].blank? || options[:dummy_path] != 'test/dummy' end @@ -304,6 +308,24 @@ task default: :test @camelized ||= name.gsub(/\W/, '_').squeeze('_').camelize end + def author + default = "TODO: Write your name" + if skip_git? + @author = default + else + @author = `git config user.name`.chomp rescue default + end + end + + def email + default = "TODO: Write your email address" + if skip_git? + @email = default + else + @email = `git config user.email`.chomp rescue default + end + end + def valid_const? if original_name =~ /[^0-9a-zA-Z_]+/ raise Error, "Invalid plugin name #{original_name}. Please give a name which use only alphabetic or numeric or \"_\" characters." diff --git a/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec index 5fdf0e1554..919c349470 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec +++ b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec @@ -7,8 +7,8 @@ require "<%= name %>/version" Gem::Specification.new do |s| s.name = "<%= name %>" s.version = <%= camelized %>::VERSION - s.authors = ["TODO: Your name"] - s.email = ["TODO: Your email"] + s.authors = ["<%= author %>"] + s.email = ["<%= email %>"] s.homepage = "TODO" s.summary = "TODO: Summary of <%= camelized %>." s.description = "TODO: Description of <%= camelized %>." diff --git a/railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE b/railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE index d7a9109894..ff2fb3ba4e 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE +++ b/railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright <%= Date.today.year %> YOURNAME +Copyright <%= Date.today.year %> <%= author %> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb index 853af80111..69ff23eb95 100644 --- a/railties/test/generators/plugin_generator_test.rb +++ b/railties/test/generators/plugin_generator_test.rb @@ -371,6 +371,40 @@ class PluginGeneratorTest < Rails::Generators::TestCase end end + def test_git_name_and_email_in_gemspec_file + name = `git config user.name`.chomp rescue "TODO: Write your name" + email = `git config user.email`.chomp rescue "TODO: Write your email address" + + run_generator [destination_root] + assert_file "bukkits.gemspec" do |contents| + assert_match(/#{Regexp.escape(name)}/, contents) + assert_match(/#{Regexp.escape(email)}/, contents) + end + end + + def test_git_name_in_license_file + name = `git config user.name`.chomp rescue "TODO: Write your name" + + run_generator [destination_root] + assert_file "MIT-LICENSE" do |contents| + assert_match(/#{Regexp.escape(name)}/, contents) + end + end + + def test_no_details_from_git_when_skip_git + name = "TODO: Write your name" + email = "TODO: Write your email address" + + run_generator [destination_root, '--skip-git'] + assert_file "MIT-LICENSE" do |contents| + assert_match(/#{Regexp.escape(name)}/, contents) + end + assert_file "bukkits.gemspec" do |contents| + assert_match(/#{Regexp.escape(name)}/, contents) + assert_match(/#{Regexp.escape(email)}/, contents) + end + end + protected def action(*args, &block) silence(:stdout){ generator.send(*args, &block) } |