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,185 @@
1
+ require 'active_support/core_ext/array/wrap'
2
+
3
+ module ActiveRecord
4
+ module Validations
5
+ class UniquenessValidator < ActiveModel::EachValidator
6
+ def initialize(options)
7
+ super(options.reverse_merge(:case_sensitive => true))
8
+ end
9
+
10
+ # Unfortunately, we have to tie Uniqueness validators to a class.
11
+ def setup(klass)
12
+ @klass = klass
13
+ end
14
+
15
+ def validate_each(record, attribute, value)
16
+ finder_class = find_finder_class_for(record)
17
+ table = finder_class.unscoped
18
+
19
+ table_name = record.class.quoted_table_name
20
+ sql, params = mount_sql_and_params(finder_class, table_name, attribute, value)
21
+
22
+ relation = table.where(sql, *params)
23
+
24
+ Array.wrap(options[:scope]).each do |scope_item|
25
+ scope_value = record.send(scope_item)
26
+ relation = relation.where(scope_item => scope_value)
27
+ end
28
+
29
+ unless record.new_record?
30
+ # TODO : This should be in Arel
31
+ relation = relation.where("#{record.class.quoted_table_name}.#{record.class.primary_key} <> ?", record.send(:id))
32
+ end
33
+
34
+ if relation.exists?
35
+ record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
36
+ end
37
+ end
38
+
39
+ protected
40
+
41
+ # The check for an existing value should be run from a class that
42
+ # isn't abstract. This means working down from the current class
43
+ # (self), to the first non-abstract class. Since classes don't know
44
+ # their subclasses, we have to build the hierarchy between self and
45
+ # the record's class.
46
+ def find_finder_class_for(record) #:nodoc:
47
+ class_hierarchy = [record.class]
48
+
49
+ while class_hierarchy.first != @klass
50
+ class_hierarchy.insert(0, class_hierarchy.first.superclass)
51
+ end
52
+
53
+ class_hierarchy.detect { |klass| !klass.abstract_class? }
54
+ end
55
+
56
+ def mount_sql_and_params(klass, table_name, attribute, value) #:nodoc:
57
+ column = klass.columns_hash[attribute.to_s]
58
+
59
+ operator = if value.nil?
60
+ "IS ?"
61
+ elsif column.text?
62
+ value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s
63
+ "#{klass.connection.case_sensitive_equality_operator} ?"
64
+ else
65
+ "= ?"
66
+ end
67
+
68
+ sql_attribute = "#{table_name}.#{klass.connection.quote_column_name(attribute)}"
69
+
70
+ if value.nil? || (options[:case_sensitive] || !column.text?)
71
+ sql = "#{sql_attribute} #{operator}"
72
+ else
73
+ sql = "LOWER(#{sql_attribute}) = LOWER(?)"
74
+ end
75
+
76
+ [sql, [value]]
77
+ end
78
+ end
79
+
80
+ module ClassMethods
81
+ # Validates whether the value of the specified attributes are unique across the system.
82
+ # Useful for making sure that only one user
83
+ # can be named "davidhh".
84
+ #
85
+ # class Person < ActiveRecord::Base
86
+ # validates_uniqueness_of :user_name, :scope => :account_id
87
+ # end
88
+ #
89
+ # It can also validate whether the value of the specified attributes are unique based on multiple
90
+ # scope parameters. For example, making sure that a teacher can only be on the schedule once
91
+ # per semester for a particular class.
92
+ #
93
+ # class TeacherSchedule < ActiveRecord::Base
94
+ # validates_uniqueness_of :teacher_id, :scope => [:semester_id, :class_id]
95
+ # end
96
+ #
97
+ # When the record is created, a check is performed to make sure that no record exists in the database
98
+ # with the given value for the specified attribute (that maps to a column). When the record is updated,
99
+ # the same check is made but disregarding the record itself.
100
+ #
101
+ # Configuration options:
102
+ # * <tt>:message</tt> - Specifies a custom error message (default is: "has already been taken").
103
+ # * <tt>:scope</tt> - One or more columns by which to limit the scope of the uniqueness constraint.
104
+ # * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by non-text columns (+true+ by default).
105
+ # * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
106
+ # * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
107
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
108
+ # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>).
109
+ # The method, proc or string should return or evaluate to a true or false value.
110
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
111
+ # not occur (e.g. <tt>:unless => :skip_validation</tt>, or
112
+ # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The method, proc or string should
113
+ # return or evaluate to a true or false value.
114
+ #
115
+ # === Concurrency and integrity
116
+ #
117
+ # Using this validation method in conjunction with ActiveRecord::Base#save
118
+ # does not guarantee the absence of duplicate record insertions, because
119
+ # uniqueness checks on the application level are inherently prone to race
120
+ # conditions. For example, suppose that two users try to post a Comment at
121
+ # the same time, and a Comment's title must be unique. At the database-level,
122
+ # the actions performed by these users could be interleaved in the following manner:
123
+ #
124
+ # User 1 | User 2
125
+ # ------------------------------------+--------------------------------------
126
+ # # User 1 checks whether there's |
127
+ # # already a comment with the title |
128
+ # # 'My Post'. This is not the case. |
129
+ # SELECT * FROM comments |
130
+ # WHERE title = 'My Post' |
131
+ # |
132
+ # | # User 2 does the same thing and also
133
+ # | # infers that his title is unique.
134
+ # | SELECT * FROM comments
135
+ # | WHERE title = 'My Post'
136
+ # |
137
+ # # User 1 inserts his comment. |
138
+ # INSERT INTO comments |
139
+ # (title, content) VALUES |
140
+ # ('My Post', 'hi!') |
141
+ # |
142
+ # | # User 2 does the same thing.
143
+ # | INSERT INTO comments
144
+ # | (title, content) VALUES
145
+ # | ('My Post', 'hello!')
146
+ # |
147
+ # | # ^^^^^^
148
+ # | # Boom! We now have a duplicate
149
+ # | # title!
150
+ #
151
+ # This could even happen if you use transactions with the 'serializable'
152
+ # isolation level. There are several ways to get around this problem:
153
+ #
154
+ # - By locking the database table before validating, and unlocking it after
155
+ # saving. However, table locking is very expensive, and thus not
156
+ # recommended.
157
+ # - By locking a lock file before validating, and unlocking it after saving.
158
+ # This does not work if you've scaled your Rails application across
159
+ # multiple web servers (because they cannot share lock files, or cannot
160
+ # do that efficiently), and thus not recommended.
161
+ # - Creating a unique index on the field, by using
162
+ # ActiveRecord::ConnectionAdapters::SchemaStatements#add_index. In the
163
+ # rare case that a race condition occurs, the database will guarantee
164
+ # the field's uniqueness.
165
+ #
166
+ # When the database catches such a duplicate insertion,
167
+ # ActiveRecord::Base#save will raise an ActiveRecord::StatementInvalid
168
+ # exception. You can either choose to let this error propagate (which
169
+ # will result in the default Rails exception page being shown), or you
170
+ # can catch it and restart the transaction (e.g. by telling the user
171
+ # that the title already exists, and asking him to re-enter the title).
172
+ # This technique is also known as optimistic concurrency control:
173
+ # http://en.wikipedia.org/wiki/Optimistic_concurrency_control
174
+ #
175
+ # Active Record currently provides no way to distinguish unique
176
+ # index constraint errors from other types of database errors, so you
177
+ # will have to parse the (database-specific) exception message to detect
178
+ # such a case.
179
+ #
180
+ def validates_uniqueness_of(*attr_names)
181
+ validates_with UniquenessValidator, _merge_attributes(attr_names)
182
+ end
183
+ end
184
+ end
185
+ end
@@ -1,205 +1,84 @@
1
1
  module ActiveRecord
