activerecord 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 (93) hide show
  1. data/CHANGELOG +6023 -0
  2. data/README.rdoc +222 -0
  3. data/examples/associations.png +0 -0
  4. data/examples/performance.rb +162 -0
  5. data/examples/simple.rb +14 -0
  6. data/lib/active_record.rb +124 -0
  7. data/lib/active_record/aggregations.rb +277 -0
  8. data/lib/active_record/association_preload.rb +403 -0
  9. data/lib/active_record/associations.rb +2254 -0
  10. data/lib/active_record/associations/association_collection.rb +562 -0
  11. data/lib/active_record/associations/association_proxy.rb +295 -0
  12. data/lib/active_record/associations/belongs_to_association.rb +91 -0
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +78 -0
  14. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +137 -0
  15. data/lib/active_record/associations/has_many_association.rb +128 -0
  16. data/lib/active_record/associations/has_many_through_association.rb +116 -0
  17. data/lib/active_record/associations/has_one_association.rb +143 -0
  18. data/lib/active_record/associations/has_one_through_association.rb +40 -0
  19. data/lib/active_record/associations/through_association_scope.rb +154 -0
  20. data/lib/active_record/attribute_methods.rb +60 -0
  21. data/lib/active_record/attribute_methods/before_type_cast.rb +33 -0
  22. data/lib/active_record/attribute_methods/dirty.rb +95 -0
  23. data/lib/active_record/attribute_methods/primary_key.rb +50 -0
  24. data/lib/active_record/attribute_methods/query.rb +39 -0
  25. data/lib/active_record/attribute_methods/read.rb +116 -0
  26. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -0
  27. data/lib/active_record/attribute_methods/write.rb +37 -0
  28. data/lib/active_record/autosave_association.rb +369 -0
  29. data/lib/active_record/base.rb +1867 -0
  30. data/lib/active_record/callbacks.rb +288 -0
  31. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +365 -0
  32. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +113 -0
  33. data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
  34. data/lib/active_record/connection_adapters/abstract/database_statements.rb +329 -0
  35. data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
  36. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -0
  37. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +739 -0
  38. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +543 -0
  39. data/lib/active_record/connection_adapters/abstract_adapter.rb +212 -0
  40. data/lib/active_record/connection_adapters/mysql_adapter.rb +643 -0
  41. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1030 -0
  42. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -0
  43. data/lib/active_record/connection_adapters/sqlite_adapter.rb +401 -0
  44. data/lib/active_record/counter_cache.rb +115 -0
  45. data/lib/active_record/dynamic_finder_match.rb +53 -0
  46. data/lib/active_record/dynamic_scope_match.rb +32 -0
  47. data/lib/active_record/errors.rb +172 -0
  48. data/lib/active_record/fixtures.rb +1008 -0
  49. data/lib/active_record/locale/en.yml +40 -0
  50. data/lib/active_record/locking/optimistic.rb +172 -0
  51. data/lib/active_record/locking/pessimistic.rb +55 -0
  52. data/lib/active_record/log_subscriber.rb +48 -0
  53. data/lib/active_record/migration.rb +617 -0
  54. data/lib/active_record/named_scope.rb +138 -0
  55. data/lib/active_record/nested_attributes.rb +417 -0
  56. data/lib/active_record/observer.rb +140 -0
  57. data/lib/active_record/persistence.rb +291 -0
  58. data/lib/active_record/query_cache.rb +36 -0
  59. data/lib/active_record/railtie.rb +91 -0
  60. data/lib/active_record/railties/controller_runtime.rb +38 -0
  61. data/lib/active_record/railties/databases.rake +512 -0
  62. data/lib/active_record/reflection.rb +403 -0
  63. data/lib/active_record/relation.rb +393 -0
  64. data/lib/active_record/relation/batches.rb +89 -0
  65. data/lib/active_record/relation/calculations.rb +286 -0
  66. data/lib/active_record/relation/finder_methods.rb +355 -0
  67. data/lib/active_record/relation/predicate_builder.rb +41 -0
  68. data/lib/active_record/relation/query_methods.rb +261 -0
  69. data/lib/active_record/relation/spawn_methods.rb +112 -0
  70. data/lib/active_record/schema.rb +59 -0
  71. data/lib/active_record/schema_dumper.rb +195 -0
  72. data/lib/active_record/serialization.rb +60 -0
  73. data/lib/active_record/serializers/xml_serializer.rb +244 -0
  74. data/lib/active_record/session_store.rb +340 -0
  75. data/lib/active_record/test_case.rb +67 -0
  76. data/lib/active_record/timestamp.rb +88 -0
  77. data/lib/active_record/transactions.rb +356 -0
  78. data/lib/active_record/validations.rb +84 -0
  79. data/lib/active_record/validations/associated.rb +48 -0
  80. data/lib/active_record/validations/uniqueness.rb +185 -0
  81. data/lib/active_record/version.rb +9 -0
  82. data/lib/rails/generators/active_record.rb +27 -0
  83. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  84. data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
  85. data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
  86. data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
  87. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  88. data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
  89. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  90. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  91. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
  92. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
  93. metadata +224 -0
