aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
authorJosé Valim <jose.valim@gmail.com>2010-01-23 22:53:26 +0100
committerJosé Valim <jose.valim@gmail.com>2010-01-23 22:53:26 +0100
commitf915f9e33903ee474920e24ad12a1625f2ef1c52 (patch)
tree5b6d41e8a809aeaadbc2c0be59603f5f58b16231 /activerecord
parentb17e358e3df34c03019e357f693611618092e1d6 (diff)
parent8ff2fb6f3aa6140f5a8bd018d5919a8a1e707cda (diff)
downloadrails-f915f9e33903ee474920e24ad12a1625f2ef1c52.tar.gz
rails-f915f9e33903ee474920e24ad12a1625f2ef1c52.tar.bz2
rails-f915f9e33903ee474920e24ad12a1625f2ef1c52.zip
Merge branch 'master' into app
Conflicts: railties/lib/rails/application.rb
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/lib/active_record.rb24
-rw-r--r--activerecord/lib/active_record/attribute_methods/before_type_cast.rb13
-rw-r--r--activerecord/lib/active_record/attribute_methods/query.rb20
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb49
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb48
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb9
-rw-r--r--activerecord/lib/active_record/attributes.rb37
-rw-r--r--activerecord/lib/active_record/attributes/aliasing.rb42
-rw-r--r--activerecord/lib/active_record/attributes/store.rb15
-rw-r--r--activerecord/lib/active_record/attributes/typecasting.rb117
-rwxr-xr-xactiverecord/lib/active_record/base.rb42
-rw-r--r--activerecord/lib/active_record/relation.rb4
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb24
-rw-r--r--activerecord/lib/active_record/types.rb38
-rw-r--r--activerecord/lib/active_record/types/number.rb30
-rw-r--r--activerecord/lib/active_record/types/object.rb37
-rw-r--r--activerecord/lib/active_record/types/serialize.rb33
-rw-r--r--activerecord/lib/active_record/types/time_with_zone.rb20
-rw-r--r--activerecord/lib/active_record/types/unknown.rb37
-rw-r--r--activerecord/test/cases/attributes/aliasing_test.rb20
-rw-r--r--activerecord/test/cases/attributes/typecasting_test.rb120
-rw-r--r--activerecord/test/cases/method_scoping_test.rb8
-rw-r--r--activerecord/test/cases/named_scope_test.rb9
-rw-r--r--activerecord/test/cases/relations_test.rb5
-rw-r--r--activerecord/test/cases/types/number_test.rb30
-rw-r--r--activerecord/test/cases/types/object_test.rb24
-rw-r--r--activerecord/test/cases/types/serialize_test.rb20
-rw-r--r--activerecord/test/cases/types/time_with_zone_test.rb42
-rw-r--r--activerecord/test/cases/types/unknown_test.rb29
-rw-r--r--activerecord/test/cases/types_test.rb32
-rw-r--r--activerecord/test/models/post.rb5
31 files changed, 191 insertions, 792 deletions
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 58673ab7bd..cc0accf90e 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -45,7 +45,6 @@ module ActiveRecord
autoload :AssociationPreload
autoload :Associations
autoload :AttributeMethods
- autoload :Attributes
autoload :AutosaveAssociation
autoload :Relation
@@ -77,7 +76,6 @@ module ActiveRecord
autoload :StateMachine
autoload :Timestamp
autoload :Transactions
- autoload :Types
autoload :Validations
end
@@ -95,28 +93,6 @@ module ActiveRecord
end
end
- module Attributes
- extend ActiveSupport::Autoload
-
- eager_autoload do
- autoload :Aliasing
- autoload :Store
- autoload :Typecasting
- end
- end
-
- module Type
- extend ActiveSupport::Autoload
-
- eager_autoload do
- autoload :Number, 'active_record/types/number'
- autoload :Object, 'active_record/types/object'
- autoload :Serialize, 'active_record/types/serialize'
- autoload :TimeWithZone, 'active_record/types/time_with_zone'
- autoload :Unknown, 'active_record/types/unknown'
- end
- end
-
module Locking
extend ActiveSupport::Autoload
diff --git a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
index 74921241f7..a4e144f233 100644
--- a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
+++ b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
@@ -8,18 +8,25 @@ module ActiveRecord
end
def read_attribute_before_type_cast(attr_name)
- _attributes.without_typecast[attr_name]
+ @attributes[attr_name]
end
# Returns a hash of attributes before typecasting and deserialization.
def attributes_before_type_cast
- _attributes.without_typecast
+ self.attribute_names.inject({}) do |attrs, name|
+ attrs[name] = read_attribute_before_type_cast(name)
+ attrs
+ end
end
private
# Handle *_before_type_cast for method_missing.
def attribute_before_type_cast(attribute_name)
- read_attribute_before_type_cast(attribute_name)
+ if attribute_name == 'id'
+ read_attribute_before_type_cast(self.class.primary_key)
+ else
+ read_attribute_before_type_cast(attribute_name)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/query.rb b/activerecord/lib/active_record/attribute_methods/query.rb
index 0154ee35f8..a949d80120 100644
--- a/activerecord/lib/active_record/attribute_methods/query.rb
+++ b/activerecord/lib/active_record/attribute_methods/query.rb
@@ -8,7 +8,23 @@ module ActiveRecord
end
def query_attribute(attr_name)
- _attributes.has?(attr_name)
+ unless value = read_attribute(attr_name)
+ false
+ else
+ column = self.class.columns_hash[attr_name]
+ if column.nil?
+ if Numeric === value || value !~ /[^0-9]/
+ !value.to_i.zero?
+ else
+ return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value)
+ !value.blank?
+ end
+ elsif column.number?
+ !value.zero?
+ else
+ !value.blank?
+ end
+ end
end
private
@@ -19,5 +35,3 @@ module ActiveRecord
end
end
end
-
-
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 97caec7744..3da3d9d8cc 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -37,7 +37,11 @@ module ActiveRecord
protected
def define_method_attribute(attr_name)
- define_read_method(attr_name.to_sym, attr_name, columns_hash[attr_name])
+ if self.serialized_attributes[attr_name]
+ define_read_method_for_serialized_attribute(attr_name)
+ else
+ define_read_method(attr_name.to_sym, attr_name, columns_hash[attr_name])
+ end
if attr_name == primary_key && attr_name != "id"
define_read_method(:id, attr_name, columns_hash[attr_name])
@@ -45,12 +49,18 @@ module ActiveRecord
end
private
+ # Define read method for serialized attribute.
+ def define_read_method_for_serialized_attribute(attr_name)
+ generated_attribute_methods.module_eval("def #{attr_name}; unserialize_attribute('#{attr_name}'); end", __FILE__, __LINE__)
+ end
# Define an attribute reader method. Cope with nil column.
def define_read_method(symbol, attr_name, column)
- access_code = "_attributes['#{attr_name}']"
+ cast_code = column.type_cast_code('v') if column
+ access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']"
+
unless attr_name.to_s == self.primary_key.to_s
- access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless _attributes.key?('#{attr_name}'); ")
+ access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
end
if cache_attribute?(attr_name)
@@ -63,7 +73,38 @@ module ActiveRecord
# 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)).
def read_attribute(attr_name)
- _attributes[attr_name]
+ attr_name = attr_name.to_s
+ attr_name = self.class.primary_key if attr_name == 'id'
+ if !(value = @attributes[attr_name]).nil?
+ if column = column_for_attribute(attr_name)
+ if unserializable_attribute?(attr_name, column)
+ unserialize_attribute(attr_name)
+ else
+ column.type_cast(value)
+ end
+ else
+ value
+ end
+ else
+ nil
+ end
+ end
+
+ # Returns true if the attribute is of a text column and marked for serialization.
+ def unserializable_attribute?(attr_name, column)
+ column.text? && self.class.serialized_attributes[attr_name]
+ end
+
+ # Returns the unserialized object of the attribute.
+ def unserialize_attribute(attr_name)
+ unserialized_object = object_from_yaml(@attributes[attr_name])
+
+ if unserialized_object.is_a?(self.class.serialized_attributes[attr_name]) || unserialized_object.nil?
+ @attributes.frozen? ? unserialized_object : @attributes[attr_name] = unserialized_object
+ else
+ raise SerializationTypeMismatch,
+ "#{attr_name} was supposed to be a #{self.class.serialized_attributes[attr_name]}, but was a #{unserialized_object.class.to_s}"
+ end
end
private
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 4ac0c7f608..a8e3e28a7a 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -12,20 +12,48 @@ module ActiveRecord
end
module ClassMethods
-
- def cache_attribute?(attr_name)
- time_zone_aware?(attr_name) || super
- end
-
protected
+ # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
+ # This enhanced read method automatically converts the UTC time stored in the database to the time zone stored in Time.zone.
+ def define_method_attribute(attr_name)
+ if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
+ method_body = <<-EOV
+ def #{attr_name}(reload = false)
+ cached = @attributes_cache['#{attr_name}']
+ return cached if cached && !reload
+ time = read_attribute('#{attr_name}')
+ @attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_time_zone : time
+ end
+ EOV
+ generated_attribute_methods.module_eval(method_body, __FILE__, __LINE__)
+ else
+ super
+ end
+ end
- def time_zone_aware?(attr_name)
- column = columns_hash[attr_name]
- time_zone_aware_attributes &&
- !skip_time_zone_conversion_for_attributes.include?(attr_name.to_sym) &&
- [:datetime, :timestamp].include?(column.type)
+ # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
+ # This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.
+ def define_method_attribute=(attr_name)
+ if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
+ method_body = <<-EOV
+ def #{attr_name}=(time)
+ unless time.acts_like?(:time)
+ time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
+ end
+ time = time.in_time_zone rescue nil if time
+ write_attribute(:#{attr_name}, time)
+ end
+ EOV
+ generated_attribute_methods.module_eval(method_body, __FILE__, __LINE__)
+ else
+ super
+ end
end
+ private
+ def create_time_zone_conversion_attribute?(name, column)
+ time_zone_aware_attributes && !skip_time_zone_conversion_for_attributes.include?(name.to_sym) && [:datetime, :timestamp].include?(column.type)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index 37eadbe0a9..e31acac050 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -17,9 +17,14 @@ module ActiveRecord
# Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings for fixnum and float
# columns are turned into +nil+.
def write_attribute(attr_name, value)
- attr_name = _attributes.unalias(attr_name)
+ attr_name = attr_name.to_s
+ attr_name = self.class.primary_key if attr_name == 'id'
@attributes_cache.delete(attr_name)
- _attributes[attr_name] = value
+ if (column = column_for_attribute(attr_name)) && column.number?
+ @attributes[attr_name] = convert_number_column_value(value)
+ else
+ @attributes[attr_name] = value
+ end
end
private
diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb
deleted file mode 100644
index e4d9e89821..0000000000
--- a/activerecord/lib/active_record/attributes.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-module ActiveRecord
- module Attributes
-
- # Returns true if the given attribute is in the attributes hash
- def has_attribute?(attr_name)
- _attributes.key?(attr_name)
- end
-
- # Returns an array of names for the attributes available on this object sorted alphabetically.
- def attribute_names
- _attributes.keys.sort!
- end
-
- # Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
- def attributes
- attributes = _attributes.dup
- attributes.typecast! unless _attributes.frozen?
- attributes.to_h
- end
-
- protected
-
- # Not to be confused with the public #attributes method, which returns a typecasted Hash.
- def _attributes
- @attributes
- end
-
- def initialize_attribute_store(merge_attributes = nil)
- @attributes = ActiveRecord::Attributes::Store.new
- @attributes.merge!(merge_attributes) if merge_attributes
- @attributes.types.merge!(self.class.attribute_types)
- @attributes.aliases.merge!('id' => self.class.primary_key) unless 'id' == self.class.primary_key
- @attributes
- end
-
- end
-end
diff --git a/activerecord/lib/active_record/attributes/aliasing.rb b/activerecord/lib/active_record/attributes/aliasing.rb
deleted file mode 100644
index db77739d1f..0000000000
--- a/activerecord/lib/active_record/attributes/aliasing.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-module ActiveRecord
- module Attributes
- module Aliasing
- # Allows access to keys using aliased names.
- #
- # Example:
- # class Attributes < Hash
- # include Aliasing
- # end
- #
- # attributes = Attributes.new
- # attributes.aliases['id'] = 'fancy_primary_key'
- # attributes['fancy_primary_key'] = 2020
- #
- # attributes['id']
- # => 2020
- #
- # Additionally, symbols are always aliases of strings:
- # attributes[:fancy_primary_key]
- # => 2020
- #
- def [](key)
- super(unalias(key))
- end
-
- def []=(key, value)
- super(unalias(key), value)
- end
-
- def aliases
- @aliases ||= {}
- end
-
- def unalias(key)
- key = key.to_s
- aliases[key] || key
- end
-
- end
- end
-end
-
diff --git a/activerecord/lib/active_record/attributes/store.rb b/activerecord/lib/active_record/attributes/store.rb
deleted file mode 100644
index 61109f4acc..0000000000
--- a/activerecord/lib/active_record/attributes/store.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-module ActiveRecord
- module Attributes
- class Store < Hash
- include ActiveRecord::Attributes::Typecasting
- include ActiveRecord::Attributes::Aliasing
-
- # Attributes not mapped to a column are handled using Type::Unknown,
- # which enables boolean typecasting for unmapped keys.
- def types
- @types ||= Hash.new(Type::Unknown.new)
- end
-
- end
- end
-end
diff --git a/activerecord/lib/active_record/attributes/typecasting.rb b/activerecord/lib/active_record/attributes/typecasting.rb
deleted file mode 100644
index 56c32f9895..0000000000
--- a/activerecord/lib/active_record/attributes/typecasting.rb
+++ /dev/null
@@ -1,117 +0,0 @@
-module ActiveRecord
- module Attributes
- module Typecasting
- # Typecasts values during access based on their key mapping to a Type.
- #
- # Example:
- # class Attributes < Hash
- # include Typecasting
- # end
- #
- # attributes = Attributes.new
- # attributes.types['comments_count'] = Type::Integer
- # attributes['comments_count'] = '5'
- #
- # attributes['comments_count']
- # => 5
- #
- # To support keys not mapped to a typecaster, add a default to types.
- # attributes.types.default = Type::Unknown
- # attributes['age'] = '25'
- # attributes['age']
- # => '25'
- #
- # A valid type supports #cast, #precast, #boolean, and #appendable? methods.
- #
- def [](key)
- value = super(key)
- typecast_read(key, value)
- end
-
- def []=(key, value)
- super(key, typecast_write(key, value))
- end
-
- def to_h
- hash = {}
- hash.merge!(self)
- hash
- end
-
- def dup # :nodoc:
- copy = super
- copy.types = types.dup
- copy
- end
-
- # Provides a duplicate with typecasting disabled.
- #
- # Example:
- # attributes = Attributes.new
- # attributes.types['comments_count'] = Type::Integer
- # attributes['comments_count'] = '5'
- #
- # attributes.without_typecast['comments_count']
- # => '5'
- #
- def without_typecast
- dup.without_typecast!
- end
-
- def without_typecast!
- types.clear
- self
- end
-
- def typecast!
- keys.each { |key| self[key] = self[key] }
- self
- end
-
- # Check if key has a value that typecasts to true.
- #
- # attributes = Attributes.new
- # attributes.types['comments_count'] = Type::Integer
- #
- # attributes['comments_count'] = 0
- # attributes.has?('comments_count')
- # => false
- #
- # attributes['comments_count'] = 1
- # attributes.has?('comments_count')
- # => true
- #
- def has?(key)
- value = self[key]
- boolean_typecast(key, value)
- end
-
- def types
- @types ||= {}
- end
-
- protected
-
- def types=(other_types)
- @types = other_types
- end
-
- def boolean_typecast(key, value)
- value ? types[key].boolean(value) : false
- end
-
- def typecast_read(key, value)
- type = types[key]
- value = type.cast(value)
- self[key] = value if type.appendable? && !frozen?
-
- value
- end
-
- def typecast_write(key, value)
- types[key].precast(value)
- end
-
- end
- end
-end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index bc1b0bde31..12feef4849 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -1200,7 +1200,7 @@ module ActiveRecord #:nodoc:
def instantiate(record)
object = find_sti_class(record[inheritance_column]).allocate
- object.send(:initialize_attribute_store, record)
+ object.instance_variable_set(:'@attributes', record)
object.instance_variable_set(:'@attributes_cache', {})
object.send(:_run_find_callbacks)
@@ -1236,7 +1236,7 @@ module ActiveRecord #:nodoc:
end
def construct_finder_arel(options = {}, scope = nil)
- relation = unscoped.apply_finder_options(options)
+ relation = options.is_a?(Hash) ? unscoped.apply_finder_options(options) : unscoped.merge(options)
relation = scope.merge(relation) if scope
relation
end
@@ -1450,7 +1450,8 @@ module ActiveRecord #:nodoc:
end
def scoped_methods #:nodoc:
- Thread.current[:"#{self}_scoped_methods"] ||= self.default_scoping.dup
+ key = :"#{self}_scoped_methods"
+ Thread.current[key] = Thread.current[key].presence || self.default_scoping.dup
end
def current_scoped_methods #:nodoc:
@@ -1663,7 +1664,7 @@ module ActiveRecord #:nodoc:
# In both instances, valid attribute keys are determined by the column names of the associated table --
# hence you can't have attributes that aren't part of the table columns.
def initialize(attributes = nil)
- initialize_attribute_store(attributes_from_column_definition)
+ @attributes = attributes_from_column_definition
@attributes_cache = {}
@new_record = true
ensure_proper_type
@@ -1694,7 +1695,7 @@ module ActiveRecord #:nodoc:
callback(:after_initialize) if respond_to_without_attributes?(:after_initialize)
cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
cloned_attributes.delete(self.class.primary_key)
- initialize_attribute_store(cloned_attributes)
+ @attributes = cloned_attributes
clear_aggregation_cache
@attributes_cache = {}
@new_record = true
@@ -1924,11 +1925,21 @@ module ActiveRecord #:nodoc:
def reload(options = nil)
clear_aggregation_cache
clear_association_cache
- _attributes.update(self.class.find(self.id, options).instance_variable_get('@attributes'))
+ @attributes.update(self.class.find(self.id, options).instance_variable_get('@attributes'))
@attributes_cache = {}
self
end
+ # Returns true if the given attribute is in the attributes hash
+ def has_attribute?(attr_name)
+ @attributes.has_key?(attr_name.to_s)
+ end
+
+ # Returns an array of names for the attributes available on this object sorted alphabetically.
+ def attribute_names
+ @attributes.keys.sort
+ 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).
@@ -2262,7 +2273,7 @@ module ActiveRecord #:nodoc:
end
def instantiate_time_object(name, values)
- if self.class.send(:time_zone_aware?, name)
+ if self.class.send(:create_time_zone_conversion_attribute?, name, column_for_attribute(name))
Time.zone.local(*values)
else
Time.time_with_datetime_fallback(@@default_timezone, *values)
@@ -2345,6 +2356,22 @@ module ActiveRecord #:nodoc:
comma_pair_list(quote_columns(quoter, hash))
end
+ def convert_number_column_value(value)
+ if value == false
+ 0
+ elsif value == true
+ 1
+ elsif value.is_a?(String) && value.blank?
+ nil
+ else
+ value
+ end
+ end
+
+ def object_from_yaml(string)
+ return string unless string.is_a?(String) && string =~ /^---/
+ YAML::load(string) rescue string
+ end
end
Base.class_eval do
@@ -2359,7 +2386,6 @@ module ActiveRecord #:nodoc:
include AttributeMethods::PrimaryKey
include AttributeMethods::TimeZoneConversion
include AttributeMethods::Dirty
- include Attributes, Types
include Callbacks, ActiveModel::Observing, Timestamp
include Associations, AssociationPreload, NamedScope
include ActiveModel::Conversion
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 04b85119cb..1a96cdad17 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -32,7 +32,7 @@ module ActiveRecord
end
def respond_to?(method, include_private = false)
- return true if arel.respond_to?(method, include_private) || Array.method_defined?(method)
+ return true if arel.respond_to?(method, include_private) || Array.method_defined?(method) || @klass.respond_to?(method, include_private)
if match = DynamicFinderMatch.match(method)
return true if @klass.send(:all_attributes_exists?, match.attribute_names)
@@ -301,6 +301,8 @@ module ActiveRecord
def method_missing(method, *args, &block)
if Array.method_defined?(method)
to_a.send(method, *args, &block)
+ elsif @klass.respond_to?(method)
+ @klass.send(:with_scope, self) { @klass.send(method, *args, &block) }
elsif arel.respond_to?(method)
arel.send(method, *args, &block)
elsif match = DynamicFinderMatch.match(method)
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index ce3e4e8eed..8954f2d12b 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -8,11 +8,10 @@ module ActiveRecord
class_eval <<-CEVAL
def #{query_method}(*args)
- spawn.tap do |new_relation|
- new_relation.#{query_method}_values ||= []
- value = Array.wrap(args.flatten).reject {|x| x.blank? }
- new_relation.#{query_method}_values += value if value.present?
- end
+ new_relation = spawn
+ value = Array.wrap(args.flatten).reject {|x| x.blank? }
+ new_relation.#{query_method}_values += value if value.present?
+ new_relation
end
CEVAL
end
@@ -20,11 +19,10 @@ module ActiveRecord
[:where, :having].each do |query_method|
class_eval <<-CEVAL
def #{query_method}(*args)
- spawn.tap do |new_relation|
- new_relation.#{query_method}_values ||= []
- value = build_where(*args)
- new_relation.#{query_method}_values += [*value] if value.present?
- end
+ new_relation = spawn
+ value = build_where(*args)
+ new_relation.#{query_method}_values += [*value] if value.present?
+ new_relation
end
CEVAL
end
@@ -34,9 +32,9 @@ module ActiveRecord
class_eval <<-CEVAL
def #{query_method}(value = true)
- spawn.tap do |new_relation|
- new_relation.#{query_method}_value = value
- end
+ new_relation = spawn
+ new_relation.#{query_method}_value = value
+ new_relation
end
CEVAL
end
diff --git a/activerecord/lib/active_record/types.rb b/activerecord/lib/active_record/types.rb
deleted file mode 100644
index 74f569352b..0000000000
--- a/activerecord/lib/active_record/types.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-module ActiveRecord
- module Types
- extend ActiveSupport::Concern
-
- module ClassMethods
-
- def attribute_types
- attribute_types = {}
- columns.each do |column|
- options = {}
- options[:time_zone_aware] = time_zone_aware?(column.name)
- options[:serialize] = serialized_attributes[column.name]
-
- attribute_types[column.name] = to_type(column, options)
- end
- attribute_types
- end
-
- private
-
- def to_type(column, options = {})
- type_class = if options[:time_zone_aware]
- Type::TimeWithZone
- elsif options[:serialize]
- Type::Serialize
- elsif [ :integer, :float, :decimal ].include?(column.type)
- Type::Number
- else
- Type::Object
- end
-
- type_class.new(column, options)
- end
-
- end
-
- end
-end
diff --git a/activerecord/lib/active_record/types/number.rb b/activerecord/lib/active_record/types/number.rb
deleted file mode 100644
index cfbe877575..0000000000
--- a/activerecord/lib/active_record/types/number.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-module ActiveRecord
- module Type
- class Number < Object
-
- def boolean(value)
- value = cast(value)
- !(value.nil? || value.zero?)
- end
-
- def precast(value)
- convert_number_column_value(value)
- end
-
- private
-
- def convert_number_column_value(value)
- if value == false
- 0
- elsif value == true
- 1
- elsif value.is_a?(String) && value.blank?
- nil
- else
- value
- end
- end
-
- end
- end
-end \ No newline at end of file
diff --git a/activerecord/lib/active_record/types/object.rb b/activerecord/lib/active_record/types/object.rb
deleted file mode 100644
index ec3f861abd..0000000000
--- a/activerecord/lib/active_record/types/object.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-module ActiveRecord
- module Type
- module Casting
-
- def cast(value)
- typecaster.type_cast(value)
- end
-
- def precast(value)
- value
- end
-
- def boolean(value)
- cast(value).present?
- end
-
- # Attributes::Typecasting stores appendable? types (e.g. serialized Arrays) when typecasting reads.
- def appendable?
- false
- end
-
- end
-
- class Object
- include Casting
-
- attr_reader :name, :options
- attr_reader :typecaster
-
- def initialize(typecaster = nil, options = {})
- @typecaster, @options = typecaster, options
- end
-
- end
-
- end
-end \ No newline at end of file
diff --git a/activerecord/lib/active_record/types/serialize.rb b/activerecord/lib/active_record/types/serialize.rb
deleted file mode 100644
index 7b6af1981f..0000000000
--- a/activerecord/lib/active_record/types/serialize.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-module ActiveRecord
- module Type
- class Serialize < Object
-
- def cast(value)
- unserialize(value)
- end
-
- def appendable?
- true
- end
-
- protected
-
- def unserialize(value)
- unserialized_object = object_from_yaml(value)
-
- if unserialized_object.is_a?(@options[:serialize]) || unserialized_object.nil?
- unserialized_object
- else
- raise SerializationTypeMismatch,
- "#{name} was supposed to be a #{@options[:serialize]}, but was a #{unserialized_object.class.to_s}"
- end
- end
-
- def object_from_yaml(string)
- return string unless string.is_a?(String) && string =~ /^---/
- YAML::load(string) rescue string
- end
-
- end
- end
-end \ No newline at end of file
diff --git a/activerecord/lib/active_record/types/time_with_zone.rb b/activerecord/lib/active_record/types/time_with_zone.rb
deleted file mode 100644
index 3a8b9292f9..0000000000
--- a/activerecord/lib/active_record/types/time_with_zone.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-module ActiveRecord
- module Type
- class TimeWithZone < Object
-
- def cast(time)
- time = super(time)
- time.acts_like?(:time) ? time.in_time_zone : time
- end
-
- def precast(time)
- unless time.acts_like?(:time)
- time = time.is_a?(String) ? ::Time.zone.parse(time) : time.to_time rescue time
- end
- time = time.in_time_zone rescue nil if time
- super(time)
- end
-
- end
- end
-end
diff --git a/activerecord/lib/active_record/types/unknown.rb b/activerecord/lib/active_record/types/unknown.rb
deleted file mode 100644
index f832c7b304..0000000000
--- a/activerecord/lib/active_record/types/unknown.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-module ActiveRecord
- module Type
- # Useful for handling attributes not mapped to types. Performs some boolean typecasting,
- # but otherwise leaves the value untouched.
- class Unknown
-
- def cast(value)
- value
- end
-
- def precast(value)
- value
- end
-
- # Attempts typecasting to handle numeric, false and blank values.
- def boolean(value)
- empty = (numeric?(value) && value.to_i.zero?) || false?(value) || value.blank?
- !empty
- end
-
- def appendable?
- false
- end
-
- protected
-
- def false?(value)
- ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value)
- end
-
- def numeric?(value)
- Numeric === value || value !~ /[^0-9]/
- end
-
- end
- end
-end \ No newline at end of file
diff --git a/activerecord/test/cases/attributes/aliasing_test.rb b/activerecord/test/cases/attributes/aliasing_test.rb
deleted file mode 100644
index 7ee25779f1..0000000000
--- a/activerecord/test/cases/attributes/aliasing_test.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-require "cases/helper"
-
-class AliasingTest < ActiveRecord::TestCase
-
- class AliasingAttributes < Hash
- include ActiveRecord::Attributes::Aliasing
- end
-
- test "attribute access with aliasing" do
- attributes = AliasingAttributes.new
- attributes[:name] = 'Batman'
- attributes.aliases['nickname'] = 'name'
-
- assert_equal 'Batman', attributes[:name], "Symbols should point to Strings"
- assert_equal 'Batman', attributes['name']
- assert_equal 'Batman', attributes['nickname']
- assert_equal 'Batman', attributes[:nickname]
- end
-
-end
diff --git a/activerecord/test/cases/attributes/typecasting_test.rb b/activerecord/test/cases/attributes/typecasting_test.rb
deleted file mode 100644
index 8a3b551375..0000000000
--- a/activerecord/test/cases/attributes/typecasting_test.rb
+++ /dev/null
@@ -1,120 +0,0 @@
-require "cases/helper"
-
-class TypecastingTest < ActiveRecord::TestCase
-
- class TypecastingAttributes < Hash
- include ActiveRecord::Attributes::Typecasting
- end
-
- module MockType
- class Object
-
- def cast(value)
- value
- end
-
- def precast(value)
- value
- end
-
- def boolean(value)
- !value.blank?
- end
-
- def appendable?
- false
- end
-
- end
-
- class Integer < Object
-
- def cast(value)
- value.to_i
- end
-
- def precast(value)
- value ? value : 0
- end
-
- def boolean(value)
- !Float(value).zero?
- end
-
- end
-
- class Serialize < Object
-
- def cast(value)
- YAML::load(value) rescue value
- end
-
- def precast(value)
- value
- end
-
- def appendable?
- true
- end
-
- end
- end
-
- def setup
- @attributes = TypecastingAttributes.new
- @attributes.types.default = MockType::Object.new
- @attributes.types['comments_count'] = MockType::Integer.new
- end
-
- test "typecast on read" do
- attributes = @attributes.merge('comments_count' => '5')
- assert_equal 5, attributes['comments_count']
- end
-
- test "typecast on write" do
- @attributes['comments_count'] = false
-
- assert_equal 0, @attributes.to_h['comments_count']
- end
-
- test "serialized objects" do
- attributes = @attributes.merge('tags' => [ 'peanut butter' ].to_yaml)
- attributes.types['tags'] = MockType::Serialize.new
- attributes['tags'] << 'jelly'
-
- assert_equal [ 'peanut butter', 'jelly' ], attributes['tags']
- end
-
- test "without typecasting" do
- @attributes.merge!('comments_count' => '5')
- attributes = @attributes.without_typecast
-
- assert_equal '5', attributes['comments_count']
- assert_equal 5, @attributes['comments_count'], "Original attributes should typecast"
- end
-
-
- test "typecast all attributes" do
- attributes = @attributes.merge('title' => 'I love sandwiches', 'comments_count' => '5')
- attributes.typecast!
-
- assert_equal({ 'title' => 'I love sandwiches', 'comments_count' => 5 }, attributes)
- end
-
- test "query for has? value" do
- attributes = @attributes.merge('comments_count' => '1')
-
- assert_equal true, attributes.has?('comments_count')
- attributes['comments_count'] = '0'
- assert_equal false, attributes.has?('comments_count')
- end
-
- test "attributes to Hash" do
- attributes_hash = { 'title' => 'I love sandwiches', 'comments_count' => '5' }
- attributes = @attributes.merge(attributes_hash)
-
- assert_equal Hash, attributes.to_h.class
- assert_equal attributes_hash, attributes.to_h
- end
-
-end
diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb
index fbd1adf088..1081aa40a9 100644
--- a/activerecord/test/cases/method_scoping_test.rb
+++ b/activerecord/test/cases/method_scoping_test.rb
@@ -588,7 +588,7 @@ class HasAndBelongsToManyScopingTest< ActiveRecord::TestCase
end
class DefaultScopingTest < ActiveRecord::TestCase
- fixtures :developers
+ fixtures :developers, :posts
def test_default_scope
expected = Developer.find(:all, :order => 'salary DESC').collect { |dev| dev.salary }
@@ -657,6 +657,12 @@ class DefaultScopingTest < ActiveRecord::TestCase
received = DeveloperOrderedBySalary.find(:all, :order => 'salary').collect { |dev| dev.salary }
assert_equal expected, received
end
+
+ def test_default_scope_using_relation
+ posts = PostWithComment.scoped
+ assert_equal 2, posts.count
+ assert_equal posts(:thinking), posts.first
+ end
end
=begin
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index 2c34ab787d..894d96346e 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -380,6 +380,15 @@ class NamedScopeTest < ActiveRecord::TestCase
assert_deprecated('named_scope has been deprecated') { Topic.named_scope :deprecated_named_scope }
end
+ def test_named_scopes_on_relations
+ # Topic.replied
+ approved_topics = Topic.scoped.approved.order('id DESC')
+ assert_equal topics(:fourth), approved_topics.first
+
+ replied_approved_topics = approved_topics.replied
+ assert_equal topics(:third), replied_approved_topics.first
+ end
+
def test_index_on_named_scope
approved = Topic.approved.order('id ASC')
assert_equal topics(:second), approved[0]
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index d34c9b4895..1e345399f5 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -164,6 +164,11 @@ class RelationTest < ActiveRecord::TestCase
end
end
+ def test_respond_to_class_methods_and_named_scopes
+ assert DeveloperOrderedBySalary.scoped.respond_to?(:all_ordered_by_name)
+ assert Topic.scoped.respond_to?(:by_lifo)
+ end
+
def test_find_with_readonly_option
Developer.scoped.each { |d| assert !d.readonly? }
Developer.scoped.readonly.each { |d| assert d.readonly? }
diff --git a/activerecord/test/cases/types/number_test.rb b/activerecord/test/cases/types/number_test.rb
deleted file mode 100644
index ee7216a0f1..0000000000
--- a/activerecord/test/cases/types/number_test.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-require "cases/helper"
-
-class NumberTest < ActiveRecord::TestCase
-
- def setup
- @column = ActiveRecord::ConnectionAdapters::Column.new('comments_count', 0, 'integer')
- @number = ActiveRecord::Type::Number.new(@column)
- end
-
- test "typecast" do
- assert_equal 1, @number.cast(1)
- assert_equal 1, @number.cast('1')
- assert_equal 0, @number.cast('')
-
- assert_equal 0, @number.precast(false)
- assert_equal 1, @number.precast(true)
- assert_equal nil, @number.precast('')
- assert_equal 0, @number.precast(0)
- end
-
- test "cast as boolean" do
- assert_equal true, @number.boolean('1')
- assert_equal true, @number.boolean(1)
-
- assert_equal false, @number.boolean(0)
- assert_equal false, @number.boolean('0')
- assert_equal false, @number.boolean(nil)
- end
-
-end
diff --git a/activerecord/test/cases/types/object_test.rb b/activerecord/test/cases/types/object_test.rb
deleted file mode 100644
index f2667a9b00..0000000000
--- a/activerecord/test/cases/types/object_test.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-require "cases/helper"
-
-class ObjectTest < ActiveRecord::TestCase
-
- def setup
- @column = ActiveRecord::ConnectionAdapters::Column.new('name', '', 'date')
- @object = ActiveRecord::Type::Object.new(@column)
- end
-
- test "typecast with column" do
- date = Date.new(2009, 7, 10)
- assert_equal date, @object.cast('10-07-2009')
- assert_equal nil, @object.cast('')
-
- assert_equal date, @object.precast(date)
- end
-
- test "cast as boolean" do
- assert_equal false, @object.boolean(nil)
- assert_equal false, @object.boolean('false')
- assert_equal true, @object.boolean('10-07-2009')
- end
-
-end
diff --git a/activerecord/test/cases/types/serialize_test.rb b/activerecord/test/cases/types/serialize_test.rb
deleted file mode 100644
index e9423a5b9d..0000000000
--- a/activerecord/test/cases/types/serialize_test.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-require "cases/helper"
-
-class SerializeTest < ActiveRecord::TestCase
-
- test "typecast" do
- serializer = ActiveRecord::Type::Serialize.new(column = nil, :serialize => Array)
-
- assert_equal [], serializer.cast([].to_yaml)
- assert_equal ['1'], serializer.cast(['1'].to_yaml)
- assert_equal nil, serializer.cast(nil.to_yaml)
- end
-
- test "cast as boolean" do
- serializer = ActiveRecord::Type::Serialize.new(column = nil, :serialize => Array)
-
- assert_equal true, serializer.boolean(['1'].to_yaml)
- assert_equal false, serializer.boolean([].to_yaml)
- end
-
-end \ No newline at end of file
diff --git a/activerecord/test/cases/types/time_with_zone_test.rb b/activerecord/test/cases/types/time_with_zone_test.rb
deleted file mode 100644
index b3de79a6c8..0000000000
--- a/activerecord/test/cases/types/time_with_zone_test.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-require "cases/helper"
-
-class TimeWithZoneTest < ActiveRecord::TestCase
-
- def setup
- @column = ActiveRecord::ConnectionAdapters::Column.new('created_at', 0, 'datetime')
- @time_with_zone = ActiveRecord::Type::TimeWithZone.new(@column)
- end
-
- test "typecast" do
- Time.use_zone("Pacific Time (US & Canada)") do
- time_string = "2009-10-07 21:29:10"
- time = Time.zone.parse(time_string)
-
- # assert_equal time, @time_with_zone.cast(time_string)
- assert_equal nil, @time_with_zone.cast('')
- assert_equal nil, @time_with_zone.cast(nil)
-
- assert_equal time, @time_with_zone.precast(time)
- assert_equal time, @time_with_zone.precast(time_string)
- assert_equal time, @time_with_zone.precast(time.to_time)
- # assert_equal "#{time.to_date.to_s} 00:00:00 -0700", @time_with_zone.precast(time.to_date).to_s
- end
- end
-
- test "cast as boolean" do
- Time.use_zone('Central Time (US & Canada)') do
- time = Time.zone.now
-
- assert_equal true, @time_with_zone.boolean(time)
- assert_equal true, @time_with_zone.boolean(time.to_date)
- assert_equal true, @time_with_zone.boolean(time.to_time)
-
- assert_equal true, @time_with_zone.boolean(time.to_s)
- assert_equal true, @time_with_zone.boolean(time.to_date.to_s)
- assert_equal true, @time_with_zone.boolean(time.to_time.to_s)
-
- assert_equal false, @time_with_zone.boolean('')
- end
- end
-
-end
diff --git a/activerecord/test/cases/types/unknown_test.rb b/activerecord/test/cases/types/unknown_test.rb
deleted file mode 100644
index 230d67b2fb..0000000000
--- a/activerecord/test/cases/types/unknown_test.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-require "cases/helper"
-
-class UnknownTest < ActiveRecord::TestCase
-
- test "typecast attributes does't modify values" do
- unkown = ActiveRecord::Type::Unknown.new
- person = { 'name' => '0' }
-
- assert_equal person['name'], unkown.cast(person['name'])
- assert_equal person['name'], unkown.precast(person['name'])
- end
-
- test "cast as boolean" do
- person = { 'id' => 0, 'name' => ' ', 'admin' => 'false', 'votes' => '0' }
- unkown = ActiveRecord::Type::Unknown.new
-
- assert_equal false, unkown.boolean(person['votes'])
- assert_equal false, unkown.boolean(person['admin'])
- assert_equal false, unkown.boolean(person['name'])
- assert_equal false, unkown.boolean(person['id'])
-
- person = { 'id' => 5, 'name' => 'Eric', 'admin' => 'true', 'votes' => '25' }
- assert_equal true, unkown.boolean(person['votes'])
- assert_equal true, unkown.boolean(person['admin'])
- assert_equal true, unkown.boolean(person['name'])
- assert_equal true, unkown.boolean(person['id'])
- end
-
-end \ No newline at end of file
diff --git a/activerecord/test/cases/types_test.rb b/activerecord/test/cases/types_test.rb
deleted file mode 100644
index 403a9a6e02..0000000000
--- a/activerecord/test/cases/types_test.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-require "cases/helper"
-require 'models/topic'
-
-class TypesTest < ActiveRecord::TestCase
-
- test "attribute types from columns" do
- begin
- ActiveRecord::Base.time_zone_aware_attributes = true
- attribute_type_classes = {}
- Topic.attribute_types.each { |key, type| attribute_type_classes[key] = type.class }
-
- expected = { "id" => ActiveRecord::Type::Number,
- "replies_count" => ActiveRecord::Type::Number,
- "parent_id" => ActiveRecord::Type::Number,
- "content" => ActiveRecord::Type::Serialize,
- "written_on" => ActiveRecord::Type::TimeWithZone,
- "title" => ActiveRecord::Type::Object,
- "author_name" => ActiveRecord::Type::Object,
- "approved" => ActiveRecord::Type::Object,
- "parent_title" => ActiveRecord::Type::Object,
- "bonus_time" => ActiveRecord::Type::Object,
- "type" => ActiveRecord::Type::Object,
- "last_read" => ActiveRecord::Type::Object,
- "author_email_address" => ActiveRecord::Type::Object }
-
- assert_equal expected, attribute_type_classes
- ensure
- ActiveRecord::Base.time_zone_aware_attributes = false
- end
- end
-
-end
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index f48b35486c..704313649a 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -100,3 +100,8 @@ end
class SubStiPost < StiPost
self.table_name = Post.table_name
end
+
+class PostWithComment < ActiveRecord::Base
+ self.table_name = 'posts'
+ default_scope where("posts.comments_count > 0").order("posts.comments_count ASC")
+end