2
- # Active Records implement validation by overwriting Base#validate (or the variations, +validate_on_create+ and
3
- # +validate_on_update+). Each of these methods can inspect the state of the object, which usually means ensuring
4
- # that a number of attributes have a certain value (such as not empty, within a given range, matching a certain regular expression).
2
+ # = Active Record Validations
5
3
  #
6
- # Example:
4
+ # Raised by <tt>save!</tt> and <tt>create!</tt> when the record is invalid. Use the
5
+ # +record+ method to retrieve the record which did not validate.
7
6
  #
8
- # class Person < ActiveRecord::Base
9
- # protected
10
- # def validate
11
- # errors.add_on_empty %w( first_name last_name )
12
- # errors.add("phone_number", "has invalid format") unless phone_number =~ /[0-9]*/
13
- # end
14
- #
15
- # def validate_on_create # is only run the first time a new object is saved
16
- # unless valid_discount?(membership_discount)
17
- # errors.add("membership_discount", "has expired")
18
- # end
19
- # end
20
- #
21
- # def validate_on_update
22
- # errors.add_to_base("No changes have occured") if unchanged_attributes?
23
- # end
7
+ # begin
8
+ # complex_operation_that_calls_save!_internally
9
+ # rescue ActiveRecord::RecordInvalid => invalid
10
+ # puts invalid.record.errors
24
11
  # end
