From 378464a2e47bb849f3351cb8c87366554b7ce74d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 21 Jan 2010 13:05:30 +0100 Subject: Default to sync instrumentation. --- activerecord/test/cases/subscriber_test.rb | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/subscriber_test.rb b/activerecord/test/cases/subscriber_test.rb index ce91d9385d..5328d4468b 100644 --- a/activerecord/test/cases/subscriber_test.rb +++ b/activerecord/test/cases/subscriber_test.rb @@ -3,7 +3,8 @@ require "models/developer" require "rails/subscriber/test_helper" require "active_record/railties/subscriber" -module SubscriberTest +class SubscriberTest < ActiveSupport::TestCase + include Rails::Subscriber::TestHelper Rails::Subscriber.add(:active_record, ActiveRecord::Railties::Subscriber.new) def setup @@ -38,14 +39,4 @@ module SubscriberTest assert_match /CACHE/, @logger.logged(:debug).last assert_match /SELECT .*?FROM .?developers.?/, @logger.logged(:debug).last end - - class SyncSubscriberTest < ActiveSupport::TestCase - include Rails::Subscriber::SyncTestHelper - include SubscriberTest - end - - class AsyncSubscriberTest < ActiveSupport::TestCase - include Rails::Subscriber::AsyncTestHelper - include SubscriberTest - end end \ No newline at end of file -- cgit v1.2.3 From da142cd86584d56299470ee4dd6f90be7127b477 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Thu, 21 Jan 2010 17:48:53 +0530 Subject: Supplying Arel::SqlLiteral is much faster --- activerecord/lib/active_record/relation.rb | 7 +++++-- .../lib/active_record/relation/query_methods.rb | 17 ++++++++++++----- 2 files changed, 17 insertions(+), 7 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index c9fff15199..04b85119cb 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -282,8 +282,11 @@ module ActiveRecord def scope_for_create @scope_for_create ||= begin - @create_with_value || wheres.inject({}) do |hash, where| - hash[where.operand1.name] = where.operand2.value if where.is_a?(Arel::Predicates::Equality) + @create_with_value || @where_values.inject({}) do |hash, where| + if where.is_a?(Arel::Predicates::Equality) + hash[where.operand1.name] = where.operand2.respond_to?(:value) ? where.operand2.value : where.operand2 + end + hash end end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index d0689cd93e..ce3e4e8eed 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -119,8 +119,16 @@ module ActiveRecord end end - @where_values.uniq.each do |w| - arel = w.is_a?(String) ? arel.where(w) : arel.where(*w) + @where_values.uniq.each do |where| + next if where.blank? + + case where + when Arel::SqlLiteral + arel = arel.where(where) + else + sql = where.is_a?(String) ? where : where.to_sql + arel = arel.where(Arel::SqlLiteral.new("(#{sql})")) + end end @having_values.uniq.each do |h| @@ -135,7 +143,7 @@ module ActiveRecord end @order_values.uniq.each do |o| - arel = arel.order(o) if o.present? + arel = arel.order(Arel::SqlLiteral.new(o.to_s)) if o.present? end selects = @select_values.uniq @@ -169,8 +177,7 @@ module ActiveRecord builder = PredicateBuilder.new(table.engine) conditions = if [String, Array].include?(args.first.class) - sql = @klass.send(:sanitize_sql, args.size > 1 ? args : args.first) - Arel::SqlLiteral.new("(#{sql})") if sql.present? + @klass.send(:sanitize_sql, args.size > 1 ? args : args.first) elsif args.first.is_a?(Hash) attributes = @klass.send(:expand_hash_conditions_for_aggregates, args.first) builder.build_from_hash(attributes, table) -- cgit v1.2.3 From a68a3e9af6a02c9ce18d3eec87558241095ce8fb Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Thu, 21 Jan 2010 21:15:01 +0530 Subject: Simplify finder method definitions --- .../lib/active_record/relation/query_methods.rb | 24 ++++++++++------------ 1 file changed, 11 insertions(+), 13 deletions(-) (limited to 'activerecord') 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 -- cgit v1.2.3 From 6d30002a52133bd105adb29084f4cc72b1ee847f Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Fri, 22 Jan 2010 00:51:45 +0530 Subject: Revert "Refactoring attributes/types" [#3348 state:open] This reverts commit f936a1f100e75082081e782e5cceb272885c2df7. Conflicts: activerecord/lib/active_record.rb activerecord/lib/active_record/base.rb Revert "Fixed: #without_typecast should only disable typecasting on the duplicated attributes" [#3387 state:open] This reverts commit 2831996483c6a045f1f38d8030256eb58d9771c3. Reason : It's not generating attribute methods properly, making object.column 5x slower. --- activerecord/lib/active_record.rb | 24 ----- .../attribute_methods/before_type_cast.rb | 13 ++- .../lib/active_record/attribute_methods/query.rb | 20 +++- .../lib/active_record/attribute_methods/read.rb | 49 ++++++++- .../attribute_methods/time_zone_conversion.rb | 48 +++++++-- .../lib/active_record/attribute_methods/write.rb | 9 +- activerecord/lib/active_record/attributes.rb | 37 ------- .../lib/active_record/attributes/aliasing.rb | 42 -------- activerecord/lib/active_record/attributes/store.rb | 15 --- .../lib/active_record/attributes/typecasting.rb | 117 -------------------- activerecord/lib/active_record/base.rb | 37 +++++-- activerecord/lib/active_record/types.rb | 38 ------- activerecord/lib/active_record/types/number.rb | 30 ------ activerecord/lib/active_record/types/object.rb | 37 ------- activerecord/lib/active_record/types/serialize.rb | 33 ------ .../lib/active_record/types/time_with_zone.rb | 20 ---- activerecord/lib/active_record/types/unknown.rb | 37 ------- .../test/cases/attributes/aliasing_test.rb | 20 ---- .../test/cases/attributes/typecasting_test.rb | 120 --------------------- activerecord/test/cases/types/number_test.rb | 30 ------ activerecord/test/cases/types/object_test.rb | 24 ----- activerecord/test/cases/types/serialize_test.rb | 20 ---- .../test/cases/types/time_with_zone_test.rb | 42 -------- activerecord/test/cases/types/unknown_test.rb | 29 ----- activerecord/test/cases/types_test.rb | 32 ------ 25 files changed, 148 insertions(+), 775 deletions(-) delete mode 100644 activerecord/lib/active_record/attributes.rb delete mode 100644 activerecord/lib/active_record/attributes/aliasing.rb delete mode 100644 activerecord/lib/active_record/attributes/store.rb delete mode 100644 activerecord/lib/active_record/attributes/typecasting.rb delete mode 100644 activerecord/lib/active_record/types.rb delete mode 100644 activerecord/lib/active_record/types/number.rb delete mode 100644 activerecord/lib/active_record/types/object.rb delete mode 100644 activerecord/lib/active_record/types/serialize.rb delete mode 100644 activerecord/lib/active_record/types/time_with_zone.rb delete mode 100644 activerecord/lib/active_record/types/unknown.rb delete mode 100644 activerecord/test/cases/attributes/aliasing_test.rb delete mode 100644 activerecord/test/cases/attributes/typecasting_test.rb delete mode 100644 activerecord/test/cases/types/number_test.rb delete mode 100644 activerecord/test/cases/types/object_test.rb delete mode 100644 activerecord/test/cases/types/serialize_test.rb delete mode 100644 activerecord/test/cases/types/time_with_zone_test.rb delete mode 100644 activerecord/test/cases/types/unknown_test.rb delete mode 100644 activerecord/test/cases/types_test.rb (limited to 'activerecord') 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 attr_name 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 attr_name 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..f1b2b3b979 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) @@ -1663,7 +1663,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 +1694,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 +1924,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 attr_name 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 +2272,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 +2355,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 +2385,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/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/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 -- cgit v1.2.3