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,40 @@
1
+ en:
2
+ # Attributes names common to most models
3
+ #attributes:
4
+ #created_at: "Created at"
5
+ #updated_at: "Updated at"
6
+
7
+ # Active Record models configuration
8
+ activerecord:
9
+ errors:
10
+ messages:
11
+ taken: "has already been taken"
12
+ record_invalid: "Validation failed: %{errors}"
13
+ # Append your own errors here or at the model/attributes scope.
14
+
15
+ # You can define own errors for models or model attributes.
16
+ # The values :model, :attribute and :value are always available for interpolation.
17
+ #
18
+ # For example,
19
+ # models:
20
+ # user:
21
+ # blank: "This is a custom blank message for %{model}: %{attribute}"
22
+ # attributes:
23
+ # login:
24
+ # blank: "This is a custom blank message for User login"
25
+ # Will define custom blank validation message for User model and
26
+ # custom blank validation message for login attribute of User model.
27
+ #models:
28
+
29
+ # Translate model names. Used in Model.human_name().
30
+ #models:
31
+ # For example,
32
+ # user: "Dude"
33
+ # will translate User model name to "Dude"
34
+
35
+ # Translate model attribute names. Used in Model.human_attribute_name(attribute).
36
+ #attributes:
37
+ # For example,
38
+ # user:
39
+ # login: "Handle"
40
+ # will translate User attribute "login" as "Handle"
@@ -0,0 +1,172 @@
1
+ module ActiveRecord
2
+ module Locking
3
+ # == What is Optimistic Locking
4
+ #
5
+ # Optimistic locking allows multiple users to access the same record for edits, and assumes a minimum of
6
+ # conflicts with the data. It does this by checking whether another process has made changes to a record since
7
+ # it was opened, an ActiveRecord::StaleObjectError is thrown if that has occurred and the update is ignored.
8
+ #
9
+ # Check out ActiveRecord::Locking::Pessimistic for an alternative.
10
+ #
11
+ # == Usage
12
+ #
13
+ # Active Records support optimistic locking if the field <tt>lock_version</tt> is present. Each update to the
14
+ # record increments the lock_version column and the locking facilities ensure that records instantiated twice
15
+ # will let the last one saved raise a StaleObjectError if the first was also updated. Example:
16
+ #
17
+ # p1 = Person.find(1)
18
+ # p2 = Person.find(1)
19
+ #
20
+ # p1.first_name = "Michael"
21
+ # p1.save
22
+ #
23
+ # p2.first_name = "should fail"
24
+ # p2.save # Raises a ActiveRecord::StaleObjectError
25
+ #
26
+ # Optimistic locking will also check for stale data when objects are destroyed. Example:
27
+ #
28
+ # p1 = Person.find(1)
29
+ # p2 = Person.find(1)
30
+ #
31
+ # p1.first_name = "Michael"
32
+ # p1.save
33
+ #
34
+ # p2.destroy # Raises a ActiveRecord::StaleObjectError
35
+ #
36
+ # You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
37
+ # or otherwise apply the business logic needed to resolve the conflict.
38
+ #
39
+ # You must ensure that your database schema defaults the lock_version column to 0.
40
+ #
41
+ # This behavior can be turned off by setting <tt>ActiveRecord::Base.lock_optimistically = false</tt>.
42
+ # To override the name of the lock_version column, invoke the <tt>set_locking_column</tt> method.
43
+ # This method uses the same syntax as <tt>set_table_name</tt>
44
+ module Optimistic
45
+ extend ActiveSupport::Concern
46
+
47
+ included do
48
+ cattr_accessor :lock_optimistically, :instance_writer => false
49
+ self.lock_optimistically = true
50
+
51
+ class << self
52
+ alias_method :locking_column=, :set_locking_column
53
+ end
54
+ end
55
+
56
+ def locking_enabled? #:nodoc:
57
+ self.class.locking_enabled?
58
+ end
59
+
60
+ private
61
+ def attributes_from_column_definition
62
+ result = super
63
+
64
+ # If the locking column has no default value set,
65
+ # start the lock version at zero. Note we can't use
66
+ # locking_enabled? at this point as @attributes may
67
+ # not have been initialized yet
68
+
69
+ if lock_optimistically && result.include?(self.class.locking_column)
70
+ result[self.class.locking_column] ||= 0
71
+ end
72
+
73
+ return result
74
+ end
75
+
76
+ def update(attribute_names = @attributes.keys) #:nodoc:
77
+ return super unless locking_enabled?
78
+ return 0 if attribute_names.empty?
79
+
80
+ lock_col = self.class.locking_column
81
+ previous_value = send(lock_col).to_i
82
+ send(lock_col + '=', previous_value + 1)
83
+
84
+ attribute_names += [lock_col]
85
+ attribute_names.uniq!
86
+
87
+ begin
88
+ relation = self.class.unscoped
89
+
90
+ affected_rows = relation.where(
91
+ relation.table[self.class.primary_key].eq(quoted_id).and(
92
+ relation.table[self.class.locking_column].eq(quote_value(previous_value))
93
+ )
94
+ ).arel.update(arel_attributes_values(false, false, attribute_names))
95
+
96
+ unless affected_rows == 1
97
+ raise ActiveRecord::StaleObjectError, "Attempted to update a stale object: #{self.class.name}"
98
+ end
99
+
100
+ affected_rows
101
+
102
+ # If something went wrong, revert the version.
103
+ rescue Exception
104
+ send(lock_col + '=', previous_value)
105
+ raise
106
+ end
107
+ end
108
+
109
+ def destroy #:nodoc:
110
+ return super unless locking_enabled?
111
+
112
+ unless new_record?
113
+ lock_col = self.class.locking_column
114
+ previous_value = send(lock_col).to_i
115
+
116
+ table = self.class.arel_table
117
+ predicate = table[self.class.primary_key].eq(id)
118
+ predicate = predicate.and(table[self.class.locking_column].eq(previous_value))
119
+
120
+ affected_rows = self.class.unscoped.where(predicate).delete_all
121
+
122
+ unless affected_rows == 1
123
+ raise ActiveRecord::StaleObjectError, "Attempted to delete a stale object: #{self.class.name}"
124
+ end
125
+ end
126
+
127
+ @destroyed = true
128
+ freeze
129
+ end
130
+
131
+ module ClassMethods
132
+ DEFAULT_LOCKING_COLUMN = 'lock_version'
133
+
134
+ # Is optimistic locking enabled for this table? Returns true if the
135
+ # +lock_optimistically+ flag is set to true (which it is, by default)
136
+ # and the table includes the +locking_column+ column (defaults to
137
+ # +lock_version+).
138
+ def locking_enabled?
139
+ lock_optimistically && columns_hash[locking_column]
140
+ end
141
+
142
+ # Set the column to use for optimistic locking. Defaults to +lock_version+.
143
+ def set_locking_column(value = nil, &block)
144
+ define_attr_method :locking_column, value, &block
145
+ value
146
+ end
147
+
148
+ # The version column used for optimistic locking. Defaults to +lock_version+.
149
+ def locking_column
150
+ reset_locking_column
151
+ end
152
+
153
+ # Quote the column name used for optimistic locking.
154
+ def quoted_locking_column
155
+ connection.quote_column_name(locking_column)
156
+ end
157
+
158
+ # Reset the column used for optimistic locking back to the +lock_version+ default.
159
+ def reset_locking_column
160
+ set_locking_column DEFAULT_LOCKING_COLUMN
161
+ end
162
+
163
+ # Make sure the lock version column gets updated when counters are
164
+ # updated.
165
+ def update_counters(id, counters)
166
+ counters = counters.merge(locking_column => 1) if locking_enabled?
167
+ super
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,55 @@
1
+ module ActiveRecord
2
+ module Locking
3
+ # Locking::Pessimistic provides support for row-level locking using
4
+ # SELECT ... FOR UPDATE and other lock types.
5
+ #
6
+ # Pass <tt>:lock => true</tt> to ActiveRecord::Base.find to obtain an exclusive
7
+ # lock on the selected rows:
8
+ # # select * from accounts where id=1 for update
9
+ # Account.find(1, :lock => true)
10
+ #
11
+ # Pass <tt>:lock => 'some locking clause'</tt> to give a database-specific locking clause
12
+ # of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'.
13
+ #
14
+ # Example:
15
+ # Account.transaction do
16
+ # # select * from accounts where name = 'shugo' limit 1 for update
17
+ # shugo = Account.find(:first, :conditions => "name = 'shugo'", :lock => true)
18
+ # yuko = Account.find(:first, :conditions => "name = 'yuko'", :lock => true)
19
+ # shugo.balance -= 100
20
+ # shugo.save!
21
+ # yuko.balance += 100
22
+ # yuko.save!
23
+ # end
24
+ #
25
+ # You can also use ActiveRecord::Base#lock! method to lock one record by id.
26
+ # This may be better if you don't need to lock every row. Example:
27
+ # Account.transaction do
28
+ # # select * from accounts where ...
29
+ # accounts = Account.find(:all, :conditions => ...)
30
+ # account1 = accounts.detect { |account| ... }
31
+ # account2 = accounts.detect { |account| ... }
32
+ # # select * from accounts where id=? for update
33
+ # account1.lock!
34
+ # account2.lock!
35
+ # account1.balance -= 100
36
+ # account1.save!
37
+ # account2.balance += 100
38
+ # account2.save!
39
+ # end
40
+ #
41
+ # Database-specific information on row locking:
42
+ # MySQL: http://dev.mysql.com/doc/refman/5.1/en/innodb-locking-reads.html
43
+ # PostgreSQL: http://www.postgresql.org/docs/8.1/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
44
+ module Pessimistic
45
+ # Obtain a row lock on this record. Reloads the record to obtain the requested
46
+ # lock. Pass an SQL locking clause to append the end of the SELECT statement
47
+ # or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns
48
+ # the locked record.
49
+ def lock!(lock = true)
50
+ reload(:lock => lock) unless new_record?
51
+ self
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,48 @@
1
+ module ActiveRecord
2
+ class LogSubscriber < ActiveSupport::LogSubscriber
3
+ def self.runtime=(value)
4
+ Thread.current["active_record_sql_runtime"] = value
5
+ end
6
+
7
+ def self.runtime
8
+ Thread.current["active_record_sql_runtime"] ||= 0
9
+ end
10
+
11
+ def self.reset_runtime
12
+ rt, self.runtime = runtime, 0
13
+ rt
14
+ end
15
+
16
+ def initialize
17
+ super
18
+ @odd_or_even = false
19
+ end
20
+
21
+ def sql(event)
22
+ self.class.runtime += event.duration
23
+ return unless logger.debug?
24
+
25
+ name = '%s (%.1fms)' % [event.payload[:name], event.duration]
26
+ sql = event.payload[:sql].squeeze(' ')
27
+
28
+ if odd?
29
+ name = color(name, CYAN, true)
30
+ sql = color(sql, nil, true)
31
+ else
32
+ name = color(name, MAGENTA, true)
33
+ end
34
+
35
+ debug " #{name} #{sql}"
36
+ end
37
+
38
+ def odd?
39
+ @odd_or_even = !@odd_or_even
40
+ end
41
+
42
+ def logger
43
+ ActiveRecord::Base.logger
44
+ end
45
+ end
46
+ end
47
+
48
+ ActiveRecord::LogSubscriber.attach_to :active_record