25
- #
26
- # person = Person.new("first_name" => "David", "phone_number" => "what?")
27
- # person.save # => false (and doesn't do the save)
28
- # person.errors.empty? # => false
29
- # person.count # => 2
30
- # person.errors.on "last_name" # => "can't be empty"
31
- # person.errors.on "phone_number" # => "has invalid format"
32
- # person.each_full { |msg| puts msg } # => "Last name can't be empty\n" +
33
- # "Phone number has invalid format"
34
- #
35
- # person.attributes = { "last_name" => "Heinemeier", "phone_number" => "555-555" }
36
- # person.save # => true (and person is now saved in the database)
37
- #
38
- # An +Errors+ object is automatically created for every Active Record.
39
- module Validations
40
- def self.append_features(base) # :nodoc:
41
- super
42
-
43
- base.class_eval do
44
- alias_method :save_without_validation, :save
45
- alias_method :save, :save_with_validation
12
+ class RecordInvalid < ActiveRecordError
13
+ attr_reader :record
14
+ def initialize(record)
15
+ @record = record
16
+ errors = @record.errors.full_messages.join(", ")
17
+ super(I18n.t("activerecord.errors.messages.record_invalid", :errors => errors))
18
+ end
19
+ end
46
20
 
47
- alias_method :update_attribute_without_validation_skipping, :update_attribute
48
- alias_method :update_attribute, :update_attribute_with_validation_skipping
21
+ module Validations
22
+ extend ActiveSupport::Concern
23
+ include ActiveModel::Validations
24
+
25
+ module ClassMethods
26
+ # Creates an object just like Base.create but calls save! instead of save
27
+ # so an exception is raised if the record is invalid.
28
+ def create!(attributes = nil, &block)
29
+ if attributes.is_a?(Array)
30
+ attributes.collect { |attr| create!(attr, &block) }
31
+ else
32
+ object = new(attributes)
33
+ yield(object) if block_given?
34
+ object.save!
35
+ object
36
+ end
49
37
  end
50
38
  end
51
39
 
52
40
  # The validation process on save can be skipped by passing false. The regular Base#save method is
53
41
  # replaced with this when the validations module is mixed in, which it is by default.
54
- def save_with_validation(perform_validation = true)
55
- if perform_validation && valid? || !perform_validation then save_without_validation else false end
42
+ def save(options={})
43
+ perform_validations(options) ? super : false
56
44
  end
57
45
 
58
- # Updates a single attribute and saves the record without going through the normal validation procedure.
59
- # This is especially useful for boolean flags on existing records. The regular +update_attribute+ method
60
- # in Base is replaced with this when the validations module is mixed in, which it is by default.
61
- def update_attribute_with_validation_skipping(name, value)
62
- @attributes[name] = value
63
- save(false)
64
- end
65
-
66
- # Runs validate and validate_on_create or validate_on_update and returns true if no errors were added otherwise false.
67
- def valid?
68
- errors.clear
69
- validate
70
- if new_record? then validate_on_create else validate_on_update end
71
- errors.empty?
72
- end
73
-
74
- # Returns the Errors object that holds all information about attribute error messages.
75
- def errors
76
- @errors = Errors.new(self) if @errors.nil?
77
- @errors
78
- end
79
-
80
- protected
81
- # Overwrite this method for validation checks on all saves and use Errors.add(field, msg) for invalid attributes.
82
- def validate #:doc:
83
- end
84
-
85
- # Overwrite this method for validation checks used only on creation.
86
- def validate_on_create #:doc:
87
- end
88
-
89
- # Overwrite this method for validation checks used only on updates.
90
- def validate_on_update # :doc:
91
- end
92
- end
93
-
94
- # Active Record validation is reported to and from this object, which is used by Base#save to
95
- # determine whether the object in a valid state to be saved. See usage example in Validations.
96
- class Errors
97
- def initialize(base) # :nodoc:
98
- @base, @errors = base, {}
99
- end
100
-
101
- # Adds an error to the base object instead of any particular attribute. This is used
102
- # to report errors that doesn't tie to any specific attribute, but rather to the object
103
- # as a whole. These error messages doesn't get prepended with any field name when iterating
104
- # with each_full, so they should be complete sentences.
105
- def add_to_base(msg)
106
- add(:base, msg)
46
+ # Attempts to save the record just like Base#save but will raise a RecordInvalid exception instead of returning false
47
+ # if the record is not valid.
48
+ def save!(options={})
49
+ perform_validations(options) ? super : raise(RecordInvalid.new(self))
107
50
  end
