activerecord 1.0.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (178) hide show
  1. data/CHANGELOG +5518 -76
  2. data/README.rdoc +222 -0
  3. data/examples/performance.rb +162 -0
  4. data/examples/simple.rb +14 -0
  5. data/lib/active_record/aggregations.rb +192 -80
  6. data/lib/active_record/association_preload.rb +403 -0
  7. data/lib/active_record/associations/association_collection.rb +545 -53
  8. data/lib/active_record/associations/association_proxy.rb +295 -0
  9. data/lib/active_record/associations/belongs_to_association.rb +91 -0
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +78 -0
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +127 -36
  12. data/lib/active_record/associations/has_many_association.rb +108 -84
  13. data/lib/active_record/associations/has_many_through_association.rb +116 -0
  14. data/lib/active_record/associations/has_one_association.rb +143 -0
  15. data/lib/active_record/associations/has_one_through_association.rb +40 -0
  16. data/lib/active_record/associations/through_association_scope.rb +154 -0
  17. data/lib/active_record/associations.rb +2086 -368
  18. data/lib/active_record/attribute_methods/before_type_cast.rb +33 -0
  19. data/lib/active_record/attribute_methods/dirty.rb +95 -0
  20. data/lib/active_record/attribute_methods/primary_key.rb +50 -0
  21. data/lib/active_record/attribute_methods/query.rb +39 -0
  22. data/lib/active_record/attribute_methods/read.rb +116 -0
  23. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -0
  24. data/lib/active_record/attribute_methods/write.rb +37 -0
  25. data/lib/active_record/attribute_methods.rb +60 -0
  26. data/lib/active_record/autosave_association.rb +369 -0
  27. data/lib/active_record/base.rb +1603 -721
  28. data/lib/active_record/callbacks.rb +176 -225
  29. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +365 -0
  30. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +113 -0
  31. data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
  32. data/lib/active_record/connection_adapters/abstract/database_statements.rb +329 -0
  33. data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
  34. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -0
  35. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +739 -0
  36. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +543 -0
  37. data/lib/active_record/connection_adapters/abstract_adapter.rb +165 -279
  38. data/lib/active_record/connection_adapters/mysql_adapter.rb +594 -82
  39. data/lib/active_record/connection_adapters/postgresql_adapter.rb +988 -135
  40. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -0
  41. data/lib/active_record/connection_adapters/sqlite_adapter.rb +365 -71
  42. data/lib/active_record/counter_cache.rb +115 -0
  43. data/lib/active_record/dynamic_finder_match.rb +53 -0
  44. data/lib/active_record/dynamic_scope_match.rb +32 -0
  45. data/lib/active_record/errors.rb +172 -0
  46. data/lib/active_record/fixtures.rb +941 -105
  47. data/lib/active_record/locale/en.yml +40 -0
  48. data/lib/active_record/locking/optimistic.rb +172 -0
  49. data/lib/active_record/locking/pessimistic.rb +55 -0
  50. data/lib/active_record/log_subscriber.rb +48 -0
  51. data/lib/active_record/migration.rb +617 -0
  52. data/lib/active_record/named_scope.rb +138 -0
  53. data/lib/active_record/nested_attributes.rb +417 -0
  54. data/lib/active_record/observer.rb +105 -36
  55. data/lib/active_record/persistence.rb +291 -0
  56. data/lib/active_record/query_cache.rb +36 -0
  57. data/lib/active_record/railtie.rb +91 -0
  58. data/lib/active_record/railties/controller_runtime.rb +38 -0
  59. data/lib/active_record/railties/databases.rake +512 -0
  60. data/lib/active_record/reflection.rb +364 -87
  61. data/lib/active_record/relation/batches.rb +89 -0
  62. data/lib/active_record/relation/calculations.rb +286 -0
  63. data/lib/active_record/relation/finder_methods.rb +355 -0
  64. data/lib/active_record/relation/predicate_builder.rb +41 -0
  65. data/lib/active_record/relation/query_methods.rb +261 -0
  66. data/lib/active_record/relation/spawn_methods.rb +112 -0
  67. data/lib/active_record/relation.rb +393 -0
  68. data/lib/active_record/schema.rb +59 -0
  69. data/lib/active_record/schema_dumper.rb +195 -0
  70. data/lib/active_record/serialization.rb +60 -0
  71. data/lib/active_record/serializers/xml_serializer.rb +244 -0
  72. data/lib/active_record/session_store.rb +340 -0
  73. data/lib/active_record/test_case.rb +67 -0
  74. data/lib/active_record/timestamp.rb +88 -0
  75. data/lib/active_record/transactions.rb +329 -75
  76. data/lib/active_record/validations/associated.rb +48 -0
  77. data/lib/active_record/validations/uniqueness.rb +185 -0
  78. data/lib/active_record/validations.rb +58 -179
  79. data/lib/active_record/version.rb +9 -0
  80. data/lib/active_record.rb +100 -24
  81. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  82. data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
  83. data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
  84. data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
  85. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  86. data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
  87. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  88. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  89. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
  90. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
  91. data/lib/rails/generators/active_record.rb +27 -0
  92. metadata +216 -158
  93. data/README +0 -361
  94. data/RUNNING_UNIT_TESTS +0 -36
  95. data/dev-utils/eval_debugger.rb +0 -9
  96. data/examples/associations.rb +0 -87
  97. data/examples/shared_setup.rb +0 -15
  98. data/examples/validation.rb +0 -88
  99. data/install.rb +0 -60
  100. data/lib/active_record/deprecated_associations.rb +0 -70
  101. data/lib/active_record/support/class_attribute_accessors.rb +0 -43
  102. data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
  103. data/lib/active_record/support/clean_logger.rb +0 -10
  104. data/lib/active_record/support/inflector.rb +0 -70
  105. data/lib/active_record/vendor/mysql.rb +0 -1117
  106. data/lib/active_record/vendor/simple.rb +0 -702
  107. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  108. data/lib/active_record/wrappings.rb +0 -59
  109. data/rakefile +0 -122
  110. data/test/abstract_unit.rb +0 -16
  111. data/test/aggregations_test.rb +0 -34
  112. data/test/all.sh +0 -8
  113. data/test/associations_test.rb +0 -477
  114. data/test/base_test.rb +0 -513
  115. data/test/class_inheritable_attributes_test.rb +0 -33
  116. data/test/connections/native_mysql/connection.rb +0 -24
  117. data/test/connections/native_postgresql/connection.rb +0 -24
  118. data/test/connections/native_sqlite/connection.rb +0 -24
  119. data/test/deprecated_associations_test.rb +0 -336
  120. data/test/finder_test.rb +0 -67
  121. data/test/fixtures/accounts/signals37 +0 -3
  122. data/test/fixtures/accounts/unknown +0 -2
  123. data/test/fixtures/auto_id.rb +0 -4
  124. data/test/fixtures/column_name.rb +0 -3
  125. data/test/fixtures/companies/first_client +0 -6
  126. data/test/fixtures/companies/first_firm +0 -4
  127. data/test/fixtures/companies/second_client +0 -6
  128. data/test/fixtures/company.rb +0 -37
  129. data/test/fixtures/company_in_module.rb +0 -33
  130. data/test/fixtures/course.rb +0 -3
  131. data/test/fixtures/courses/java +0 -2
  132. data/test/fixtures/courses/ruby +0 -2
  133. data/test/fixtures/customer.rb +0 -30
  134. data/test/fixtures/customers/david +0 -6
  135. data/test/fixtures/db_definitions/mysql.sql +0 -96
  136. data/test/fixtures/db_definitions/mysql2.sql +0 -4
  137. data/test/fixtures/db_definitions/postgresql.sql +0 -113
  138. data/test/fixtures/db_definitions/postgresql2.sql +0 -4
  139. data/test/fixtures/db_definitions/sqlite.sql +0 -85
  140. data/test/fixtures/db_definitions/sqlite2.sql +0 -4
  141. data/test/fixtures/default.rb +0 -2
  142. data/test/fixtures/developer.rb +0 -8
  143. data/test/fixtures/developers/david +0 -2
  144. data/test/fixtures/developers/jamis +0 -2
  145. data/test/fixtures/developers_projects/david_action_controller +0 -2
  146. data/test/fixtures/developers_projects/david_active_record +0 -2
  147. data/test/fixtures/developers_projects/jamis_active_record +0 -2
  148. data/test/fixtures/entrant.rb +0 -3
  149. data/test/fixtures/entrants/first +0 -3
  150. data/test/fixtures/entrants/second +0 -3
  151. data/test/fixtures/entrants/third +0 -3
  152. data/test/fixtures/fixture_database.sqlite +0 -0
  153. data/test/fixtures/fixture_database_2.sqlite +0 -0
  154. data/test/fixtures/movie.rb +0 -5
  155. data/test/fixtures/movies/first +0 -2
  156. data/test/fixtures/movies/second +0 -2
  157. data/test/fixtures/project.rb +0 -3
  158. data/test/fixtures/projects/action_controller +0 -2
  159. data/test/fixtures/projects/active_record +0 -2
  160. data/test/fixtures/reply.rb +0 -21
  161. data/test/fixtures/subscriber.rb +0 -5
  162. data/test/fixtures/subscribers/first +0 -2
  163. data/test/fixtures/subscribers/second +0 -2
  164. data/test/fixtures/topic.rb +0 -20
  165. data/test/fixtures/topics/first +0 -9
  166. data/test/fixtures/topics/second +0 -8
  167. data/test/fixtures_test.rb +0 -20
  168. data/test/inflector_test.rb +0 -104
  169. data/test/inheritance_test.rb +0 -125
  170. data/test/lifecycle_test.rb +0 -110
  171. data/test/modules_test.rb +0 -21
  172. data/test/multiple_db_test.rb +0 -46
  173. data/test/pk_test.rb +0 -57
  174. data/test/reflection_test.rb +0 -78
  175. data/test/thread_safety_test.rb +0 -33
  176. data/test/transactions_test.rb +0 -83
  177. data/test/unconnected_test.rb +0 -24
  178. data/test/validations_test.rb +0 -126