@@ -0,0 +1,40 @@
1
+ require "active_record/associations/through_association_scope"
2
+
3
+ module ActiveRecord
4
+ # = Active Record Has One Through Association
5
+ module Associations
6
+ class HasOneThroughAssociation < HasOneAssociation
7
+ include ThroughAssociationScope
8
+
9
+ def replace(new_value)
10
+ create_through_record(new_value)
11
+ @target = new_value
12
+ end
13
+
14
+ private
15
+
16
+ def create_through_record(new_value) #nodoc:
17
+ klass = @reflection.through_reflection.klass
18
+
19
+ current_object = @owner.send(@reflection.through_reflection.name)
20
+
21
+ if current_object
22
+ new_value ? current_object.update_attributes(construct_join_attributes(new_value)) : current_object.destroy
23
+ elsif new_value
24
+ if @owner.new_record?
25
+ self.target = new_value
26
+ through_association = @owner.send(:association_instance_get, @reflection.through_reflection.name)
27
+ through_association.build(construct_join_attributes(new_value))
28
+ else
29
+ @owner.send(@reflection.through_reflection.name, klass.create(construct_join_attributes(new_value)))
30
+ end
31
+ end
32
+ end
33
+
34
+ private
35
+ def find_target
36
+ with_scope(construct_scope) { @reflection.klass.find(:first) }
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,154 @@
1
+ module ActiveRecord
2
+ # = Active Record Through Association Scope
3
+ module Associations
4
+ module ThroughAssociationScope
5
+
6
+ protected
7
+
8
+ def construct_scope
9
+ { :create => construct_owner_attributes(@reflection),
10
+ :find => { :conditions => construct_conditions,
11
+ :joins => construct_joins,
12
+ :include => @reflection.options[:include] || @reflection.source_reflection.options[:include],
13
+ :select => construct_select,
14
+ :order => @reflection.options[:order],
15
+ :limit => @reflection.options[:limit],
16
+ :readonly => @reflection.options[:readonly],
17
+ } }
18
+ end
19
+
20
+ # Build SQL conditions from attributes, qualified by table name.
21
+ def construct_conditions
22
+ table_name = @reflection.through_reflection.quoted_table_name
23
+ conditions = construct_quoted_owner_attributes(@reflection.through_reflection).map do |attr, value|
24
+ "#{table_name}.#{attr} = #{value}"
25
+ end
26
+ conditions << sql_conditions if sql_conditions
27
+ "(" + conditions.join(') AND (') + ")"
28
+ end
29
+
30
+ # Associate attributes pointing to owner, quoted.
31
+ def construct_quoted_owner_attributes(reflection)
32
+ if as = reflection.options[:as]
33
+ { "#{as}_id" => owner_quoted_id,
34
+ "#{as}_type" => reflection.klass.quote_value(
35
+ @owner.class.base_class.name.to_s,
36
+ reflection.klass.columns_hash["#{as}_type"]) }
37
+ elsif reflection.macro == :belongs_to
38
+ { reflection.klass.primary_key => @owner.class.quote_value(@owner[reflection.primary_key_name]) }
39
+ else
40
+ { reflection.primary_key_name => owner_quoted_id }
41
+ end
42
+ end
43
+
44
+ def construct_from
45
+ @reflection.table_name
46
+ end
47
+
48
+ def construct_select(custom_select = nil)
49
+ distinct = "DISTINCT " if @reflection.options[:uniq]
50
+ selected = custom_select || @reflection.options[:select] || "#{distinct}#{@reflection.quoted_table_name}.*"
51
+ end
52
+
53
+ def construct_joins(custom_joins = nil)
54
+ polymorphic_join = nil
55
+ if @reflection.source_reflection.macro == :belongs_to
56
+ reflection_primary_key = @reflection.klass.primary_key
57
+ source_primary_key = @reflection.source_reflection.primary_key_name
58
+ if @reflection.options[:source_type]
59
+ polymorphic_join = "AND %s.%s = %s" % [
60
+ @reflection.through_reflection.quoted_table_name, "#{@reflection.source_reflection.options[:foreign_type]}",
61
+ @owner.class.quote_value(@reflection.options[:source_type])
62
+ ]
63
+ end
64
+ else
65
+ reflection_primary_key = @reflection.source_reflection.primary_key_name
66
+ source_primary_key = @reflection.through_reflection.klass.primary_key
67
+ if @reflection.source_reflection.options[:as]
68
+ polymorphic_join = "AND %s.%s = %s" % [
69
+ @reflection.quoted_table_name, "#{@reflection.source_reflection.options[:as]}_type",
70
+ @owner.class.quote_value(@reflection.through_reflection.klass.name)
71
+ ]
72
+ end
73
+ end
74
+
75
+ "INNER JOIN %s ON %s.%s = %s.%s %s #{@reflection.options[:joins]} #{custom_joins}" % [
76
+ @reflection.through_reflection.quoted_table_name,
77
+ @reflection.quoted_table_name, reflection_primary_key,
78
+ @reflection.through_reflection.quoted_table_name, source_primary_key,
79
+ polymorphic_join
80
+ ]
81
+ end
82
+
83
+ # Construct attributes for associate pointing to owner.
84
+ def construct_owner_attributes(reflection)
85
+ if as = reflection.options[:as]
86
+ { "#{as}_id" => @owner.id,
87
+ "#{as}_type" => @owner.class.base_class.name.to_s }
88
+ else
89
+ { reflection.primary_key_name => @owner.id }
90
+ end
91
+ end
92
+
93
+ # Construct attributes for :through pointing to owner and associate.
94
+ def construct_join_attributes(associate)
95
+ # TODO: revisit this to allow it for deletion, supposing dependent option is supported
96
+ raise ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(@owner, @reflection) if [:has_one, :has_many].include?(@reflection.source_reflection.macro)
97
+
98
+ join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
99
+
100
+ if @reflection.options[:source_type]
101
+ join_attributes.merge!(@reflection.source_reflection.options[:foreign_type] => associate.class.base_class.name.to_s)
102
+ end
103
+
104
+ if @reflection.through_reflection.options[:conditions].is_a?(Hash)
105
+ join_attributes.merge!(@reflection.through_reflection.options[:conditions])
106
+ end
107
+
108
+ join_attributes
109
+ end
110
+
111
+ def conditions
112
+ @conditions = build_conditions unless defined?(@conditions)
113
+ @conditions
114
+ end
115
+
116
+ def build_conditions
117
+ association_conditions = @reflection.options[:conditions]
118
+ through_conditions = build_through_conditions
119
+ source_conditions = @reflection.source_reflection.options[:conditions]
120
+ uses_sti = !@reflection.through_reflection.klass.descends_from_active_record?
121
+
122
+ if association_conditions || through_conditions || source_conditions || uses_sti
123
+ all = []
124
+
125
+ [association_conditions, source_conditions].each do |conditions|
126
+ all << interpolate_sql(sanitize_sql(conditions)) if conditions
127
+ end
128
+
129
+ all << through_conditions if through_conditions
130
+ all << build_sti_condition if uses_sti
131
+
132
+ all.map { |sql| "(#{sql})" } * ' AND '
133
+ end
134
+ end
135
+
136
+ def build_through_conditions
137
+ conditions = @reflection.through_reflection.options[:conditions]
138
+ if conditions.is_a?(Hash)
139
+ interpolate_sql(@reflection.through_reflection.klass.send(:sanitize_sql, conditions)).gsub(
140
+ @reflection.quoted_table_name,
141
+ @reflection.through_reflection.quoted_table_name)
142
+ elsif conditions
143
+ interpolate_sql(sanitize_sql(conditions))
144
+ end
145
+ end
146
+
147
+ def build_sti_condition
148
+ @reflection.through_reflection.klass.send(:type_condition).to_sql
149
+ end
150
+
151
+ alias_method :sql_conditions, :conditions
152
+ end
153
+ end
154
+ 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
@@ -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