108
51
 
109
- # Adds an error message (+msg+) to the +attribute+, which will be returned on a call to <tt>on(attribute)</tt>
110
- # for the same attribute and ensure that this error object returns false when asked if +empty?+. More than one
111
- # error can be added to the same +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>.
112
- # If no +msg+ is supplied, "invalid" is assumed.
113
- def add(attribute, msg = "invalid")
114
- @errors[attribute] = [] if @errors[attribute].nil?
115
- @errors[attribute] << msg
116
- end
52
+ # Runs all the specified validations and returns true if no errors were added otherwise false.
53
+ def valid?(context = nil)
54
+ context ||= (new_record? ? :create : :update)
55
+ output = super(context)
117
56
 
118
- # Will add an error message to each of the attributes in +attributes+ that is empty (defined by <tt>attribute_present?</tt>).
119
- def add_on_empty(attributes, msg = "can't be empty")
120
- [attributes].flatten.each { |attr| add(attr, msg) unless @base.attribute_present?(attr) }
121
- end
57
+ deprecated_callback_method(:validate)
58
+ deprecated_callback_method(:"validate_on_#{context}")
122
59
 
123
- # Will add an error message to each of the attributes in +attributes+ that has a length outside of the passed boundary +range+.
124
- # If the length is above the boundary, the too_long_msg message will be used. If below, the too_short_msg.
125
- def add_on_boundary_breaking(attributes, range, too_long_msg = "is too long (max is %d characters)", too_short_msg = "is too short (min is %d characters)")
126
- for attr in [attributes].flatten
127
- add(attr, too_short_msg % range.begin) if @base.attribute_present?(attr) && @base.send(attr).length < range.begin
128
- add(attr, too_long_msg % range.end) if @base.attribute_present?(attr) && @base.send(attr).length > range.end
129
- end
60
+ errors.empty? && output
130
61
  end
131
62
 
132
- alias :add_on_boundry_breaking :add_on_boundary_breaking
63
+ protected
133
64
 
134
- # Returns true if the specified +attribute+ has errors associated with it.
135
- def invalid?(attribute)
136
- !@errors[attribute].nil?
137
- end
138
-
139
- # * Returns nil, if no errors are associated with the specified +attribute+.
140
- # * Returns the error message, if one error is associated with the specified +attribute+.
141
- # * Returns an array of error messages, if more than one error is associated with the specified +attribute+.
142
- def on(attribute)
143
- if @errors[attribute].nil?
144
- nil
145
- elsif @errors[attribute].length == 1
146
- @errors[attribute].first
65
+ def perform_validations(options={})
66
+ perform_validation = case options
67
+ when Hash
68
+ options[:validate] != false
147
69
  else
148
- @errors[attribute]
70
+ ActiveSupport::Deprecation.warn "save(#{options}) is deprecated, please give save(:validate => #{options}) instead", caller
71
+ options
149
72
  end
150
- end
151
73
 
