diff options
45 files changed, 530 insertions, 605 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 8c2d887cb3..345b5aa330 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,5 +1,15 @@ ## Rails 4.0.0 (unreleased) ## +* Support unicode characters in routes. Route will be automatically escaped, so instead of manually escaping: + + get Rack::Utils.escape('こんにちは') => 'home#index' + + You just have to write the unicode route: + + get 'こんにちは' => 'home#index' + + *kennyj* + * Return proper format on exceptions. *Santiago Pastorino* * Allow to use mounted_helpers (helpers for accessing mounted engines) in ActionView::TestCase. *Piotr Sarnacki* diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index 38a0270151..29090882a5 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -1,3 +1,4 @@ +# encoding: UTF-8 require 'active_support/core_ext/object/to_param' require 'active_support/core_ext/regexp' @@ -218,6 +219,12 @@ module ActionDispatch # # match "/stories" => redirect("/posts") # + # == Unicode character routes + # + # You can specify unicode character routes in your router: + # + # match "こんにちは" => "welcome#index" + # # == Routing to Rack Applications # # Instead of a String, like <tt>posts#index</tt>, which corresponds to the diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 25d099d83e..94242ad962 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1387,7 +1387,7 @@ module ActionDispatch options[:as] = name_for_action(options[:as], action) end - mapping = Mapping.new(@set, @scope, path, options) + mapping = Mapping.new(@set, @scope, URI.parser.escape(path), options) app, conditions, requirements, defaults, as, anchor = mapping.to_route @set.add_route(app, conditions, requirements, defaults, as, anchor) end diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index 3823f87027..03dfa110e3 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -36,7 +36,7 @@ module ActionView autoload :LookupContext autoload :PathSet autoload :Template - autoload :TestCase + autoload_under "renderer" do autoload :Renderer @@ -73,6 +73,8 @@ module ActionView end end + autoload :TestCase + ENCODING_FLAG = '#.*coding[:=]\s*(\S+)[ \t]*' end diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index 306b8a93da..6cc1370105 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -1760,6 +1760,7 @@ class RackMountIntegrationTests < ActiveSupport::TestCase get 'account(/:action)' => "account#subscription" get 'pages/:page_id/:controller(/:action(/:id))' get ':controller/ping', :action => 'ping' + get 'こんにちは/世界', :controller => 'news', :action => 'index' match ':controller(/:action(/:id))(.:format)', :via => :all root :to => "news#index" } @@ -1876,6 +1877,10 @@ class RackMountIntegrationTests < ActiveSupport::TestCase assert_equal({:controller => 'people', :action => 'create', :person => { :name => 'Josh'}}, params) end + def test_unicode_path + assert_equal({:controller => 'news', :action => 'index'}, @routes.recognize_path(URI.parser.escape('こんにちは/世界'), :method => :get)) + end + private def sort_extras!(extras) if extras.length == 2 diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index fa4cb301eb..f15dd7214b 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -2472,7 +2472,7 @@ end class TestUnicodePaths < ActionDispatch::IntegrationTest Routes = ActionDispatch::Routing::RouteSet.new.tap do |app| app.draw do - get "/#{Rack::Utils.escape("ほげ")}" => lambda { |env| + get "/ほげ" => lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }, :as => :unicode_path end @@ -2727,4 +2727,4 @@ class TestInvalidUrls < ActionDispatch::IntegrationTest assert_response :bad_request end end -end
\ No newline at end of file +end diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb index ded1b752df..f6bacf5ec0 100644 --- a/activemodel/lib/active_model.rb +++ b/activemodel/lib/active_model.rb @@ -30,7 +30,6 @@ module ActiveModel autoload :AttributeMethods autoload :BlockValidator, 'active_model/validator' autoload :Callbacks - autoload :Configuration autoload :Conversion autoload :Dirty autoload :EachValidator, 'active_model/validator' diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index 846d0d7f86..b5b50847c2 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -61,8 +61,7 @@ module ActiveModel CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/ included do - extend ActiveModel::Configuration - config_attribute :attribute_method_matchers + class_attribute :attribute_method_matchers, instance_writer: false self.attribute_method_matchers = [ClassMethods::AttributeMethodMatcher.new] end diff --git a/activemodel/lib/active_model/configuration.rb b/activemodel/lib/active_model/configuration.rb deleted file mode 100644 index ba5a6a2075..0000000000 --- a/activemodel/lib/active_model/configuration.rb +++ /dev/null @@ -1,134 +0,0 @@ -require 'active_support/concern' -require 'active_support/core_ext/class/attribute' -require 'active_support/core_ext/class/attribute_accessors' - -module ActiveModel - # This API is for Rails' internal use and is not currently considered 'public', so - # it may change in the future without warning. - # - # It creates configuration attributes that can be inherited from a module down - # to a class that includes the module. E.g. - # - # module MyModel - # extend ActiveModel::Configuration - # config_attribute :awesome - # self.awesome = true - # end - # - # class Post - # include MyModel - # end - # - # Post.awesome # => true - # - # Post.awesome = false - # Post.awesome # => false - # MyModel.awesome # => true - # - # We assume that the module will have a ClassMethods submodule containing methods - # to be transferred to the including class' singleton class. - # - # Config options can also be defined directly on a class: - # - # class Post - # extend ActiveModel::Configuration - # config_attribute :awesome - # end - # - # So this allows us to define a module that doesn't care about whether it is being - # included in a class or a module: - # - # module Awesomeness - # extend ActiveSupport::Concern - # - # included do - # extend ActiveModel::Configuration - # config_attribute :awesome - # self.awesome = true - # end - # end - # - # class Post - # include Awesomeness - # end - # - # module AwesomeModel - # include Awesomeness - # end - module Configuration #:nodoc: - def config_attribute(name, options = {}) - klass = self.is_a?(Class) ? ClassAttribute : ModuleAttribute - klass.new(self, name, options).define - end - - class Attribute - attr_reader :host, :name, :options - - def initialize(host, name, options) - @host, @name, @options = host, name, options - end - - def instance_writer? - options.fetch(:instance_writer, false) - end - end - - class ClassAttribute < Attribute - def define - if options[:global] - host.cattr_accessor name, :instance_writer => instance_writer? - else - host.class_attribute name, :instance_writer => instance_writer? - end - end - end - - class ModuleAttribute < Attribute - def class_methods - @class_methods ||= begin - if host.const_defined?(:ClassMethods, false) - host.const_get(:ClassMethods) - else - host.const_set(:ClassMethods, Module.new) - end - end - end - - def define - host.singleton_class.class_eval <<-CODE, __FILE__, __LINE__ + 1 - attr_accessor :#{name} - def #{name}?; !!#{name}; end - CODE - - name, host = self.name, self.host - - class_methods.class_eval do - define_method(name) { host.send(name) } - define_method("#{name}?") { !!send(name) } - end - - host.class_eval <<-CODE, __FILE__, __LINE__ + 1 - def #{name}; defined?(@#{name}) ? @#{name} : self.class.#{name}; end - def #{name}?; !!#{name}; end - CODE - - if options[:global] - class_methods.class_eval do - define_method("#{name}=") { |val| host.send("#{name}=", val) } - end - else - class_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1 - def #{name}=(val) - singleton_class.class_eval do - remove_possible_method(:#{name}) - define_method(:#{name}) { val } - end - end - CODE - end - - host.send(:attr_writer, name) if instance_writer? - end - end - end -end diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb index cfce1542b1..8f2c0bf00a 100644 --- a/activemodel/lib/active_model/mass_assignment_security.rb +++ b/activemodel/lib/active_model/mass_assignment_security.rb @@ -9,13 +9,11 @@ module ActiveModel extend ActiveSupport::Concern included do - extend ActiveModel::Configuration + class_attribute :_accessible_attributes, instance_writer: false + class_attribute :_protected_attributes, instance_writer: false + class_attribute :_active_authorizer, instance_writer: false - config_attribute :_accessible_attributes - config_attribute :_protected_attributes - config_attribute :_active_authorizer - - config_attribute :_mass_assignment_sanitizer + class_attribute :_mass_assignment_sanitizer, instance_writer: false self.mass_assignment_sanitizer = :logger end diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb index e668b4a009..b4baf3a946 100644 --- a/activemodel/lib/active_model/serializers/json.rb +++ b/activemodel/lib/active_model/serializers/json.rb @@ -10,9 +10,8 @@ module ActiveModel included do extend ActiveModel::Naming - extend ActiveModel::Configuration - config_attribute :include_root_in_json + class_attribute :include_root_in_json self.include_root_in_json = false end @@ -106,4 +105,4 @@ module ActiveModel end end end -end
\ No newline at end of file +end diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index 611e9ffd55..06eebf79d9 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -52,8 +52,7 @@ module ActiveModel attr_accessor :validation_context define_callbacks :validate, :scope => :name - extend ActiveModel::Configuration - config_attribute :_validators + class_attribute :_validators self._validators = Hash.new { |h,k| h[k] = [] } end diff --git a/activemodel/test/cases/configuration_test.rb b/activemodel/test/cases/configuration_test.rb deleted file mode 100644 index a172fa26a3..0000000000 --- a/activemodel/test/cases/configuration_test.rb +++ /dev/null @@ -1,154 +0,0 @@ -require 'cases/helper' - -class ConfigurationOnModuleTest < ActiveModel::TestCase - def setup - @mod = mod = Module.new do - extend ActiveSupport::Concern - extend ActiveModel::Configuration - - config_attribute :omg - self.omg = "default" - - config_attribute :wtf, global: true - self.wtf = "default" - - config_attribute :boolean - - config_attribute :lol, instance_writer: true - end - - @klass = Class.new do - include mod - end - - @subklass = Class.new(@klass) - end - - test "default" do - assert_equal "default", @mod.omg - assert_equal "default", @klass.omg - assert_equal "default", @klass.new.omg - end - - test "setting" do - @mod.omg = "lol" - assert_equal "lol", @mod.omg - end - - test "setting on class including the module" do - @klass.omg = "lol" - assert_equal "lol", @klass.omg - assert_equal "lol", @klass.new.omg - assert_equal "default", @mod.omg - end - - test "setting on subclass of class including the module" do - @subklass.omg = "lol" - assert_equal "lol", @subklass.omg - assert_equal "default", @klass.omg - assert_equal "default", @mod.omg - end - - test "setting on instance" do - assert !@klass.new.respond_to?(:omg=) - - @klass.lol = "lol" - obj = @klass.new - assert_equal "lol", obj.lol - obj.lol = "omg" - assert_equal "omg", obj.lol - assert_equal "lol", @klass.lol - assert_equal "lol", @klass.new.lol - obj.lol = false - assert !obj.lol? - end - - test "global attribute" do - assert_equal "default", @mod.wtf - assert_equal "default", @klass.wtf - - @mod.wtf = "wtf" - - assert_equal "wtf", @mod.wtf - assert_equal "wtf", @klass.wtf - - @klass.wtf = "lol" - - assert_equal "lol", @mod.wtf - assert_equal "lol", @klass.wtf - end - - test "boolean" do - assert_equal false, @mod.boolean? - assert_equal false, @klass.new.boolean? - @mod.boolean = true - assert_equal true, @mod.boolean? - assert_equal true, @klass.new.boolean? - end -end - -class ConfigurationOnClassTest < ActiveModel::TestCase - def setup - @klass = Class.new do - extend ActiveModel::Configuration - - config_attribute :omg - self.omg = "default" - - config_attribute :wtf, global: true - self.wtf = "default" - - config_attribute :omg2, instance_writer: true - config_attribute :wtf2, instance_writer: true, global: true - end - - @subklass = Class.new(@klass) - end - - test "defaults" do - assert_equal "default", @klass.omg - assert_equal "default", @klass.wtf - assert_equal "default", @subklass.omg - assert_equal "default", @subklass.wtf - end - - test "changing" do - @klass.omg = "lol" - assert_equal "lol", @klass.omg - assert_equal "lol", @subklass.omg - end - - test "changing in subclass" do - @subklass.omg = "lol" - assert_equal "lol", @subklass.omg - assert_equal "default", @klass.omg - end - - test "changing global" do - @klass.wtf = "wtf" - assert_equal "wtf", @klass.wtf - assert_equal "wtf", @subklass.wtf - - @subklass.wtf = "lol" - assert_equal "lol", @klass.wtf - assert_equal "lol", @subklass.wtf - end - - test "instance_writer" do - obj = @klass.new - - @klass.omg2 = "omg" - @klass.wtf2 = "wtf" - - assert_equal "omg", obj.omg2 - assert_equal "wtf", obj.wtf2 - - obj.omg2 = "lol" - obj.wtf2 = "lol" - - assert_equal "lol", obj.omg2 - assert_equal "lol", obj.wtf2 - assert_equal "omg", @klass.omg2 - assert_equal "lol", @klass.wtf2 - end -end diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb index abc2fa546a..df4de8ac35 100644 --- a/activerecord/lib/active_record/attribute_assignment.rb +++ b/activerecord/lib/active_record/attribute_assignment.rb @@ -1,11 +1,25 @@ require 'active_support/concern' module ActiveRecord + ActiveSupport.on_load(:active_record_config) do + mattr_accessor :whitelist_attributes, instance_accessor: false + mattr_accessor :mass_assignment_sanitizer, instance_accessor: false + end + module AttributeAssignment extend ActiveSupport::Concern include ActiveModel::MassAssignmentSecurity + included do + initialize_mass_assignment_sanitizer + end + module ClassMethods + def inherited(child) # :nodoc: + child.send :initialize_mass_assignment_sanitizer if self == Base + super + end + private # The primary key and inheritance column can never be set by mass-assignment for security reasons. @@ -14,6 +28,11 @@ module ActiveRecord default << 'id' unless primary_key.eql? 'id' default end + + def initialize_mass_assignment_sanitizer + attr_accessible(nil) if Model.whitelist_attributes + self.mass_assignment_sanitizer = Model.mass_assignment_sanitizer if Model.mass_assignment_sanitizer + end end # Allows you to set all the attributes at once by passing in a hash with keys diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 172026d150..f36df4a444 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -16,19 +16,6 @@ module ActiveRecord include TimeZoneConversion include Dirty include Serialization - - # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example, - # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)). - # (Alias for the protected read_attribute method). - def [](attr_name) - read_attribute(attr_name) - end - - # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. - # (Alias for the protected write_attribute method). - def []=(attr_name, value) - write_attribute(attr_name, value) - end end module ClassMethods @@ -192,6 +179,19 @@ module ActiveRecord self.class.columns_hash[name.to_s] end + # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example, + # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)). + # (Alias for the protected read_attribute method). + def [](attr_name) + read_attribute(attr_name) + end + + # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. + # (Alias for the protected write_attribute method). + def []=(attr_name, value) + write_attribute(attr_name, value) + end + protected def clone_attributes(reader_method = :read_attribute, attributes = {}) diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 11c63591e3..f8a40ad520 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -1,12 +1,18 @@ require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/object/blank' +require 'active_support/core_ext/module/attribute_accessors' module ActiveRecord + ActiveSupport.on_load(:active_record_config) do + mattr_accessor :partial_updates, instance_accessor: false + self.partial_updates = true + end + module AttributeMethods module Dirty extend ActiveSupport::Concern + include ActiveModel::Dirty - include AttributeMethods::Write included do if self < ::ActiveRecord::Timestamp @@ -14,7 +20,6 @@ module ActiveRecord end config_attribute :partial_updates - self.partial_updates = true end # Attempts to +save+ the record and clears changed attributes if successful. diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index dcc3d79de9..a7af086e43 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -1,13 +1,17 @@ module ActiveRecord + ActiveSupport.on_load(:active_record_config) do + mattr_accessor :attribute_types_cached_by_default, instance_accessor: false + end + module AttributeMethods module Read extend ActiveSupport::Concern ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date] + ActiveRecord::Model.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT included do - config_attribute :attribute_types_cached_by_default, :global => true - self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT + config_attribute :attribute_types_cached_by_default end module ClassMethods diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index 706fbf0546..4af4d28b74 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -6,7 +6,7 @@ module ActiveRecord included do # Returns a hash of all the attributes that have been specified for serialization as # keys and their class restriction as values. - config_attribute :serialized_attributes + class_attribute :serialized_attributes self.serialized_attributes = {} end diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb index 58a5d82e14..e300c9721f 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -2,6 +2,14 @@ require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/object/inclusion' module ActiveRecord + ActiveSupport.on_load(:active_record_config) do + mattr_accessor :time_zone_aware_attributes, instance_accessor: false + self.time_zone_aware_attributes = false + + mattr_accessor :skip_time_zone_conversion_for_attributes, instance_accessor: false + self.skip_time_zone_conversion_for_attributes = [] + end + module AttributeMethods module TimeZoneConversion class Type # :nodoc: @@ -22,11 +30,8 @@ module ActiveRecord extend ActiveSupport::Concern included do - config_attribute :time_zone_aware_attributes, :global => true - self.time_zone_aware_attributes = false - + config_attribute :time_zone_aware_attributes, global: true config_attribute :skip_time_zone_conversion_for_attributes - self.skip_time_zone_conversion_for_attributes = [] end module ClassMethods diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index a050fabf35..cc11567506 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -231,30 +231,6 @@ module ActiveRecord # Returns true or false depending on whether the proc is contained in the before_save callback chain on a Topic model. # module Callbacks - # We can't define callbacks directly on ActiveRecord::Model because - # it is a module. So we queue up the definitions and execute them - # when ActiveRecord::Model is included. - module Register #:nodoc: - def self.extended(base) - base.config_attribute :_callbacks_register - base._callbacks_register = [] - end - - def self.setup(base) - base._callbacks_register.each do |item| - base.send(*item) - end - end - - def define_callbacks(*args) - self._callbacks_register << [:define_callbacks, *args] - end - - def define_model_callbacks(*args) - self._callbacks_register << [:define_model_callbacks, *args] - end - end - extend ActiveSupport::Concern CALLBACKS = [ diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 61ff603006..347d794fa3 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -448,11 +448,11 @@ module ActiveRecord end def new_connection - ActiveRecord::Base.send(spec.adapter_method, spec.config) + ActiveRecord::Model.send(spec.adapter_method, spec.config) end def current_connection_id #:nodoc: - ActiveRecord::Base.connection_id ||= Thread.current.object_id + ActiveRecord::Model.connection_id ||= Thread.current.object_id end def checkout_new_connection @@ -563,10 +563,12 @@ module ActiveRecord end def retrieve_connection_pool(klass) - pool = get_pool_for_class klass.name - return pool if pool - return nil if ActiveRecord::Model == klass - retrieve_connection_pool klass.active_record_super + if !(klass < Model::Tag) + get_pool_for_class('ActiveRecord::Model') # default connection + else + pool = get_pool_for_class(klass.name) + pool || retrieve_connection_pool(klass.superclass) + end end private diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb index 7b218a5570..bda41df80f 100644 --- a/activerecord/lib/active_record/connection_handling.rb +++ b/activerecord/lib/active_record/connection_handling.rb @@ -90,6 +90,10 @@ module ActiveRecord connection_handler.remove_connection(klass) end + def clear_cache! # :nodoc: + connection.schema_cache.clear! + end + delegate :clear_active_connections!, :clear_reloadable_connections!, :clear_all_connections!, :to => :connection_handler end diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index dbad561ca2..f5d60d11b6 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -1,80 +1,88 @@ require 'active_support/concern' require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/object/deep_dup' +require 'active_support/core_ext/module/delegation' require 'thread' module ActiveRecord - module Core - extend ActiveSupport::Concern + ActiveSupport.on_load(:active_record_config) do + ## + # :singleton-method: + # + # Accepts a logger conforming to the interface of Log4r which is then + # passed on to any new database connections made and which can be + # retrieved on both a class and instance level by calling +logger+. + mattr_accessor :logger, instance_accessor: false - included do - ## - # :singleton-method: - # - # Accepts a logger conforming to the interface of Log4r which is then - # passed on to any new database connections made and which can be - # retrieved on both a class and instance level by calling +logger+. - config_attribute :logger, :global => true + ## + # :singleton-method: + # Contains the database configuration - as is typically stored in config/database.yml - + # as a Hash. + # + # For example, the following database.yml... + # + # development: + # adapter: sqlite3 + # database: db/development.sqlite3 + # + # production: + # adapter: sqlite3 + # database: db/production.sqlite3 + # + # ...would result in ActiveRecord::Base.configurations to look like this: + # + # { + # 'development' => { + # 'adapter' => 'sqlite3', + # 'database' => 'db/development.sqlite3' + # }, + # 'production' => { + # 'adapter' => 'sqlite3', + # 'database' => 'db/production.sqlite3' + # } + # } + mattr_accessor :configurations, instance_accessor: false + self.configurations = {} - ## - # :singleton-method: - # Contains the database configuration - as is typically stored in config/database.yml - - # as a Hash. - # - # For example, the following database.yml... - # - # development: - # adapter: sqlite3 - # database: db/development.sqlite3 - # - # production: - # adapter: sqlite3 - # database: db/production.sqlite3 - # - # ...would result in ActiveRecord::Base.configurations to look like this: - # - # { - # 'development' => { - # 'adapter' => 'sqlite3', - # 'database' => 'db/development.sqlite3' - # }, - # 'production' => { - # 'adapter' => 'sqlite3', - # 'database' => 'db/production.sqlite3' - # } - # } - config_attribute :configurations, :global => true - self.configurations = {} + ## + # :singleton-method: + # Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling + # dates and times from the database. This is set to :utc by default. + mattr_accessor :default_timezone, instance_accessor: false + self.default_timezone = :utc - ## - # :singleton-method: - # Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling - # dates and times from the database. This is set to :utc by default. - config_attribute :default_timezone, :global => true - self.default_timezone = :utc + ## + # :singleton-method: + # Specifies the format to use when dumping the database schema with Rails' + # Rakefile. If :sql, the schema is dumped as (potentially database- + # specific) SQL statements. If :ruby, the schema is dumped as an + # ActiveRecord::Schema file which can be loaded into any database that + # supports migrations. Use :ruby if you want to have different database + # adapters for, e.g., your development and test environments. + mattr_accessor :schema_format, instance_accessor: false + self.schema_format = :ruby - ## - # :singleton-method: - # Specifies the format to use when dumping the database schema with Rails' - # Rakefile. If :sql, the schema is dumped as (potentially database- - # specific) SQL statements. If :ruby, the schema is dumped as an - # ActiveRecord::Schema file which can be loaded into any database that - # supports migrations. Use :ruby if you want to have different database - # adapters for, e.g., your development and test environments. - config_attribute :schema_format, :global => true - self.schema_format = :ruby + ## + # :singleton-method: + # Specify whether or not to use timestamps for migration versions + mattr_accessor :timestamped_migrations, instance_accessor: false + self.timestamped_migrations = true - ## - # :singleton-method: - # Specify whether or not to use timestamps for migration versions - config_attribute :timestamped_migrations, :global => true - self.timestamped_migrations = true + mattr_accessor :connection_handler, instance_accessor: false + self.connection_handler = ConnectionAdapters::ConnectionHandler.new + + mattr_accessor :dependent_restrict_raises, instance_accessor: false + self.dependent_restrict_raises = true + end + module Core + extend ActiveSupport::Concern + + included do ## # :singleton-method: # The connection handler config_attribute :connection_handler - self.connection_handler = ConnectionAdapters::ConnectionHandler.new ## # :singleton-method: @@ -83,8 +91,11 @@ module ActiveRecord # ActiveRecord::DeleteRestrictionError exception will be raised # along with a DEPRECATION WARNING. If set to false, an error would # be added to the model instead. - config_attribute :dependent_restrict_raises, :global => true - self.dependent_restrict_raises = true + config_attribute :dependent_restrict_raises + + %w(logger configurations default_timezone schema_format timestamped_migrations).each do |name| + config_attribute name, global: true + end end module ClassMethods diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb index b163ef3c12..b27a19f89a 100644 --- a/activerecord/lib/active_record/counter_cache.rb +++ b/activerecord/lib/active_record/counter_cache.rb @@ -1,111 +1,115 @@ module ActiveRecord # = Active Record Counter Cache module CounterCache - # Resets one or more counter caches to their correct value using an SQL - # count query. This is useful when adding new counter caches, or if the - # counter has been corrupted or modified directly by SQL. - # - # ==== Parameters - # - # * +id+ - The id of the object you wish to reset a counter on. - # * +counters+ - One or more counter names to reset - # - # ==== Examples - # - # # For Post with id #1 records reset the comments_count - # Post.reset_counters(1, :comments) - def reset_counters(id, *counters) - object = find(id) - counters.each do |association| - has_many_association = reflect_on_association(association.to_sym) + extend ActiveSupport::Concern - foreign_key = has_many_association.foreign_key.to_s - child_class = has_many_association.klass - belongs_to = child_class.reflect_on_all_associations(:belongs_to) - reflection = belongs_to.find { |e| e.foreign_key.to_s == foreign_key } - counter_name = reflection.counter_cache_column + module ClassMethods + # Resets one or more counter caches to their correct value using an SQL + # count query. This is useful when adding new counter caches, or if the + # counter has been corrupted or modified directly by SQL. + # + # ==== Parameters + # + # * +id+ - The id of the object you wish to reset a counter on. + # * +counters+ - One or more counter names to reset + # + # ==== Examples + # + # # For Post with id #1 records reset the comments_count + # Post.reset_counters(1, :comments) + def reset_counters(id, *counters) + object = find(id) + counters.each do |association| + has_many_association = reflect_on_association(association.to_sym) - stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({ - arel_table[counter_name] => object.send(association).count - }) - connection.update stmt - end - return true - end + foreign_key = has_many_association.foreign_key.to_s + child_class = has_many_association.klass + belongs_to = child_class.reflect_on_all_associations(:belongs_to) + reflection = belongs_to.find { |e| e.foreign_key.to_s == foreign_key } + counter_name = reflection.counter_cache_column - # A generic "counter updater" implementation, intended primarily to be - # used by increment_counter and decrement_counter, but which may also - # be useful on its own. It simply does a direct SQL update for the record - # with the given ID, altering the given hash of counters by the amount - # given by the corresponding value: - # - # ==== 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 - # to update as keys and the amount to update the field by as values. - # - # ==== Examples - # - # # For the Post with id of 5, decrement the comment_count by 1, and - # # increment the action_count by 1 - # Post.update_counters 5, :comment_count => -1, :action_count => 1 - # # Executes the following SQL: - # # UPDATE posts - # # SET comment_count = COALESCE(comment_count, 0) - 1, - # # action_count = COALESCE(action_count, 0) + 1 - # # WHERE id = 5 - # - # # For the Posts with id of 10 and 15, increment the comment_count by 1 - # Post.update_counters [10, 15], :comment_count => 1 - # # Executes the following SQL: - # # UPDATE posts - # # SET comment_count = COALESCE(comment_count, 0) + 1 - # # WHERE id IN (10, 15) - def update_counters(id, counters) - updates = counters.map do |counter_name, value| - operator = value < 0 ? '-' : '+' - quoted_column = connection.quote_column_name(counter_name) - "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}" + stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({ + arel_table[counter_name] => object.send(association).count + }) + connection.update stmt + end + return true end - where(primary_key => id).update_all updates.join(', ') - end + # A generic "counter updater" implementation, intended primarily to be + # used by increment_counter and decrement_counter, but which may also + # be useful on its own. It simply does a direct SQL update for the record + # with the given ID, altering the given hash of counters by the amount + # given by the corresponding value: + # + # ==== 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 + # to update as keys and the amount to update the field by as values. + # + # ==== Examples + # + # # For the Post with id of 5, decrement the comment_count by 1, and + # # increment the action_count by 1 + # Post.update_counters 5, :comment_count => -1, :action_count => 1 + # # Executes the following SQL: + # # UPDATE posts + # # SET comment_count = COALESCE(comment_count, 0) - 1, + # # action_count = COALESCE(action_count, 0) + 1 + # # WHERE id = 5 + # + # # For the Posts with id of 10 and 15, increment the comment_count by 1 + # Post.update_counters [10, 15], :comment_count => 1 + # # Executes the following SQL: + # # UPDATE posts + # # SET comment_count = COALESCE(comment_count, 0) + 1 + # # WHERE id IN (10, 15) + def update_counters(id, counters) + updates = counters.map do |counter_name, value| + operator = value < 0 ? '-' : '+' + quoted_column = connection.quote_column_name(counter_name) + "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}" + end - # Increment a number field by one, usually representing a count. - # - # This is used for caching aggregate values, so that they don't need to be computed every time. - # For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is - # shown it would have to run an SQL query to find how many posts and comments there are. - # - # ==== Parameters - # - # * +counter_name+ - The name of the field that should be incremented. - # * +id+ - The id of the object that should be incremented. - # - # ==== Examples - # - # # Increment the post_count column for the record with an id of 5 - # DiscussionBoard.increment_counter(:post_count, 5) - def increment_counter(counter_name, id) - update_counters(id, counter_name => 1) - end + where(primary_key => id).update_all updates.join(', ') + end + + # Increment a number field by one, usually representing a count. + # + # This is used for caching aggregate values, so that they don't need to be computed every time. + # For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is + # shown it would have to run an SQL query to find how many posts and comments there are. + # + # ==== Parameters + # + # * +counter_name+ - The name of the field that should be incremented. + # * +id+ - The id of the object that should be incremented. + # + # ==== Examples + # + # # Increment the post_count column for the record with an id of 5 + # DiscussionBoard.increment_counter(:post_count, 5) + def increment_counter(counter_name, id) + update_counters(id, counter_name => 1) + end - # Decrement a number field by one, usually representing a count. - # - # This works the same as increment_counter but reduces the column value by 1 instead of increasing it. - # - # ==== Parameters - # - # * +counter_name+ - The name of the field that should be decremented. - # * +id+ - The id of the object that should be decremented. - # - # ==== Examples - # - # # Decrement the post_count column for the record with an id of 5 - # DiscussionBoard.decrement_counter(:post_count, 5) - def decrement_counter(counter_name, id) - update_counters(id, counter_name => -1) + # Decrement a number field by one, usually representing a count. + # + # This works the same as increment_counter but reduces the column value by 1 instead of increasing it. + # + # ==== Parameters + # + # * +counter_name+ - The name of the field that should be decremented. + # * +id+ - The id of the object that should be decremented. + # + # ==== Examples + # + # # Decrement the post_count column for the record with an id of 5 + # DiscussionBoard.decrement_counter(:post_count, 5) + def decrement_counter(counter_name, id) + update_counters(id, counter_name => -1) + end end end end diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb index b0eda8ef34..7ade385c70 100644 --- a/activerecord/lib/active_record/explain.rb +++ b/activerecord/lib/active_record/explain.rb @@ -1,12 +1,13 @@ +require 'active_support/lazy_load_hooks' require 'active_support/core_ext/class/attribute' module ActiveRecord + ActiveSupport.on_load(:active_record_config) do + mattr_accessor :auto_explain_threshold_in_seconds, instance_accessor: false + end + module Explain - def self.extended(base) - # If a query takes longer than these many seconds we log its query plan - # automatically. nil disables this feature. - base.config_attribute :auto_explain_threshold_in_seconds, :global => true - end + delegate :auto_explain_threshold_in_seconds, :auto_explain_threshold_in_seconds=, to: 'ActiveRecord::Model' # If auto explain is enabled, this method triggers EXPLAIN logging for the # queries triggered by the block if it takes more than the threshold as a diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index 46d253b0a7..770083ac13 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -1,13 +1,17 @@ require 'active_support/concern' module ActiveRecord + ActiveSupport.on_load(:active_record_config) do + # Determine whether to store the full constant name including namespace when using STI + mattr_accessor :store_full_sti_class, instance_accessor: false + self.store_full_sti_class = true + end + module Inheritance extend ActiveSupport::Concern included do - # Determine whether to store the full constant name including namespace when using STI config_attribute :store_full_sti_class - self.store_full_sti_class = true end module ClassMethods @@ -95,7 +99,7 @@ module ActiveRecord # Returns the class descending directly from ActiveRecord::Base or an # abstract class, if any, in the inheritance hierarchy. def class_of_active_record_descendant(klass) - unless klass < Model + unless klass < Model::Tag raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord" end diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 05e052b953..4ce42feb74 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -1,4 +1,9 @@ module ActiveRecord + ActiveSupport.on_load(:active_record_config) do + mattr_accessor :lock_optimistically, instance_accessor: false + self.lock_optimistically = true + end + module Locking # == What is Optimistic Locking # @@ -51,8 +56,7 @@ module ActiveRecord extend ActiveSupport::Concern included do - config_attribute :lock_optimistically, :global => true - self.lock_optimistically = true + config_attribute :lock_optimistically end def locking_enabled? #:nodoc: diff --git a/activerecord/lib/active_record/model.rb b/activerecord/lib/active_record/model.rb index 105d1e0e2b..d51cb30ec1 100644 --- a/activerecord/lib/active_record/model.rb +++ b/activerecord/lib/active_record/model.rb @@ -1,6 +1,34 @@ require 'active_support/deprecation' +require 'active_support/concern' +require 'active_support/core_ext/module/delegation' +require 'active_support/core_ext/module/attribute_accessors' module ActiveRecord + module Configuration # :nodoc: + # This just abstracts out how we define configuration options in AR. Essentially we + # have mattr_accessors on the ActiveRecord:Model constant that define global defaults. + # Classes that then use AR get class_attributes defined, which means that when they + # are assigned the default will be overridden for that class and subclasses. (Except + # when options[:global] == true, in which case there is one global value always.) + def config_attribute(name, options = {}) + if options[:global] + class_eval <<-CODE, __FILE__, __LINE__ + 1 + def self.#{name}; ActiveRecord::Model.#{name}; end + def #{name}; ActiveRecord::Model.#{name}; end + def self.#{name}=(val); ActiveRecord::Model.#{name} = val; end + CODE + else + options[:instance_writer] ||= false + class_attribute name, options + + singleton_class.class_eval <<-CODE, __FILE__, __LINE__ + 1 + remove_method :#{name} + def #{name}; ActiveRecord::Model.#{name}; end + CODE + end + end + end + # <tt>ActiveRecord::Model</tt> can be included into a class to add Active Record persistence. # This is an alternative to inheriting from <tt>ActiveRecord::Base</tt>. Example: # @@ -9,41 +37,35 @@ module ActiveRecord # end # module Model - module ClassMethods #:nodoc: - include ActiveSupport::Callbacks::ClassMethods - include ActiveModel::Naming - include QueryCache::ClassMethods - include ActiveSupport::Benchmarkable - include ActiveSupport::DescendantsTracker - - include Querying - include Translation - include DynamicMatchers - include CounterCache - include Explain - include ConnectionHandling - end + extend ActiveSupport::Concern + extend ConnectionHandling + extend ActiveModel::Observing::ClassMethods - def self.included(base) - return if base.singleton_class < ClassMethods + # This allows us to detect an ActiveRecord::Model while it's in the process of being included. + module Tag; end + def self.append_features(base) base.class_eval do - extend ClassMethods - Callbacks::Register.setup(self) - initialize_generated_modules unless self == Base + include Tag + extend Configuration end + + super end - extend ActiveModel::Configuration - extend ActiveModel::Callbacks - extend ActiveModel::MassAssignmentSecurity::ClassMethods - extend ActiveModel::AttributeMethods::ClassMethods - extend Callbacks::Register - extend Explain - extend ConnectionHandling + included do + extend ActiveModel::Naming + extend ActiveSupport::Benchmarkable + extend ActiveSupport::DescendantsTracker + + extend QueryCache::ClassMethods + extend Querying + extend Translation + extend DynamicMatchers + extend Explain + extend ConnectionHandling - def self.extend(*modules) - ClassMethods.send(:include, *modules) + initialize_generated_modules unless self == Base end include Persistence @@ -56,13 +78,22 @@ module ActiveRecord include AttributeAssignment include ActiveModel::Conversion include Validations - include Locking::Optimistic, Locking::Pessimistic + include CounterCache + include Locking::Optimistic + include Locking::Pessimistic include AttributeMethods - include Callbacks, ActiveModel::Observing, Timestamp + include Callbacks + include ActiveModel::Observing + include Timestamp include Associations include ActiveModel::SecurePassword - include AutosaveAssociation, NestedAttributes - include Aggregations, Transactions, Reflection, Serialization, Store + include AutosaveAssociation + include NestedAttributes + include Aggregations + include Transactions + include Reflection + include Serialization + include Store include Core class << self @@ -103,6 +134,15 @@ module ActiveRecord end end + # This hook is where config accessors on Model get defined. + # + # We don't want to just open the Model module and add stuff to it in other files, because + # that would cause Model to load, which causes all sorts of loading order issues. + # + # We need this hook rather than just using the :active_record one, because users of the + # :active_record hook may need to use config options. + ActiveSupport.run_load_hooks(:active_record_config, Model) + # Load Base at this point, because the active_record load hook is run in that file. Base end diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index 7f38dda11e..e6b76ddc4c 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -1,7 +1,19 @@ require 'active_support/concern' -require 'active_support/core_ext/class/attribute_accessors' module ActiveRecord + ActiveSupport.on_load(:active_record_config) do + mattr_accessor :primary_key_prefix_type, instance_accessor: false + + mattr_accessor :table_name_prefix, instance_accessor: false + self.table_name_prefix = "" + + mattr_accessor :table_name_suffix, instance_accessor: false + self.table_name_suffix = "" + + mattr_accessor :pluralize_table_names, instance_accessor: false + self.pluralize_table_names = true + end + module ModelSchema extend ActiveSupport::Concern @@ -13,7 +25,7 @@ module ActiveRecord # the Product class will look for "productid" instead of "id" as the primary column. If the # latter is specified, the Product class will look for "product_id" instead of "id". Remember # that this is a global setting for all Active Records. - config_attribute :primary_key_prefix_type, :global => true + config_attribute :primary_key_prefix_type, global: true ## # :singleton-method: @@ -26,14 +38,12 @@ module ActiveRecord # a namespace by defining a singleton method in the parent module called table_name_prefix which # returns your chosen prefix. config_attribute :table_name_prefix - self.table_name_prefix = "" ## # :singleton-method: # Works like +table_name_prefix+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp", # "people_basecamp"). By default, the suffix is the empty string. config_attribute :table_name_suffix - self.table_name_suffix = "" ## # :singleton-method: @@ -41,7 +51,6 @@ module ActiveRecord # If true, the default table name for a Product class will be +products+. If false, it would just be +product+. # See table_name for the full rules on table/class naming. This is true, by default. config_attribute :pluralize_table_names - self.pluralize_table_names = true end module ClassMethods @@ -308,10 +317,6 @@ module ActiveRecord @relation = nil end - def clear_cache! # :nodoc: - connection.schema_cache.clear! - end - private # Guesses the table name, but does not decorate it with prefix and suffix information. diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 95a2ddcc11..841681e542 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -5,6 +5,11 @@ require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/class/attribute' module ActiveRecord + ActiveSupport.on_load(:active_record_config) do + mattr_accessor :nested_attributes_options, instance_accessor: false + self.nested_attributes_options = {} + end + module NestedAttributes #:nodoc: class TooManyRecords < ActiveRecordError end @@ -13,7 +18,6 @@ module ActiveRecord included do config_attribute :nested_attributes_options - self.nested_attributes_options = {} end # = Active Record Nested Attributes diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 319516413b..9432a70c41 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -68,9 +68,6 @@ module ActiveRecord initializer "active_record.set_configs" do |app| ActiveSupport.on_load(:active_record) do - if app.config.active_record.delete(:whitelist_attributes) - attr_accessible(nil) - end app.config.active_record.each do |k,v| send "#{k}=", v end @@ -97,18 +94,12 @@ module ActiveRecord end initializer "active_record.set_reloader_hooks" do |app| - hook = lambda do - ActiveRecord::Base.clear_reloadable_connections! - ActiveRecord::Base.clear_cache! - end + hook = app.config.reload_classes_only_on_change ? :to_prepare : :to_cleanup - if app.config.reload_classes_only_on_change - ActiveSupport.on_load(:active_record) do - ActionDispatch::Reloader.to_prepare(&hook) - end - else - ActiveSupport.on_load(:active_record) do - ActionDispatch::Reloader.to_cleanup(&hook) + ActiveSupport.on_load(:active_record) do + ActionDispatch::Reloader.send(hook) do + ActiveRecord::Model.clear_reloadable_connections! + ActiveRecord::Model.clear_cache! end end end @@ -119,20 +110,21 @@ module ActiveRecord config.after_initialize do |app| ActiveSupport.on_load(:active_record) do - ActiveRecord::Base.instantiate_observers + ActiveRecord::Model.instantiate_observers ActionDispatch::Reloader.to_prepare do - ActiveRecord::Base.instantiate_observers + ActiveRecord::Model.instantiate_observers end end ActiveSupport.on_load(:active_record) do if app.config.use_schema_cache_dump filename = File.join(app.config.paths["db"].first, "schema_cache.dump") + if File.file?(filename) cache = Marshal.load File.binread filename if cache.version == ActiveRecord::Migrator.current_version - ActiveRecord::Base.connection.schema_cache = cache + ActiveRecord::Model.connection.schema_cache = cache else warn "schema_cache.dump is expired. Current version is #{ActiveRecord::Migrator.current_version}, but cache version is #{cache.version}." end diff --git a/activerecord/lib/active_record/readonly_attributes.rb b/activerecord/lib/active_record/readonly_attributes.rb index 836b15e2ce..960b78dc38 100644 --- a/activerecord/lib/active_record/readonly_attributes.rb +++ b/activerecord/lib/active_record/readonly_attributes.rb @@ -6,7 +6,7 @@ module ActiveRecord extend ActiveSupport::Concern included do - config_attribute :_attr_readonly + class_attribute :_attr_readonly, instance_writer: false self._attr_readonly = [] end diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index c380b5c029..ec13d27323 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -7,8 +7,7 @@ module ActiveRecord extend ActiveSupport::Concern included do - extend ActiveModel::Configuration - config_attribute :reflections + class_attribute :reflections self.reflections = {} end diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb index db833fc7f1..af51c803a7 100644 --- a/activerecord/lib/active_record/scoping/default.rb +++ b/activerecord/lib/active_record/scoping/default.rb @@ -8,7 +8,7 @@ module ActiveRecord included do # Stores the default scope for the class - config_attribute :default_scopes + class_attribute :default_scopes, instance_writer: false self.default_scopes = [] end diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb index 41e3b92499..e8dd312a47 100644 --- a/activerecord/lib/active_record/serialization.rb +++ b/activerecord/lib/active_record/serialization.rb @@ -1,9 +1,21 @@ module ActiveRecord #:nodoc: + ActiveSupport.on_load(:active_record_config) do + mattr_accessor :include_root_in_json, instance_accessor: false + self.include_root_in_json = true + end + # = Active Record Serialization module Serialization extend ActiveSupport::Concern include ActiveModel::Serializers::JSON + included do + singleton_class.class_eval do + remove_method :include_root_in_json + delegate :include_root_in_json, to: 'ActiveRecord::Model' + end + end + def serializable_hash(options = nil) options = options.try(:clone) || {} diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index c717fdea47..e5b7a6bfba 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -1,6 +1,11 @@ require 'active_support/core_ext/class/attribute' module ActiveRecord + ActiveSupport.on_load(:active_record_config) do + mattr_accessor :record_timestamps, instance_accessor: false + self.record_timestamps = true + end + # = Active Record Timestamp # # Active Record automatically timestamps create and update operations if the @@ -33,8 +38,7 @@ module ActiveRecord extend ActiveSupport::Concern included do - config_attribute :record_timestamps, :instance_writer => true - self.record_timestamps = true + config_attribute :record_timestamps, instance_writer: true end def initialize_dup(other) diff --git a/activerecord/test/active_record/connection_adapters/fake_adapter.rb b/activerecord/test/active_record/connection_adapters/fake_adapter.rb index 69dfd2503e..1199be68eb 100644 --- a/activerecord/test/active_record/connection_adapters/fake_adapter.rb +++ b/activerecord/test/active_record/connection_adapters/fake_adapter.rb @@ -1,6 +1,6 @@ module ActiveRecord - class Base - def self.fake_connection(config) + module ConnectionHandling + def fake_connection(config) ConnectionAdapters::FakeAdapter.new nil, logger end end diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb index 98c38535a6..f4c40b8b97 100644 --- a/activerecord/test/cases/attribute_methods/read_test.rb +++ b/activerecord/test/cases/attribute_methods/read_test.rb @@ -15,6 +15,7 @@ module ActiveRecord def self.active_record_super; Base; end def self.base_class; self; end + extend ActiveRecord::Configuration include ActiveRecord::AttributeMethods def self.define_attribute_methods diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb index dc99ac665c..17cb447105 100644 --- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb @@ -7,12 +7,11 @@ module ActiveRecord @handler = ConnectionHandler.new @handler.establish_connection 'america', Base.connection_pool.spec @klass = Class.new do + include Model::Tag def self.name; 'america'; end - class << self - alias active_record_super superclass - end end @subklass = Class.new(@klass) do + include Model::Tag def self.name; 'north america'; end end end diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb index 214546802a..73a01906b9 100644 --- a/activerecord/test/cases/mass_assignment_security_test.rb +++ b/activerecord/test/cases/mass_assignment_security_test.rb @@ -251,6 +251,65 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase assert !Task.new.respond_to?("#{method}=") end end + + test "ActiveRecord::Model.whitelist_attributes works for models which include Model" do + begin + prev, ActiveRecord::Model.whitelist_attributes = ActiveRecord::Model.whitelist_attributes, true + + klass = Class.new { include ActiveRecord::Model } + assert_equal ActiveModel::MassAssignmentSecurity::WhiteList, klass.active_authorizers[:default].class + assert_equal [], klass.active_authorizers[:default].to_a + ensure + ActiveRecord::Model.whitelist_attributes = prev + end + end + + test "ActiveRecord::Model.whitelist_attributes works for models which inherit Base" do + begin + prev, ActiveRecord::Model.whitelist_attributes = ActiveRecord::Model.whitelist_attributes, true + + klass = Class.new(ActiveRecord::Base) + assert_equal ActiveModel::MassAssignmentSecurity::WhiteList, klass.active_authorizers[:default].class + assert_equal [], klass.active_authorizers[:default].to_a + + klass.attr_accessible 'foo' + assert_equal ['foo'], Class.new(klass).active_authorizers[:default].to_a + ensure + ActiveRecord::Model.whitelist_attributes = prev + end + end + + test "ActiveRecord::Model.mass_assignment_sanitizer works for models which include Model" do + begin + sanitizer = Object.new + prev, ActiveRecord::Model.mass_assignment_sanitizer = ActiveRecord::Model.mass_assignment_sanitizer, sanitizer + + klass = Class.new { include ActiveRecord::Model } + assert_equal sanitizer, klass._mass_assignment_sanitizer + + ActiveRecord::Model.mass_assignment_sanitizer = nil + klass = Class.new { include ActiveRecord::Model } + assert_not_nil klass._mass_assignment_sanitizer + ensure + ActiveRecord::Model.mass_assignment_sanitizer = prev + end + end + + test "ActiveRecord::Model.mass_assignment_sanitizer works for models which inherit Base" do + begin + sanitizer = Object.new + prev, ActiveRecord::Model.mass_assignment_sanitizer = ActiveRecord::Model.mass_assignment_sanitizer, sanitizer + + klass = Class.new(ActiveRecord::Base) + assert_equal sanitizer, klass._mass_assignment_sanitizer + + sanitizer2 = Object.new + klass.mass_assignment_sanitizer = sanitizer2 + assert_equal sanitizer2, Class.new(klass)._mass_assignment_sanitizer + ensure + ActiveRecord::Model.mass_assignment_sanitizer = prev + end + end end diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index 070bfd7af6..efa2d43f20 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -107,7 +107,7 @@ class String # Replaces underscores with dashes in the string. # - # 'puni_puni' # => "puni-puni" + # 'puni_puni'.dasherize # => "puni-puni" def dasherize ActiveSupport::Inflector.dasherize(self) end diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb index 6735c561d3..e3d8cf48ce 100644 --- a/activesupport/lib/active_support/notifications.rb +++ b/activesupport/lib/active_support/notifications.rb @@ -33,7 +33,7 @@ module ActiveSupport # end # # That code returns right away, you are just subscribing to "render" events. - # The block will be called asynchronously whenever someone instruments "render": + # The block is saved and will be called whenever someone instruments "render": # # ActiveSupport::Notifications.instrument("render", :extra => :information) do # render :text => "Foo" diff --git a/guides/source/routing.textile b/guides/source/routing.textile index 7941e655bb..dae25853cd 100644 --- a/guides/source/routing.textile +++ b/guides/source/routing.textile @@ -644,6 +644,14 @@ 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. +h4. Unicode character routes + +You can specify unicode character routes directly. For example + +<ruby> +match 'こんにちは' => 'welcome#index' +</ruby> + h3. Customizing Resourceful Routes While the default routes and helpers generated by +resources :posts+ will usually serve you well, you may want to customize them in some way. Rails allows you to customize virtually any generic part of the resourceful helpers. diff --git a/guides/source/upgrading_ruby_on_rails.textile b/guides/source/upgrading_ruby_on_rails.textile index 6cdc6ab289..4bf4751127 100644 --- a/guides/source/upgrading_ruby_on_rails.textile +++ b/guides/source/upgrading_ruby_on_rails.textile @@ -50,6 +50,8 @@ h4(#action_pack4_0). Action Pack Rails 4.0 changed how <tt>assert_generates</tt>, <tt>assert_recognizes</tt>, and <tt>assert_routing</tt> work. Now all these assertions raise <tt>Assertion</tt> instead of <tt>ActionController::RoutingError</tt>. +Rails 4.0 also changed the way unicode character routes are drawn. Now you can draw unicode character routes directly. If you already draw such routes, you must change them, e.g. <tt>get Rack::Utils.escape('こんにちは'), :controller => 'welcome', :action => 'index'</tt> to <tt>get 'こんにちは', :controller => 'welcome', :action => 'index'</tt>. + h4(#helpers_order). Helpers Loading Order The loading order of helpers from more than one directory has changed in Rails 4.0. Previously, helpers from all directories were gathered and then sorted alphabetically. After upgrade to Rails 4.0 helpers will preserve the order of loaded directories and will be sorted alphabetically only within each directory. Unless you explicitly use <tt>helpers_path</tt> parameter, this change will only impact the way of loading helpers from engines. If you rely on the fact that particular helper from engine loads before or after another helper from application or another engine, you should check if correct methods are available after upgrade. If you would like to change order in which engines are loaded, you can use <tt>config.railties_order=</tt> method. diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index 7193dfdef5..c1aa98e481 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -148,6 +148,15 @@ module ApplicationTests assert AppTemplate::Application.config.allow_concurrency end + test "initialize a threadsafe app" do + add_to_config <<-RUBY + config.threadsafe! + RUBY + + require "#{app_path}/config/application" + assert AppTemplate::Application.initialize! + end + test "asset_path defaults to nil for application" do require "#{app_path}/config/environment" assert_equal nil, AppTemplate::Application.config.asset_path @@ -179,7 +188,7 @@ module ApplicationTests require "#{app_path}/config/environment" - assert !ActionController.autoload?(:Caching) + assert !ActionView.autoload?(:AssetPaths) end test "filter_parameters should be able to set via config.filter_parameters" do @@ -374,9 +383,10 @@ module ApplicationTests require "#{app_path}/config/environment" - assert_equal ActiveModel::MassAssignmentSecurity::WhiteList, - ActiveRecord::Base.active_authorizers[:default].class - assert_equal [], ActiveRecord::Base.active_authorizers[:default].to_a + klass = Class.new(ActiveRecord::Base) + + assert_equal ActiveModel::MassAssignmentSecurity::WhiteList, klass.active_authorizers[:default].class + assert_equal [], klass.active_authorizers[:default].to_a end test "registers interceptors with ActionMailer" do @@ -617,5 +627,26 @@ module ApplicationTests make_basic_app assert app.config.colorize_logging end + + test "config.active_record.observers" do + add_to_config <<-RUBY + config.active_record.observers = :foo_observer + RUBY + + app_file 'app/models/foo.rb', <<-RUBY + class Foo < ActiveRecord::Base + end + RUBY + + app_file 'app/models/foo_observer.rb', <<-RUBY + class FooObserver < ActiveRecord::Observer + end + RUBY + + require "#{app_path}/config/environment" + + ActiveRecord::Base + assert defined?(FooObserver) + end end end |