@@ -0,0 +1,33 @@
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ module BeforeTypeCast
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ attribute_method_suffix "_before_type_cast"
8
+ end
9
+
10
+ def read_attribute_before_type_cast(attr_name)
11
+ @attributes[attr_name]
12
+ end
13
+
14
+ # Returns a hash of attributes before typecasting and deserialization.
15
+ def attributes_before_type_cast
16
+ self.attribute_names.inject({}) do |attrs, name|
17
+ attrs[name] = read_attribute_before_type_cast(name)
18
+ attrs
19
+ end
20
+ end
21
+
22
+ private
23
+ # Handle *_before_type_cast for method_missing.
24
+ def attribute_before_type_cast(attribute_name)
25
+ if attribute_name == 'id'
26
+ read_attribute_before_type_cast(self.class.primary_key)
27
+ else
28
+ read_attribute_before_type_cast(attribute_name)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,95 @@
1
+ require 'active_support/core_ext/object/blank'
2
+
3
+ module ActiveRecord
4
+ module AttributeMethods
5
+ module Dirty
6
+ extend ActiveSupport::Concern
7
+ include ActiveModel::Dirty
8
+ include AttributeMethods::Write
9
+
10
+ included do
11
+ if self < ::ActiveRecord::Timestamp
12
+ raise "You cannot include Dirty after Timestamp"
13
+ end
14
+
15
+ superclass_delegating_accessor :partial_updates
16
+ self.partial_updates = true
17
+ end
18
+
19
+ # Attempts to +save+ the record and clears changed attributes if successful.
20
+ def save(*) #:nodoc:
21
+ if status = super
22
+ @previously_changed = changes
23
+ @changed_attributes.clear
24
+ end
25
+ status
26
+ end
27
+
28
+ # Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
29
+ def save!(*) #:nodoc:
30
+ super.tap do
31
+ @previously_changed = changes
32
+ @changed_attributes.clear
33
+ end
34
+ end
35
+
36
+ # <tt>reload</tt> the record and clears changed attributes.
37
+ def reload(*) #:nodoc:
38
+ super.tap do
39
+ @previously_changed.clear
40
+ @changed_attributes.clear
41
+ end
42
+ end
43
+
44
+ private
45
+ # Wrap write_attribute to remember original attribute value.
46
+ def write_attribute(attr, value)
47
+ attr = attr.to_s
48
+
49
+ # The attribute already has an unsaved change.
50
+ if attribute_changed?(attr)
51
+ old = @changed_attributes[attr]
52
+ @changed_attributes.delete(attr) unless field_changed?(attr, old, value)
53
+ else
54
+ old = clone_attribute_value(:read_attribute, attr)
55
+ # Save Time objects as TimeWithZone if time_zone_aware_attributes == true
56
+ old = old.in_time_zone if clone_with_time_zone_conversion_attribute?(attr, old)
57
+ @changed_attributes[attr] = old if field_changed?(attr, old, value)
58
+ end
59
+
60
+ # Carry on.
61
+ super(attr, value)
62
+ end
63
+
64
+ def update(*)
65
+ if partial_updates?
66
+ # Serialized attributes should always be written in case they've been
67
+ # changed in place.
68
+ super(changed | (attributes.keys & self.class.serialized_attributes.keys))
69
+ else
70
+ super
71
+ end
72
+ end
73
+
74
+ def field_changed?(attr, old, value)
75
+ if column = column_for_attribute(attr)
76
+ if column.number? && column.null && (old.nil? || old == 0) && value.blank?
77
+ # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
78
+ # Hence we don't record it as a change if the value changes from nil to ''.
79
+ # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
80
+ # be typecast back to 0 (''.to_i => 0)
81
+ value = nil
82
+ else
83
+ value = column.type_cast(value)
84
+ end
85
+ end
86
+
87
+ old != value
88
+ end
89
+
90
+ def clone_with_time_zone_conversion_attribute?(attr, old)
91
+ old.class.name == "Time" && time_zone_aware_attributes && !skip_time_zone_conversion_for_attributes.include?(attr.to_sym)
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,50 @@
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ module PrimaryKey
4
+ extend ActiveSupport::Concern
5
+
6
+ # Returns this record's primary key value wrapped in an Array
7
+ # or nil if the record is a new_record?
8
+ def to_key
9
+ new_record? ? nil : [ id ]
10
+ end
11
+
12
+ module ClassMethods
13
+ # Defines the primary key field -- can be overridden in subclasses. Overwriting will negate any effect of the
14
+ # primary_key_prefix_type setting, though.
15
+ def primary_key
16
+ reset_primary_key
17
+ end
18
+
19
+ def reset_primary_key #:nodoc:
20
+ key = get_primary_key(base_class.name)
21
+ set_primary_key(key)
22
+ key
23
+ end
24
+
25
+ def get_primary_key(base_name) #:nodoc:
26
+ key = 'id'
27
+ case primary_key_prefix_type
28
+ when :table_name
29
+ key = base_name.to_s.foreign_key(false)
30
+ when :table_name_with_underscore
31
+ key = base_name.to_s.foreign_key
32
+ end
33
+ key
34
+ end
35
+
36
+ # Sets the name of the primary key column to use to the given value,
37
+ # or (if the value is nil or false) to the value returned by the given
38
+ # block.
39
+ #
40
+ # class Project < ActiveRecord::Base
41
+ # set_primary_key "sysid"
42
+ # end
43
+ def set_primary_key(value = nil, &block)
44
+ define_attr_method :primary_key, value, &block
45
+ end
46
+ alias :primary_key= :set_primary_key
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,39 @@
1
+ require 'active_support/core_ext/object/blank'
2
+
3
+ module ActiveRecord
4
+ module AttributeMethods
5
+ module Query
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ attribute_method_suffix "?"
10
+ end
11
+
12
+ def query_attribute(attr_name)
13
+ unless value = read_attribute(attr_name)
14
+ false
15
+ else
16
+ column = self.class.columns_hash[attr_name]
17
+ if column.nil?
18
+ if Numeric === value || value !~ /[^0-9]/
19
+ !value.to_i.zero?
20
+ else
21
+ return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value)
22
+ !value.blank?
23
+ end
24
+ elsif column.number?
25
+ !value.zero?
26
+ else
27
+ !value.blank?
28
+ end
29
+ end
30
+ end
31
+
32
+ private
33
+ # Handle *? for method_missing.
34
+ def attribute?(attribute_name)
35
+ query_attribute(attribute_name)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,116 @@
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ module Read
4
+ extend ActiveSupport::Concern
5
+
6
+ ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
7
+
8
+ included do
9
+ attribute_method_suffix ""
10
+
11
+ cattr_accessor :attribute_types_cached_by_default, :instance_writer => false
12
+ self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
13
+
14
+ # Undefine id so it can be used as an attribute name
15
+ undef_method(:id) if method_defined?(:id)
16
+ end
17
+
18
+ module ClassMethods
19
+ # +cache_attributes+ allows you to declare which converted attribute values should
20
+ # be cached. Usually caching only pays off for attributes with expensive conversion
21
+ # methods, like time related columns (e.g. +created_at+, +updated_at+).
22
+ def cache_attributes(*attribute_names)
23
+ attribute_names.each {|attr| cached_attributes << attr.to_s}
24
+ end
25
+
26
+ # Returns the attributes which are cached. By default time related columns
27
+ # with datatype <tt>:datetime, :timestamp, :time, :date</tt> are cached.
28
+ def cached_attributes
29
+ @cached_attributes ||=
30
+ columns.select{|c| attribute_types_cached_by_default.include?(c.type)}.map{|col| col.name}.to_set
31
+ end
32
+
33
+ # Returns +true+ if the provided attribute is being cached.
34
+ def cache_attribute?(attr_name)
35
+ cached_attributes.include?(attr_name)
36
+ end
37
+
38
+ protected
39
+ def define_method_attribute(attr_name)
40
+ if self.serialized_attributes[attr_name]
41
+ define_read_method_for_serialized_attribute(attr_name)
42
+ else
43
+ define_read_method(attr_name.to_sym, attr_name, columns_hash[attr_name])
44
+ end
45
+
46
+ if attr_name == primary_key && attr_name != "id"
47
+ define_read_method(:id, attr_name, columns_hash[attr_name])
48
+ end
49
+ end
50
+
51
+ private
52
+ # Define read method for serialized attribute.
53
+ def define_read_method_for_serialized_attribute(attr_name)
54
+ generated_attribute_methods.module_eval("def #{attr_name}; unserialize_attribute('#{attr_name}'); end", __FILE__, __LINE__)
55
+ end
56
+
57
+ # Define an attribute reader method. Cope with nil column.
58
+ def define_read_method(symbol, attr_name, column)
59
+ cast_code = column.type_cast_code('v') if column
60
+ access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']"
61
+
62
+ unless attr_name.to_s == self.primary_key.to_s
63
+ access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
64
+ end
65
+
66
+ if cache_attribute?(attr_name)
67
+ access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})"
68
+ end
69
+ generated_attribute_methods.module_eval("def #{symbol}; #{access_code}; end", __FILE__, __LINE__)
70
+ end
71
+ end
72
+
73
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
74
+ # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
75
+ def read_attribute(attr_name)
76
+ attr_name = attr_name.to_s
77
+ attr_name = self.class.primary_key if attr_name == 'id'
78
+ if !(value = @attributes[attr_name]).nil?
79
+ if column = column_for_attribute(attr_name)
80
+ if unserializable_attribute?(attr_name, column)
81
+ unserialize_attribute(attr_name)
82
+ else
83
+ column.type_cast(value)
84
+ end
85
+ else
86
+ value
87
+ end
88
+ else
89
+ nil
90
+ end
91
+ end
92
+
93
+ # Returns true if the attribute is of a text column and marked for serialization.
94
+ def unserializable_attribute?(attr_name, column)
95
+ column.text? && self.class.serialized_attributes[attr_name]
96
+ end
97
+
98
+ # Returns the unserialized object of the attribute.
99
+ def unserialize_attribute(attr_name)
100
+ unserialized_object = object_from_yaml(@attributes[attr_name])
101
+
102
+ if unserialized_object.is_a?(self.class.serialized_attributes[attr_name]) || unserialized_object.nil?
103
+ @attributes.frozen? ? unserialized_object : @attributes[attr_name] = unserialized_object
104
+ else
105
+ raise SerializationTypeMismatch,
106
+ "#{attr_name} was supposed to be a #{self.class.serialized_attributes[attr_name]}, but was a #{unserialized_object.class.to_s}"
107
+ end
108
+ end
109
+
110
+ private
111
+ def attribute(attribute_name)
112
+ read_attribute(attribute_name)
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,61 @@
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ module TimeZoneConversion
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ cattr_accessor :time_zone_aware_attributes, :instance_writer => false
8
+ self.time_zone_aware_attributes = false
9
+
10
+ class_inheritable_accessor :skip_time_zone_conversion_for_attributes, :instance_writer => false
11
+ self.skip_time_zone_conversion_for_attributes = []
12
+ end
13
+
14
+ module ClassMethods
15
+ protected
16
+ # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
17
+ # This enhanced read method automatically converts the UTC time stored in the database to the time
18
+ # zone stored in Time.zone.
19
+ def define_method_attribute(attr_name)
20
+ if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
21
+ method_body, line = <<-EOV, __LINE__ + 1
22
+ def #{attr_name}(reload = false)
23
+ cached = @attributes_cache['#{attr_name}']
24
+ return cached if cached && !reload
25
+ time = read_attribute('#{attr_name}')
26
+ @attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_time_zone : time
27
+ end
28
+ EOV
29
+ generated_attribute_methods.module_eval(method_body, __FILE__, line)
30
+ else
31
+ super
32
+ end
33
+ end
34
+
35
+ # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
36
+ # This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.
37
+ def define_method_attribute=(attr_name)
38
+ if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
39
+ method_body, line = <<-EOV, __LINE__ + 1
40
+ def #{attr_name}=(time)
41
+ unless time.acts_like?(:time)
42
+ time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
43
+ end
44
+ time = time.in_time_zone rescue nil if time
45
+ write_attribute(:#{attr_name}, time)
46
+ end
47
+ EOV
48
+ generated_attribute_methods.module_eval(method_body, __FILE__, line)
49
+ else
50
+ super
51
+ end
52
+ end
53
+
54
+ private
55
+ def create_time_zone_conversion_attribute?(name, column)
56
+ time_zone_aware_attributes && !skip_time_zone_conversion_for_attributes.include?(name.to_sym) && [:datetime, :timestamp].include?(column.type)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,37 @@
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ module Write
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ attribute_method_suffix "="
8
+ end
9
+
10
+ module ClassMethods
11
+ protected
12
+ def define_method_attribute=(attr_name)
13
+ generated_attribute_methods.module_eval("def #{attr_name}=(new_value); write_attribute('#{attr_name}', new_value); end", __FILE__, __LINE__)
14
+ end
15
+ end
16
+
17
+ # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings
18
+ # for fixnum and float columns are turned into +nil+.
19
+ def write_attribute(attr_name, value)
20
+ attr_name = attr_name.to_s
21
+ attr_name = self.class.primary_key if attr_name == 'id'
22
+ @attributes_cache.delete(attr_name)
23
+ if (column = column_for_attribute(attr_name)) && column.number?
24
+ @attributes[attr_name] = convert_number_column_value(value)
25
+ else
26
+ @attributes[attr_name] = value
27
+ end
28
+ end
29
+
30
+ private
31
+ # Handle *= for method_missing.
32
+ def attribute=(attribute_name, value)
33
+ write_attribute(attribute_name, value)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,60 @@
1
+ require 'active_support/core_ext/enumerable'
2
+
3
+ module ActiveRecord
4
+ # = Active Record Attribute Methods
5
+ module AttributeMethods #:nodoc:
6
+ extend ActiveSupport::Concern
7
+ include ActiveModel::AttributeMethods
8
+
9
+ module ClassMethods
10
+ # Generates all the attribute related methods for columns in the database
11
+ # accessors, mutators and query methods.
12
+ def define_attribute_methods
13
+ super(columns_hash.keys)
14
+ end
15
+
16
+ # Checks whether the method is defined in the model or any of its subclasses
17
+ # that also derive from Active Record. Raises DangerousAttributeError if the
18
+ # method is defined by Active Record though.
19
+ def instance_method_already_implemented?(method_name)
20
+ method_name = method_name.to_s
21
+ @_defined_class_methods ||= ancestors.first(ancestors.index(ActiveRecord::Base)).sum([]) { |m| m.public_instance_methods(false) | m.private_instance_methods(false) | m.protected_instance_methods(false) }.map {|m| m.to_s }.to_set
22
+ @@_defined_activerecord_methods ||= defined_activerecord_methods
23
+ raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" if @@_defined_activerecord_methods.include?(method_name)
24
+ @_defined_class_methods.include?(method_name)
25
+ end
26
+
27
+ def defined_activerecord_methods
28
+ active_record = ActiveRecord::Base
29
+ super_klass = ActiveRecord::Base.superclass
30
+ methods = active_record.public_instance_methods - super_klass.public_instance_methods
31
+ methods += active_record.private_instance_methods - super_klass.private_instance_methods
32
+ methods += active_record.protected_instance_methods - super_klass.protected_instance_methods
33
+ methods.map {|m| m.to_s }.to_set
34
+ end
35
+ end
36
+
37
+ def method_missing(method_id, *args, &block)
38
+ # If we haven't generated any methods yet, generate them, then
39
+ # see if we've created the method we're looking for.
40
+ if !self.class.attribute_methods_generated?
41
+ self.class.define_attribute_methods
42
+ method_name = method_id.to_s
43
+ guard_private_attribute_method!(method_name, args)
44
+ send(method_id, *args, &block)
45
+ else
46
+ super
47
+ end
48
+ end
49
+
50
+ def respond_to?(*args)
51
+ self.class.define_attribute_methods
52
+ super
53
+ end
54
+
55
+ protected
56
+ def attribute_method?(attr_name)
57
+ attr_name == 'id' || attributes.include?(attr_name)
58
+ end
59
+ end
60
+ end