152
- alias :[] :on
153
-
154
- # Returns errors assigned to base object through add_to_base according to the normal rules of on(attribute).
155
- def on_base
156
- on(:base)
157
- end
158
-
159
- # Yields each attribute and associated message per error added.
160
- def each
161
- @errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } }
162
- end
163
-
164
- # Yields each full error message added. So Person.errors.add("first_name", "can't be empty") will be returned
165
- # through iteration as "First name can't be empty".
166
- def each_full
167
- full_messages.each { |msg| yield msg }
168
- end
169
-
170
- # Returns all the full error messages in an array.
171
- def full_messages
172
- full_messages = []
173
-
174
- @errors.each_key do |attr|
175
- @errors[attr].each do |msg|
176
- if attr == :base
177
- full_messages << msg
178
- else
179
- full_messages << @base.class.human_attribute_name(attr) + " " + msg
180
- end
181
- end
74
+ if perform_validation
75
+ valid?(options.is_a?(Hash) ? options[:context] : nil)
76
+ else
77
+ true
182
78
  end
183
-
184
- return full_messages
185
- end
186
-
187
- # Returns true if no errors have been added.
188
- def empty?
189
- return @errors.empty?
190
- end
191
-
192
- # Removes all the errors that have been added.
193
- def clear
194
- @errors = {}
195
- end
196
-
197
- # Returns the total number of errors added. Two errors added to the same attribute will be counted as such
198
- # with this as well.
199
- def count
200
- error_count = 0
201
- @errors.each_value { |attribute| error_count += attribute.length }
202
- error_count
203
79
  end
204
80
  end
205
81
  end
82
+
83
+ require "active_record/validations/associated"
84
+ require "active_record/validations/uniqueness"
@@ -0,0 +1,9 @@
1
+ module ActiveRecord
2
+ module VERSION #:nodoc:
3
+ MAJOR = 3
4
+ MINOR = 0
5
+ TINY = 0
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
data/lib/active_record.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2004 David Heinemeier Hansson
2
+ # Copyright (c) 2004-2010 David Heinemeier Hansson
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining
5
5
  # a copy of this software and associated documentation files (the
@@ -21,28 +21,104 @@
21
21
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
  #++
23
23
 
24
- $:.unshift(File.dirname(__FILE__))
25
-
26
- require 'active_record/support/clean_logger'
27
-
28
- require 'active_record/base'
29
- require 'active_record/observer'
30
- require 'active_record/validations'
31
- require 'active_record/callbacks'
32
- require 'active_record/associations'
33
- require 'active_record/aggregations'
34
- require 'active_record/transactions'
35
- require 'active_record/reflection'
36
-
37
- ActiveRecord::Base.class_eval do
38
- include ActiveRecord::Validations
39
- include ActiveRecord::Callbacks
40
- include ActiveRecord::Associations
41
- include ActiveRecord::Aggregations
42
- include ActiveRecord::Transactions
43
- include ActiveRecord::Reflection
24
+
25
+ activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__)
26
+ $:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
27
+
28
+ activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__)
29
+ $:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path)
30
+
31
+ require 'active_support'
32
+ require 'active_support/i18n'
33
+ require 'active_model'
34
+ require 'arel'
35
+
36
+ module ActiveRecord
37
+ extend ActiveSupport::Autoload
38
+
39
+ eager_autoload do
40
+ autoload :VERSION
41
+
42
+ autoload :ActiveRecordError, 'active_record/errors'
43
+ autoload :ConnectionNotEstablished, 'active_record/errors'
44
+
45
+ autoload :Aggregations
46
+ autoload :AssociationPreload
47
+ autoload :Associations
48
+ autoload :AttributeMethods
49
+ autoload :AutosaveAssociation
50
+
51
+ autoload :Relation
52
+
53
+ autoload_under 'relation' do
54
+ autoload :QueryMethods
55
+ autoload :FinderMethods
56
+ autoload :Calculations
57
+ autoload :PredicateBuilder
58
+ autoload :SpawnMethods
59
+ autoload :Batches
60
+ end
61
+
62
+ autoload :Base
63
+ autoload :Callbacks
64
+ autoload :CounterCache
65
+ autoload :DynamicFinderMatch
66
+ autoload :DynamicScopeMatch
67
+ autoload :Migration
68
+ autoload :Migrator, 'active_record/migration'
69
+ autoload :NamedScope
70
+ autoload :NestedAttributes
71
+ autoload :Observer
72
+ autoload :Persistence
73
+ autoload :QueryCache
74
+ autoload :Reflection
75
+ autoload :Schema
76
+ autoload :SchemaDumper
77
+ autoload :Serialization
78
+ autoload :SessionStore
79
+ autoload :Timestamp
80
+ autoload :Transactions
81
+ autoload :Validations
82
+ end
83
+
84
+ module AttributeMethods
85
+ extend ActiveSupport::Autoload
86
+
87
+ eager_autoload do
88
+ autoload :BeforeTypeCast
89
+ autoload :Dirty
90
+ autoload :PrimaryKey
91
+ autoload :Query
92
+ autoload :Read
93
+ autoload :TimeZoneConversion
94
+ autoload :Write
95
+ end
96
+ end
97
+
98
+ module Locking
99
+ extend ActiveSupport::Autoload
100
+
101
+ eager_autoload do
102
+ autoload :Optimistic
103
+ autoload :Pessimistic
104
+ end
105
+ end
106
+
107
+ module ConnectionAdapters
108
+ extend ActiveSupport::Autoload
109
+
110
+ eager_autoload do
111
+ autoload :AbstractAdapter
112
+ autoload :ConnectionManagement, "active_record/connection_adapters/abstract/connection_pool"
113
+ end
114
+ end
115
+
116
+ autoload :TestCase
117
+ autoload :TestFixtures, 'active_record/fixtures'
118
+ end
119
+
120
+ ActiveSupport.on_load(:active_record) do
121
+ Arel::Table.engine = Arel::Sql::Engine.new(self)
44
122
  end
