diff options
128 files changed, 1003 insertions, 536 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6d3cf072c1..19b7b638b6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,6 +4,8 @@ Ruby on Rails is a volunteer effort. We encourage you to pitch in. [Join the tea * If you want to submit a patch, please read the [Contributing to Ruby on Rails](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html) guide. +* If you want to contribute to Rails documentation, please read the [Contributing to the Rails Documentation](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation) section of the aforementioned guide. + *We only accept bug reports and pull requests in GitHub*. * If you have a question about how to use Ruby on Rails, please [ask the rubyonrails-talk mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-talk). diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 5b914f293d..d055acb296 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -77,7 +77,7 @@ module ActionDispatch # domain and subdomains. # # * <tt>:expires</tt> - The time at which this cookie expires, as a \Time object. - # * <tt>:secure</tt> - Whether this cookie is a only transmitted to HTTPS servers. + # * <tt>:secure</tt> - Whether this cookie is only transmitted to HTTPS servers. # Default is +false+. # * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or # only HTTP. Defaults to +false+. diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb index edf37bb9a5..2dfaab3587 100644 --- a/actionpack/lib/action_dispatch/railtie.rb +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -20,8 +20,7 @@ module ActionDispatch config.action_dispatch.default_headers = { 'X-Frame-Options' => 'SAMEORIGIN', 'X-XSS-Protection' => '1; mode=block', - 'X-Content-Type-Options' => 'nosniff', - 'X-UA-Compatible' => 'chrome=1' + 'X-Content-Type-Options' => 'nosniff' } config.eager_load_namespaces << ActionDispatch diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index c25b1efc2b..e2c50fec47 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -267,7 +267,7 @@ module ActionView method_name = self.method_name code = @handler.call(self) - # Make sure that the resulting String to be evalled is in the + # Make sure that the resulting String to be eval'd is in the # encoding of the code source = <<-end_src def #{method_name}(local_assigns, output_buffer) diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 74f5253c11..2fbe7358f9 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -182,8 +182,7 @@ class ResponseTest < ActiveSupport::TestCase ActionDispatch::Response.default_headers = { 'X-Frame-Options' => 'DENY', 'X-Content-Type-Options' => 'nosniff', - 'X-XSS-Protection' => '1;', - 'X-UA-Compatible' => 'chrome=1' + 'X-XSS-Protection' => '1;' } resp = ActionDispatch::Response.new.tap { |response| response.body = 'Hello' @@ -193,7 +192,6 @@ class ResponseTest < ActiveSupport::TestCase assert_equal('DENY', resp.headers['X-Frame-Options']) assert_equal('nosniff', resp.headers['X-Content-Type-Options']) assert_equal('1;', resp.headers['X-XSS-Protection']) - assert_equal('chrome=1', resp.headers['X-UA-Compatible']) ensure ActionDispatch::Response.default_headers = nil end diff --git a/actionpack/test/template/dependency_tracker_test.rb b/actionpack/test/template/dependency_tracker_test.rb index 8588925de3..7a9b4b26ac 100644 --- a/actionpack/test/template/dependency_tracker_test.rb +++ b/actionpack/test/template/dependency_tracker_test.rb @@ -45,7 +45,7 @@ class DependencyTrackerTest < ActionView::TestCase end end -class ERBTrackerTest < MiniTest::Unit::TestCase +class ERBTrackerTest < Minitest::Test def make_tracker(name, template) ActionView::DependencyTracker::ERBTracker.new(name, template) end diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb index 8b09f8b203..377aa6ee27 100644 --- a/activemodel/lib/active_model/callbacks.rb +++ b/activemodel/lib/active_model/callbacks.rb @@ -135,7 +135,10 @@ module ActiveModel klass.define_singleton_method("after_#{callback}") do |*args, &block| options = args.extract_options! options[:prepend] = true - options[:if] = Array(options[:if]) << "value != false" + conditional = ActiveSupport::Callbacks::Conditionals::Value.new { |v| + v != false + } + options[:if] = Array(options[:if]) << conditional set_callback(:"#{callback}", :after, *(args << options), &block) end end diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index cafdb946c0..ea5ddf71de 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -142,23 +142,23 @@ module ActiveModel @changed_attributes ||= {} end - private + # Handle <tt>*_changed?</tt> for +method_missing+. + def attribute_changed?(attr) + changed_attributes.include?(attr) + end - # Handle <tt>*_changed?</tt> for +method_missing+. - def attribute_changed?(attr) - changed_attributes.include?(attr) - end + # Handle <tt>*_was</tt> for +method_missing+. + def attribute_was(attr) + attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr) + end + + private # Handle <tt>*_change</tt> for +method_missing+. def attribute_change(attr) [changed_attributes[attr], __send__(attr)] if attribute_changed?(attr) end - # Handle <tt>*_was</tt> for +method_missing+. - def attribute_was(attr) - attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr) - end - # Handle <tt>*_will_change!</tt> for +method_missing+. def attribute_will_change!(attr) return if attribute_changed?(attr) diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb index 9d984b7a18..05e2e089e5 100644 --- a/activemodel/lib/active_model/serializers/json.rb +++ b/activemodel/lib/active_model/serializers/json.rb @@ -109,7 +109,7 @@ module ActiveModel # # def attributes=(hash) # hash.each do |key, value| - # instance_variable_set("@#{key}", value) + # send("#{key}=", value) # end # end # diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index 92206450d2..31c2245265 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -142,7 +142,9 @@ module ActiveModel if options.key?(:on) options = options.dup options[:if] = Array(options[:if]) - options[:if].unshift("validation_context == :#{options[:on]}") + options[:if].unshift lambda { |o| + o.validation_context == options[:on] + } end args << options set_callback(:validate, *args, &block) @@ -226,7 +228,6 @@ module ActiveModel # Person.validators_on(:name) # # => [ # # #<ActiveModel::Validations::PresenceValidator:0x007fe604914e60 @attributes=[:name], @options={}>, - # # #<ActiveModel::Validations::InclusionValidator:0x007fe603bb8780 @attributes=[:age], @options={in:0..99}> # # ] def validators_on(*attributes) attributes.flat_map do |attribute| diff --git a/activemodel/test/cases/railtie_test.rb b/activemodel/test/cases/railtie_test.rb index d44a3448df..0643fa775d 100644 --- a/activemodel/test/cases/railtie_test.rb +++ b/activemodel/test/cases/railtie_test.rb @@ -7,8 +7,12 @@ class RailtieTest < ActiveModel::TestCase def setup require 'active_model/railtie' + # Set a fake logger to avoid creating the log directory automatically + fake_logger = mock() + @app ||= Class.new(::Rails::Application) do config.eager_load = false + config.logger = fake_logger end end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index e5312d7d7c..ab1eb49286 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,71 @@ +* Ambiguous reflections are on :through relationships are no longer supported. + For example, you need to change this: + + class Author < ActiveRecord::Base + has_many :posts + has_many :taggings, :through => :posts + end + + class Post < ActiveRecord::Base + has_one :tagging + has_many :taggings + end + + class Tagging < ActiveRecord::Base + end + + To this: + + class Author < ActiveRecord::Base + has_many :posts + has_many :taggings, :through => :posts, :source => :tagging + end + + class Post < ActiveRecord::Base + has_one :tagging + has_many :taggings + end + + class Tagging < ActiveRecord::Base + end + +* Remove column restrictions for `count`, let the database raise if the SQL is + invalid. The previous behavior was untested and surprising for the user. + Fixes #5554. + + Example: + + User.select("name, username").count + # Before => SELECT count(*) FROM users + # After => ActiveRecord::StatementInvalid + + # you can still use `count(:all)` to perform a query unrelated to the + # selected columns + User.select("name, username").count(:all) # => SELECT count(*) FROM users + + *Yves Senn* + +* Rails now automatically detects inverse associations. If you do not set the + `:inverse_of` option on the association, then Active Record will guess the + inverse association based on heuristics. + + Note that automatic inverse detection only works on `has_many`, `has_one`, + and `belongs_to` associations. Extra options on the associations will + also prevent the association's inverse from being found automatically. + + The automatic guessing of the inverse association uses a heuristic based + on the name of the class, so it may not work for all associations, + especially the ones with non-standard names. + + You can turn off the automatic detection of inverse associations by setting + the `:inverse_of` option to `false` like so: + + class Taggable < ActiveRecord::Base + belongs_to :tag, inverse_of: false + end + + *John Wang* + * Fix `add_column` with `array` option when using PostgreSQL. Fixes #10432 *Adam Anderson* @@ -19,7 +87,7 @@ *Yves Senn* -* Fix bug where tiny types are incorectly coerced as booleand when the length is more than 1. +* Fix bug where tiny types are incorrectly coerced as boolean when the length is more than 1. Fixes #10620. @@ -102,8 +170,8 @@ *Olek Janiszewski* -* fixes bug introduced by #3329. Now, when autosaving associations, - deletions happen before inserts and saves. This prevents a 'duplicate +* 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 the same value on a unique indexed field as that of a record being destroyed. diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 3490057298..6fd4f3042c 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -586,9 +586,10 @@ module ActiveRecord # belongs_to :tag, inverse_of: :taggings # end # - # If you do not set the +:inverse_of+ record, the association will do its - # best to match itself up with the correct inverse. Automatic +:inverse_of+ - # detection only works on +has_many+, +has_one+, and +belongs_to+ associations. + # If you do not set the <tt>:inverse_of</tt> record, the association will + # do its best to match itself up with the correct inverse. Automatic + # inverse detection only works on <tt>has_many</tt>, <tt>has_one</tt>, and + # <tt>belongs_to</tt> associations. # # Extra options on the associations, as defined in the # <tt>AssociationReflection::INVALID_AUTOMATIC_INVERSE_OPTIONS</tt> constant, will @@ -599,10 +600,10 @@ module ActiveRecord # especially the ones with non-standard names. # # You can turn off the automatic detection of inverse associations by setting - # the +:automatic_inverse_of+ option to +false+ like so: + # the <tt>:inverse_of</tt> option to <tt>false</tt> like so: # # class Taggable < ActiveRecord::Base - # belongs_to :tag, automatic_inverse_of: false + # belongs_to :tag, inverse_of: false # end # # == Nested \Associations diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index 608a6af16c..ee62298793 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -200,13 +200,14 @@ module ActiveRecord creation_attributes.each { |key, value| record[key] = value } end - # Should be true if there is a foreign key present on the owner which + # Returns true if there is a foreign key present on the owner which # references the target. This is used to determine whether we can load # the target if the owner is currently a new record (and therefore - # without a key). + # without a key). If the owner is a new record then foreign_key must + # be present in order to load target. # # Currently implemented by belongs_to (vanilla and polymorphic) and - # has_one/has_many :through associations which go through a belongs_to + # has_one/has_many :through associations which go through a belongs_to. def foreign_key_present? false end diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index aa5551fe0c..f1bec5787a 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -96,7 +96,7 @@ module ActiveRecord item = eval_scope(klass, scope_chain_item) if scope_chain_item == self.reflection.scope - scope.merge! item.except(:where, :includes) + scope.merge! item.except(:where, :includes, :bind) end scope.includes! item.includes_values diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb index 6a3658f328..3254da4677 100644 --- a/activerecord/lib/active_record/associations/builder/association.rb +++ b/activerecord/lib/active_record/associations/builder/association.rb @@ -111,13 +111,8 @@ module ActiveRecord::Associations::Builder ) end - mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 - def #{macro}_dependent_for_#{name} - association(:#{name}).handle_dependency - end - CODE - - model.before_destroy "#{macro}_dependent_for_#{name}" + n = name + model.before_destroy lambda { |o| o.association(n).handle_dependency } end def valid_dependent_options diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb index 63e9526436..d4e1a0dda1 100644 --- a/activerecord/lib/active_record/associations/builder/belongs_to.rb +++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb @@ -19,82 +19,116 @@ module ActiveRecord::Associations::Builder reflection end - def add_counter_cache_callbacks(reflection) - cache_column = reflection.counter_cache_column - foreign_key = reflection.foreign_key + def valid_dependent_options + [:destroy, :delete] + end + + private - mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 - def belongs_to_counter_cache_after_create_for_#{name} - if record = #{name} - record.class.increment_counter(:#{cache_column}, record.id) + def add_counter_cache_methods(mixin) + return if mixin.method_defined? :belongs_to_counter_cache_after_create + + mixin.class_eval do + def belongs_to_counter_cache_after_create(association, reflection) + if record = send(association.name) + cache_column = reflection.counter_cache_column + record.class.increment_counter(cache_column, record.id) @_after_create_counter_called = true end end - 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} + def belongs_to_counter_cache_before_destroy(association, reflection) + foreign_key = reflection.foreign_key.to_sym + unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key + record = send association.name if record && !self.destroyed? - record.class.decrement_counter(:#{cache_column}, record.id) + cache_column = reflection.counter_cache_column + record.class.decrement_counter(cache_column, record.id) end end end - def belongs_to_counter_cache_after_update_for_#{name} + def belongs_to_counter_cache_after_update(association, reflection) + foreign_key = reflection.foreign_key + cache_column = reflection.counter_cache_column + if (@_after_create_counter_called ||= false) @_after_create_counter_called = false - elsif self.#{foreign_key}_changed? && !new_record? && defined?(#{name.to_s.camelize}) - model = #{name.to_s.camelize} - foreign_key_was = self.#{foreign_key}_was - foreign_key = self.#{foreign_key} + elsif attribute_changed?(foreign_key) && !new_record? && association.constructable? + model = reflection.klass + foreign_key_was = attribute_was foreign_key + foreign_key = attribute foreign_key if foreign_key && model.respond_to?(:increment_counter) - model.increment_counter(:#{cache_column}, foreign_key) + model.increment_counter(cache_column, foreign_key) end if foreign_key_was && model.respond_to?(:decrement_counter) - model.decrement_counter(:#{cache_column}, foreign_key_was) + model.decrement_counter(cache_column, foreign_key_was) end end end - CODE + end + end + + def add_counter_cache_callbacks(reflection) + cache_column = reflection.counter_cache_column + add_counter_cache_methods mixin + association = self + + model.after_create lambda { |record| + record.belongs_to_counter_cache_after_create(association, reflection) + } - model.after_create "belongs_to_counter_cache_after_create_for_#{name}" - model.before_destroy "belongs_to_counter_cache_before_destroy_for_#{name}" - model.after_update "belongs_to_counter_cache_after_update_for_#{name}" + model.before_destroy lambda { |record| + record.belongs_to_counter_cache_before_destroy(association, reflection) + } + + model.after_update lambda { |record| + record.belongs_to_counter_cache_after_update(association, reflection) + } klass = reflection.class_name.safe_constantize klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly) end - def add_touch_callbacks(reflection) - mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 - def belongs_to_touch_after_save_or_destroy_for_#{name} - foreign_key_field = #{reflection.foreign_key.inspect} - old_foreign_id = attribute_was(foreign_key_field) + def self.touch_record(o, foreign_key, name, touch) # :nodoc: + old_foreign_id = o.attribute_was(foreign_key) - if old_foreign_id - klass = association(#{name.inspect}).klass - old_record = klass.find_by(klass.primary_key => old_foreign_id) + if old_foreign_id + klass = o.association(name).klass + old_record = klass.find_by(klass.primary_key => old_foreign_id) - if old_record - old_record.touch #{options[:touch].inspect if options[:touch] != true} - end - end - - record = #{name} - unless record.nil? || record.new_record? - record.touch #{options[:touch].inspect if options[:touch] != true} + if old_record + if touch != true + old_record.touch touch + else + old_record.touch end end - CODE - - model.after_save "belongs_to_touch_after_save_or_destroy_for_#{name}" - model.after_touch "belongs_to_touch_after_save_or_destroy_for_#{name}" - model.after_destroy "belongs_to_touch_after_save_or_destroy_for_#{name}" + end + + record = o.send name + unless record.nil? || record.new_record? + if touch != true + record.touch touch + else + record.touch + end + end end - def valid_dependent_options - [:destroy, :delete] + def add_touch_callbacks(reflection) + foreign_key = reflection.foreign_key + n = name + touch = options[:touch] + + callback = lambda { |record| + BelongsTo.touch_record(record, foreign_key, n, touch) + } + + model.after_save callback + model.after_touch callback + model.after_destroy callback end end end diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb index 429def5455..0d1bdd21ee 100644 --- a/activerecord/lib/active_record/associations/builder/has_many.rb +++ b/activerecord/lib/active_record/associations/builder/has_many.rb @@ -5,7 +5,7 @@ module ActiveRecord::Associations::Builder end def valid_options - super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :automatic_inverse_of, :counter_cache] + super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache] end def valid_dependent_options diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb index 96ccbeb8a3..76e48e66e5 100644 --- a/activerecord/lib/active_record/associations/builder/singular_association.rb +++ b/activerecord/lib/active_record/associations/builder/singular_association.rb @@ -3,7 +3,7 @@ module ActiveRecord::Associations::Builder class SingularAssociation < Association #:nodoc: def valid_options - super + [:remote, :dependent, :counter_cache, :primary_key, :inverse_of, :automatic_inverse_of] + super + [:remote, :dependent, :counter_cache, :primary_key, :inverse_of] end def constructable? diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index c982d65d65..0be4b5cb19 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -25,6 +25,9 @@ module ActiveRecord end end + class ChangeColumnDefinition < Struct.new(:column, :type, :options) #:nodoc: + end + # Represents the schema of an SQL table in an abstract way. This class # provides methods for manipulating the schema representation. # diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 2a4eda3622..3ac55a0f11 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -694,17 +694,6 @@ module ActiveRecord end end - def add_column_options!(sql, options) #:nodoc: - sql << " DEFAULT #{quote(options[:default], options[:column])}" if options_include_default?(options) - # must explicitly check for :null to allow change_column to work on migrations - if options[:null] == false - sql << " NOT NULL" - end - if options[:auto_increment] == true - sql << " AUTO_INCREMENT" - end - end - # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause. # # distinct("posts.id", ["posts.created_at desc"]) diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 26586f0974..e232cad982 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -116,6 +116,12 @@ module ActiveRecord send m, o end + def visit_AddColumn(o) + sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale) + sql = "ADD #{quote_column_name(o.name)} #{sql_type}" + add_column_options!(sql, column_options(o)) + end + private def visit_AlterTable(o) @@ -123,12 +129,6 @@ module ActiveRecord sql << o.adds.map { |col| visit_AddColumn col }.join(' ') end - def visit_AddColumn(o) - sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale) - sql = "ADD #{quote_column_name(o.name)} #{sql_type}" - add_column_options!(sql, column_options(o)) - end - def visit_ColumnDefinition(o) sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale) column_sql = "#{quote_column_name(o.name)} #{sql_type}" @@ -149,6 +149,8 @@ module ActiveRecord column_options[:null] = o.null unless o.null.nil? column_options[:default] = o.default unless o.default.nil? column_options[:column] = o + column_options[:first] = o.first + column_options[:after] = o.after column_options end @@ -164,9 +166,20 @@ module ActiveRecord @conn.type_to_sql type.to_sym, limit, precision, scale end - def add_column_options!(column_sql, column_options) - @conn.add_column_options! column_sql, column_options - column_sql + def add_column_options!(sql, options) + sql << " DEFAULT #{@conn.quote(options[:default], options[:column])}" if options_include_default?(options) + # must explicitly check for :null to allow change_column to work on migrations + if options[:null] == false + sql << " NOT NULL" + end + if options[:auto_increment] == true + sql << " AUTO_INCREMENT" + end + sql + end + + def options_include_default?(options) + options.include?(:default) && !(options[:null] == false && options[:default].nil?) end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index d098ded77c..5b25b26164 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -4,17 +4,26 @@ module ActiveRecord module ConnectionAdapters class AbstractMysqlAdapter < AbstractAdapter class SchemaCreation < AbstractAdapter::SchemaCreation - private def visit_AddColumn(o) - add_column_position!(super, o) + add_column_position!(super, column_options(o)) + end + + private + def visit_ChangeColumnDefinition(o) + column = o.column + options = o.options + sql_type = type_to_sql(o.type, options[:limit], options[:precision], options[:scale]) + change_column_sql = "CHANGE #{quote_column_name(column.name)} #{quote_column_name(options[:name])} #{sql_type}" + add_column_options!(change_column_sql, options) + add_column_position!(change_column_sql, options) end - def add_column_position!(sql, column) - if column.first + def add_column_position!(sql, options) + if options[:first] sql << " FIRST" - elsif column.after - sql << " AFTER #{quote_column_name(column.after)}" + elsif options[:after] + sql << " AFTER #{quote_column_name(options[:after])}" end sql end @@ -661,10 +670,9 @@ module ActiveRecord end def add_column_sql(table_name, column_name, type, options = {}) - add_column_sql = "ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" - add_column_options!(add_column_sql, options) - add_column_position!(add_column_sql, options) - add_column_sql + td = create_table_definition table_name, options[:temporary], options[:options] + cd = td.new_column_definition(column_name, type, options) + schema_creation.visit_AddColumn cd end def change_column_sql(table_name, column_name, type, options = {}) @@ -678,14 +686,12 @@ module ActiveRecord options[:null] = column.null end - change_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" - add_column_options!(change_column_sql, options) - add_column_position!(change_column_sql, options) - change_column_sql + options[:name] = column.name + schema_creation.accept ChangeColumnDefinition.new column, type, options end def rename_column_sql(table_name, column_name, new_column_name) - options = {} + options = { name: new_column_name } if column = columns(table_name).find { |c| c.name == column_name.to_s } options[:default] = column.default @@ -696,9 +702,7 @@ module ActiveRecord end current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"] - rename_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}" - add_column_options!(rename_column_sql, options) - rename_column_sql + schema_creation.accept ChangeColumnDefinition.new column, current_type, options end def remove_column_sql(table_name, column_name, type = nil, options = {}) diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb index 81cca37939..e1faadf1ab 100644 --- a/activerecord/lib/active_record/counter_cache.rb +++ b/activerecord/lib/active_record/counter_cache.rb @@ -50,7 +50,7 @@ module ActiveRecord # ==== Parameters # # * +id+ - The id of the object you wish to update a counter on or an Array of ids. - # * +counters+ - An Array of Hashes containing the names of the fields + # * +counters+ - A Hash containing the names of the fields # to update as keys and the amount to update the field by as values. # # ==== Examples diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 017b8bace6..7e38719811 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -69,10 +69,6 @@ module ActiveRecord end end - # Raised when SQL statement is invalid and the application gets a blank result. - class ThrowResult < ActiveRecordError - end - # Defunct wrapper class kept for compatibility. # +StatementInvalid+ wraps the original exception now. class WrappedDatabaseException < StatementInvalid diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index 40976bc29e..e826762def 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -5,7 +5,7 @@ module ActiveRecord extend ActiveSupport::Concern included do - # Determine whether to store the full constant name including namespace when using STI + # Determines whether to store the full constant name including namespace when using STI. class_attribute :store_full_sti_class, instance_writer: false self.store_full_sti_class = true end @@ -13,7 +13,7 @@ module ActiveRecord module ClassMethods # Determines if one of the attributes passed in is the inheritance column, # and if the inheritance column is attr accessible, it initializes an - # instance of the given subclass instead of the base class + # instance of the given subclass instead of the base class. def new(*args, &block) if abstract_class? || self == Base raise NotImplementedError, "#{self} is an abstract class and can not be instantiated." @@ -27,7 +27,8 @@ module ActiveRecord super end - # True if this isn't a concrete subclass needing a STI type condition. + # Returns +true+ if this does not need STI type condition. Returns + # +false+ if STI type condition needs to be applied. def descends_from_active_record? if self == Base false diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 8bdaeef924..e53e8553ad 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -230,6 +230,10 @@ module ActiveRecord # validates_presence_of :member # end # + # Note that if you do not specify the <tt>inverse_of</tt> option, then + # Active Record will try to automatically guess the inverse association + # based on heuristics. + # # 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.: @@ -302,14 +306,9 @@ module ActiveRecord attr_names.each do |association_name| if reflection = reflect_on_association(association_name) - reflection.options[:autosave] = true + reflection.autosave = true add_autosave_association_callbacks(reflection) - # Clear cached values of any inverse associations found in the - # reflection and prevent the reflection from finding inverses - # automatically in the future. - reflection.remove_automatic_inverse_of! - nested_attributes_options = self.nested_attributes_options.dup nested_attributes_options[association_name.to_sym] = options self.nested_attributes_options = nested_attributes_options diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb index 711fc8b883..d166f0dd66 100644 --- a/activerecord/lib/active_record/null_relation.rb +++ b/activerecord/lib/active_record/null_relation.rb @@ -39,7 +39,7 @@ module ActiveRecord end def to_sql - @to_sql ||= "" + "" end def where_values_hash @@ -55,7 +55,11 @@ module ActiveRecord end def calculate(_operation, _column_name, _options = {}) - nil + if _operation == :count + 0 + else + nil + end end def exists?(_id = false) diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index f69e9b2217..0b74553bf8 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -324,7 +324,7 @@ db_namespace = namespace :db do ActiveRecord::Schema.verbose = false db_namespace["schema:load"].invoke ensure - ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[Rails.env]) + ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[ActiveRecord::Tasks::DatabaseTasks.env]) end end diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 27aa20b6c0..8a9488656b 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -5,7 +5,9 @@ module ActiveRecord included do class_attribute :reflections + class_attribute :aggregate_reflections self.reflections = {} + self.aggregate_reflections = {} end # \Reflection enables to interrogate Active Record classes and objects @@ -27,13 +29,18 @@ module ActiveRecord reflection = klass.new(macro, name, scope, options, active_record) - self.reflections = self.reflections.merge(name => reflection) + if klass == AggregateReflection + self.aggregate_reflections = self.aggregate_reflections.merge(name => reflection) + else + self.reflections = self.reflections.merge(name => reflection) + end + reflection end # Returns an array of AggregateReflection objects for all the aggregations in the class. def reflect_on_all_aggregations - reflections.values.grep(AggregateReflection) + aggregate_reflections.values end # Returns the AggregateReflection object for the named +aggregation+ (use the symbol). @@ -41,8 +48,7 @@ module ActiveRecord # Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection # def reflect_on_aggregation(aggregation) - reflection = reflections[aggregation] - reflection if reflection.is_a?(AggregateReflection) + aggregate_reflections[aggregation] end # Returns an array of AssociationReflection objects for all the @@ -56,7 +62,7 @@ module ActiveRecord # Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations # def reflect_on_all_associations(macro = nil) - association_reflections = reflections.values.grep(AssociationReflection) + association_reflections = reflections.values macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections end @@ -66,8 +72,7 @@ module ActiveRecord # Invoice.reflect_on_association(:line_items).macro # returns :has_many # def reflect_on_association(association) - reflection = reflections[association] - reflection if reflection.is_a?(AssociationReflection) + reflections[association] end # Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled. @@ -118,6 +123,11 @@ module ActiveRecord name.to_s.pluralize : name.to_s end + def autosave=(autosave) + @automatic_inverse_of = false + @options[:autosave] = autosave + end + # Returns the class for the macro. # # <tt>composed_of :balance, class_name: 'Money'</tt> returns the Money class @@ -179,10 +189,14 @@ module ActiveRecord @klass ||= active_record.send(:compute_type, class_name) end + attr_reader :type, :foreign_type + def initialize(*args) super @collection = [:has_many, :has_and_belongs_to_many].include?(macro) @automatic_inverse_of = nil + @type = options[:as] && "#{options[:as]}_type" + @foreign_type = options[:foreign_type] || "#{name}_type" end # Returns a new, unsaved instance of the associated class. +attributes+ will @@ -192,11 +206,11 @@ module ActiveRecord end def table_name - @table_name ||= klass.table_name + klass.table_name end def quoted_table_name - @quoted_table_name ||= klass.quoted_table_name + klass.quoted_table_name end def join_table @@ -207,16 +221,8 @@ module ActiveRecord @foreign_key ||= options[:foreign_key] || derive_foreign_key end - def foreign_type - @foreign_type ||= options[:foreign_type] || "#{name}_type" - end - - def type - @type ||= options[:as] && "#{options[:as]}_type" - end - def primary_key_column - @primary_key_column ||= klass.columns.find { |c| c.name == klass.primary_key } + klass.columns_hash[klass.primary_key] end def association_foreign_key @@ -240,14 +246,6 @@ module ActiveRecord end end - def columns(tbl_name) - @columns ||= klass.connection.columns(tbl_name) - end - - def reset_column_information - @columns = nil - end - def check_validity! check_validity_of_inverse! @@ -291,30 +289,13 @@ module ActiveRecord alias :source_macro :macro def has_inverse? - @options[:inverse_of] || find_inverse_of_automatically + inverse_name end def inverse_of - @inverse_of ||= if options[:inverse_of] - klass.reflect_on_association(options[:inverse_of]) - else - find_inverse_of_automatically - end - end + return unless inverse_name - # Clears the cached value of +@inverse_of+ on this object. This will - # not remove the :inverse_of option however, so future calls on the - # +inverse_of+ will have to recompute the inverse. - def clear_inverse_of_cache! - @inverse_of = nil - end - - # Removes the cached inverse association that was found automatically - # and prevents this object from finding the inverse association - # automatically in the future. - def remove_automatic_inverse_of! - @automatic_inverse_of = nil - options[:automatic_inverse_of] = false + @inverse_of ||= klass.reflect_on_association inverse_name end def polymorphic_inverse_of(associated_class) @@ -389,26 +370,21 @@ module ActiveRecord INVALID_AUTOMATIC_INVERSE_OPTIONS = [:conditions, :through, :polymorphic, :foreign_key] private - # Attempts to find the inverse association automatically. - # If it cannot find a suitable inverse association, it returns + # Attempts to find the inverse association name automatically. + # If it cannot find a suitable inverse association name, it returns # nil. - def find_inverse_of_automatically - if @automatic_inverse_of == false - nil - elsif @automatic_inverse_of.nil? - set_automatic_inverse_of - else - klass.reflect_on_association(@automatic_inverse_of) + def inverse_name + options.fetch(:inverse_of) do + if @automatic_inverse_of == false + nil + else + @automatic_inverse_of ||= automatic_inverse_of + end end end - # Sets the +@automatic_inverse_of+ instance variable, and returns - # either nil or the inverse association that it finds. - # - # This method caches the inverse association that is found so that - # future calls to +find_inverse_of_automatically+ have much less - # overhead. - def set_automatic_inverse_of + # returns either nil or the inverse association name that it finds. + def automatic_inverse_of if can_find_inverse_of_automatically?(self) inverse_name = active_record.name.downcase.to_sym @@ -421,16 +397,11 @@ module ActiveRecord end if valid_inverse_reflection?(reflection) - @automatic_inverse_of = inverse_name - reflection - else - @automatic_inverse_of = false - nil + return inverse_name end - else - @automatic_inverse_of = false - nil end + + false end # Checks if the inverse reflection that is returned from the @@ -442,22 +413,23 @@ module ActiveRecord # from calling +klass+, +reflection+ will already be set to false. def valid_inverse_reflection?(reflection) reflection && - klass.name == reflection.active_record.try(:name) && + klass.name == reflection.active_record.name && klass.primary_key == reflection.active_record_primary_key && can_find_inverse_of_automatically?(reflection) end # Checks to see if the reflection doesn't have any options that prevent # us from being able to guess the inverse automatically. First, the - # +automatic_inverse_of+ option cannot be set to false. Second, we must - # have +has_many+, +has_one+, +belongs_to+ associations. Third, we must - # not have options such as +:polymorphic+ or +:foreign_key+ which prevent us - # from correctly guessing the inverse association. + # <tt>inverse_of</tt> option cannot be set to false. Second, we must + # have <tt>has_many</tt>, <tt>has_one</tt>, <tt>belongs_to</tt> associations. + # Third, we must not have options such as <tt>:polymorphic</tt> or + # <tt>:foreign_key</tt> which prevent us from correctly guessing the + # inverse association. # # Anything with a scope can additionally ruin our attempt at finding an # inverse, so we exclude reflections with scopes. def can_find_inverse_of_automatically?(reflection) - reflection.options[:automatic_inverse_of] != false && + reflection.options[:inverse_of] != false && VALID_AUTOMATIC_INVERSE_MACROS.include?(reflection.macro) && !INVALID_AUTOMATIC_INVERSE_OPTIONS.any? { |opt| reflection.options[opt] } && !reflection.scope @@ -494,6 +466,11 @@ module ActiveRecord delegate :foreign_key, :foreign_type, :association_foreign_key, :active_record_primary_key, :type, :to => :source_reflection + def initialize(macro, name, scope, options, active_record) + super + @source_reflection_name = options[:source] + end + # Returns the source of the through reflection. It checks both a singularized # and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>. # @@ -512,7 +489,7 @@ module ActiveRecord # # => <ActiveRecord::Reflection::AssociationReflection: @macro=:belongs_to, @name=:tag, @active_record=Tagging, @plural_name="tags"> # def source_reflection - @source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first + through_reflection.klass.reflect_on_association(source_reflection_name) end # Returns the AssociationReflection object specified in the <tt>:through</tt> option @@ -528,7 +505,7 @@ module ActiveRecord # # => <ActiveRecord::Reflection::AssociationReflection: @macro=:has_many, @name=:taggings, @active_record=Post, @plural_name="taggings"> # def through_reflection - @through_reflection ||= active_record.reflect_on_association(options[:through]) + active_record.reflect_on_association(options[:through]) end # Returns an array of reflections which are involved in this association. Each item in the @@ -630,7 +607,32 @@ module ActiveRecord # # => [:tag, :tags] # def source_reflection_names - @source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym } + (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }.uniq + end + + def source_reflection_name # :nodoc: + return @source_reflection_name if @source_reflection_name + + names = [name.to_s.singularize, name].collect { |n| n.to_sym }.uniq + names = names.find_all { |n| + through_reflection.klass.reflect_on_association(n) + } + + if names.length > 1 + example_options = options.dup + example_options[:source] = source_reflection_names.first + ActiveSupport::Deprecation.warn <<-eowarn +Ambiguous source reflection for through association. Please specify a :source +directive on your declaration like: + + class #{active_record.name} < ActiveRecord::Base + #{macro} :#{name}, #{example_options} + end + + eowarn + end + + @source_reflection_name = names.first end def source_options diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index d54479edbb..d37471e9ad 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -247,7 +247,7 @@ module ActiveRecord def empty? return @records.empty? if loaded? - c = count + c = count(:all) c.respond_to?(:zero?) ? c.zero? : c.empty? end diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index b921f2eddb..41291844fc 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -11,7 +11,7 @@ module ActiveRecord # The #find_each method uses #find_in_batches with a batch size of 1000 (or as # specified by the +:batch_size+ option). # - # Person.all.find_each do |person| + # Person.find_each do |person| # person.do_awesome_stuff # end # @@ -19,8 +19,26 @@ module ActiveRecord # person.party_all_night! # end # - # You can also pass the +:start+ option to specify - # an offset to control the starting point. + # ==== Options + # * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000. + # * <tt>:start</tt> - Specifies the starting point for the batch processing. + # This is especially useful if you want multiple workers dealing with + # the same processing queue. You can make worker 1 handle all the records + # between id 0 and 10,000 and worker 2 handle from 10,000 and beyond + # (by setting the +:start+ option on that worker). + # + # # Let's process for a batch of 2000 records, skiping the first 2000 rows + # Person.find_each(start: 2000, batch_size: 2000) do |person| + # person.party_all_night! + # end + # + # NOTE: It's not possible to set the order. That is automatically set to + # ascending on the primary key ("id ASC") to make the batch ordering + # work. This also means that this method only works with integer-based + # primary keys. + # + # NOTE: You can't set the limit either, that's used to control + # the batch sizes. def find_each(options = {}) find_in_batches(options) do |records| records.each { |record| yield record } @@ -28,31 +46,33 @@ module ActiveRecord end # Yields each batch of records that was found by the find +options+ as - # an array. The size of each batch is set by the +:batch_size+ - # option; the default is 1000. - # - # You can control the starting point for the batch processing by - # supplying the +:start+ option. This is especially useful if you - # want multiple workers dealing with the same processing queue. You can - # make worker 1 handle all the records between id 0 and 10,000 and - # worker 2 handle from 10,000 and beyond (by setting the +:start+ - # option on that worker). - # - # It's not possible to set the order. That is automatically set to - # ascending on the primary key ("id ASC") to make the batch ordering - # work. This also means that this method only works with integer-based - # primary keys. You can't set the limit either, that's used to control - # the batch sizes. + # an array. # # Person.where("age > 21").find_in_batches do |group| # sleep(50) # Make sure it doesn't get too crowded in there! # group.each { |person| person.party_all_night! } # end # + # ==== Options + # * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000. + # * <tt>:start</tt> - Specifies the starting point for the batch processing. + # This is especially useful if you want multiple workers dealing with + # the same processing queue. You can make worker 1 handle all the records + # between id 0 and 10,000 and worker 2 handle from 10,000 and beyond + # (by setting the +:start+ option on that worker). + # # # Let's process the next 2000 records - # Person.all.find_in_batches(start: 2000, batch_size: 2000) do |group| + # Person.find_in_batches(start: 2000, batch_size: 2000) do |group| # group.each { |person| person.party_all_night! } # end + # + # NOTE: It's not possible to set the order. That is automatically set to + # ascending on the primary key ("id ASC") to make the batch ordering + # work. This also means that this method only works with integer-based + # primary keys. + # + # NOTE: You can't set the limit either, that's used to control + # the batch sizes. def find_in_batches(options = {}) options.assert_valid_keys(:start, :batch_size) diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index e234e02032..4becf3980d 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -106,8 +106,6 @@ module ActiveRecord else relation.calculate(operation, column_name, options) end - rescue ThrowResult - 0 end # Use <tt>pluck</tt> as a shortcut to select one or more attributes without @@ -209,15 +207,18 @@ module ActiveRecord end if operation == "count" - column_name ||= (select_for_count || :all) + if select_values.present? + column_name ||= select_values.join(", ") + else + column_name ||= :all + end unless arel.ast.grep(Arel::Nodes::OuterJoin).empty? distinct = true end column_name = primary_key if column_name == :all && distinct - - distinct = nil if column_name =~ /\s*DISTINCT\s+/i + distinct = nil if column_name =~ /\s*DISTINCT[\s(]+/i end if group_values.any? @@ -378,13 +379,6 @@ module ActiveRecord column ? column.type_cast(value) : value end - def select_for_count - if select_values.present? - select = select_values.join(", ") - select if select !~ /[,*]/ - end - end - def build_count_subquery(relation, column_name, distinct) column_alias = Arel.sql('count_column') subquery_alias = Arel.sql('subquery_for_count') diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index ba222aac93..3ea3c33fcc 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -11,9 +11,11 @@ module ActiveRecord # Person.find([1]) # returns an array for the object with ID = 1 # Person.where("administrator = 1").order("created_on DESC").find(1) # - # Note that returned records may not be in the same order as the ids you - # provide since database rows are unordered. Give an explicit <tt>order</tt> - # to ensure the results are sorted. + # <tt>ActiveRecord::RecordNotFound</tt> will be raised if one or more ids are not found. + # + # NOTE: The returned records may not be in the same order as the ids you + # provide since database rows are unordered. You'd need to provide an explicit <tt>order</tt> + # option if you want the results are sorted. # # ==== Find with lock # @@ -28,6 +30,34 @@ module ActiveRecord # person.visits += 1 # person.save! # end + # + # ==== Variations of +find+ + # + # Person.where(name: 'Spartacus', rating: 4) + # # returns a chainable list (which can be empty). + # + # Person.find_by(name: 'Spartacus', rating: 4) + # # returns the first item or nil. + # + # Person.where(name: 'Spartacus', rating: 4).first_or_initialize + # # returns the first item or returns a new instance (requires you call .save to persist against the database). + # + # Person.where(name: 'Spartacus', rating: 4).first_or_create + # # returns the first item or creates it and returns it, available since Rails 3.2.1. + # + # ==== Alternatives for +find+ + # + # Person.where(name: 'Spartacus', rating: 4).exists?(conditions = :none) + # # returns a boolean indicating if any record with the given conditions exist. + # + # Person.where(name: 'Spartacus', rating: 4).select("field1, field2, field3") + # # returns a chainable list of instances with only the mentioned fields. + # + # Person.where(name: 'Spartacus', rating: 4).ids + # # returns an Array of ids, available since Rails 3.2.1. + # + # Person.where(name: 'Spartacus', rating: 4).pluck(:field1, :field2) + # # returns an Array of the required fields, available since Rails 3.1. def find(*args) if block_given? to_a.find { |*block_args| yield(*block_args) } @@ -79,13 +109,22 @@ module ActiveRecord # Person.where(["user_name = :u", { u: user_name }]).first # Person.order("created_on DESC").offset(5).first # Person.first(3) # returns the first three objects fetched by SELECT * FROM people LIMIT 3 + # + # ==== Rails 3 + # + # Person.first # SELECT "people".* FROM "people" LIMIT 1 + # + # NOTE: Rails 3 may not order this query by the primary key and the order + # will depend on the database implementation. In order to ensure that behavior, + # use <tt>User.order(:id).first</tt> instead. + # + # ==== Rails 4 + # + # Person.first # SELECT "people".* FROM "people" ORDER BY "people"."id" ASC LIMIT 1 + # def first(limit = nil) if limit - if order_values.empty? && primary_key - order(arel_table[primary_key].asc).limit(limit).to_a - else - limit(limit).to_a - end + find_first_with_limit(order_values, limit) else find_first end @@ -160,8 +199,9 @@ module ActiveRecord conditions = conditions.id if Base === conditions return false if !conditions - join_dependency = construct_join_dependency - relation = construct_relation_for_association_find(join_dependency) + relation = construct_relation_for_association_find(construct_join_dependency) + return false if ActiveRecord::NullRelation === relation + relation = relation.except(:select, :order).select("1 AS one").limit(1) case conditions @@ -171,9 +211,8 @@ module ActiveRecord relation = relation.where(table[primary_key].eq(conditions)) if conditions != :none end - connection.select_value(relation, "#{name} Exists", relation.bind_values) - rescue ThrowResult - false + relation = relation.with_default_scope + connection.select_value(relation.arel, "#{name} Exists", relation.bind_values) end # This method is called whenever no records are found with either a single @@ -203,10 +242,12 @@ module ActiveRecord def find_with_associations join_dependency = construct_join_dependency relation = construct_relation_for_association_find(join_dependency) - rows = connection.select_all(relation, 'SQL', relation.bind_values.dup) - join_dependency.instantiate(rows) - rescue ThrowResult - [] + if ActiveRecord::NullRelation === relation + [] + else + rows = connection.select_all(relation, 'SQL', relation.bind_values.dup) + join_dependency.instantiate(rows) + end end def construct_join_dependency(joins = []) @@ -230,21 +271,22 @@ module ActiveRecord if using_limitable_reflections?(join_dependency.reflections) relation else - relation.where!(construct_limited_ids_condition(relation)) if relation.limit_value + if relation.limit_value + limited_ids = limited_ids_for(relation) + limited_ids.empty? ? relation.none! : relation.where!(table[primary_key].in(limited_ids)) + end relation.except(:limit, :offset) end end - def construct_limited_ids_condition(relation) + def limited_ids_for(relation) values = @klass.connection.columns_for_distinct( "#{quoted_table_name}.#{quoted_primary_key}", relation.order_values) relation = relation.except(:select).select(values).distinct! id_rows = @klass.connection.select_all(relation.arel, 'SQL', relation.bind_values) - ids_array = id_rows.map {|row| row[primary_key]} - - ids_array.empty? ? raise(ThrowResult) : table[primary_key].in(ids_array) + id_rows.map {|row| row[primary_key]} end def find_with_ids(*ids) @@ -312,12 +354,15 @@ module ActiveRecord if loaded? @records.first else - @first ||= - if with_default_scope.order_values.empty? && primary_key - order(arel_table[primary_key].asc).limit(1).to_a.first - else - limit(1).to_a.first - end + @first ||= find_first_with_limit(with_default_scope.order_values, 1).first + end + end + + def find_first_with_limit(order_values, limit) + if order_values.empty? && primary_key + order(arel_table[primary_key].asc).limit(limit).to_a + else + limit(limit).to_a end end diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index 095b78c6c8..0f3f9aecfc 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -6,6 +6,7 @@ require 'models/edge' require 'models/organization' require 'models/possession' require 'models/topic' +require 'models/reply' require 'models/minivan' require 'models/speedometer' require 'models/ship_part' @@ -166,6 +167,15 @@ class CalculationsTest < ActiveRecord::TestCase assert_no_match(/OFFSET/, queries.first) end + def test_count_on_invalid_columns_raises + e = assert_raises(ActiveRecord::StatementInvalid) { + Account.select("credit_limit, firm_name").count + } + + assert_match "accounts", e.message + assert_match "credit_limit, firm_name", e.message + end + def test_should_group_by_summed_field_having_condition c = Account.group(:firm_id).having('sum(credit_limit) > 50').sum(:credit_limit) assert_nil c[1] @@ -472,6 +482,11 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal [1,2,3,4], Topic.order(:id).pluck(:id) end + def test_pluck_without_column_names + assert_equal [[1, "Firm", 1, nil, "37signals", nil, 1, nil, ""]], + Company.order(:id).limit(1).pluck + end + def test_pluck_type_cast topic = topics(:first) relation = Topic.where(:id => topic.id) @@ -533,6 +548,11 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal Company.all.map(&:id).sort, Company.ids.sort end + def test_pluck_with_includes_limit_and_empty_result + assert_equal [], Topic.includes(:replies).limit(0).pluck(:id) + assert_equal [], Topic.includes(:replies).limit(1).where('0 = 1').pluck(:id) + end + def test_pluck_not_auto_table_name_prefix_if_column_included Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)]) ids = Company.includes(:contracts).pluck(:developer_id) diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index 77891b9156..0030f1b464 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -242,7 +242,7 @@ class OptimisticLockingTest < ActiveRecord::TestCase car = Car.create! assert_difference 'car.wheels.count' do - car.wheels << Wheel.create! + car.wheels << Wheel.create! end assert_difference 'car.wheels.count', -1 do car.destroy diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index 6fe81e0d96..2f89699df7 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -797,26 +797,6 @@ module NestedAttributesOnACollectionAssociationTests end end - def test_validate_presence_of_parent_fails_without_inverse_of - Man.accepts_nested_attributes_for(:interests) - Man.reflect_on_association(:interests).options.delete(:inverse_of) - Man.reflect_on_association(:interests).clear_inverse_of_cache! - Interest.reflect_on_association(:man).options.delete(:inverse_of) - Interest.reflect_on_association(:man).clear_inverse_of_cache! - - repair_validations(Interest) do - Interest.validates_presence_of(:man) - assert_no_difference ['Man.count', 'Interest.count'] do - man = Man.create(:name => 'John', - :interests_attributes => [{:topic=>'Cars'}, {:topic=>'Sports'}]) - assert !man.errors[:"interests.man"].empty? - end - end - ensure - Man.reflect_on_association(:interests).options[:inverse_of] = :man - Interest.reflect_on_association(:man).options[:inverse_of] = :interests - end - def test_can_use_symbols_as_object_identifier @pirate.attributes = { :parrots_attributes => { :foo => { :name => 'Lovely Day' }, :bar => { :name => 'Blown Away' } } } assert_nothing_raised(NoMethodError) { @pirate.save! } diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index b64ff13d29..e746ca2805 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -278,8 +278,9 @@ class RelationTest < ActiveRecord::TestCase def test_null_relation_calculations_methods assert_no_queries do - assert_equal 0, Developer.none.count - assert_equal nil, Developer.none.calculate(:average, 'salary') + assert_equal 0, Developer.none.count + assert_equal 0, Developer.none.calculate(:count, nil, {}) + assert_equal nil, Developer.none.calculate(:average, 'salary') end end diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb index a96899ae10..af80b1ba42 100644 --- a/activerecord/test/models/author.rb +++ b/activerecord/test/models/author.rb @@ -85,7 +85,7 @@ class Author < ActiveRecord::Base has_many :author_favorites has_many :favorite_authors, -> { order('name') }, :through => :author_favorites - has_many :taggings, :through => :posts + has_many :taggings, :through => :posts, :source => :taggings has_many :taggings_2, :through => :posts, :source => :tagging has_many :tags, :through => :posts has_many :post_categories, :through => :posts, :source => :categories diff --git a/activerecord/test/models/club.rb b/activerecord/test/models/club.rb index 7d7c205041..816c5e6937 100644 --- a/activerecord/test/models/club.rb +++ b/activerecord/test/models/club.rb @@ -1,6 +1,6 @@ class Club < ActiveRecord::Base has_one :membership - has_many :memberships, :automatic_inverse_of => false + has_many :memberships, :inverse_of => false has_many :members, :through => :memberships has_many :current_memberships has_one :sponsor diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index dcda62e71d..b988184f34 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -141,7 +141,7 @@ class Client < Company belongs_to :firm_with_primary_key_symbols, :class_name => "Firm", :primary_key => :name, :foreign_key => :firm_name belongs_to :readonly_firm, -> { readonly }, :class_name => "Firm", :foreign_key => "firm_id" belongs_to :bob_firm, -> { where :name => "Bob" }, :class_name => "Firm", :foreign_key => "client_of" - has_many :accounts, :through => :firm + has_many :accounts, :through => :firm, :source => :accounts belongs_to :account class RaisedOnSave < RuntimeError; end diff --git a/activerecord/test/models/interest.rb b/activerecord/test/models/interest.rb index f772bb1c7f..d5d9226204 100644 --- a/activerecord/test/models/interest.rb +++ b/activerecord/test/models/interest.rb @@ -1,5 +1,5 @@ class Interest < ActiveRecord::Base - belongs_to :man, :inverse_of => :interests, :automatic_inverse_of => false + belongs_to :man, :inverse_of => :interests belongs_to :polymorphic_man, :polymorphic => true, :inverse_of => :polymorphic_interests belongs_to :zine, :inverse_of => :interests end diff --git a/activerecord/test/models/man.rb b/activerecord/test/models/man.rb index 49f002aa9a..4bff92dc98 100644 --- a/activerecord/test/models/man.rb +++ b/activerecord/test/models/man.rb @@ -1,7 +1,7 @@ class Man < ActiveRecord::Base has_one :face, :inverse_of => :man has_one :polymorphic_face, :class_name => 'Face', :as => :polymorphic_man, :inverse_of => :polymorphic_man - has_many :interests, :inverse_of => :man, :automatic_inverse_of => false + has_many :interests, :inverse_of => :man has_many :polymorphic_interests, :class_name => 'Interest', :as => :polymorphic_man, :inverse_of => :polymorphic_man # These are "broken" inverse_of associations for the purposes of testing has_one :dirty_face, :class_name => 'Face', :inverse_of => :dirty_man diff --git a/activerecord/test/models/member.rb b/activerecord/test/models/member.rb index b81304b8e0..cc47c7bc18 100644 --- a/activerecord/test/models/member.rb +++ b/activerecord/test/models/member.rb @@ -9,7 +9,7 @@ class Member < ActiveRecord::Base has_one :hairy_club, -> { where :clubs => {:name => "Moustache and Eyebrow Fancier Club"} }, :through => :membership, :source => :club has_one :sponsor, :as => :sponsorable has_one :sponsor_club, :through => :sponsor - has_one :member_detail, :automatic_inverse_of => false + has_one :member_detail, :inverse_of => false has_one :organization, :through => :member_detail belongs_to :member_type diff --git a/activerecord/test/models/member_detail.rb b/activerecord/test/models/member_detail.rb index a256c73c7e..9d253aa126 100644 --- a/activerecord/test/models/member_detail.rb +++ b/activerecord/test/models/member_detail.rb @@ -1,5 +1,5 @@ class MemberDetail < ActiveRecord::Base - belongs_to :member, :automatic_inverse_of => false + belongs_to :member, :inverse_of => false belongs_to :organization has_one :member_type, :through => :member diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index ae3fd811b3..b69333851f 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,5 +1,29 @@ +* `HashWithIndifferentAccess#select` now returns a `HashWithIndifferentAccess` + instance instead of a `Hash` instance. + + Fixes #10723 + + *Albert Llop* + +* Add `DateTime#usec` and `DateTime#nsec` so that `ActiveSupport::TimeWithZone` keeps + sub-second resolution when wrapping a `DateTime` value. + + Fixes #10855 + + *Andrew White* + +* Fix `ActiveSupport::Dependencies::Loadable#load_dependency` calling + `#blame_file!` on Exceptions that do not have the Blamable mixin + + *Andrew Kreiling* + +* Override `Time.at` to support the passing of Time-like values when called with a single argument. + + *Andrew White* + * Prevent side effects to hashes inside arrays when `Hash#with_indifferent_access` is called. + Fixes #10526 *Yves Senn* diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb index 4b41e6247d..6f9dc679c2 100644 --- a/activesupport/lib/active_support/backtrace_cleaner.rb +++ b/activesupport/lib/active_support/backtrace_cleaner.rb @@ -13,17 +13,17 @@ module ActiveSupport # can focus on the rest. # # bc = BacktraceCleaner.new - # bc.add_filter { |line| line.gsub(Rails.root, '') } - # bc.add_silencer { |line| line =~ /mongrel|rubygems/ } - # bc.clean(exception.backtrace) # will strip the Rails.root prefix and skip any lines from mongrel or rubygems + # bc.add_filter { |line| line.gsub(Rails.root, '') } # strip the Rails.root prefix + # bc.add_silencer { |line| line =~ /mongrel|rubygems/ } # skip any lines from mongrel or rubygems + # bc.clean(exception.backtrace) # perform the cleanup # # To reconfigure an existing BacktraceCleaner (like the default one in Rails) # and show as much data as possible, you can always call # <tt>BacktraceCleaner#remove_silencers!</tt>, which will restore the # backtrace to a pristine state. If you need to reconfigure an existing # BacktraceCleaner so that it does not filter or modify the paths of any lines - # of the backtrace, you can call BacktraceCleaner#remove_filters! These two - # methods will give you a completely untouched backtrace. + # of the backtrace, you can call <tt>BacktraceCleaner#remove_filters!<tt> + # These two methods will give you a completely untouched backtrace. # # Inspired by the Quiet Backtrace gem by Thoughtbot. class BacktraceCleaner diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index b1ab5570a8..c539048a85 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -228,7 +228,7 @@ module ActiveSupport # # Setting <tt>:race_condition_ttl</tt> is very useful in situations where # a cache entry is used very frequently and is under heavy load. If a - # cache expires and due to heavy load seven different processes will try + # cache expires and due to heavy load several different processes will try # to read data natively and then they all will try to write to cache. To # avoid that case the first process to find an expired cache entry will # bump the cache expiration time by the value set in <tt>:race_condition_ttl</tt>. diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 85b7669353..2cffa342ef 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -94,6 +94,15 @@ module ActiveSupport def halted_callback_hook(filter) end + module Conditionals # :nodoc: + class Value + def initialize(&block) + @block = block + end + def call(target, value); @block.call(value); end + end + end + module Filters Environment = Struct.new(:target, :halted, :value, :run_block) @@ -403,8 +412,8 @@ module ActiveSupport # the same after this point: # # Symbols:: Already methods. - # Strings:: class_eval'ed into methods. - # Procs:: define_method'ed into methods. + # Strings:: class_eval'd into methods. + # Procs:: using define_method compiled into methods. # Objects:: # a method is created that calls the before_foo method # on the object. @@ -415,6 +424,7 @@ module ActiveSupport when String l = eval "lambda { |value| #{filter} }" lambda { |target, value| target.instance_exec(value, &l) } + when Conditionals::Value then filter when ::Proc if filter.arity > 1 return lambda { |target, _, &block| @@ -648,7 +658,7 @@ module ActiveSupport # # * <tt>:terminator</tt> - Determines when a before filter will halt the # callback chain, preventing following callbacks from being called and - # the event from being triggered. This is a string to be eval'ed. The + # the event from being triggered. This is a string to be eval'd. The # result of the callback is available in the +result+ variable. # # define_callbacks :validate, terminator: 'result == false' diff --git a/activesupport/lib/active_support/core_ext/date_time/conversions.rb b/activesupport/lib/active_support/core_ext/date_time/conversions.rb index df07917d19..c44626aed9 100644 --- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb @@ -80,6 +80,16 @@ class DateTime seconds_since_unix_epoch.to_i end + # Returns the fraction of a second as microseconds + def usec + (sec_fraction * 1_000_000).to_i + end + + # Returns the fraction of a second as nanoseconds + def nsec + (sec_fraction * 1_000_000_000).to_i + end + private def offset_in_seconds diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index 6d42667e97..3dde87ac2e 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -164,7 +164,7 @@ class Module # # Reason is twofold: On one hand doing less calls is in general better. # On the other hand it could be that the target has side-effects, - # whereas conceptualy, from the user point of view, the delegator should + # whereas conceptually, from the user point of view, the delegator should # be doing one call. if allow_nil module_eval(<<-EOS, file, line - 3) diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index a3ce7dbe3f..c65f20c2d5 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -65,6 +65,18 @@ class Time def current ::Time.zone ? ::Time.zone.now : ::Time.now end + + # Layers additional behavior on Time.at so that ActiveSupport::TimeWithZone and DateTime + # instances can be used when called with a single argument + def at_with_coercion(*args) + if args.size == 1 && args.first.acts_like?(:time) + at_without_coercion(args.first.to_i) + else + at_without_coercion(*args) + end + end + alias_method :at_without_coercion, :at + alias_method :at, :at_with_coercion end # Seconds since midnight: Time.now.seconds_since_midnight diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index fff4c776a9..d38e4b0732 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -213,7 +213,7 @@ module ActiveSupport #:nodoc: yield end rescue Exception => exception # errors from loading file - exception.blame_file! file + exception.blame_file! file if exception.respond_to? :blame_file! raise end diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb index 20136dd1b0..d6918bede2 100644 --- a/activesupport/lib/active_support/file_update_checker.rb +++ b/activesupport/lib/active_support/file_update_checker.rb @@ -115,7 +115,7 @@ module ActiveSupport end def compile_glob(hash) - hash.freeze # Freeze so changes aren't accidently pushed + hash.freeze # Freeze so changes aren't accidentally pushed return if hash.empty? globs = hash.map do |key, value| diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 0a81a8393d..56213de50e 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -227,6 +227,10 @@ module ActiveSupport def deep_symbolize_keys; to_hash.deep_symbolize_keys end def to_options!; self end + def select(*args, &block) + dup.select!(*args, &block) + end + # Convert to a regular hash with string keys. def to_hash _new_hash= {} diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index 39648727fd..9f417af826 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -219,7 +219,12 @@ module ActiveSupport # unknown. def constantize(camel_cased_word) names = camel_cased_word.split('::') - names.shift if names.empty? || names.first.empty? + + # Trigger a builtin NameError exception including the ill-formed constant in the message. + Object.const_get(camel_cased_word) if names.empty? + + # Remove the first blank element in case of '::ClassName' notation. + names.shift if names.size > 1 && names.first.empty? names.inject(Object) do |constant, name| if constant == Object @@ -317,6 +322,9 @@ module ActiveSupport # For instance, Foo::Bar::Baz will generate Foo(::Bar(::Baz)?)? def const_regexp(camel_cased_word) #:nodoc: parts = camel_cased_word.split("::") + + return Regexp.escape(camel_cased_word) if parts.blank? + last = parts.pop parts.reverse.inject(last) do |acc, part| diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb index c45358bba9..b32aa75e59 100644 --- a/activesupport/lib/active_support/notifications.rb +++ b/activesupport/lib/active_support/notifications.rb @@ -143,8 +143,8 @@ module ActiveSupport # # == Default Queue # - # Notifications ships with a queue implementation that consumes and publish events - # to log subscribers in a thread. You can use any queue implementation you want. + # Notifications ships with a queue implementation that consumes and publishes events + # to all log subscribers. You can use any queue implementation you want. # module Notifications class << self diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 4a032b0ad0..95b9b8e5ae 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -292,7 +292,7 @@ module ActiveSupport end end - %w(year mon month day mday wday yday hour min sec to_date).each do |method_name| + %w(year mon month day mday wday yday hour min sec usec nsec to_date).each do |method_name| class_eval <<-EOV, __FILE__, __LINE__ + 1 def #{method_name} # def month time.#{method_name} # time.month @@ -300,10 +300,6 @@ module ActiveSupport EOV end - def usec - time.respond_to?(:usec) ? time.usec : 0 - end - def to_a [time.sec, time.min, time.hour, time.day, time.mon, time.year, time.wday, time.yday, dst?, zone] end diff --git a/activesupport/test/constantize_test_cases.rb b/activesupport/test/constantize_test_cases.rb index 9b62295c96..bbeb710a0c 100644 --- a/activesupport/test/constantize_test_cases.rb +++ b/activesupport/test/constantize_test_cases.rb @@ -34,8 +34,6 @@ module ConstantizeTestCases assert_equal Case::Dice, yield("Object::Case::Dice") assert_equal ConstantizeTestCases, yield("ConstantizeTestCases") assert_equal ConstantizeTestCases, yield("::ConstantizeTestCases") - assert_equal Object, yield("") - assert_equal Object, yield("::") assert_raises(NameError) { yield("UnknownClass") } assert_raises(NameError) { yield("UnknownClass::Ace") } assert_raises(NameError) { yield("UnknownClass::Ace::Base") } @@ -45,6 +43,8 @@ module ConstantizeTestCases assert_raises(NameError) { yield("Ace::Base::ConstantizeTestCases") } assert_raises(NameError) { yield("Ace::Gas::Base") } assert_raises(NameError) { yield("Ace::Gas::ConstantizeTestCases") } + assert_raises(NameError) { yield("") } + assert_raises(NameError) { yield("::") } end def run_safe_constantize_tests_on @@ -58,8 +58,8 @@ module ConstantizeTestCases assert_equal Case::Dice, yield("Object::Case::Dice") assert_equal ConstantizeTestCases, yield("ConstantizeTestCases") assert_equal ConstantizeTestCases, yield("::ConstantizeTestCases") - assert_equal Object, yield("") - assert_equal Object, yield("::") + assert_nil yield("") + assert_nil yield("::") assert_nil yield("UnknownClass") assert_nil yield("UnknownClass::Ace") assert_nil yield("UnknownClass::Ace::Base") diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index 3e76b8a9b1..571344b728 100644 --- a/activesupport/test/core_ext/date_time_ext_test.rb +++ b/activesupport/test/core_ext/date_time_ext_test.rb @@ -327,6 +327,16 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase assert_equal 946684800, DateTime.civil(1999,12,31,19,0,0,Rational(-5,24)).to_i end + def test_usec + assert_equal 0, DateTime.civil(2000).usec + assert_equal 500000, DateTime.civil(2000, 1, 1, 0, 0, Rational(1,2)).usec + end + + def test_nsec + assert_equal 0, DateTime.civil(2000).nsec + assert_equal 500000000, DateTime.civil(2000, 1, 1, 0, 0, Rational(1,2)).nsec + end + protected def with_env_tz(new_tz = 'US/Eastern') old_tz, ENV['TZ'] = ENV['TZ'], new_tz diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 39bd0a2dd4..c6d7ab618c 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -480,6 +480,36 @@ class HashExtTest < ActiveSupport::TestCase assert_equal hash.delete('a'), nil end + def test_indifferent_select + hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select {|k,v| v == 1} + + assert_equal({ 'a' => 1 }, hash) + assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash + end + + def test_indifferent_select_bang + indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings) + indifferent_strings.select! {|k,v| v == 1} + + assert_equal({ 'a' => 1 }, indifferent_strings) + assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings + end + + def test_indifferent_reject + hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).reject {|k,v| v != 1} + + assert_equal({ 'a' => 1 }, hash) + assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash + end + + def test_indifferent_reject_bang + indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings) + indifferent_strings.reject! {|k,v| v != 1} + + assert_equal({ 'a' => 1 }, indifferent_strings) + assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings + end + def test_indifferent_to_hash # Should convert to a Hash with String keys. assert_equal @strings, @mixed.with_indifferent_access.to_hash diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index 4e53aff00b..eefcdbb1b3 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -741,6 +741,28 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal(-1, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 1), ActiveSupport::TimeZone['UTC'] )) end + def test_at_with_datetime + assert_equal Time.utc(2000, 1, 1, 0, 0, 0), Time.at(DateTime.civil(2000, 1, 1, 0, 0, 0)) + + # Only test this if the underlying Time.at raises a TypeError + begin + Time.at_without_coercion(Time.now, 0) + rescue TypeError + assert_raise(TypeError) { assert_equal(Time.utc(2000, 1, 1, 0, 0, 0), Time.at(DateTime.civil(2000, 1, 1, 0, 0, 0), 0)) } + end + end + + def test_at_with_time_with_zone + assert_equal Time.utc(2000, 1, 1, 0, 0, 0), Time.at(ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone['UTC'])) + + # Only test this if the underlying Time.at raises a TypeError + begin + Time.at_without_coercion(Time.now, 0) + rescue TypeError + assert_raise(TypeError) { assert_equal(Time.utc(2000, 1, 1, 0, 0, 0), Time.at(ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone['UTC']), 0)) } + end + end + def test_eql? assert_equal true, Time.utc(2000).eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['UTC']) ) assert_equal true, Time.utc(2000).eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Hawaii"]) ) diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index 3ce3297874..ddcfcc491f 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -445,6 +445,16 @@ class TimeWithZoneTest < ActiveSupport::TestCase assert_equal 0, twz.usec end + def test_usec_returns_sec_fraction_when_datetime_is_wrapped + twz = ActiveSupport::TimeWithZone.new(DateTime.civil(2000, 1, 1, 0, 0, Rational(1,2)), @time_zone) + assert_equal 500000, twz.usec + end + + def test_nsec_returns_sec_fraction_when_datetime_is_wrapped + twz = ActiveSupport::TimeWithZone.new(DateTime.civil(2000, 1, 1, 0, 0, Rational(1,2)), @time_zone) + assert_equal 500000000, twz.nsec + end + def test_utc_to_local_conversion_saves_period_in_instance_variable assert_nil @twz.instance_variable_get('@period') @twz.time diff --git a/activesupport/test/dependencies/raises_exception_without_blame_file.rb b/activesupport/test/dependencies/raises_exception_without_blame_file.rb new file mode 100644 index 0000000000..4b2da6ff30 --- /dev/null +++ b/activesupport/test/dependencies/raises_exception_without_blame_file.rb @@ -0,0 +1,5 @@ +exception = Exception.new('I am not blamable!') +class << exception + undef_method(:blame_file!) +end +raise exception diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index 4b1426bb2e..68b6cc6e8c 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -76,6 +76,14 @@ class DependenciesTest < ActiveSupport::TestCase end end + def test_dependency_which_raises_doesnt_blindly_call_blame_file! + with_loading do + filename = 'dependencies/raises_exception_without_blame_file' + + assert_raises(Exception) { require_dependency filename } + end + end + def test_warnings_should_be_enabled_on_first_load with_loading 'dependencies' do old_warnings, ActiveSupport::Dependencies.warnings_on_first_load = ActiveSupport::Dependencies.warnings_on_first_load, true diff --git a/activesupport/test/load_paths_test.rb b/activesupport/test/load_paths_test.rb index 979e25bdf3..ac617a9fd8 100644 --- a/activesupport/test/load_paths_test.rb +++ b/activesupport/test/load_paths_test.rb @@ -10,7 +10,7 @@ class LoadPathsTest < ActiveSupport::TestCase } load_paths_count[File.expand_path('../../lib', __FILE__)] -= 1 - filtered = load_paths_count.select { |k, v| v > 1 } - assert filtered.empty?, filtered.inspect + load_paths_count.select! { |k, v| v > 1 } + assert load_paths_count.empty?, load_paths_count.inspect end end diff --git a/guides/assets/images/edge_badge.png b/guides/assets/images/edge_badge.png Binary files differindex a35dc9f8ee..a3c1843d1d 100644 --- a/guides/assets/images/edge_badge.png +++ b/guides/assets/images/edge_badge.png diff --git a/guides/assets/images/feature_tile.gif b/guides/assets/images/feature_tile.gif Binary files differindex 75469361db..5268ef8373 100644 --- a/guides/assets/images/feature_tile.gif +++ b/guides/assets/images/feature_tile.gif diff --git a/guides/assets/images/footer_tile.gif b/guides/assets/images/footer_tile.gif Binary files differindex bb33fc1ff0..3fe21a8275 100644 --- a/guides/assets/images/footer_tile.gif +++ b/guides/assets/images/footer_tile.gif diff --git a/guides/assets/images/fxn.png b/guides/assets/images/fxn.png Binary files differindex 9b531ee584..733d380cba 100644 --- a/guides/assets/images/fxn.png +++ b/guides/assets/images/fxn.png diff --git a/guides/assets/images/getting_started/challenge.png b/guides/assets/images/getting_started/challenge.png Binary files differindex 30be3d7028..4a30e49e6d 100644 --- a/guides/assets/images/getting_started/challenge.png +++ b/guides/assets/images/getting_started/challenge.png diff --git a/guides/assets/images/getting_started/forbidden_attributes_for_new_post.png b/guides/assets/images/getting_started/forbidden_attributes_for_new_post.png Binary files differindex 500dfc2c02..6c78e52173 100644 --- a/guides/assets/images/getting_started/forbidden_attributes_for_new_post.png +++ b/guides/assets/images/getting_started/forbidden_attributes_for_new_post.png diff --git a/guides/assets/images/getting_started/new_post.png b/guides/assets/images/getting_started/new_post.png Binary files differindex b573cb164c..b20b0192d4 100644 --- a/guides/assets/images/getting_started/new_post.png +++ b/guides/assets/images/getting_started/new_post.png diff --git a/guides/assets/images/getting_started/routing_error_no_controller.png b/guides/assets/images/getting_started/routing_error_no_controller.png Binary files differindex 43ccd25252..35ee4f348f 100644 --- a/guides/assets/images/getting_started/routing_error_no_controller.png +++ b/guides/assets/images/getting_started/routing_error_no_controller.png diff --git a/guides/assets/images/getting_started/routing_error_no_route_matches.png b/guides/assets/images/getting_started/routing_error_no_route_matches.png Binary files differindex 1b8c0ea57e..1cbddfa0f1 100644 --- a/guides/assets/images/getting_started/routing_error_no_route_matches.png +++ b/guides/assets/images/getting_started/routing_error_no_route_matches.png diff --git a/guides/assets/images/getting_started/template_is_missing_posts_new.png b/guides/assets/images/getting_started/template_is_missing_posts_new.png Binary files differindex 75980432b2..f03db05fb8 100644 --- a/guides/assets/images/getting_started/template_is_missing_posts_new.png +++ b/guides/assets/images/getting_started/template_is_missing_posts_new.png diff --git a/guides/assets/images/getting_started/unknown_action_create_for_posts.png b/guides/assets/images/getting_started/unknown_action_create_for_posts.png Binary files differindex d60a30465f..8fdd4c574a 100644 --- a/guides/assets/images/getting_started/unknown_action_create_for_posts.png +++ b/guides/assets/images/getting_started/unknown_action_create_for_posts.png diff --git a/guides/assets/images/getting_started/unknown_action_new_for_posts.png b/guides/assets/images/getting_started/unknown_action_new_for_posts.png Binary files differindex f4b3eff9dc..7e72feee38 100644 --- a/guides/assets/images/getting_started/unknown_action_new_for_posts.png +++ b/guides/assets/images/getting_started/unknown_action_new_for_posts.png diff --git a/guides/assets/images/header_tile.gif b/guides/assets/images/header_tile.gif Binary files differindex e2c878d492..6b1af15eab 100644 --- a/guides/assets/images/header_tile.gif +++ b/guides/assets/images/header_tile.gif diff --git a/guides/assets/images/icons/callouts/11.png b/guides/assets/images/icons/callouts/11.png Binary files differindex 9244a1ac4b..3b7b9318e7 100644 --- a/guides/assets/images/icons/callouts/11.png +++ b/guides/assets/images/icons/callouts/11.png diff --git a/guides/assets/images/icons/callouts/12.png b/guides/assets/images/icons/callouts/12.png Binary files differindex ae56459f4c..7b95925e9d 100644 --- a/guides/assets/images/icons/callouts/12.png +++ b/guides/assets/images/icons/callouts/12.png diff --git a/guides/assets/images/icons/callouts/13.png b/guides/assets/images/icons/callouts/13.png Binary files differindex 1181f9f892..4b99fe8efc 100644 --- a/guides/assets/images/icons/callouts/13.png +++ b/guides/assets/images/icons/callouts/13.png diff --git a/guides/assets/images/icons/callouts/15.png b/guides/assets/images/icons/callouts/15.png Binary files differindex 39304de94f..70e4bba615 100644 --- a/guides/assets/images/icons/callouts/15.png +++ b/guides/assets/images/icons/callouts/15.png diff --git a/guides/assets/images/icons/caution.png b/guides/assets/images/icons/caution.png Binary files differindex 031e19c776..7227b54b32 100644 --- a/guides/assets/images/icons/caution.png +++ b/guides/assets/images/icons/caution.png diff --git a/guides/assets/images/icons/example.png b/guides/assets/images/icons/example.png Binary files differindex 1b0e482059..de23c0aa87 100644 --- a/guides/assets/images/icons/example.png +++ b/guides/assets/images/icons/example.png diff --git a/guides/assets/images/jaimeiniesta.jpg b/guides/assets/images/jaimeiniesta.jpg Binary files differdeleted file mode 100644 index 445f048d92..0000000000 --- a/guides/assets/images/jaimeiniesta.jpg +++ /dev/null diff --git a/guides/assets/images/radar.png b/guides/assets/images/radar.png Binary files differindex f61e08763f..421b62b623 100644 --- a/guides/assets/images/radar.png +++ b/guides/assets/images/radar.png diff --git a/guides/assets/images/rails4_features.png b/guides/assets/images/rails4_features.png Binary files differindex a979f02207..b3bd5ef69e 100644 --- a/guides/assets/images/rails4_features.png +++ b/guides/assets/images/rails4_features.png diff --git a/guides/assets/images/rails_guides_kindle_cover.jpg b/guides/assets/images/rails_guides_kindle_cover.jpg Binary files differindex 9eb16720a9..f068bd9a04 100644 --- a/guides/assets/images/rails_guides_kindle_cover.jpg +++ b/guides/assets/images/rails_guides_kindle_cover.jpg diff --git a/guides/assets/images/vijaydev.jpg b/guides/assets/images/vijaydev.jpg Binary files differindex e21d3cabfc..fe5e4f1cb4 100644 --- a/guides/assets/images/vijaydev.jpg +++ b/guides/assets/images/vijaydev.jpg diff --git a/guides/source/2_3_release_notes.md b/guides/source/2_3_release_notes.md index 3c08f148cf..0f05cc6b85 100644 --- a/guides/source/2_3_release_notes.md +++ b/guides/source/2_3_release_notes.md @@ -40,7 +40,7 @@ Here's a summary of the rack-related changes: * `ActiveRecord::QueryCache` middleware is automatically inserted onto the middleware stack if `ActiveRecord` has been loaded. This middleware sets up and flushes the per-request Active Record query cache. * The Rails router and controller classes follow the Rack spec. You can call a controller directly with `SomeController.call(env)`. The router stores the routing parameters in `rack.routing_args`. * `ActionController::Request` inherits from `Rack::Request`. -* Instead of `config.action_controller.session = { :session_key => 'foo', ...` use `config.action_controller.session = { :key => 'foo', ...`. +* Instead of `config.action_controller.session = { :session_key => 'foo', ...` use `config.action_controller.session = { :key => 'foo', ...`. * Using the `ParamsParser` middleware preprocesses any XML, JSON, or YAML requests so they can be read normally with any `Rack::Request` object after it. ### Renewed Support for Rails Engines @@ -173,8 +173,8 @@ before_save :update_credit_rating, :if => :active, Rails now has a `:having` option on find (as well as on `has_many` and `has_and_belongs_to_many` associations) for filtering records in grouped finds. As those with heavy SQL backgrounds know, this allows filtering based on grouped results: ```ruby -developers = Developer.find(:all, :group => "salary", - :having => "sum(salary) > 10000", :select => "salary") +developers = Developer.find(:all, :group => "salary", + :having => "sum(salary) > 10000", :select => "salary") ``` * Lead Contributor: [Emilio Tagua](http://github.com/miloops) diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md index b9dbe820c8..6ebeeed0bf 100644 --- a/guides/source/4_0_release_notes.md +++ b/guides/source/4_0_release_notes.md @@ -83,7 +83,7 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/railt ### Notable changes -* New test locations `test/models`, `test/helpers`, `test/controllers`, and `test/mailers`. Corresponding rake tasks added as well. ([Pull Request](https://github.com/rails/rails/pull/7878)) +* New test locations `test/models`, `test/helpers`, `test/controllers`, and `test/mailers`. Corresponding rake tasks added as well. ([Pull Request](https://github.com/rails/rails/pull/7878)) * Your app's executables now live in the `bin/` directory. Run `rake rails:update:bin` to get `bin/bundle`, `bin/rails`, and `bin/rake`. @@ -111,10 +111,9 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activ ### Notable changes -* Add `ActiveModel::ForbiddenAttributesProtection`, a simple module to protect attributes from mass assignment when non-permitted attributes are passed. +* Add `ActiveModel::ForbiddenAttributesProtection`, a simple module to protect attributes from mass assignment when non-permitted attributes are passed. -* Added `ActiveModel::Model`, a mixin to make Ruby objects work with - Action Pack out of box. +* Added `ActiveModel::Model`, a mixin to make Ruby objects work with Action Pack out of box. ### Deprecations @@ -125,27 +124,27 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activ ### Notable changes -* Replace deprecated `memcache-client` gem with `dalli` in ActiveSupport::Cache::MemCacheStore. +* Replace deprecated `memcache-client` gem with `dalli` in ActiveSupport::Cache::MemCacheStore. -* Optimize ActiveSupport::Cache::Entry to reduce memory and processing overhead. +* Optimize ActiveSupport::Cache::Entry to reduce memory and processing overhead. -* Inflections can now be defined per locale. `singularize` and `pluralize` accept locale as an extra argument. +* Inflections can now be defined per locale. `singularize` and `pluralize` accept locale as an extra argument. -* `Object#try` will now return nil instead of raise a NoMethodError if the receiving object does not implement the method, but you can still get the old behavior by using the new `Object#try!`. +* `Object#try` will now return nil instead of raise a NoMethodError if the receiving object does not implement the method, but you can still get the old behavior by using the new `Object#try!`. ### Deprecations -* Deprecate `ActiveSupport::TestCase#pending` method, use `skip` from MiniTest instead. +* Deprecate `ActiveSupport::TestCase#pending` method, use `skip` from MiniTest instead. -* ActiveSupport::Benchmarkable#silence has been deprecated due to its lack of thread safety. It will be removed without replacement in Rails 4.1. +* ActiveSupport::Benchmarkable#silence has been deprecated due to its lack of thread safety. It will be removed without replacement in Rails 4.1. -* `ActiveSupport::JSON::Variable` is deprecated. Define your own `#as_json` and `#encode_json` methods for custom JSON string literals. +* `ActiveSupport::JSON::Variable` is deprecated. Define your own `#as_json` and `#encode_json` methods for custom JSON string literals. -* Deprecates the compatibility method Module#local_constant_names, use Module#local_constants instead (which returns symbols). +* Deprecates the compatibility method Module#local_constant_names, use Module#local_constants instead (which returns symbols). -* BufferedLogger is deprecated. Use ActiveSupport::Logger, or the logger from Ruby standard library. +* BufferedLogger is deprecated. Use ActiveSupport::Logger, or the logger from Ruby standard library. -* Deprecate `assert_present` and `assert_blank` in favor of `assert object.blank?` and `assert object.present?` +* Deprecate `assert_present` and `assert_blank` in favor of `assert object.blank?` and `assert object.present?` Action Pack ----------- @@ -166,7 +165,7 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activ ### Notable changes -* Improve ways to write `change` migrations, making the old `up` & `down` methods no longer necessary. +* Improve ways to write `change` migrations, making the old `up` & `down` methods no longer necessary. * The methods `drop_table` and `remove_column` are now reversible, as long as the necessary information is given. The method `remove_column` used to accept multiple column names; instead use `remove_columns` (which is not revertible). @@ -179,36 +178,36 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activ If migrating down, the given migration / block is run normally. See the [Guide on Migration](https://github.com/rails/rails/blob/master/guides/source/migrations.md#reverting-previous-migrations) -* Adds PostgreSQL array type support. Any datatype can be used to create an array column, with full migration and schema dumper support. +* Adds PostgreSQL array type support. Any datatype can be used to create an array column, with full migration and schema dumper support. -* Add `Relation#load` to explicitly load the record and return `self`. +* Add `Relation#load` to explicitly load the record and return `self`. -* `Model.all` now returns an `ActiveRecord::Relation`, rather than an array of records. Use `Relation#to_a` if you really want an array. In some specific cases, this may cause breakage when upgrading. +* `Model.all` now returns an `ActiveRecord::Relation`, rather than an array of records. Use `Relation#to_a` if you really want an array. In some specific cases, this may cause breakage when upgrading. -* Added `ActiveRecord::Migration.check_pending!` that raises an error if migrations are pending. +* Added `ActiveRecord::Migration.check_pending!` that raises an error if migrations are pending. -* Added custom coders support for `ActiveRecord::Store`. Now you can set your custom coder like this: +* Added custom coders support for `ActiveRecord::Store`. Now you can set your custom coder like this: store :settings, accessors: [ :color, :homepage ], coder: JSON -* `mysql` and `mysql2` connections will set `SQL_MODE=STRICT_ALL_TABLES` by default to avoid silent data loss. This can be disabled by specifying `strict: false` in your `database.yml`. +* `mysql` and `mysql2` connections will set `SQL_MODE=STRICT_ALL_TABLES` by default to avoid silent data loss. This can be disabled by specifying `strict: false` in your `database.yml`. -* Remove IdentityMap. +* Remove IdentityMap. -* Remove automatic execution of EXPLAIN queries. The option `active_record.auto_explain_threshold_in_seconds` is no longer used and should be removed. +* Remove automatic execution of EXPLAIN queries. The option `active_record.auto_explain_threshold_in_seconds` is no longer used and should be removed. -* Adds `ActiveRecord::NullRelation` and `ActiveRecord::Relation#none` implementing the null object pattern for the Relation class. +* Adds `ActiveRecord::NullRelation` and `ActiveRecord::Relation#none` implementing the null object pattern for the Relation class. -* Added `create_join_table` migration helper to create HABTM join tables. +* Added `create_join_table` migration helper to create HABTM join tables. -* Allows PostgreSQL hstore records to be created. +* Allows PostgreSQL hstore records to be created. ### Deprecations -* Deprecated the old-style hash based finder API. This means that methods which previously accepted "finder options" no longer do. +* Deprecated the old-style hash based finder API. This means that methods which previously accepted "finder options" no longer do. -* All dynamic methods except for `find_by_...` and `find_by_...!` are deprecated. Here's - how you can rewrite the code: +* All dynamic methods except for `find_by_...` and `find_by_...!` are deprecated. Here's + how you can rewrite the code: * `find_all_by_...` can be rewritten using `where(...)`. * `find_last_by_...` can be rewritten using `where(...).last`. diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index 28939f307f..2701f5bb72 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -257,7 +257,7 @@ params.require(:log_entry).permit! ``` This will mark the `:log_entry` parameters hash and any subhash of it -permitted. Extreme care should be taken when using `permit!` as it +permitted. Extreme care should be taken when using `permit!` as it will allow all current and future model attributes to be mass-assigned. diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md index c351339117..d1dd231cf6 100644 --- a/guides/source/action_mailer_basics.md +++ b/guides/source/action_mailer_basics.md @@ -496,7 +496,7 @@ end There may be cases in which you want to skip the template rendering step and supply the email body as a string. You can achieve this using the `:body` -option. In such cases don't forget to add the `:content_type` option. Rails +option. In such cases don't forget to add the `:content_type` option. Rails will default to `text/plain` otherwise. ```ruby @@ -623,7 +623,7 @@ files (environment.rb, production.rb, etc...) | Configuration | Description | |---------------|-------------| |`logger`|Generates information on the mailing run if available. Can be set to `nil` for no logging. Compatible with both Ruby's own `Logger` and `Log4r` loggers.| -|`smtp_settings`|Allows detailed configuration for `:smtp` delivery method:<ul><li>`:address` - Allows you to use a remote mail server. Just change it from its default "localhost" setting.</li><li>`:port` - On the off chance that your mail server doesn't run on port 25, you can change it.</li><li>`:domain` - If you need to specify a HELO domain, you can do it here.</li><li>`:user_name` - If your mail server requires authentication, set the username in this setting.</li><li>`:password` - If your mail server requires authentication, set the password in this setting.</li><li>`:authentication` - If your mail server requires authentication, you need to specify the authentication type here. This is a symbol and one of `:plain`, `:login`, `:cram_md5`.</li><li>`:enable_starttls_auto` - Set this to `false` if there is a problem with your server certificate that you cannot resolve.</li></ul>| +|`smtp_settings`|Allows detailed configuration for `:smtp` delivery method:<ul><li>`:address` - Allows you to use a remote mail server. Just change it from its default "localhost" setting.</li><li>`:port` - On the off chance that your mail server doesn't run on port 25, you can change it.</li><li>`:domain` - If you need to specify a HELO domain, you can do it here.</li><li>`:user_name` - If your mail server requires authentication, set the username in this setting.</li><li>`:password` - If your mail server requires authentication, set the password in this setting.</li><li>`:authentication` - If your mail server requires authentication, you need to specify the authentication type here. This is a symbol and one of `:plain`, `:login`, `:cram_md5`.</li><li>`:enable_starttls_auto` - Set this to `false` if there is a problem with your server certificate that you cannot resolve.</li></ul>| |`sendmail_settings`|Allows you to override options for the `:sendmail` delivery method.<ul><li>`:location` - The location of the sendmail executable. Defaults to `/usr/sbin/sendmail`.</li><li>`:arguments` - The command line arguments to be passed to sendmail. Defaults to `-i -t`.</li></ul>| |`raise_delivery_errors`|Whether or not errors should be raised if the email fails to be delivered. This only works if the external email server is configured for immediate delivery.| |`delivery_method`|Defines a delivery method. Possible values are `:smtp` (default), `:sendmail`, `:file` and `:test`.| @@ -649,7 +649,7 @@ config.action_mailer.delivery_method = :sendmail # } config.action_mailer.perform_deliveries = true config.action_mailer.raise_delivery_errors = true -config.action_mailer.default_options = {from: 'no-replay@example.com'} +config.action_mailer.default_options = {from: 'no-reply@example.com'} ``` ### Action Mailer Configuration for Gmail diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md index fc8fac4651..1f25c6ae95 100644 --- a/guides/source/active_record_basics.md +++ b/guides/source/active_record_basics.md @@ -62,9 +62,9 @@ may be necessary to write a lot of configuration code. This is particularly true for ORM frameworks in general. However, if you follow the conventions adopted by Rails, you'll need to write very little configuration (in some case no configuration at all) when creating Active Record models. The idea is that if -you configure your applications in the very same way most of the times then this -should be the default way. In this cases, explicit configuration would be needed -only in those cases where you can't follow the conventions for any reason. +you configure your applications in the very same way most of the time then this +should be the default way. Thus, explicit configuration would be needed +only in those cases where you can't follow the standard convention. ### Naming Conventions diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index c4d69908ed..2555927d15 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -91,7 +91,7 @@ The primary operation of `Model.find(options)` can be summarized as: ### Retrieving a Single Object -Active Record provides five different ways of retrieving a single object. +Active Record provides several different ways of retrieving a single object. #### Using a Primary Key @@ -687,6 +687,10 @@ The SQL that would be executed: ```sql SELECT * FROM posts WHERE id > 10 LIMIT 20 + +# Original query without `except` +SELECT * FROM posts WHERE id > 10 ORDER BY id asc LIMIT 20 + ``` ### `unscope` @@ -722,6 +726,10 @@ The SQL that would be executed: ```sql SELECT * FROM posts WHERE id > 10 ORDER BY id DESC + +# Original query without `only` +SELECT "posts".* FROM "posts" WHERE (id > 10) ORDER BY id desc LIMIT 20 + ``` ### `reorder` @@ -1230,7 +1238,7 @@ One important caveat is that `default_scope` will be overridden by ```ruby class User < ActiveRecord::Base - default_scope { where state: 'pending' } + default_scope { where state: 'pending' } scope :active, -> { where state: 'active' } scope :inactive, -> { where state: 'inactive' } end diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md index 621d2222ff..37790c62b1 100644 --- a/guides/source/active_record_validations.md +++ b/guides/source/active_record_validations.md @@ -243,7 +243,7 @@ line of code you can add the same kind of validation to several attributes. All of them accept the `:on` and `:message` options, which define when the validation should be run and what message should be added to the `errors` collection if it fails, respectively. The `:on` option takes one of the values -`:save` (the default), `:create` or `:update`. There is a default error +`:save` (the default), `:create` or `:update`. There is a default error message for each one of the validation helpers. These messages are used when the `:message` option isn't specified. Let's take a look at each one of the available helpers. @@ -357,7 +357,7 @@ given regular expression, which is specified using the `:with` option. ```ruby class Product < ActiveRecord::Base validates :legacy_code, format: { with: /\A[a-zA-Z]+\z/, - message: "Only letters allowed" } + message: "only allows letters" } end ``` @@ -677,13 +677,13 @@ class GoodnessValidator def initialize(person) @person = person end - + def validate if some_complex_condition_involving_ivars_and_private_methods? @person.errors[:base] << "This person is evil" end end - + # … end ``` @@ -736,8 +736,8 @@ class Topic < ActiveRecord::Base validates :title, length: { is: 5 }, allow_blank: true end -Topic.create("title" => "").valid? # => true -Topic.create("title" => nil).valid? # => true +Topic.create(title: "").valid? # => true +Topic.create(title: nil).valid? # => true ``` ### `:message` diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index c012ded888..7f65d920df 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -418,6 +418,14 @@ TIP: Since `with_options` forwards calls to its receiver they can be nested. Eac NOTE: Defined in `active_support/core_ext/object/with_options.rb`. +### JSON support + +Active Support provides a better implemention of `to_json` than the +json+ gem ordinarily provides for Ruby objects. This is because some classes, like +Hash+ and +OrderedHash+ needs special handling in order to provide a proper JSON representation. + +Active Support also provides an implementation of `as_json` for the <tt>Process::Status</tt> class. + +NOTE: Defined in `active_support/core_ext/object/to_json.rb`. + ### Instance Variables Active Support provides several methods to ease access to instance variables. @@ -439,6 +447,22 @@ C.new(0, 1).instance_values # => {"x" => 0, "y" => 1} NOTE: Defined in `active_support/core_ext/object/instance_variables.rb`. +#### `instance_variable_names` + +The method `instance_variable_names` returns an array. Each name includes the "@" sign. + +```ruby +class C + def initialize(x, y) + @x, @y = x, y + end +end + +C.new(0, 1).instance_variable_names # => ["@x", "@y"] +``` + +NOTE: Defined in `active_support/core_ext/object/instance_variables.rb`. + ### Silencing Warnings, Streams, and Exceptions The methods `silence_warnings` and `enable_warnings` change the value of `$VERBOSE` accordingly for the duration of their block, and reset it afterwards: @@ -1038,7 +1062,7 @@ For convenience `class_attribute` also defines an instance predicate which is th When `:instance_reader` is `false`, the instance predicate returns a `NoMethodError` just like the reader method. -If you do not want the instance predicate, pass `instance_predicate: false` and it will not be defined. +If you do not want the instance predicate, pass `instance_predicate: false` and it will not be defined. NOTE: Defined in `active_support/core_ext/class/attribute.rb` @@ -1423,7 +1447,7 @@ The method `pluralize` returns the plural of its receiver: As the previous example shows, Active Support knows some irregular plurals and uncountable nouns. Built-in rules can be extended in `config/initializers/inflections.rb`. That file is generated by the `rails` command and has instructions in comments. -`pluralize` can also take an optional `count` parameter. If `count == 1` the singular form will be returned. For any other value of `count` the plural form will be returned: +`pluralize` can also take an optional `count` parameter. If `count == 1` the singular form will be returned. For any other value of `count` the plural form will be returned: ```ruby "dude".pluralize(0) # => "dudes" @@ -2011,8 +2035,33 @@ NOTE: Defined in `active_support/core_ext/integer/inflections.rb`. Extensions to `BigDecimal` -------------------------- +### `to_s` -... +The method `to_s` is aliased to `to_formatted_s`. This provides a convenient way to display a BigDecimal value in floating-point notation: + +```ruby +BigDecimal.new(5.00, 6).to_s # => "5.0" +``` + +### `to_formatted_s` + +Te method `to_formatted_s` provides a default specifier of "F". This means that a simple call to `to_formatted_s` or `to_s` will result in floating point representation instead of engineering notation: + +```ruby +BigDecimal.new(5.00, 6).to_formatted_s # => "5.0" +``` + +and that symbol specifiers are also supported: + +```ruby +BigDecimal.new(5.00, 6).to_formatted_s(:db) # => "5.0" +``` + +Engineering notation is still supported: + +```ruby +BigDecimal.new(5.00, 6).to_formatted_s("e") # => "0.5E1" +``` Extensions to `Enumerable` -------------------------- diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md index d0499878da..7e056d970c 100644 --- a/guides/source/api_documentation_guidelines.md +++ b/guides/source/api_documentation_guidelines.md @@ -25,7 +25,7 @@ Write in present tense: "Returns a hash that...", rather than "Returned a hash t Start comments in upper case. Follow regular punctuation rules: ```ruby -# Declares an attribute reader backed by an internally-named +# Declares an attribute reader backed by an internally-named # instance variable. def attr_internal_reader(*attrs) ... @@ -57,7 +57,7 @@ Use two spaces to indent chunks of code--that is, for markup purposes, two space Short docs do not need an explicit "Examples" label to introduce snippets; they just follow paragraphs: ```ruby -# Converts a collection of elements into a formatted string by +# Converts a collection of elements into a formatted string by # calling +to_s+ on all elements and joining them. # # Blog.all.to_formatted_s # => "First PostSecond PostThird Post" @@ -141,7 +141,7 @@ class Array end ``` -WARNING: Using a pair of `+...+` for fixed-width font only works with **words**; that is: anything matching `\A\w+\z`. For anything else use `<tt>...</tt>`, notably symbols, setters, inline snippets, etc. +WARNING: Using a pair of `+...+` for fixed-width font only works with **words**; that is: anything matching `\A\w+\z`. For anything else use `<tt>...</tt>`, notably symbols, setters, inline snippets, etc. ### Regular Font diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index 3b3707d9e5..b68c24acfd 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -419,17 +419,6 @@ The rake task is: $ RAILS_ENV=production bundle exec rake assets:precompile ``` -For faster asset precompiles, you can partially load your application by setting -`config.assets.initialize_on_precompile` to false in `config/application.rb`, though in that case templates -cannot see application objects or methods. **Heroku requires this to be false.** - -WARNING: If you set `config.assets.initialize_on_precompile` to false, be sure to -test `rake assets:precompile` locally before deploying. It may expose bugs where -your assets reference application objects or methods, since those are still -in scope in development mode regardless of the value of this flag. Changing this flag also affects -engines. Engines can define assets for precompilation as well. Since the complete environment is not loaded, -engines (or other gems) will not be loaded, which can cause missing assets. - Capistrano (v2.15.1 and above) includes a recipe to handle this in deployment. Add the following line to `Capfile`: ```ruby @@ -570,16 +559,8 @@ In `config/environments/development.rb`, place the following line: config.assets.prefix = "/dev-assets" ``` -You will also need this in application.rb: - -```ruby -config.assets.initialize_on_precompile = false -``` - The `prefix` change makes Rails use a different URL for serving assets in development mode, and pass all requests to Sprockets. The prefix is still set to `/assets` in the production environment. Without this change, the application would serve the precompiled assets from `public/assets` in development, and you would not see any local changes until you compile assets again. -The `initialize_on_precompile` change tells the precompile task to run without invoking Rails. This is because the precompile task runs in production mode by default, and will attempt to connect to your specified production database. Please note that you cannot have code in pipeline files that relies on Rails resources (such as the database) when compiling locally with this option. - You will also need to ensure that any compressors or minifiers are available on your development system. In practice, this will allow you to precompile locally, have those files in your working tree, and commit those files to source control when needed. Development mode will work as expected. diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index 16a5241319..d7277b487f 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -721,6 +721,7 @@ When you declare a `belongs_to` association, the declaring class automatically g * `association=(associate)` * `build_association(attributes = {})` * `create_association(attributes = {})` +* `create_association!(attributes = {})` In all of these methods, `association` is replaced with the symbol passed as the first argument to `belongs_to`. For example, given the declaration: @@ -737,6 +738,7 @@ customer customer= build_customer create_customer +create_customer! ``` NOTE: When initializing a new `has_one` or `belongs_to` association you must use the `build_` prefix to build the association, rather than the `association.build` method that would be used for `has_many` or `has_and_belongs_to_many` associations. To create one, use the `create_` prefix. @@ -777,6 +779,10 @@ The `create_association` method returns a new object of the associated type. Thi customer_name: "John Doe") ``` +##### `create_association!(attributes = {})` + +Does the same as <tt>create_association</tt> above, but raises <tt>ActiveRecord::RecordInvalid</tt> if the record is invalid. + #### Options for `belongs_to` @@ -1019,6 +1025,7 @@ When you declare a `has_one` association, the declaring class automatically gain * `association=(associate)` * `build_association(attributes = {})` * `create_association(attributes = {})` +* `create_association!(attributes = {})` In all of these methods, `association` is replaced with the symbol passed as the first argument to `has_one`. For example, given the declaration: @@ -1035,6 +1042,7 @@ account account= build_account create_account +create_account! ``` NOTE: When initializing a new `has_one` or `belongs_to` association you must use the `build_` prefix to build the association, rather than the `association.build` method that would be used for `has_many` or `has_and_belongs_to_many` associations. To create one, use the `create_` prefix. @@ -1073,6 +1081,10 @@ The `create_association` method returns a new object of the associated type. Thi @account = @supplier.create_account(terms: "Net 30") ``` +##### `create_association!(attributes = {})` + +Does the same as <tt>create_association</tt> above, but raises <tt>ActiveRecord::RecordInvalid</tt> if the record is invalid. + #### Options for `has_one` While Rails uses intelligent defaults that will work well in most situations, there may be times when you want to customize the behavior of the `has_one` association reference. Such customizations can easily be accomplished by passing options when you create the association. For example, this association uses two such options: @@ -1285,6 +1297,7 @@ When you declare a `has_many` association, the declaring class automatically gai * `collection.exists?(...)` * `collection.build(attributes = {}, ...)` * `collection.create(attributes = {})` +* `collection.create!(attributes = {})` In all of these methods, `collection` is replaced with the symbol passed as the first argument to `has_many`, and `collection_singular` is replaced with the singularized version of that symbol. For example, given the declaration: @@ -1312,6 +1325,7 @@ orders.where(...) orders.exists?(...) orders.build(attributes = {}, ...) orders.create(attributes = {}) +orders.create!(attributes = {}) ``` ##### `collection(force_reload = false)` @@ -1427,6 +1441,10 @@ The `collection.create` method returns a new object of the associated type. This order_number: "A12345") ``` +##### `collection.create!(attributes = {})` + +Does the same as <tt>collection.create</tt> above, but raises <tt>ActiveRecord::RecordInvalid</tt> if the record is invalid. + #### Options for `has_many` While Rails uses intelligent defaults that will work well in most situations, there may be times when you want to customize the behavior of the `has_many` association reference. Such customizations can easily be accomplished by passing options when you create the association. For example, this association uses two such options: @@ -1511,6 +1529,20 @@ end By convention, Rails assumes that the column used to hold the primary key of the association is `id`. You can override this and explicitly specify the primary key with the `:primary_key` option. +Let's say that `users` table has `id` as the primary_key but it also has +`guid` column. And the requirement is that `todos` table should hold +`guid` column value and not `id` value. This can be achieved like this + +```ruby +class User < ActiveRecord::Base + has_many :todos, primary_key: :guid +end +``` + +Now if we execute `@user.todos.create` then `@todo` record will have +`user_id` value as the `guid` value of `@user`. + + ##### `:source` The `:source` option specifies the source association name for a `has_many :through` association. You only need to use this option if the name of the source association cannot be automatically inferred from the association name. @@ -1678,7 +1710,7 @@ person.posts.inspect # => [#<Post id: 5, name: "a1">, #<Post id: 5, name: "a1">] Reading.all.inspect # => [#<Reading id: 12, person_id: 5, post_id: 5>, #<Reading id: 13, person_id: 5, post_id: 5>] ``` -In the above case there are two readings and `person.posts` brings out both of +In the above case there are two readings and `person.posts` brings out both of them even though these records are pointing to the same post. Now let's set `distinct`: @@ -1697,24 +1729,24 @@ person.posts.inspect # => [#<Post id: 7, name: "a1">] Reading.all.inspect # => [#<Reading id: 16, person_id: 7, post_id: 7>, #<Reading id: 17, person_id: 7, post_id: 7>] ``` -In the above case there are still two readings. However `person.posts` shows +In the above case there are still two readings. However `person.posts` shows only one post because the collection loads only unique records. -If you want to make sure that, upon insertion, all of the records in the -persisted association are distinct (so that you can be sure that when you -inspect the association that you will never find duplicate records), you should -add a unique index on the table itself. For example, if you have a table named -``person_posts`` and you want to make sure all the posts are unique, you could +If you want to make sure that, upon insertion, all of the records in the +persisted association are distinct (so that you can be sure that when you +inspect the association that you will never find duplicate records), you should +add a unique index on the table itself. For example, if you have a table named +``person_posts`` and you want to make sure all the posts are unique, you could add the following in a migration: ```ruby add_index :person_posts, :post, :unique => true ``` -Note that checking for uniqueness using something like ``include?`` is subject -to race conditions. Do not attempt to use ``include?`` to enforce distinctness -in an association. For instance, using the post example from above, the -following code would be racy because multiple users could be attempting this +Note that checking for uniqueness using something like ``include?`` is subject +to race conditions. Do not attempt to use ``include?`` to enforce distinctness +in an association. For instance, using the post example from above, the +following code would be racy because multiple users could be attempting this at the same time: ```ruby @@ -1754,6 +1786,7 @@ When you declare a `has_and_belongs_to_many` association, the declaring class au * `collection.exists?(...)` * `collection.build(attributes = {})` * `collection.create(attributes = {})` +* `collection.create!(attributes = {})` In all of these methods, `collection` is replaced with the symbol passed as the first argument to `has_and_belongs_to_many`, and `collection_singular` is replaced with the singularized version of that symbol. For example, given the declaration: @@ -1781,6 +1814,7 @@ assemblies.where(...) assemblies.exists?(...) assemblies.build(attributes = {}, ...) assemblies.create(attributes = {}) +assemblies.create!(attributes = {}) ``` ##### Additional Column Methods @@ -1900,6 +1934,10 @@ The `collection.create` method returns a new object of the associated type. This @assembly = @part.assemblies.create({assembly_name: "Transmission housing"}) ``` +##### `collection.create!(attributes = {})` + +Does the same as <tt>collection.create</tt>, but raises <tt>ActiveRecord::RecordInvalid</tt> if the record is invalid. + #### Options for `has_and_belongs_to_many` While Rails uses intelligent defaults that will work well in most situations, there may be times when you want to customize the behavior of the `has_and_belongs_to_many` association reference. Such customizations can easily be accomplished by passing options when you create the association. For example, this association uses two such options: @@ -1928,7 +1966,7 @@ TIP: The `:foreign_key` and `:association_foreign_key` options are useful when s ```ruby class User < ActiveRecord::Base - has_and_belongs_to_many :friends, + has_and_belongs_to_many :friends, class_name: "User", foreign_key: "this_user_id", association_foreign_key: "other_user_id" @@ -2161,4 +2199,4 @@ Extensions can refer to the internals of the association proxy using these three * `proxy_association.owner` returns the object that the association is a part of. * `proxy_association.reflection` returns the reflection object that describes the association. -* `proxy_association.target` returns the associated object for `belongs_to` or `has_one`, or the collection of associated objects for `has_many` or `has_and_belongs_to_many`. +* `proxy_association.target` returns the associated object for `belongs_to` or `has_one`, or the collection of associated objects for `has_many` or `has_and_belongs_to_many`.
\ No newline at end of file diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md index 456abaf612..1e196b0e42 100644 --- a/guides/source/caching_with_rails.md +++ b/guides/source/caching_with_rails.md @@ -104,6 +104,15 @@ This method generates a cache key that depends on all products and can be used i All available products: <% end %> ``` + +If you want to cache a fragment under certain condition you can use `cache_if` or `cache_unless` + +```erb +<% cache_if (condition, cache_key_for_products) do %> + All available products: +<% end %> +``` + You can also use an Active Record model as the cache key: ```erb @@ -236,7 +245,7 @@ config.cache_store = :ehcache_store When initializing the cache, you may use the `:ehcache_config` option to specify the Ehcache config file to use (where the default is "ehcache.xml" in your Rails config directory), and the :cache_name option to provide a custom name for your cache (the default is rails_cache). -In addition to the standard `:expires_in` option, the `write` method on this cache can also accept the additional `:unless_exist` option, which will cause the cache store to use Ehcache's `putIfAbsent` method instead of `put`, and therefore will not overwrite an existing entry. Additionally, the `write` method supports all of the properties exposed by the [Ehcache Element class](http://ehcache.org/apidocs/net/sf/ehcache/Element.html) , including: +In addition to the standard `:expires_in` option, the `write` method on this cache can also accept the additional `:unless_exist` option, which will cause the cache store to use Ehcache's `putIfAbsent` method instead of `put`, and therefore will not overwrite an existing entry. Additionally, the `write` method supports all of the properties exposed by the [Ehcache Element class](http://ehcache.org/apidocs/net/sf/ehcache/Element.html) , including: | Property | Argument Type | Description | | --------------------------- | ------------------- | ----------------------------------------------------------- | diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 9e40165d15..ee0d373287 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -30,10 +30,10 @@ Configuring Rails Components In general, the work of configuring Rails means configuring the components of Rails, as well as configuring Rails itself. The configuration file `config/application.rb` and environment-specific configuration files (such as `config/environments/production.rb`) allow you to specify the various settings that you want to pass down to all of the components. -For example, the default `config/application.rb` file includes this setting: +For example, the `config/application.rb` file includes this setting: ```ruby -config.filter_parameters += [:password] +config.autoload_paths += %W(#{config.root}/extras) ``` This is a setting for Rails itself. If you want to pass settings to individual Rails components, you can do so via the same `config` object in `config/application.rb`: @@ -97,7 +97,9 @@ These configuration methods are to be called on a `Rails::Railtie` object, such * `config.file_watcher` the class used to detect file updates in the filesystem when `config.reload_classes_only_on_change` is true. Must conform to `ActiveSupport::FileUpdateChecker` API. -* `config.filter_parameters` used for filtering out the parameters that you don't want shown in the logs, such as passwords or credit card numbers. +* `config.filter_parameters` used for filtering out the parameters that +you don't want shown in the logs, such as passwords or credit card +numbers. New applications filter out passwords by adding the following `config.filter_parameters+=[:password]` in `config/initializers/filter_parameter_logging.rb`. * `config.force_ssl` forces all requests to be under HTTPS protocol by using `ActionDispatch::SSL` middleware. @@ -131,7 +133,8 @@ These configuration methods are to be called on a `Rails::Railtie` object, such ### Configuring Assets -* `config.assets.enabled` a flag that controls whether the asset pipeline is enabled. It is explicitly initialized in `config/application.rb`. +* `config.assets.enabled` a flag that controls whether the asset +pipeline is enabled. It is set to true by default. * `config.assets.compress` a flag that enables the compression of compiled assets. It is explicitly set to true in `config/production.rb`. @@ -421,13 +424,13 @@ There are a few configuration options available in Active Support: ### Configuring a Database -Just about every Rails application will interact with a database. The database to use is specified in a configuration file called `config/database.yml`. If you open this file in a new Rails application, you'll see a default database configured to use SQLite3. The file contains sections for three different environments in which Rails can run by default: +Just about every Rails application will interact with a database. The database to use is specified in a configuration file called `config/database.yml`. If you open this file in a new Rails application, you'll see a default database configured to use SQLite3. The file contains sections for three different environments in which Rails can run by default: * The `development` environment is used on your development/local computer as you interact manually with the application. * The `test` environment is used when running automated tests. * The `production` environment is used when you deploy your application for the world to use. -TIP: You don't have to update the database configurations manually. If you look at the options of the application generator, you will see that one of the options is named `--database`. This option allows you to choose an adapter from a list of the most used relational databases. You can even run the generator repeatedly: `cd .. && rails new blog --database=mysql`. When you confirm the overwriting of the `config/database.yml` file, your application will be configured for MySQL instead of SQLite. Detailed examples of the common database connections are below. +TIP: You don't have to update the database configurations manually. If you look at the options of the application generator, you will see that one of the options is named `--database`. This option allows you to choose an adapter from a list of the most used relational databases. You can even run the generator repeatedly: `cd .. && rails new blog --database=mysql`. When you confirm the overwriting of the `config/database.yml` file, your application will be configured for MySQL instead of SQLite. Detailed examples of the common database connections are below. #### Configuring an SQLite3 Database @@ -527,7 +530,7 @@ By default Rails ships with three environments: "development", "test", and "prod Imagine you have a server which mirrors the production environment but is only used for testing. Such a server is commonly called a "staging server". To define an environment called "staging" for this server just by create a file called `config/environments/staging.rb`. Please use the contents of any existing file in `config/environments` as a starting point and make the necessary changes from there. -That environment is no different than the default ones, start a server with `rails server -e staging`, a console with `rails console staging`, `Rails.env.staging?` works, etc. +That environment is no different than the default ones, start a server with `rails server -e staging`, a console with `rails console staging`, `Rails.env.staging?` works, etc. Rails Environment Settings diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index 0be9bb1ced..0bfa646b81 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -182,9 +182,9 @@ Contributing to the Rails Documentation Ruby on Rails has two main sets of documentation: the guides help you in learning about Ruby on Rails, and the API is a reference. -You can help improve the Rails guides by making them more coherent, consistent or readable, adding missing information, correcting factual errors, fixing typos, or bringing it up to date with the latest edge Rails. To get involved in the translation of Rails guides, please see [Translating Rails Guides](https://wiki.github.com/lifo/docrails/translating-rails-guides). +You can help improve the Rails guides by making them more coherent, consistent or readable, adding missing information, correcting factual errors, fixing typos, or bringing it up to date with the latest edge Rails. To get involved in the translation of Rails guides, please see [Translating Rails Guides](https://wiki.github.com/rails/docrails/translating-rails-guides). -If you're confident about your changes, you can push them directly yourself via [docrails](https://github.com/lifo/docrails). Docrails is a branch with an **open commit policy** and public write access. Commits to docrails are still reviewed, but this happens after they are pushed. Docrails is merged with master regularly, so you are effectively editing the Ruby on Rails documentation. +If you're confident about your changes, you can push them directly yourself via [docrails](https://github.com/rails/docrails). Docrails is a branch with an **open commit policy** and public write access. Commits to docrails are still reviewed, but this happens after they are pushed. Docrails is merged with master regularly, so you are effectively editing the Ruby on Rails documentation. If you are unsure of the documentation changes, you can create an issue in the [Rails](https://github.com/rails/rails/issues) issues tracker on GitHub. diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md index a4dab39d55..b409534cb0 100644 --- a/guides/source/form_helpers.md +++ b/guides/source/form_helpers.md @@ -568,7 +568,7 @@ NOTE: In many cases the built-in date pickers are clumsy as they do not aid the ### Individual Components -Occasionally you need to display just a single date component such as a year or a month. Rails provides a series of helpers for this, one for each component `select_year`, `select_month`, `select_day`, `select_hour`, `select_minute`, `select_second`. These helpers are fairly straightforward. By default they will generate an input field named after the time component (for example "year" for `select_year`, "month" for `select_month` etc.) although this can be overridden with the `:field_name` option. The `:prefix` option works in the same way that it does for `select_date` and `select_time` and has the same default value. +Occasionally you need to display just a single date component such as a year or a month. Rails provides a series of helpers for this, one for each component `select_year`, `select_month`, `select_day`, `select_hour`, `select_minute`, `select_second`. These helpers are fairly straightforward. By default they will generate an input field named after the time component (for example "year" for `select_year`, "month" for `select_month` etc.) although this can be overridden with the `:field_name` option. The `:prefix` option works in the same way that it does for `select_date` and `select_time` and has the same default value. The first parameter specifies which value should be selected and can either be an instance of a Date, Time or DateTime, in which case the relevant component will be extracted, or a numerical value. For example @@ -830,7 +830,7 @@ Many apps grow beyond simple forms editing a single object. For example when cre ### Configuring the Model -Active Record provides model level support via the `accepts_nested_attributes_for` method: +Active Record provides model level support via the `accepts_nested_attributes_for` method: ```ruby class Person < ActiveRecord::Base @@ -885,7 +885,7 @@ end :name => 'John Doe', :addresses_attributes => { '0' => { - :kind => 'Home', + :kind => 'Home', :street => '221b Baker Street', }, '1' => { diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index 599e47949d..2fb0cd7c72 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -64,7 +64,7 @@ Creating a New Rails Project The best way to use this guide is to follow each step as it happens, no code or step needed to make this example application has been left out, so you can literally follow along step by step. You can get the complete code -[here](https://github.com/lifo/docrails/tree/master/guides/code/getting_started). +[here](https://github.com/rails/docrails/tree/master/guides/code/getting_started). By following along with this guide, you'll create a Rails project called `blog`, a @@ -135,7 +135,7 @@ application. Most of the work in this tutorial will happen in the `app/` folder, | ----------- | ------- | |app/|Contains the controllers, models, views, helpers, mailers and assets for your application. You'll focus on this folder for the remainder of this guide.| |bin/|Contains the rails script that starts your app and can contain other scripts you use to deploy or run your application.| -|config/|Configure your application's runtime rules, routes, database, and more. This is covered in more detail in [Configuring Rails Applications](configuring.html)| +|config/|Configure your application's runtime rules, routes, database, and more. This is covered in more detail in [Configuring Rails Applications](configuring.html)| |config.ru|Rack configuration for Rack based servers used to start the application.| |db/|Contains your current database schema, as well as the database migrations.| |Gemfile<br />Gemfile.lock|These files allow you to specify what gem dependencies are needed for your Rails application. These files are used by the Bundler gem. For more information about Bundler, see [the Bundler website](http://gembundler.com) | @@ -264,7 +264,7 @@ Blog::Application.routes.draw do end ``` -If you run `rake routes`, you'll see that all the routes for the +If you run `rake routes`, you'll see that all the routes for the standard RESTful actions. ```bash @@ -288,7 +288,7 @@ It will look a little basic for now, but that's ok. We'll look at improving the ### Laying down the ground work -The first thing that you are going to need to create a new post within the application is a place to do that. A great place for that would be at `/posts/new`. With the route already defined, requests can now be made to `/posts/new` in the application. Navigate to <http://localhost:3000/posts/new> and you'll see a routing error: +The first thing that you are going to need to create a new post within the application is a place to do that. A great place for that would be at `/posts/new`. With the route already defined, requests can now be made to `/posts/new` in the application. Navigate to <http://localhost:3000/posts/new> and you'll see a routing error: ![Another routing error, uninitialized constant PostsController](images/getting_started/routing_error_no_controller.png) @@ -531,21 +531,28 @@ and change the `create` action to look like this: ```ruby def create - @post = Post.new(params[:post]) - + @post = Post.new(post_params) + @post.save - redirect_to @post + redirect_to @post end + +private + def post_params + params.require(:post).permit(:title, :text) + end ``` Here's what's going on: every Rails model can be initialized with its respective attributes, which are automatically mapped to the respective database columns. In the first line we do just that (remember that -`params[:post]` contains the attributes we're interested in). Then, +`post_params` contains the attributes we're interested in). Then, `@post.save` is responsible for saving the model in the database. Finally, we redirect the user to the `show` action, which we'll define later. +TIP: Note that `def post_params` is private. This new approach prevents an attacker from setting the model's attributes by manipulating the hash passed to the model. For more information, refer to [this blog post about Strong Parameters](http://weblog.rubyonrails.org/2012/3/21/strong-parameters/). + TIP: As we'll see later, `@post.save` returns a boolean indicating whether the model was saved or not. @@ -553,14 +560,14 @@ whether the model was saved or not. If you submit the form again now, Rails will complain about not finding the `show` action. That's not very useful though, so let's add the -`show` action before proceeding. +`show` action before proceeding. ```ruby post GET /posts/:id(.:format) posts#show ``` The special syntax `:id` tells rails that this route expects an `:id` -parameter, which in our case will be the id of the post. +parameter, which in our case will be the id of the post. As we did before, we need to add the `show` action in `app/controllers/posts_controller.rb` and its respective view. @@ -621,7 +628,7 @@ Visit <http://localhost:3000/posts/new> and give it a try! ### Listing all posts -We still need a way to list all our posts, so let's do that. +We still need a way to list all our posts, so let's do that. We'll use a specific route from `config/routes.rb`: ```ruby @@ -742,7 +749,7 @@ end ``` These changes will ensure that all posts have a title that is at least five -characters long. Rails can validate a variety of conditions in a model, +characters long. Rails can validate a variety of conditions in a model, including the presence or uniqueness of columns, their format, and the existence of associated objects. Validations are covered in detail in [Active Record Validations](active_record_validations.html) @@ -763,7 +770,7 @@ def create @post = Post.new(params[:post].permit(:title, :text)) if @post.save - redirect_to @post + redirect_to @post else render 'new' end @@ -1084,7 +1091,7 @@ together. </table> ``` -Here we're using `link_to` in a different way. We pass the named route as the first argument, +Here we're using `link_to` in a different way. We pass the named route as the first argument, and then the final two keys as another argument. The `:method` and `:'data-confirm'` options are used as HTML5 attributes so that when the link is clicked, Rails will first show a confirm dialog to the user, and then submit the link with method `delete`. @@ -1095,7 +1102,7 @@ generated the application. Without this file, the confirmation dialog box wouldn ![Confirm Dialog](images/getting_started/confirm_dialog.png) Congratulations, you can now create, show, list, update and destroy -posts. +posts. TIP: In general, Rails encourages the use of resources objects in place of declaring routes manually. diff --git a/guides/source/i18n.md b/guides/source/i18n.md index 65a85efa69..b248c7645a 100644 --- a/guides/source/i18n.md +++ b/guides/source/i18n.md @@ -174,7 +174,7 @@ end # in your /etc/hosts file to try this out locally def extract_locale_from_tld parsed_locale = request.host.split('.').last - I18n.available_locales.include?(parsed_locale.to_sym) ? parsed_locale : nil + I18n.available_locales.include?(parsed_locale.to_sym) ? parsed_locale : nil end ``` diff --git a/guides/source/initialization.md b/guides/source/initialization.md index 9fcd530183..738591659d 100644 --- a/guides/source/initialization.md +++ b/guides/source/initialization.md @@ -36,8 +36,8 @@ This file is as follows: ```ruby #!/usr/bin/env ruby -APP_PATH = File.expand_path('../../config/application', __FILE__) -require File.expand_path('../../config/boot', __FILE__) +APP_PATH = File.expand_path('../../config/application', __FILE__) +require File.expand_path('../../config/boot', __FILE__) require 'rails/commands' ``` @@ -373,7 +373,7 @@ The `options[:config]` value defaults to `config.ru` which contains this: ```ruby # This file is used by Rack-based servers to start the application. -require ::File.expand_path('../config/environment', __FILE__) +require ::File.expand_path('../config/environment', __FILE__) run <%= app_const %> ``` @@ -388,7 +388,7 @@ app = eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_app", The `initialize` method of `Rack::Builder` will take the block here and execute it within an instance of `Rack::Builder`. This is where the majority of the initialization process of Rails happens. The `require` line for `config/environment.rb` in `config.ru` is the first to run: ```ruby -require ::File.expand_path('../config/environment', __FILE__) +require ::File.expand_path('../config/environment', __FILE__) ``` ### `config/environment.rb` @@ -546,7 +546,7 @@ def self.run(app, options={}) else server.register('/', Rack::Handler::Mongrel.new(app)) end - yield server if block_given? + yield server if block_given? server.run.join end ``` diff --git a/guides/source/layout.html.erb b/guides/source/layout.html.erb index 397dd62638..ef2bdf0753 100644 --- a/guides/source/layout.html.erb +++ b/guides/source/layout.html.erb @@ -102,10 +102,10 @@ </p> <p> If you see any typos or factual errors you are confident to - patch, please clone <%= link_to 'docrails', 'https://github.com/lifo/docrails' %> + patch, please clone <%= link_to 'docrails', 'https://github.com/rails/docrails' %> and push the change yourself. That branch of Rails has public write access. Commits are still reviewed, but that happens after you've submitted your - contribution. <%= link_to 'docrails', 'https://github.com/lifo/docrails' %> is + contribution. <%= link_to 'docrails', 'https://github.com/rails/docrails' %> is cross-merged with master periodically. </p> <p> diff --git a/guides/source/migrations.md b/guides/source/migrations.md index 550f8fdc3c..eb0cfd9451 100644 --- a/guides/source/migrations.md +++ b/guides/source/migrations.md @@ -366,26 +366,24 @@ create_join_table :products, :categories which creates a `categories_products` table with two columns called `category_id` and `product_id`. These columns have the option `:null` set to -`false` by default. - -You can pass the option `:table_name` with you want to customize the table -name. For example, +`false` by default. This can be overridden by specifying the `:column_options` +option. ```ruby -create_join_table :products, :categories, table_name: :categorization +create_join_table :products, :categories, column_options: {null: true} ``` -will create a `categorization` table. +will create the `product_id` and `category_id` with the `:null` option as +`true`. -By default, `create_join_table` will create two columns with no options, but -you can specify these options using the `:column_options` option. For example, +You can pass the option `:table_name` with you want to customize the table +name. For example, ```ruby -create_join_table :products, :categories, column_options: {null: true} +create_join_table :products, :categories, table_name: :categorization ``` -will create the `product_id` and `category_id` with the `:null` option as -`true`. +will create a `categorization` table. `create_join_table` also accepts a block, which you can use to add indices (which are not created by default) or additional columns: @@ -448,7 +446,7 @@ definitions: * `create_table` * `create_join_table` * `drop_table` (must supply a block) -* `drop_join_table` (must supply a block) +* `drop_join_table` (must supply a block) * `remove_timestamps` * `rename_column` * `rename_index` @@ -877,7 +875,7 @@ end # app/models/product.rb class Product < ActiveRecord::Base - validates :flag, inclusion: { in: [true, false] } + validates :flag, inclusion: { in: [true, false] } validates :fuzz, presence: true end ``` diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md index b548eaede8..f8062a1f7c 100644 --- a/guides/source/rails_application_templates.md +++ b/guides/source/rails_application_templates.md @@ -91,7 +91,7 @@ Adds a line inside the `Application` class for `config/application.rb`. If `options[:env]` is specified, the line is appended to the corresponding file in `config/environments`. ```ruby -environment 'config.action_mailer.default_url_options = {host: 'http://yourwebsite.example.com'}, env: 'production' +environment 'config.action_mailer.default_url_options = {host: "http://yourwebsite.example.com"}', env: 'production' ``` A block can be used in place of the `data` argument. diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md index de8f3f483f..d144fba762 100644 --- a/guides/source/rails_on_rack.md +++ b/guides/source/rails_on_rack.md @@ -82,7 +82,7 @@ To use `rackup` instead of Rails' `rails server`, you can put the following insi ```ruby # Rails.root/config.ru -require ::File.expand_path('../config/environment', __FILE__) +require ::File.expand_path('../config/environment', __FILE__) use Rack::Debugger use Rack::ContentLength @@ -131,6 +131,7 @@ use ActionDispatch::DebugExceptions use ActionDispatch::RemoteIp use ActionDispatch::Reloader use ActionDispatch::Callbacks +use ActiveRecord::Migration::CheckPending use ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache use ActionDispatch::Cookies @@ -272,6 +273,10 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol * Runs the prepare callbacks before serving the request. + **`ActiveRecord::Migration::CheckPending`** + +* Checks pending migrations and raises `ActiveRecord::PendingMigrationError` if any migrations are pending. + **`ActiveRecord::ConnectionAdapters::ConnectionManagement`** * Cleans active connections after each request, unless the `rack.test` key in the request environment is set to `true`. diff --git a/guides/source/routing.md b/guides/source/routing.md index c26a827172..076b9dd176 100644 --- a/guides/source/routing.md +++ b/guides/source/routing.md @@ -36,7 +36,7 @@ the request is dispatched to the `patients` controller's `show` action with `{ i ### Generating Paths and URLs from Code -You can also generate paths and URLs. If the route above is modified to be: +You can also generate paths and URLs. If the route above is modified to be: ```ruby get '/patients/:id', to: 'patients#show', as: 'patient' @@ -803,7 +803,7 @@ You should put the `root` route at the top of the file, because it is the most p NOTE: The `root` route only routes `GET` requests to the action. -You can also use root inside namespaces and scopes as well. For example: +You can also use root inside namespaces and scopes as well. For example: ```ruby namespace :admin do diff --git a/guides/source/ruby_on_rails_guides_guidelines.md b/guides/source/ruby_on_rails_guides_guidelines.md index d5d1ee0a38..5564b0648b 100644 --- a/guides/source/ruby_on_rails_guides_guidelines.md +++ b/guides/source/ruby_on_rails_guides_guidelines.md @@ -63,7 +63,7 @@ Those guidelines apply also to guides. HTML Guides ----------- -Before generating the guides, make sure that you have the latest version of Bundler installed on your system. As of this writing, you must install Bundler 1.3.5 on your device. +Before generating the guides, make sure that you have the latest version of Bundler installed on your system. As of this writing, you must install Bundler 1.3.5 on your device. To install the latest version of Bundler, simply run the `gem install bundler` command diff --git a/guides/source/security.md b/guides/source/security.md index f04129acdb..ad0546810d 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -346,13 +346,13 @@ Intranet and administration interfaces are popular attack targets, because they In 2007 there was the first tailor-made trojan which stole information from an Intranet, namely the "Monster for employers" web site of Monster.com, an online recruitment web application. Tailor-made Trojans are very rare, so far, and the risk is quite low, but it is certainly a possibility and an example of how the security of the client host is important, too. However, the highest threat to Intranet and Admin applications are XSS and CSRF.
-**XSS** If your application re-displays malicious user input from the extranet, the application will be vulnerable to XSS. User names, comments, spam reports, order addresses are just a few uncommon examples, where there can be XSS. +**XSS** If your application re-displays malicious user input from the extranet, the application will be vulnerable to XSS. User names, comments, spam reports, order addresses are just a few uncommon examples, where there can be XSS. Having one single place in the admin interface or Intranet, where the input has not been sanitized, makes the entire application vulnerable. Possible exploits include stealing the privileged administrator's cookie, injecting an iframe to steal the administrator's password or installing malicious software through browser security holes to take over the administrator's computer. Refer to the Injection section for countermeasures against XSS. It is _recommended to use the SafeErb plugin_ also in an Intranet or administration interface. -**CSRF** Cross-Site Reference Forgery (CSRF) is a gigantic attack method, it allows the attacker to do everything the administrator or Intranet user may do. As you have already seen above how CSRF works, here are a few examples of what attackers can do in the Intranet or admin interface. +**CSRF** Cross-Site Reference Forgery (CSRF) is a gigantic attack method, it allows the attacker to do everything the administrator or Intranet user may do. As you have already seen above how CSRF works, here are a few examples of what attackers can do in the Intranet or admin interface. A real-world example is a [router reconfiguration by CSRF](http://www.h-online.com/security/Symantec-reports-first-active-attack-on-a-DSL-router--/news/102352). The attackers sent a malicious e-mail, with CSRF in it, to Mexican users. The e-mail claimed there was an e-card waiting for them, but it also contained an image tag that resulted in a HTTP-GET request to reconfigure the user's router (which is a popular model in Mexico). The request changed the DNS-settings so that requests to a Mexico-based banking site would be mapped to the attacker's site. Everyone who accessed the banking site through that router saw the attacker's fake web site and had his credentials stolen. diff --git a/guides/source/testing.md b/guides/source/testing.md index b02d0b663c..416a8b592f 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -64,7 +64,7 @@ YAML-formatted fixtures are a very human-friendly way to describe your sample da Here's a sample YAML fixture file: ```yaml -# lo & behold! I am a YAML comment! +# lo & behold! I am a YAML comment! david: name: David Heinemeier Hansson birthday: 1979-10-15 @@ -159,7 +159,7 @@ class PostTest < ActiveSupport::TestCase The `PostTest` class defines a _test case_ because it inherits from `ActiveSupport::TestCase`. `PostTest` thus has all the methods available from `ActiveSupport::TestCase`. You'll see those methods a little later in this guide. -Any method defined within a class inherited from `MiniTest::Unit::TestCase` +Any method defined within a class inherited from `MiniTest::Unit::TestCase` (which is the superclass of `ActiveSupport::TestCase`) that begins with `test` (case sensitive) is simply called a test. So, `test_password`, `test_valid_password` and `testValidPassword` all are legal test names and are run automatically when the test case is run. Rails adds a `test` method that takes a test name and a block. It generates a normal `MiniTest::Unit` test with method names prefixed with `test_`. So, @@ -396,7 +396,7 @@ Rails adds some custom assertions of its own to the `test/unit` framework: | `assert_no_difference(expressions, message = nil, &block)` | Asserts that the numeric result of evaluating an expression is not changed before and after invoking the passed in block.| | `assert_recognizes(expected_options, path, extras={}, message=nil)` | Asserts that the routing of the given path was handled correctly and that the parsed options (given in the expected_options hash) match path. Basically, it asserts that Rails recognizes the route given by expected_options.| | `assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)` | Asserts that the provided options can be used to generate the provided path. This is the inverse of assert_recognizes. The extras parameter is used to tell the request the names and values of additional request parameters that would be in a query string. The message parameter allows you to specify a custom error message for assertion failures.| -| `assert_response(type, message = nil)` | Asserts that the response comes with a specific status code. You can specify `:success` to indicate 200-299, `:redirect` to indicate 300-399, `:missing` to indicate 404, or `:error` to match the 500-599 range| +| `assert_response(type, message = nil)` | Asserts that the response comes with a specific status code. You can specify `:success` to indicate 200-299, `:redirect` to indicate 300-399, `:missing` to indicate 404, or `:error` to match the 500-599 range| | `assert_redirected_to(options = {}, message=nil)` | Assert that the redirection options passed in match those of the redirect called in the latest action. This match can be partial, such that `assert_redirected_to(controller: "weblog")` will also match the redirection of `redirect_to(controller: "weblog", action: "show")` and so on.| | `assert_template(expected = nil, message=nil)` | Asserts that the request was rendered with the appropriate template file.| @@ -622,11 +622,11 @@ The `assert_select` assertion is quite powerful. For more advanced usage, refer There are more assertions that are primarily used in testing views: -| Assertion | Purpose | -| ---------------------------------------------------------- | ------- | -| `assert_select_email` | Allows you to make assertions on the body of an e-mail. | -| `assert_select_encoded` | Allows you to make assertions on encoded HTML. It does this by un-encoding the contents of each element and then calling the block with all the un-encoded elements.| -| `css_select(selector)` or `css_select(element, selector)` | Returns an array of all the elements selected by the _selector_. In the second variant it first matches the base _element_ and tries to match the _selector_ expression on any of its children. If there are no matches both variants return an empty array.| +| Assertion | Purpose | +| --------------------------------------------------------- | ------- | +| `assert_select_email` | Allows you to make assertions on the body of an e-mail. | +| `assert_select_encoded` | Allows you to make assertions on encoded HTML. It does this by un-encoding the contents of each element and then calling the block with all the un-encoded elements.| +| `css_select(selector)` or `css_select(element, selector)` | Returns an array of all the elements selected by the _selector_. In the second variant it first matches the base _element_ and tries to match the _selector_ expression on any of its children. If there are no matches both variants return an empty array.| Here's an example of using `assert_select_email`: @@ -767,8 +767,8 @@ Rake Tasks for Running your Tests You don't need to set up and run your tests by hand on a test-by-test basis. Rails comes with a number of commands to help in testing. The table below lists all commands that come along in the default Rakefile when you initiate a Rails project. -| Tasks | Description | -| ------------------------ | ----------- | +| Tasks | Description | +| ----------------------- | ----------- | | `rake test` | Runs all unit, functional and integration tests. You can also simply run `rake test` as Rails will run all the tests by default| | `rake test:controllers` | Runs all the controller tests from `test/controllers`| | `rake test:functionals` | Runs all the functional tests from `test/controllers`, `test/mailers`, and `test/functional`| @@ -778,13 +778,6 @@ You don't need to set up and run your tests by hand on a test-by-test basis. Rai | `rake test:models` | Runs all the model tests from `test/models`| | `rake test:units` | Runs all the unit tests from `test/models`, `test/helpers`, and `test/unit`| -There're also some test commands which you can initiate by running rake tasks: - -| Tasks | Description | -| ------------------------ | ----------- | -| `rake test` | Runs all unit, functional and integration tests. You can also simply run `rake` as the _test_ target is the default.| -| `rake test:recent` | Tests recent changes| -| `rake test:uncommitted` | Runs all the tests which are uncommitted. Supports Subversion and Git| Brief Note About `MiniTest` ----------------------------- diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index 5a1594102a..35a9617b80 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -69,6 +69,10 @@ end If the action is not being used in a public API and you are free to change the HTTP method, you can update your route to use `patch` instead of `put`: +`PUT` requests to `/users/:id` in Rails 4 get routed to `update` as they are +today. So, if you have an API that gets real PUT requests it is going to work. +The router also routes `PATCH` requests to `/users/:id` to the `update` action. + ```ruby resources :users do patch :update_name, on: :member diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md index ddefaf6ff8..22a59cdfec 100644 --- a/guides/source/working_with_javascript_in_rails.md +++ b/guides/source/working_with_javascript_in_rails.md @@ -394,4 +394,4 @@ Here are some helpful links to help you learn even more: * [jquery-ujs list of external articles](https://github.com/rails/jquery-ujs/wiki/External-articles) * [Rails 3 Remote Links and Forms: A Definitive Guide](http://www.alfajango.com/blog/rails-3-remote-links-and-forms/) * [Railscasts: Unobtrusive JavaScript](http://railscasts.com/episodes/205-unobtrusive-javascript) -* [Railscasts: Turbolinks](http://railscasts.com/episodes/390-turbolinks)
\ No newline at end of file +* [Railscasts: Turbolinks](http://railscasts.com/episodes/390-turbolinks) diff --git a/rails.gemspec b/rails.gemspec index 1993467455..4a17beac69 100644 --- a/rails.gemspec +++ b/rails.gemspec @@ -25,5 +25,5 @@ Gem::Specification.new do |s| s.add_dependency 'railties', version s.add_dependency 'bundler', '>= 1.3.0', '< 2.0' - s.add_dependency 'sprockets-rails', '~> 2.0.0.rc4' + s.add_dependency 'sprockets-rails', '~> 2.0.0' end diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index d11b0b7e85..23a7cf6ca3 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,7 @@ +* Clearing autoloaded constants triggers routes reloading [Fixes #10685]. + + *Xavier Noria* + * Fixes bug with scaffold generator with `--assets=false --resource-route=false`. Fixes #9525. diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb index bb98bbe5bf..3a3c11d8b3 100644 --- a/railties/lib/rails.rb +++ b/railties/lib/rails.rb @@ -3,7 +3,9 @@ require 'rails/ruby_version_check' require 'pathname' require 'active_support' +require 'active_support/dependencies/autoload' require 'active_support/core_ext/kernel/reporting' +require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/array/extract_options' require 'rails/application' @@ -20,26 +22,22 @@ silence_warnings do end module Rails - autoload :Info, 'rails/info' - autoload :InfoController, 'rails/info_controller' - autoload :WelcomeController, 'rails/welcome_controller' + extend ActiveSupport::Autoload + + autoload :Info + autoload :InfoController + autoload :WelcomeController class << self attr_accessor :application, :cache, :logger + delegate :initialize!, :initialized?, to: :application + # The Configuration instance used to configure the Rails environment def configuration application.config end - def initialize! - application.initialize! - end - - def initialized? - application.initialized? - end - def backtrace_cleaner @backtrace_cleaner ||= begin # Relies on Active Support, so we have to lazy load to postpone definition until AS has been loaded @@ -76,7 +74,7 @@ module Rails env = Rails.env groups.unshift(:default, env) groups.concat ENV["RAILS_GROUPS"].to_s.split(",") - groups.concat hash.map { |k,v| k if v.map(&:to_s).include?(env) } + groups.concat hash.map { |k, v| k if v.map(&:to_s).include?(env) } groups.compact! groups.uniq! groups diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 914e4393c4..753e870a8c 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -207,9 +207,7 @@ module Rails end # Initialize the application passing the given group. By default, the - # group is :default but sprockets precompilation passes group equals - # to assets if initialize_on_precompile is false to avoid booting the - # whole app. + # group is :default def initialize!(group=:default) #:nodoc: raise "Application has been already initialized." if @initialized run_initializers(group, self) diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 31fc80e544..644893d9e1 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -61,7 +61,6 @@ module Rails @assets.cache_store = [ :file_store, "#{root}/tmp/cache/assets/#{Rails.env}/" ] @assets.js_compressor = nil @assets.css_compressor = nil - @assets.initialize_on_precompile = true @assets.logger = nil end diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb index 3ae60312c5..7a1bb1e25c 100644 --- a/railties/lib/rails/application/finisher.rb +++ b/railties/lib/rails/application/finisher.rb @@ -62,17 +62,28 @@ module Rails ActiveSupport.run_load_hooks(:after_initialize, self) end - # Set app reload just after the finisher hook to ensure - # routes added in the hook are still loaded. + # Set routes reload after the finisher hook to ensure routes added in + # the hook are taken into account. initializer :set_routes_reloader_hook do reloader = routes_reloader reloader.execute_if_updated self.reloaders << reloader - ActionDispatch::Reloader.to_prepare { reloader.execute_if_updated } + ActionDispatch::Reloader.to_prepare do + # We configure #execute rather than #execute_if_updated because if + # autoloaded constants are cleared we need to reload routes also in + # case any was used there, as in + # + # mount MailPreview => 'mail_view' + # + # This means routes are also reloaded if i18n is updated, which + # might not be necessary, but in order to be more precise we need + # some sort of reloaders dependency support, to be added. + reloader.execute + end end - # Set app reload just after the finisher hook to ensure - # paths added in the hook are still loaded. + # Set clearing dependencies after the finisher hook to ensure paths + # added in the hook are taken into account. initializer :set_clear_dependencies_hook, group: :all do callback = lambda do ActiveSupport::DescendantsTracker.clear @@ -82,9 +93,17 @@ module Rails if config.reload_classes_only_on_change reloader = config.file_watcher.new(*watchable_args, &callback) self.reloaders << reloader - # We need to set a to_prepare callback regardless of the reloader result, i.e. - # models should be reloaded if any of the reloaders (i18n, routes) were updated. - ActionDispatch::Reloader.to_prepare(prepend: true){ reloader.execute } + + # Prepend this callback to have autoloaded constants cleared before + # any other possible reloading, in case they need to autoload fresh + # constants. + ActionDispatch::Reloader.to_prepare(prepend: true) do + # In addition to changes detected by the file watcher, if routes + # or i18n have been updated we also need to clear constants, + # that's why we run #execute rather than #execute_if_updated, this + # callback has to clear autoloaded constants after any update. + reloader.execute + end else ActionDispatch::Reloader.to_cleanup(&callback) end diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake index 2116330b45..76a95304c3 100644 --- a/railties/lib/rails/tasks/framework.rake +++ b/railties/lib/rails/tasks/framework.rake @@ -47,7 +47,7 @@ namespace :rails do gen = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: Rails.root File.exists?(Rails.root.join("config", "application.rb")) ? - gen.send(:app_const) : gen.send(:valid_app_const?) + gen.send(:app_const) : gen.send(:valid_const?) gen end end diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb index 34432eac3a..fd0ddc4d08 100644 --- a/railties/test/application/assets_test.rb +++ b/railties/test/application/assets_test.rb @@ -376,18 +376,6 @@ module ApplicationTests assert_equal "Post;\n", File.read(Dir["#{app_path}/public/assets/application-*.js"].first) end - test "assets can't access model information when precompiling if not initializing the app" do - app_file "app/models/post.rb", "class Post; end" - app_file "app/assets/javascripts/application.js", "//= require_tree ." - app_file "app/assets/javascripts/xmlhr.js.erb", "<%= defined?(Post) || :NoPost %>" - - add_to_config "config.assets.digest = false" - add_to_config "config.assets.initialize_on_precompile = false" - - precompile! - assert_equal "NoPost;\n", File.read(Dir["#{app_path}/public/assets/application-*.js"].first) - end - test "initialization on the assets group should set assets_dir" do require "#{app_path}/config/application" Rails.application.initialize!(:assets) diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb index 96aa35b678..17926ac444 100644 --- a/railties/test/application/loading_test.rb +++ b/railties/test/application/loading_test.rb @@ -179,7 +179,7 @@ class LoadingTest < ActiveSupport::TestCase RUBY app_file 'config/routes.rb', <<-RUBY - $counter = 0 + $counter ||= 0 Rails.application.routes.draw do get '/c', to: lambda { |env| User; [200, {"Content-Type" => "text/plain"}, [$counter.to_s]] } end @@ -205,6 +205,39 @@ class LoadingTest < ActiveSupport::TestCase assert_equal "2", last_response.body end + test "dependencies reloading is followed by routes reloading" do + add_to_config <<-RUBY + config.cache_classes = false + RUBY + + app_file 'config/routes.rb', <<-RUBY + $counter ||= 1 + $counter *= 2 + AppTemplate::Application.routes.draw do + get '/c', to: lambda { |env| User; [200, {"Content-Type" => "text/plain"}, [$counter.to_s]] } + end + RUBY + + app_file "app/models/user.rb", <<-MODEL + class User + $counter += 1 + end + MODEL + + require 'rack/test' + extend Rack::Test::Methods + + require "#{rails_root}/config/environment" + + get "/c" + assert_equal "3", last_response.body + + app_file "db/schema.rb", "" + + get "/c" + assert_equal "7", last_response.body + end + test "columns migrations also trigger reloading" do add_to_config <<-RUBY config.cache_classes = false diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index d8076c7151..833114a34c 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -69,6 +69,14 @@ module ApplicationTests assert_equal "Rack::Cache", middleware.first end + test "ActiveRecord::Migration::CheckPending is present when active_record.migration_error is set to :page_load" do + add_to_config "config.active_record.migration_error = :page_load" + + boot! + + assert middleware.include?("ActiveRecord::Migration::CheckPending") + end + test "ActionDispatch::SSL is present when force_ssl is set" do add_to_config "config.force_ssl = true" boot! |