45
123
 
46
- require 'active_record/connection_adapters/mysql_adapter'
47
- require 'active_record/connection_adapters/postgresql_adapter'
48
- require 'active_record/connection_adapters/sqlite_adapter'
124
+ I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml'
@@ -0,0 +1,25 @@
1
+ require 'rails/generators/active_record'
2
+
3
+ module ActiveRecord
4
+ module Generators
5
+ class MigrationGenerator < Base
6
+ argument :attributes, :type => :array, :default => [], :banner => "field:type field:type"
7
+
8
+ def create_migration_file
9
+ set_local_assigns!
10
+ migration_template "migration.rb", "db/migrate/#{file_name}.rb"
11
+ end
12
+
13
+ protected
14
+ attr_reader :migration_action
15
+
16
+ def set_local_assigns!
17
+ if file_name =~ /^(add|remove)_.*_(?:to|from)_(.*)/
18
+ @migration_action = $1
19
+ @table_name = $2.pluralize
20
+ end
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,17 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration
2
+ def self.up
3
+ <% attributes.each do |attribute| -%>
4
+ <%- if migration_action -%>
5
+ <%= migration_action %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'add' %>, :<%= attribute.type %><% end %>
6
+ <%- end -%>
7
+ <%- end -%>
8
+ end
9
+
10
+ def self.down
11
+ <% attributes.reverse.each do |attribute| -%>
12
+ <%- if migration_action -%>
13
+ <%= migration_action == 'add' ? 'remove' : 'add' %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'remove' %>, :<%= attribute.type %><% end %>
14
+ <%- end -%>
15
+ <%- end -%>
16
+ end
17
+ end
@@ -0,0 +1,38 @@
1
+ require 'rails/generators/active_record'
2
+
3
+ module ActiveRecord
4
+ module Generators
5
+ class ModelGenerator < Base
6
+ argument :attributes, :type => :array, :default => [], :banner => "field:type field:type"
7
+
8
+ check_class_collision
9
+
10
+ class_option :migration, :type => :boolean
11
+ class_option :timestamps, :type => :boolean
12
+ class_option :parent, :type => :string, :desc => "The parent class for the generated model"
13
+
14
+ def create_migration_file
15
+ return unless options[:migration] && options[:parent].nil?
16
+ migration_template "migration.rb", "db/migrate/create_#{table_name}.rb"
17
+ end
18
+
19
+ def create_model_file
20
+ template 'model.rb', File.join('app/models', class_path, "#{file_name}.rb")
21
+ end
22
+
23
+ def create_module_file
24
+ return if class_path.empty?
25
+ template 'module.rb', File.join('app/models', "#{class_path.join('/')}.rb") if behavior == :invoke
26
+ end
27
+
28
+ hook_for :test_framework
29
+
30
+ protected
31
+
32
+ def parent_class_name
33
+ options[:parent] || "ActiveRecord::Base"
34
+ end
35
+
36
+ end
37
+ end
38
+ end