activerecord_authorails 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (270) hide show
  1. data/CHANGELOG +3043 -0
  2. data/README +360 -0
  3. data/RUNNING_UNIT_TESTS +64 -0
  4. data/Rakefile +226 -0
  5. data/examples/associations.png +0 -0
  6. data/examples/associations.rb +87 -0
  7. data/examples/shared_setup.rb +15 -0
  8. data/examples/validation.rb +85 -0
  9. data/install.rb +30 -0
  10. data/lib/active_record.rb +85 -0
  11. data/lib/active_record/acts/list.rb +244 -0
  12. data/lib/active_record/acts/nested_set.rb +211 -0
  13. data/lib/active_record/acts/tree.rb +89 -0
  14. data/lib/active_record/aggregations.rb +191 -0
  15. data/lib/active_record/associations.rb +1637 -0
  16. data/lib/active_record/associations/association_collection.rb +190 -0
  17. data/lib/active_record/associations/association_proxy.rb +158 -0
  18. data/lib/active_record/associations/belongs_to_association.rb +56 -0
  19. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
  20. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +169 -0
  21. data/lib/active_record/associations/has_many_association.rb +210 -0
  22. data/lib/active_record/associations/has_many_through_association.rb +247 -0
  23. data/lib/active_record/associations/has_one_association.rb +80 -0
  24. data/lib/active_record/attribute_methods.rb +75 -0
  25. data/lib/active_record/base.rb +2164 -0
  26. data/lib/active_record/calculations.rb +270 -0
  27. data/lib/active_record/callbacks.rb +367 -0
  28. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +279 -0
  29. data/lib/active_record/connection_adapters/abstract/database_statements.rb +130 -0
  30. data/lib/active_record/connection_adapters/abstract/quoting.rb +58 -0
  31. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +343 -0
  32. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +310 -0
  33. data/lib/active_record/connection_adapters/abstract_adapter.rb +161 -0
  34. data/lib/active_record/connection_adapters/db2_adapter.rb +228 -0
  35. data/lib/active_record/connection_adapters/firebird_adapter.rb +728 -0
  36. data/lib/active_record/connection_adapters/frontbase_adapter.rb +861 -0
  37. data/lib/active_record/connection_adapters/mysql_adapter.rb +414 -0
  38. data/lib/active_record/connection_adapters/openbase_adapter.rb +350 -0
  39. data/lib/active_record/connection_adapters/oracle_adapter.rb +689 -0
  40. data/lib/active_record/connection_adapters/postgresql_adapter.rb +584 -0
  41. data/lib/active_record/connection_adapters/sqlite_adapter.rb +407 -0
  42. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +591 -0
  43. data/lib/active_record/connection_adapters/sybase_adapter.rb +662 -0
  44. data/lib/active_record/deprecated_associations.rb +104 -0
  45. data/lib/active_record/deprecated_finders.rb +44 -0
  46. data/lib/active_record/fixtures.rb +628 -0
  47. data/lib/active_record/locking/optimistic.rb +106 -0
  48. data/lib/active_record/locking/pessimistic.rb +77 -0
  49. data/lib/active_record/migration.rb +394 -0
  50. data/lib/active_record/observer.rb +178 -0
  51. data/lib/active_record/query_cache.rb +64 -0
  52. data/lib/active_record/reflection.rb +222 -0
  53. data/lib/active_record/schema.rb +58 -0
  54. data/lib/active_record/schema_dumper.rb +149 -0
  55. data/lib/active_record/timestamp.rb +51 -0
  56. data/lib/active_record/transactions.rb +136 -0
  57. data/lib/active_record/validations.rb +843 -0
  58. data/lib/active_record/vendor/db2.rb +362 -0
  59. data/lib/active_record/vendor/mysql.rb +1214 -0
  60. data/lib/active_record/vendor/simple.rb +693 -0
  61. data/lib/active_record/version.rb +9 -0
  62. data/lib/active_record/wrappers/yaml_wrapper.rb +15 -0
  63. data/lib/active_record/wrappings.rb +58 -0
  64. data/lib/active_record/xml_serialization.rb +308 -0
  65. data/test/aaa_create_tables_test.rb +59 -0
  66. data/test/abstract_unit.rb +77 -0
  67. data/test/active_schema_test_mysql.rb +31 -0
  68. data/test/adapter_test.rb +87 -0
  69. data/test/adapter_test_sqlserver.rb +81 -0
  70. data/test/aggregations_test.rb +95 -0
  71. data/test/all.sh +8 -0
  72. data/test/ar_schema_test.rb +33 -0
  73. data/test/association_inheritance_reload.rb +14 -0
  74. data/test/associations/callbacks_test.rb +126 -0
  75. data/test/associations/cascaded_eager_loading_test.rb +138 -0
  76. data/test/associations/eager_test.rb +393 -0
  77. data/test/associations/extension_test.rb +42 -0
  78. data/test/associations/join_model_test.rb +497 -0
  79. data/test/associations_test.rb +1809 -0
  80. data/test/attribute_methods_test.rb +49 -0
  81. data/test/base_test.rb +1586 -0
  82. data/test/binary_test.rb +37 -0
  83. data/test/calculations_test.rb +219 -0
  84. data/test/callbacks_test.rb +377 -0
  85. data/test/class_inheritable_attributes_test.rb +32 -0
  86. data/test/column_alias_test.rb +17 -0
  87. data/test/connection_test_firebird.rb +8 -0
  88. data/test/connections/native_db2/connection.rb +25 -0
  89. data/test/connections/native_firebird/connection.rb +26 -0
  90. data/test/connections/native_frontbase/connection.rb +27 -0
  91. data/test/connections/native_mysql/connection.rb +24 -0
  92. data/test/connections/native_openbase/connection.rb +21 -0
  93. data/test/connections/native_oracle/connection.rb +27 -0
  94. data/test/connections/native_postgresql/connection.rb +23 -0
  95. data/test/connections/native_sqlite/connection.rb +34 -0
  96. data/test/connections/native_sqlite3/connection.rb +34 -0
  97. data/test/connections/native_sqlite3/in_memory_connection.rb +18 -0
  98. data/test/connections/native_sqlserver/connection.rb +23 -0
  99. data/test/connections/native_sqlserver_odbc/connection.rb +25 -0
  100. data/test/connections/native_sybase/connection.rb +23 -0
  101. data/test/copy_table_sqlite.rb +64 -0
  102. data/test/datatype_test_postgresql.rb +52 -0
  103. data/test/default_test_firebird.rb +16 -0
  104. data/test/defaults_test.rb +60 -0
  105. data/test/deprecated_associations_test.rb +396 -0
  106. data/test/deprecated_finder_test.rb +151 -0
  107. data/test/empty_date_time_test.rb +25 -0
  108. data/test/finder_test.rb +504 -0
  109. data/test/fixtures/accounts.yml +28 -0
  110. data/test/fixtures/author.rb +99 -0
  111. data/test/fixtures/author_favorites.yml +4 -0
  112. data/test/fixtures/authors.yml +7 -0
  113. data/test/fixtures/auto_id.rb +4 -0
  114. data/test/fixtures/bad_fixtures/attr_with_numeric_first_char +1 -0
  115. data/test/fixtures/bad_fixtures/attr_with_spaces +1 -0
  116. data/test/fixtures/bad_fixtures/blank_line +3 -0
  117. data/test/fixtures/bad_fixtures/duplicate_attributes +3 -0
  118. data/test/fixtures/bad_fixtures/missing_value +1 -0
  119. data/test/fixtures/binary.rb +2 -0
  120. data/test/fixtures/categories.yml +14 -0
  121. data/test/fixtures/categories/special_categories.yml +9 -0
  122. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
  123. data/test/fixtures/categories_ordered.yml +7 -0
  124. data/test/fixtures/categories_posts.yml +23 -0
  125. data/test/fixtures/categorization.rb +5 -0
  126. data/test/fixtures/categorizations.yml +17 -0
  127. data/test/fixtures/category.rb +20 -0
  128. data/test/fixtures/column_name.rb +3 -0
  129. data/test/fixtures/comment.rb +23 -0
  130. data/test/fixtures/comments.yml +59 -0
  131. data/test/fixtures/companies.yml +55 -0
  132. data/test/fixtures/company.rb +107 -0
  133. data/test/fixtures/company_in_module.rb +59 -0
  134. data/test/fixtures/computer.rb +3 -0
  135. data/test/fixtures/computers.yml +4 -0
  136. data/test/fixtures/course.rb +3 -0
  137. data/test/fixtures/courses.yml +7 -0
  138. data/test/fixtures/customer.rb +55 -0
  139. data/test/fixtures/customers.yml +17 -0
  140. data/test/fixtures/db_definitions/db2.drop.sql +32 -0
  141. data/test/fixtures/db_definitions/db2.sql +231 -0
  142. data/test/fixtures/db_definitions/db22.drop.sql +2 -0
  143. data/test/fixtures/db_definitions/db22.sql +5 -0
  144. data/test/fixtures/db_definitions/firebird.drop.sql +63 -0
  145. data/test/fixtures/db_definitions/firebird.sql +304 -0
  146. data/test/fixtures/db_definitions/firebird2.drop.sql +2 -0
  147. data/test/fixtures/db_definitions/firebird2.sql +6 -0
  148. data/test/fixtures/db_definitions/frontbase.drop.sql +32 -0
  149. data/test/fixtures/db_definitions/frontbase.sql +268 -0
  150. data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
  151. data/test/fixtures/db_definitions/frontbase2.sql +4 -0
  152. data/test/fixtures/db_definitions/mysql.drop.sql +32 -0
  153. data/test/fixtures/db_definitions/mysql.sql +234 -0
  154. data/test/fixtures/db_definitions/mysql2.drop.sql +2 -0
  155. data/test/fixtures/db_definitions/mysql2.sql +5 -0
  156. data/test/fixtures/db_definitions/openbase.drop.sql +2 -0
  157. data/test/fixtures/db_definitions/openbase.sql +302 -0
  158. data/test/fixtures/db_definitions/openbase2.drop.sql +2 -0
  159. data/test/fixtures/db_definitions/openbase2.sql +7 -0
  160. data/test/fixtures/db_definitions/oracle.drop.sql +65 -0
  161. data/test/fixtures/db_definitions/oracle.sql +325 -0
  162. data/test/fixtures/db_definitions/oracle2.drop.sql +2 -0
  163. data/test/fixtures/db_definitions/oracle2.sql +6 -0
  164. data/test/fixtures/db_definitions/postgresql.drop.sql +37 -0
  165. data/test/fixtures/db_definitions/postgresql.sql +263 -0
  166. data/test/fixtures/db_definitions/postgresql2.drop.sql +2 -0
  167. data/test/fixtures/db_definitions/postgresql2.sql +5 -0
  168. data/test/fixtures/db_definitions/schema.rb +60 -0
  169. data/test/fixtures/db_definitions/sqlite.drop.sql +32 -0
  170. data/test/fixtures/db_definitions/sqlite.sql +215 -0
  171. data/test/fixtures/db_definitions/sqlite2.drop.sql +2 -0
  172. data/test/fixtures/db_definitions/sqlite2.sql +5 -0
  173. data/test/fixtures/db_definitions/sqlserver.drop.sql +34 -0
  174. data/test/fixtures/db_definitions/sqlserver.sql +243 -0
  175. data/test/fixtures/db_definitions/sqlserver2.drop.sql +2 -0
  176. data/test/fixtures/db_definitions/sqlserver2.sql +5 -0
  177. data/test/fixtures/db_definitions/sybase.drop.sql +34 -0
  178. data/test/fixtures/db_definitions/sybase.sql +218 -0
  179. data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
  180. data/test/fixtures/db_definitions/sybase2.sql +5 -0
  181. data/test/fixtures/default.rb +2 -0
  182. data/test/fixtures/developer.rb +52 -0
  183. data/test/fixtures/developers.yml +21 -0
  184. data/test/fixtures/developers_projects.yml +17 -0
  185. data/test/fixtures/developers_projects/david_action_controller +3 -0
  186. data/test/fixtures/developers_projects/david_active_record +3 -0
  187. data/test/fixtures/developers_projects/jamis_active_record +2 -0
  188. data/test/fixtures/edge.rb +5 -0
  189. data/test/fixtures/edges.yml +6 -0
  190. data/test/fixtures/entrant.rb +3 -0
  191. data/test/fixtures/entrants.yml +14 -0
  192. data/test/fixtures/fk_test_has_fk.yml +3 -0
  193. data/test/fixtures/fk_test_has_pk.yml +2 -0
  194. data/test/fixtures/flowers.jpg +0 -0
  195. data/test/fixtures/funny_jokes.yml +10 -0
  196. data/test/fixtures/joke.rb +6 -0
  197. data/test/fixtures/keyboard.rb +3 -0
  198. data/test/fixtures/legacy_thing.rb +3 -0
  199. data/test/fixtures/legacy_things.yml +3 -0
  200. data/test/fixtures/migrations/1_people_have_last_names.rb +9 -0
  201. data/test/fixtures/migrations/2_we_need_reminders.rb +12 -0
  202. data/test/fixtures/migrations/3_innocent_jointable.rb +12 -0
  203. data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
  204. data/test/fixtures/migrations_with_duplicate/1_people_have_last_names.rb +9 -0
  205. data/test/fixtures/migrations_with_duplicate/2_we_need_reminders.rb +12 -0
  206. data/test/fixtures/migrations_with_duplicate/3_foo.rb +7 -0
  207. data/test/fixtures/migrations_with_duplicate/3_innocent_jointable.rb +12 -0
  208. data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
  209. data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
  210. data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
  211. data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
  212. data/test/fixtures/mixed_case_monkey.rb +3 -0
  213. data/test/fixtures/mixed_case_monkeys.yml +6 -0
  214. data/test/fixtures/mixin.rb +63 -0
  215. data/test/fixtures/mixins.yml +127 -0
  216. data/test/fixtures/movie.rb +5 -0
  217. data/test/fixtures/movies.yml +7 -0
  218. data/test/fixtures/naked/csv/accounts.csv +1 -0
  219. data/test/fixtures/naked/yml/accounts.yml +1 -0
  220. data/test/fixtures/naked/yml/companies.yml +1 -0
  221. data/test/fixtures/naked/yml/courses.yml +1 -0
  222. data/test/fixtures/order.rb +4 -0
  223. data/test/fixtures/people.yml +3 -0
  224. data/test/fixtures/person.rb +4 -0
  225. data/test/fixtures/post.rb +58 -0
  226. data/test/fixtures/posts.yml +48 -0
  227. data/test/fixtures/project.rb +27 -0
  228. data/test/fixtures/projects.yml +7 -0
  229. data/test/fixtures/reader.rb +4 -0
  230. data/test/fixtures/readers.yml +4 -0
  231. data/test/fixtures/reply.rb +37 -0
  232. data/test/fixtures/subject.rb +4 -0
  233. data/test/fixtures/subscriber.rb +6 -0
  234. data/test/fixtures/subscribers/first +2 -0
  235. data/test/fixtures/subscribers/second +2 -0
  236. data/test/fixtures/tag.rb +7 -0
  237. data/test/fixtures/tagging.rb +6 -0
  238. data/test/fixtures/taggings.yml +18 -0
  239. data/test/fixtures/tags.yml +7 -0
  240. data/test/fixtures/task.rb +3 -0
  241. data/test/fixtures/tasks.yml +7 -0
  242. data/test/fixtures/topic.rb +25 -0
  243. data/test/fixtures/topics.yml +22 -0
  244. data/test/fixtures/vertex.rb +9 -0
  245. data/test/fixtures/vertices.yml +4 -0
  246. data/test/fixtures_test.rb +401 -0
  247. data/test/inheritance_test.rb +205 -0
  248. data/test/lifecycle_test.rb +137 -0
  249. data/test/locking_test.rb +190 -0
  250. data/test/method_scoping_test.rb +416 -0
  251. data/test/migration_test.rb +768 -0
  252. data/test/migration_test_firebird.rb +124 -0
  253. data/test/mixin_nested_set_test.rb +196 -0
  254. data/test/mixin_test.rb +550 -0
  255. data/test/modules_test.rb +34 -0
  256. data/test/multiple_db_test.rb +60 -0
  257. data/test/pk_test.rb +104 -0
  258. data/test/readonly_test.rb +107 -0
  259. data/test/reflection_test.rb +159 -0
  260. data/test/schema_authorization_test_postgresql.rb +75 -0
  261. data/test/schema_dumper_test.rb +96 -0
  262. data/test/schema_test_postgresql.rb +64 -0
  263. data/test/synonym_test_oracle.rb +17 -0
  264. data/test/table_name_test_sqlserver.rb +23 -0
  265. data/test/threaded_connections_test.rb +48 -0
  266. data/test/transactions_test.rb +230 -0
  267. data/test/unconnected_test.rb +32 -0
  268. data/test/validations_test.rb +1097 -0
  269. data/test/xml_serialization_test.rb +125 -0
  270. metadata +365 -0
@@ -0,0 +1,58 @@
1
+ module ActiveRecord
2
+ # Allows programmers to programmatically define a schema in a portable
3
+ # DSL. This means you can define tables, indexes, etc. without using SQL
4
+ # directly, so your applications can more easily support multiple
5
+ # databases.
6
+ #
7
+ # Usage:
8
+ #
9
+ # ActiveRecord::Schema.define do
10
+ # create_table :authors do |t|
11
+ # t.column :name, :string, :null => false
12
+ # end
13
+ #
14
+ # add_index :authors, :name, :unique
15
+ #
16
+ # create_table :posts do |t|
17
+ # t.column :author_id, :integer, :null => false
18
+ # t.column :subject, :string
19
+ # t.column :body, :text
20
+ # t.column :private, :boolean, :default => false
21
+ # end
22
+ #
23
+ # add_index :posts, :author_id
24
+ # end
25
+ #
26
+ # ActiveRecord::Schema is only supported by database adapters that also
27
+ # support migrations, the two features being very similar.
28
+ class Schema < Migration
29
+ private_class_method :new
30
+
31
+ # Eval the given block. All methods available to the current connection
32
+ # adapter are available within the block, so you can easily use the
33
+ # database definition DSL to build up your schema (#create_table,
34
+ # #add_index, etc.).
35
+ #
36
+ # The +info+ hash is optional, and if given is used to define metadata
37
+ # about the current schema (like the schema's version):
38
+ #
39
+ # ActiveRecord::Schema.define(:version => 15) do
40
+ # ...
41
+ # end
42
+ def self.define(info={}, &block)
43
+ instance_eval(&block)
44
+
45
+ unless info.empty?
46
+ initialize_schema_information
47
+ cols = columns('schema_info')
48
+
49
+ info = info.map do |k,v|
50
+ v = Base.connection.quote(v, cols.detect { |c| c.name == k.to_s })
51
+ "#{k} = #{v}"
52
+ end
53
+
54
+ Base.connection.update "UPDATE #{Migrator.schema_info_table_name} SET #{info.join(", ")}"
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,149 @@
1
+ require 'stringio'
2
+ require 'bigdecimal'
3
+
4
+ module ActiveRecord
5
+ # This class is used to dump the database schema for some connection to some
6
+ # output format (i.e., ActiveRecord::Schema).
7
+ class SchemaDumper #:nodoc:
8
+ private_class_method :new
9
+
10
+ # A list of tables which should not be dumped to the schema.
11
+ # Acceptable values are strings as well as regexp.
12
+ # This setting is only used if ActiveRecord::Base.schema_format == :ruby
13
+ cattr_accessor :ignore_tables
14
+ @@ignore_tables = []
15
+
16
+ def self.dump(connection=ActiveRecord::Base.connection, stream=STDOUT)
17
+ new(connection).dump(stream)
18
+ stream
19
+ end
20
+
21
+ def dump(stream)
22
+ header(stream)
23
+ tables(stream)
24
+ trailer(stream)
25
+ stream
26
+ end
27
+
28
+ private
29
+
30
+ def initialize(connection)
31
+ @connection = connection
32
+ @types = @connection.native_database_types
33
+ @info = @connection.select_one("SELECT * FROM schema_info") rescue nil
34
+ end
35
+
36
+ def header(stream)
37
+ define_params = @info ? ":version => #{@info['version']}" : ""
38
+
39
+ stream.puts <<HEADER
40
+ # This file is autogenerated. Instead of editing this file, please use the
41
+ # migrations feature of ActiveRecord to incrementally modify your database, and
42
+ # then regenerate this schema definition.
43
+
44
+ ActiveRecord::Schema.define(#{define_params}) do
45
+
46
+ HEADER
47
+ end
48
+
49
+ def trailer(stream)
50
+ stream.puts "end"
51
+ end
52
+
53
+ def tables(stream)
54
+ @connection.tables.sort.each do |tbl|
55
+ next if ["schema_info", ignore_tables].flatten.any? do |ignored|
56
+ case ignored
57
+ when String: tbl == ignored
58
+ when Regexp: tbl =~ ignored
59
+ else
60
+ raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
61
+ end
62
+ end
63
+ table(tbl, stream)
64
+ end
65
+ end
66
+
67
+ def table(table, stream)
68
+ columns = @connection.columns(table)
69
+ begin
70
+ tbl = StringIO.new
71
+
72
+ if @connection.respond_to?(:pk_and_sequence_for)
73
+ pk, pk_seq = @connection.pk_and_sequence_for(table)
74
+ end
75
+ pk ||= 'id'
76
+
77
+ tbl.print " create_table #{table.inspect}"
78
+ if columns.detect { |c| c.name == pk }
79
+ if pk != 'id'
80
+ tbl.print %Q(, :primary_key => "#{pk}")
81
+ end
82
+ else
83
+ tbl.print ", :id => false"
84
+ end
85
+ tbl.print ", :force => true"
86
+ tbl.puts " do |t|"
87
+
88
+ column_specs = columns.map do |column|
89
+ raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" if @types[column.type].nil?
90
+ next if column.name == pk
91
+ spec = {}
92
+ spec[:name] = column.name.inspect
93
+ spec[:type] = column.type.inspect
94
+ spec[:limit] = column.limit.inspect if column.limit != @types[column.type][:limit] && column.type != :decimal
95
+ spec[:precision] = column.precision.inspect if !column.precision.nil?
96
+ spec[:scale] = column.scale.inspect if !column.scale.nil?
97
+ spec[:null] = 'false' if !column.null
98
+ spec[:default] = default_string(column.default) if !column.default.nil?
99
+ (spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k.inspect} => ")}
100
+ spec
101
+ end.compact
102
+ keys = [:name, :type, :limit, :precision, :scale, :default, :null] & column_specs.map{ |spec| spec.keys }.inject([]){ |a,b| a | b }
103
+ lengths = keys.map{ |key| column_specs.map{ |spec| spec[key] ? spec[key].length + 2 : 0 }.max }
104
+ format_string = lengths.map{ |len| "%-#{len}s" }.join("")
105
+ column_specs.each do |colspec|
106
+ values = keys.zip(lengths).map{ |key, len| colspec.key?(key) ? colspec[key] + ", " : " " * len }
107
+ tbl.print " t.column "
108
+ tbl.print((format_string % values).gsub(/,\s*$/, ''))
109
+ tbl.puts
110
+ end
111
+
112
+ tbl.puts " end"
113
+ tbl.puts
114
+
115
+ indexes(table, tbl)
116
+
117
+ tbl.rewind
118
+ stream.print tbl.read
119
+ rescue => e
120
+ stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
121
+ stream.puts "# #{e.message}"
122
+ stream.puts
123
+ end
124
+
125
+ stream
126
+ end
127
+
128
+ def default_string(value)
129
+ case value
130
+ when BigDecimal
131
+ value.to_s
132
+ when Date, DateTime, Time
133
+ "'" + value.to_s(:db) + "'"
134
+ else
135
+ value.inspect
136
+ end
137
+ end
138
+
139
+ def indexes(table, stream)
140
+ indexes = @connection.indexes(table)
141
+ indexes.each do |index|
142
+ stream.print " add_index #{index.table.inspect}, #{index.columns.inspect}, :name => #{index.name.inspect}"
143
+ stream.print ", :unique => true" if index.unique
144
+ stream.puts
145
+ end
146
+ stream.puts unless indexes.empty?
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,51 @@
1
+ module ActiveRecord
2
+ # Active Record automatically timestamps create and update if the table has fields
3
+ # created_at/created_on or updated_at/updated_on.
4
+ #
5
+ # Timestamping can be turned off by setting
6
+ # <tt>ActiveRecord::Base.record_timestamps = false</tt>
7
+ #
8
+ # Keep in mind that, via inheritance, you can turn off timestamps on a per
9
+ # model basis by setting <tt>record_timestamps</tt> to false in the desired
10
+ # models.
11
+ #
12
+ # class Feed < ActiveRecord::Base
13
+ # self.record_timestamps = false
14
+ # # ...
15
+ # end
16
+ #
17
+ # Timestamps are in the local timezone by default but can use UTC by setting
18
+ # <tt>ActiveRecord::Base.default_timezone = :utc</tt>
19
+ module Timestamp
20
+ def self.included(base) #:nodoc:
21
+ super
22
+
23
+ base.alias_method_chain :create, :timestamps
24
+ base.alias_method_chain :update, :timestamps
25
+
26
+ base.cattr_accessor :record_timestamps, :instance_writer => false
27
+ base.record_timestamps = true
28
+ end
29
+
30
+ def create_with_timestamps #:nodoc:
31
+ if record_timestamps
32
+ t = self.class.default_timezone == :utc ? Time.now.utc : Time.now
33
+ write_attribute('created_at', t) if respond_to?(:created_at) && created_at.nil?
34
+ write_attribute('created_on', t) if respond_to?(:created_on) && created_on.nil?
35
+
36
+ write_attribute('updated_at', t) if respond_to?(:updated_at)
37
+ write_attribute('updated_on', t) if respond_to?(:updated_on)
38
+ end
39
+ create_without_timestamps
40
+ end
41
+
42
+ def update_with_timestamps #:nodoc:
43
+ if record_timestamps
44
+ t = self.class.default_timezone == :utc ? Time.now.utc : Time.now
45
+ write_attribute('updated_at', t) if respond_to?(:updated_at)
46
+ write_attribute('updated_on', t) if respond_to?(:updated_on)
47
+ end
48
+ update_without_timestamps
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,136 @@
1
+ require 'active_record/vendor/simple.rb'
2
+ Transaction::Simple.send(:remove_method, :transaction)
3
+ require 'thread'
4
+
5
+ module ActiveRecord
6
+ module Transactions # :nodoc:
7
+ class TransactionError < ActiveRecordError # :nodoc:
8
+ end
9
+
10
+ def self.included(base)
11
+ base.extend(ClassMethods)
12
+
13
+ base.class_eval do
14
+ [:destroy, :save, :save!].each do |method|
15
+ alias_method_chain method, :transactions
16
+ end
17
+ end
18
+ end
19
+
20
+ # Transactions are protective blocks where SQL statements are only permanent if they can all succeed as one atomic action.
21
+ # The classic example is a transfer between two accounts where you can only have a deposit if the withdrawal succeeded and
22
+ # vice versa. Transactions enforce the integrity of the database and guard the data against program errors or database break-downs.
23
+ # So basically you should use transaction blocks whenever you have a number of statements that must be executed together or
24
+ # not at all. Example:
25
+ #
26
+ # transaction do
27
+ # david.withdrawal(100)
28
+ # mary.deposit(100)
29
+ # end
30
+ #
31
+ # This example will only take money from David and give to Mary if neither +withdrawal+ nor +deposit+ raises an exception.
32
+ # Exceptions will force a ROLLBACK that returns the database to the state before the transaction was begun. Be aware, though,
33
+ # that the objects by default will _not_ have their instance data returned to their pre-transactional state.
34
+ #
35
+ # == Transactions are not distributed across database connections
36
+ #
37
+ # A transaction acts on a single database connection. If you have
38
+ # multiple class-specific databases, the transaction will not protect
39
+ # interaction among them. One workaround is to begin a transaction
40
+ # on each class whose models you alter:
41
+ #
42
+ # Student.transaction do
43
+ # Course.transaction do
44
+ # course.enroll(student)
45
+ # student.units += course.units
46
+ # end
47
+ # end
48
+ #
49
+ # This is a poor solution, but full distributed transactions are beyond
50
+ # the scope of Active Record.
51
+ #
52
+ # == Save and destroy are automatically wrapped in a transaction
53
+ #
54
+ # Both Base#save and Base#destroy come wrapped in a transaction that ensures that whatever you do in validations or callbacks
55
+ # will happen under the protected cover of a transaction. So you can use validations to check for values that the transaction
56
+ # depend on or you can raise exceptions in the callbacks to rollback.
57
+ #
58
+ # == Object-level transactions (deprecated)
59
+ #
60
+ # You can enable object-level transactions for Active Record objects, though. You do this by naming each of the Active Records
61
+ # that you want to enable object-level transactions for, like this:
62
+ #
63
+ # Account.transaction(david, mary) do
64
+ # david.withdrawal(100)
65
+ # mary.deposit(100)
66
+ # end
67
+ #
68
+ # If the transaction fails, David and Mary will be returned to their
69
+ # pre-transactional state. No money will have changed hands in neither
70
+ # object nor database.
71
+ #
72
+ # However, useful state such as validation errors are also rolled back,
73
+ # limiting the usefulness of this feature. As such it is deprecated in
74
+ # Rails 1.2 and will be removed in the next release. Install the
75
+ # object_transactions plugin if you wish to continue using it.
76
+ #
77
+ # == Exception handling
78
+ #
79
+ # Also have in mind that exceptions thrown within a transaction block will be propagated (after triggering the ROLLBACK), so you
80
+ # should be ready to catch those in your application code.
81
+ #
82
+ # Tribute: Object-level transactions are implemented by Transaction::Simple by Austin Ziegler.
83
+ module ClassMethods
84
+ def transaction(*objects, &block)
85
+ previous_handler = trap('TERM') { raise TransactionError, "Transaction aborted" }
86
+ increment_open_transactions
87
+
88
+ begin
89
+ unless objects.empty?
90
+ ActiveSupport::Deprecation.warn "Object transactions are deprecated and will be removed from Rails 2.0. See http://www.rubyonrails.org/deprecation for details.", caller
91
+ objects.each { |o| o.extend(Transaction::Simple) }
92
+ objects.each { |o| o.start_transaction }
93
+ end
94
+
95
+ result = connection.transaction(Thread.current['start_db_transaction'], &block)
96
+
97
+ objects.each { |o| o.commit_transaction }
98
+ return result
99
+ rescue Exception => object_transaction_rollback
100
+ objects.each { |o| o.abort_transaction }
101
+ raise
102
+ ensure
103
+ decrement_open_transactions
104
+ trap('TERM', previous_handler)
105
+ end
106
+ end
107
+
108
+ private
109
+ def increment_open_transactions #:nodoc:
110
+ open = Thread.current['open_transactions'] ||= 0
111
+ Thread.current['start_db_transaction'] = open.zero?
112
+ Thread.current['open_transactions'] = open + 1
113
+ end
114
+
115
+ def decrement_open_transactions #:nodoc:
116
+ Thread.current['open_transactions'] -= 1
117
+ end
118
+ end
119
+
120
+ def transaction(*objects, &block)
121
+ self.class.transaction(*objects, &block)
122
+ end
123
+
124
+ def destroy_with_transactions #:nodoc:
125
+ transaction { destroy_without_transactions }
126
+ end
127
+
128
+ def save_with_transactions(perform_validation = true) #:nodoc:
129
+ transaction { save_without_transactions(perform_validation) }
130
+ end
131
+
132
+ def save_with_transactions! #:nodoc:
133
+ transaction { save_without_transactions! }
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,843 @@
1
+ module ActiveRecord
2
+ # Raised by save! and create! when the record is invalid. Use the
3
+ # record method to retrieve the record which did not validate.
4
+ # begin
5
+ # complex_operation_that_calls_save!_internally
6
+ # rescue ActiveRecord::RecordInvalid => invalid
7
+ # puts invalid.record.errors
8
+ # end
9
+ class RecordInvalid < ActiveRecordError #:nodoc:
10
+ attr_reader :record
11
+ def initialize(record)
12
+ @record = record
13
+ super("Validation failed: #{@record.errors.full_messages.join(", ")}")
14
+ end
15
+ end
16
+
17
+ # Active Record validation is reported to and from this object, which is used by Base#save to
18
+ # determine whether the object in a valid state to be saved. See usage example in Validations.
19
+ class Errors
20
+ include Enumerable
21
+
22
+ def initialize(base) # :nodoc:
23
+ @base, @errors = base, {}
24
+ end
25
+
26
+ @@default_error_messages = {
27
+ :inclusion => "is not included in the list",
28
+ :exclusion => "is reserved",
29
+ :invalid => "is invalid",
30
+ :confirmation => "doesn't match confirmation",
31
+ :accepted => "must be accepted",
32
+ :empty => "can't be empty",
33
+ :blank => "can't be blank",
34
+ :too_long => "is too long (maximum is %d characters)",
35
+ :too_short => "is too short (minimum is %d characters)",
36
+ :wrong_length => "is the wrong length (should be %d characters)",
37
+ :taken => "has already been taken",
38
+ :not_a_number => "is not a number"
39
+ }
40
+
41
+ # Holds a hash with all the default error messages, such that they can be replaced by your own copy or localizations.
42
+ cattr_accessor :default_error_messages
43
+
44
+
45
+ # Adds an error to the base object instead of any particular attribute. This is used
46
+ # to report errors that don't tie to any specific attribute, but rather to the object
47
+ # as a whole. These error messages don't get prepended with any field name when iterating
48
+ # with each_full, so they should be complete sentences.
49
+ def add_to_base(msg)
50
+ add(:base, msg)
51
+ end
52
+
53
+ # Adds an error message (+msg+) to the +attribute+, which will be returned on a call to <tt>on(attribute)</tt>
54
+ # for the same attribute and ensure that this error object returns false when asked if <tt>empty?</tt>. More than one
55
+ # error can be added to the same +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>.
56
+ # If no +msg+ is supplied, "invalid" is assumed.
57
+ def add(attribute, msg = @@default_error_messages[:invalid])
58
+ @errors[attribute.to_s] = [] if @errors[attribute.to_s].nil?
59
+ @errors[attribute.to_s] << msg
60
+ end
61
+
62
+ # Will add an error message to each of the attributes in +attributes+ that is empty.
63
+ def add_on_empty(attributes, msg = @@default_error_messages[:empty])
64
+ for attr in [attributes].flatten
65
+ value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
66
+ is_empty = value.respond_to?("empty?") ? value.empty? : false
67
+ add(attr, msg) unless !value.nil? && !is_empty
68
+ end
69
+ end
70
+
71
+ # Will add an error message to each of the attributes in +attributes+ that is blank (using Object#blank?).
72
+ def add_on_blank(attributes, msg = @@default_error_messages[:blank])
73
+ for attr in [attributes].flatten
74
+ value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
75
+ add(attr, msg) if value.blank?
76
+ end
77
+ end
78
+
79
+ # Will add an error message to each of the attributes in +attributes+ that has a length outside of the passed boundary +range+.
80
+ # If the length is above the boundary, the too_long_msg message will be used. If below, the too_short_msg.
81
+ def add_on_boundary_breaking(attributes, range, too_long_msg = @@default_error_messages[:too_long], too_short_msg = @@default_error_messages[:too_short])
82
+ for attr in [attributes].flatten
83
+ value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
84
+ add(attr, too_short_msg % range.begin) if value && value.length < range.begin
85
+ add(attr, too_long_msg % range.end) if value && value.length > range.end
86
+ end
87
+ end
88
+
89
+ alias :add_on_boundry_breaking :add_on_boundary_breaking
90
+ deprecate :add_on_boundary_breaking => :validates_length_of, :add_on_boundry_breaking => :validates_length_of
91
+
92
+ # Returns true if the specified +attribute+ has errors associated with it.
93
+ def invalid?(attribute)
94
+ !@errors[attribute.to_s].nil?
95
+ end
96
+
97
+ # * Returns nil, if no errors are associated with the specified +attribute+.
98
+ # * Returns the error message, if one error is associated with the specified +attribute+.
99
+ # * Returns an array of error messages, if more than one error is associated with the specified +attribute+.
100
+ def on(attribute)
101
+ errors = @errors[attribute.to_s]
102
+ return nil if errors.nil?
103
+ errors.size == 1 ? errors.first : errors
104
+ end
105
+
106
+ alias :[] :on
107
+
108
+ # Returns errors assigned to base object through add_to_base according to the normal rules of on(attribute).
109
+ def on_base
110
+ on(:base)
111
+ end
112
+
113
+ # Yields each attribute and associated message per error added.
114
+ def each
115
+ @errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } }
116
+ end
117
+
118
+ # Yields each full error message added. So Person.errors.add("first_name", "can't be empty") will be returned
119
+ # through iteration as "First name can't be empty".
120
+ def each_full
121
+ full_messages.each { |msg| yield msg }
122
+ end
123
+
124
+ # Returns all the full error messages in an array.
125
+ def full_messages
126
+ full_messages = []
127
+
128
+ @errors.each_key do |attr|
129
+ @errors[attr].each do |msg|
130
+ next if msg.nil?
131
+
132
+ if attr == "base"
133
+ full_messages << msg
134
+ else
135
+ full_messages << @base.class.human_attribute_name(attr) + " " + msg
136
+ end
137
+ end
138
+ end
139
+ full_messages
140
+ end
141
+
142
+ # Returns true if no errors have been added.
143
+ def empty?
144
+ @errors.empty?
145
+ end
146
+
147
+ # Removes all the errors that have been added.
148
+ def clear
149
+ @errors = {}
150
+ end
151
+
152
+ # Returns the total number of errors added. Two errors added to the same attribute will be counted as such
153
+ # with this as well.
154
+ def size
155
+ @errors.values.inject(0) { |error_count, attribute| error_count + attribute.size }
156
+ end
157
+
158
+ alias_method :count, :size
159
+ alias_method :length, :size
160
+
161
+ # Return an XML representation of this error object.
162
+ def to_xml(options={})
163
+ options[:root] ||= "errors"
164
+ options[:indent] ||= 2
165
+ options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
166
+
167
+ options[:builder].instruct! unless options.delete(:skip_instruct)
168
+ options[:builder].errors do |e|
169
+ full_messages.each { |msg| e.error(msg) }
170
+ end
171
+ end
172
+ end
173
+
174
+
175
+ # Active Records implement validation by overwriting Base#validate (or the variations, +validate_on_create+ and
176
+ # +validate_on_update+). Each of these methods can inspect the state of the object, which usually means ensuring
177
+ # that a number of attributes have a certain value (such as not empty, within a given range, matching a certain regular expression).
178
+ #
179
+ # Example:
180
+ #
181
+ # class Person < ActiveRecord::Base
182
+ # protected
183
+ # def validate
184
+ # errors.add_on_empty %w( first_name last_name )
185
+ # errors.add("phone_number", "has invalid format") unless phone_number =~ /[0-9]*/
186
+ # end
187
+ #
188
+ # def validate_on_create # is only run the first time a new object is saved
189
+ # unless valid_discount?(membership_discount)
190
+ # errors.add("membership_discount", "has expired")
191
+ # end
192
+ # end
193
+ #
194
+ # def validate_on_update
195
+ # errors.add_to_base("No changes have occurred") if unchanged_attributes?
196
+ # end
197
+ # end
198
+ #
199
+ # person = Person.new("first_name" => "David", "phone_number" => "what?")
200
+ # person.save # => false (and doesn't do the save)
201
+ # person.errors.empty? # => false
202
+ # person.errors.count # => 2
203
+ # person.errors.on "last_name" # => "can't be empty"
204
+ # person.errors.on "phone_number" # => "has invalid format"
205
+ # person.errors.each_full { |msg| puts msg }
206
+ # # => "Last name can't be empty\n" +
207
+ # "Phone number has invalid format"
208
+ #
209
+ # person.attributes = { "last_name" => "Heinemeier", "phone_number" => "555-555" }
210
+ # person.save # => true (and person is now saved in the database)
211
+ #
212
+ # An +Errors+ object is automatically created for every Active Record.
213
+ #
214
+ # Please do have a look at ActiveRecord::Validations::ClassMethods for a higher level of validations.
215
+ module Validations
216
+ VALIDATIONS = %w( validate validate_on_create validate_on_update )
217
+
218
+ def self.included(base) # :nodoc:
219
+ base.extend ClassMethods
220
+ base.class_eval do
221
+ alias_method_chain :save, :validation
222
+ alias_method_chain :save!, :validation
223
+ alias_method_chain :update_attribute, :validation_skipping
224
+ end
225
+ end
226
+
227
+ # All of the following validations are defined in the class scope of the model that you're interested in validating.
228
+ # They offer a more declarative way of specifying when the model is valid and when it is not. It is recommended to use
229
+ # these over the low-level calls to validate and validate_on_create when possible.
230
+ module ClassMethods
231
+ DEFAULT_VALIDATION_OPTIONS = {
232
+ :on => :save,
233
+ :allow_nil => false,
234
+ :message => nil
235
+ }.freeze
236
+
237
+ ALL_RANGE_OPTIONS = [ :is, :within, :in, :minimum, :maximum ].freeze
238
+
239
+ def validate(*methods, &block)
240
+ methods << block if block_given?
241
+ write_inheritable_set(:validate, methods)
242
+ end
243
+
244
+ def validate_on_create(*methods, &block)
245
+ methods << block if block_given?
246
+ write_inheritable_set(:validate_on_create, methods)
247
+ end
248
+
249
+ def validate_on_update(*methods, &block)
250
+ methods << block if block_given?
251
+ write_inheritable_set(:validate_on_update, methods)
252
+ end
253
+
254
+ def condition_block?(condition)
255
+ condition.respond_to?("call") && (condition.arity == 1 || condition.arity == -1)
256
+ end
257
+
258
+ # Determine from the given condition (whether a block, procedure, method or string)
259
+ # whether or not to validate the record. See #validates_each.
260
+ def evaluate_condition(condition, record)
261
+ case condition
262
+ when Symbol: record.send(condition)
263
+ when String: eval(condition, binding)
264
+ else
265
+ if condition_block?(condition)
266
+ condition.call(record)
267
+ else
268
+ raise(
269
+ ActiveRecordError,
270
+ "Validations need to be either a symbol, string (to be eval'ed), proc/method, or " +
271
+ "class implementing a static validation method"
272
+ )
273
+ end
274
+ end
275
+ end
276
+
277
+ # Validates each attribute against a block.
278
+ #
279
+ # class Person < ActiveRecord::Base
280
+ # validates_each :first_name, :last_name do |record, attr, value|
281
+ # record.errors.add attr, 'starts with z.' if value[0] == ?z
282
+ # end
283
+ # end
284
+ #
285
+ # Options:
286
+ # * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
287
+ # * <tt>allow_nil</tt> - Skip validation if attribute is nil.
288
+ # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
289
+ # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
290
+ # method, proc or string should return or evaluate to a true or false value.
291
+ def validates_each(*attrs)
292
+ options = attrs.last.is_a?(Hash) ? attrs.pop.symbolize_keys : {}
293
+ attrs = attrs.flatten
294
+
295
+ # Declare the validation.
296
+ send(validation_method(options[:on] || :save)) do |record|
297
+ # Don't validate when there is an :if condition and that condition is false
298
+ unless options[:if] && !evaluate_condition(options[:if], record)
299
+ attrs.each do |attr|
300
+ value = record.send(attr)
301
+ next if value.nil? && options[:allow_nil]
302
+ yield record, attr, value
303
+ end
304
+ end
305
+ end
306
+ end
307
+
308
+ # Encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example:
309
+ #
310
+ # Model:
311
+ # class Person < ActiveRecord::Base
312
+ # validates_confirmation_of :user_name, :password
313
+ # validates_confirmation_of :email_address, :message => "should match confirmation"
314
+ # end
315
+ #
316
+ # View:
317
+ # <%= password_field "person", "password" %>
318
+ # <%= password_field "person", "password_confirmation" %>
319
+ #
320
+ # The person has to already have a password attribute (a column in the people table), but the password_confirmation is virtual.
321
+ # It exists only as an in-memory variable for validating the password. This check is performed only if password_confirmation
322
+ # is not nil and by default on save.
323
+ #
324
+ # Configuration options:
325
+ # * <tt>message</tt> - A custom error message (default is: "doesn't match confirmation")
326
+ # * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
327
+ # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
328
+ # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
329
+ # method, proc or string should return or evaluate to a true or false value.
330
+ def validates_confirmation_of(*attr_names)
331
+ configuration = { :message => ActiveRecord::Errors.default_error_messages[:confirmation], :on => :save }
332
+ configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
333
+
334
+ attr_accessor *(attr_names.map { |n| "#{n}_confirmation" })
335
+
336
+ validates_each(attr_names, configuration) do |record, attr_name, value|
337
+ record.errors.add(attr_name, configuration[:message]) unless record.send("#{attr_name}_confirmation").nil? or value == record.send("#{attr_name}_confirmation")
338
+ end
339
+ end
340
+
341
+ # Encapsulates the pattern of wanting to validate the acceptance of a terms of service check box (or similar agreement). Example:
342
+ #
343
+ # class Person < ActiveRecord::Base
344
+ # validates_acceptance_of :terms_of_service
345
+ # validates_acceptance_of :eula, :message => "must be abided"
346
+ # end
347
+ #
348
+ # The terms_of_service attribute is entirely virtual. No database column is needed. This check is performed only if
349
+ # terms_of_service is not nil and by default on save.
350
+ #
351
+ # Configuration options:
352
+ # * <tt>message</tt> - A custom error message (default is: "must be accepted")
353
+ # * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
354
+ # * <tt>accept</tt> - Specifies value that is considered accepted. The default value is a string "1", which
355
+ # makes it easy to relate to an HTML checkbox.
356
+ # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
357
+ # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
358
+ # method, proc or string should return or evaluate to a true or false value.
359
+ def validates_acceptance_of(*attr_names)
360
+ configuration = { :message => ActiveRecord::Errors.default_error_messages[:accepted], :on => :save, :allow_nil => true, :accept => "1" }
361
+ configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
362
+
363
+ attr_accessor *attr_names
364
+
365
+ validates_each(attr_names,configuration) do |record, attr_name, value|
366
+ record.errors.add(attr_name, configuration[:message]) unless value == configuration[:accept]
367
+ end
368
+ end
369
+
370
+ # Validates that the specified attributes are not blank (as defined by Object#blank?). Happens by default on save. Example:
371
+ #
372
+ # class Person < ActiveRecord::Base
373
+ # validates_presence_of :first_name
374
+ # end
375
+ #
376
+ # The first_name attribute must be in the object and it cannot be blank.
377
+ #
378
+ # If you want to validate the presence of a boolean field (where the real values are true and false),
379
+ # you will want to use validates_inclusion_of :field_name, :in => [true, false]
380
+ # This is due to the way Object#blank? handles boolean values. false.blank? # => true
381
+ #
382
+ # Configuration options:
383
+ # * <tt>message</tt> - A custom error message (default is: "can't be blank")
384
+ # * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
385
+ # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
386
+ # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
387
+ # method, proc or string should return or evaluate to a true or false value.
388
+ #
389
+ # === Warning
390
+ # Validate the presence of the foreign key, not the instance variable itself.
391
+ # Do this:
392
+ # validate_presence_of :invoice_id
393
+ #
394
+ # Not this:
395
+ # validate_presence_of :invoice
396
+ #
397
+ # If you validate the presence of the associated object, you will get
398
+ # failures on saves when both the parent object and the child object are
399
+ # new.
400
+ def validates_presence_of(*attr_names)
401
+ configuration = { :message => ActiveRecord::Errors.default_error_messages[:blank], :on => :save }
402
+ configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
403
+
404
+ # can't use validates_each here, because it cannot cope with nonexistent attributes,
405
+ # while errors.add_on_empty can
406
+ attr_names.each do |attr_name|
407
+ send(validation_method(configuration[:on])) do |record|
408
+ unless configuration[:if] and not evaluate_condition(configuration[:if], record)
409
+ record.errors.add_on_blank(attr_name,configuration[:message])
410
+ end
411
+ end
412
+ end
413
+ end
414
+
415
+ # Validates that the specified attribute matches the length restrictions supplied. Only one option can be used at a time:
416
+ #
417
+ # class Person < ActiveRecord::Base
418
+ # validates_length_of :first_name, :maximum=>30
419
+ # validates_length_of :last_name, :maximum=>30, :message=>"less than %d if you don't mind"
420
+ # validates_length_of :fax, :in => 7..32, :allow_nil => true
421
+ # validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name"
422
+ # validates_length_of :fav_bra_size, :minimum=>1, :too_short=>"please enter at least %d character"
423
+ # validates_length_of :smurf_leader, :is=>4, :message=>"papa is spelled with %d characters... don't play me."
424
+ # end
425
+ #
426
+ # Configuration options:
427
+ # * <tt>minimum</tt> - The minimum size of the attribute
428
+ # * <tt>maximum</tt> - The maximum size of the attribute
429
+ # * <tt>is</tt> - The exact size of the attribute
430
+ # * <tt>within</tt> - A range specifying the minimum and maximum size of the attribute
431
+ # * <tt>in</tt> - A synonym(or alias) for :within
432
+ # * <tt>allow_nil</tt> - Attribute may be nil; skip validation.
433
+ #
434
+ # * <tt>too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (maximum is %d characters)")
435
+ # * <tt>too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is %d characters)")
436
+ # * <tt>wrong_length</tt> - The error message if using the :is method and the attribute is the wrong size (default is: "is the wrong length (should be %d characters)")
437
+ # * <tt>message</tt> - The error message to use for a :minimum, :maximum, or :is violation. An alias of the appropriate too_long/too_short/wrong_length message
438
+ # * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
439
+ # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
440
+ # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
441
+ # method, proc or string should return or evaluate to a true or false value.
442
+ def validates_length_of(*attrs)
443
+ # Merge given options with defaults.
444
+ options = {
445
+ :too_long => ActiveRecord::Errors.default_error_messages[:too_long],
446
+ :too_short => ActiveRecord::Errors.default_error_messages[:too_short],
447
+ :wrong_length => ActiveRecord::Errors.default_error_messages[:wrong_length]
448
+ }.merge(DEFAULT_VALIDATION_OPTIONS)
449
+ options.update(attrs.pop.symbolize_keys) if attrs.last.is_a?(Hash)
450
+
451
+ # Ensure that one and only one range option is specified.
452
+ range_options = ALL_RANGE_OPTIONS & options.keys
453
+ case range_options.size
454
+ when 0
455
+ raise ArgumentError, 'Range unspecified. Specify the :within, :maximum, :minimum, or :is option.'
456
+ when 1
457
+ # Valid number of options; do nothing.
458
+ else
459
+ raise ArgumentError, 'Too many range options specified. Choose only one.'
460
+ end
461
+
462
+ # Get range option and value.
463
+ option = range_options.first
464
+ option_value = options[range_options.first]
465
+
466
+ case option
467
+ when :within, :in
468
+ raise ArgumentError, ":#{option} must be a Range" unless option_value.is_a?(Range)
469
+
470
+ too_short = options[:too_short] % option_value.begin
471
+ too_long = options[:too_long] % option_value.end
472
+
473
+ validates_each(attrs, options) do |record, attr, value|
474
+ if value.nil? or value.split(//).size < option_value.begin
475
+ record.errors.add(attr, too_short)
476
+ elsif value.split(//).size > option_value.end
477
+ record.errors.add(attr, too_long)
478
+ end
479
+ end
480
+ when :is, :minimum, :maximum
481
+ raise ArgumentError, ":#{option} must be a nonnegative Integer" unless option_value.is_a?(Integer) and option_value >= 0
482
+
483
+ # Declare different validations per option.
484
+ validity_checks = { :is => "==", :minimum => ">=", :maximum => "<=" }
485
+ message_options = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }
486
+
487
+ message = (options[:message] || options[message_options[option]]) % option_value
488
+
489
+ validates_each(attrs, options) do |record, attr, value|
490
+ if value.kind_of?(String)
491
+ record.errors.add(attr, message) unless !value.nil? and value.split(//).size.method(validity_checks[option])[option_value]
492
+ else
493
+ record.errors.add(attr, message) unless !value.nil? and value.size.method(validity_checks[option])[option_value]
494
+ end
495
+ end
496
+ end
497
+ end
498
+
499
+ alias_method :validates_size_of, :validates_length_of
500
+
501
+
502
+ # Validates whether the value of the specified attributes are unique across the system. Useful for making sure that only one user
503
+ # can be named "davidhh".
504
+ #
505
+ # class Person < ActiveRecord::Base
506
+ # validates_uniqueness_of :user_name, :scope => :account_id
507
+ # end
508
+ #
509
+ # It can also validate whether the value of the specified attributes are unique based on multiple scope parameters. For example,
510
+ # making sure that a teacher can only be on the schedule once per semester for a particular class.
511
+ #
512
+ # class TeacherSchedule < ActiveRecord::Base
513
+ # validates_uniqueness_of :teacher_id, :scope => [:semester_id, :class_id]
514
+ # end
515
+ #
516
+ # When the record is created, a check is performed to make sure that no record exists in the database with the given value for the specified
517
+ # attribute (that maps to a column). When the record is updated, the same check is made but disregarding the record itself.
518
+ #
519
+ # Configuration options:
520
+ # * <tt>message</tt> - Specifies a custom error message (default is: "has already been taken")
521
+ # * <tt>scope</tt> - One or more columns by which to limit the scope of the uniquness constraint.
522
+ # * <tt>case_sensitive</tt> - Looks for an exact match. Ignored by non-text columns (true by default).
523
+ # * <tt>allow_nil</tt> - If set to true, skips this validation if the attribute is null (default is: false)
524
+ # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
525
+ # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
526
+ # method, proc or string should return or evaluate to a true or false value.
527
+
528
+ def validates_uniqueness_of(*attr_names)
529
+ configuration = { :message => ActiveRecord::Errors.default_error_messages[:taken], :case_sensitive => true }
530
+ configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
531
+
532
+ validates_each(attr_names,configuration) do |record, attr_name, value|
533
+ if value.nil? || (configuration[:case_sensitive] || !columns_hash[attr_name.to_s].text?)
534
+ condition_sql = "#{record.class.table_name}.#{attr_name} #{attribute_condition(value)}"
535
+ condition_params = [value]
536
+ else
537
+ condition_sql = "LOWER(#{record.class.table_name}.#{attr_name}) #{attribute_condition(value)}"
538
+ condition_params = [value.downcase]
539
+ end
540
+ if scope = configuration[:scope]
541
+ Array(scope).map do |scope_item|
542
+ scope_value = record.send(scope_item)
543
+ condition_sql << " AND #{record.class.table_name}.#{scope_item} #{attribute_condition(scope_value)}"
544
+ condition_params << scope_value
545
+ end
546
+ end
547
+ unless record.new_record?
548
+ condition_sql << " AND #{record.class.table_name}.#{record.class.primary_key} <> ?"
549
+ condition_params << record.send(:id)
550
+ end
551
+ if record.class.find(:first, :conditions => [condition_sql, *condition_params])
552
+ record.errors.add(attr_name, configuration[:message])
553
+ end
554
+ end
555
+ end
556
+
557
+
558
+
559
+ # Validates whether the value of the specified attribute is of the correct form by matching it against the regular expression
560
+ # provided.
561
+ #
562
+ # class Person < ActiveRecord::Base
563
+ # validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create
564
+ # end
565
+ #
566
+ # Note: use \A and \Z to match the start and end of the string, ^ and $ match the start/end of a line.
567
+ #
568
+ # A regular expression must be provided or else an exception will be raised.
569
+ #
570
+ # Configuration options:
571
+ # * <tt>message</tt> - A custom error message (default is: "is invalid")
572
+ # * <tt>with</tt> - The regular expression used to validate the format with (note: must be supplied!)
573
+ # * <tt>on</tt> Specifies when this validation is active (default is :save, other options :create, :update)
574
+ # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
575
+ # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
576
+ # method, proc or string should return or evaluate to a true or false value.
577
+ def validates_format_of(*attr_names)
578
+ configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid], :on => :save, :with => nil }
579
+ configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
580
+
581
+ raise(ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash") unless configuration[:with].is_a?(Regexp)
582
+
583
+ validates_each(attr_names, configuration) do |record, attr_name, value|
584
+ record.errors.add(attr_name, configuration[:message]) unless value.to_s =~ configuration[:with]
585
+ end
586
+ end
587
+
588
+ # Validates whether the value of the specified attribute is available in a particular enumerable object.
589
+ #
590
+ # class Person < ActiveRecord::Base
591
+ # validates_inclusion_of :gender, :in=>%w( m f ), :message=>"woah! what are you then!??!!"
592
+ # validates_inclusion_of :age, :in=>0..99
593
+ # end
594
+ #
595
+ # Configuration options:
596
+ # * <tt>in</tt> - An enumerable object of available items
597
+ # * <tt>message</tt> - Specifies a customer error message (default is: "is not included in the list")
598
+ # * <tt>allow_nil</tt> - If set to true, skips this validation if the attribute is null (default is: false)
599
+ # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
600
+ # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
601
+ # method, proc or string should return or evaluate to a true or false value.
602
+ def validates_inclusion_of(*attr_names)
603
+ configuration = { :message => ActiveRecord::Errors.default_error_messages[:inclusion], :on => :save }
604
+ configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
605
+
606
+ enum = configuration[:in] || configuration[:within]
607
+
608
+ raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?("include?")
609
+
610
+ validates_each(attr_names, configuration) do |record, attr_name, value|
611
+ record.errors.add(attr_name, configuration[:message]) unless enum.include?(value)
612
+ end
613
+ end
614
+
615
+ # Validates that the value of the specified attribute is not in a particular enumerable object.
616
+ #
617
+ # class Person < ActiveRecord::Base
618
+ # validates_exclusion_of :username, :in => %w( admin superuser ), :message => "You don't belong here"
619
+ # validates_exclusion_of :age, :in => 30..60, :message => "This site is only for under 30 and over 60"
620
+ # end
621
+ #
622
+ # Configuration options:
623
+ # * <tt>in</tt> - An enumerable object of items that the value shouldn't be part of
624
+ # * <tt>message</tt> - Specifies a customer error message (default is: "is reserved")
625
+ # * <tt>allow_nil</tt> - If set to true, skips this validation if the attribute is null (default is: false)
626
+ # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
627
+ # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
628
+ # method, proc or string should return or evaluate to a true or false value.
629
+ def validates_exclusion_of(*attr_names)
630
+ configuration = { :message => ActiveRecord::Errors.default_error_messages[:exclusion], :on => :save }
631
+ configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
632
+
633
+ enum = configuration[:in] || configuration[:within]
634
+
635
+ raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?("include?")
636
+
637
+ validates_each(attr_names, configuration) do |record, attr_name, value|
638
+ record.errors.add(attr_name, configuration[:message]) if enum.include?(value)
639
+ end
640
+ end
641
+
642
+ # Validates whether the associated object or objects are all valid themselves. Works with any kind of association.
643
+ #
644
+ # class Book < ActiveRecord::Base
645
+ # has_many :pages
646
+ # belongs_to :library
647
+ #
648
+ # validates_associated :pages, :library
649
+ # end
650
+ #
651
+ # Warning: If, after the above definition, you then wrote:
652
+ #
653
+ # class Page < ActiveRecord::Base
654
+ # belongs_to :book
655
+ #
656
+ # validates_associated :book
657
+ # end
658
+ #
659
+ # ...this would specify a circular dependency and cause infinite recursion.
660
+ #
661
+ # NOTE: This validation will not fail if the association hasn't been assigned. If you want to ensure that the association
662
+ # is both present and guaranteed to be valid, you also need to use validates_presence_of.
663
+ #
664
+ # Configuration options:
665
+ # * <tt>on</tt> Specifies when this validation is active (default is :save, other options :create, :update)
666
+ # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
667
+ # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
668
+ # method, proc or string should return or evaluate to a true or false value.
669
+ def validates_associated(*attr_names)
670
+ configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid], :on => :save }
671
+ configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
672
+
673
+ validates_each(attr_names, configuration) do |record, attr_name, value|
674
+ record.errors.add(attr_name, configuration[:message]) unless
675
+ (value.is_a?(Array) ? value : [value]).all? { |r| r.nil? or r.valid? }
676
+ end
677
+ end
678
+
679
+ # Validates whether the value of the specified attribute is numeric by trying to convert it to
680
+ # a float with Kernel.Float (if <tt>integer</tt> is false) or applying it to the regular expression
681
+ # <tt>/\A[\+\-]?\d+\Z/</tt> (if <tt>integer</tt> is set to true).
682
+ #
683
+ # class Person < ActiveRecord::Base
684
+ # validates_numericality_of :value, :on => :create
685
+ # end
686
+ #
687
+ # Configuration options:
688
+ # * <tt>message</tt> - A custom error message (default is: "is not a number")
689
+ # * <tt>on</tt> Specifies when this validation is active (default is :save, other options :create, :update)
690
+ # * <tt>only_integer</tt> Specifies whether the value has to be an integer, e.g. an integral value (default is false)
691
+ # * <tt>allow_nil</tt> Skip validation if attribute is nil (default is false). Notice that for fixnum and float columns empty strings are converted to nil
692
+ # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
693
+ # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
694
+ # method, proc or string should return or evaluate to a true or false value.
695
+ def validates_numericality_of(*attr_names)
696
+ configuration = { :message => ActiveRecord::Errors.default_error_messages[:not_a_number], :on => :save,
697
+ :only_integer => false, :allow_nil => false }
698
+ configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
699
+
700
+ if configuration[:only_integer]
701
+ validates_each(attr_names,configuration) do |record, attr_name,value|
702
+ record.errors.add(attr_name, configuration[:message]) unless record.send("#{attr_name}_before_type_cast").to_s =~ /\A[+-]?\d+\Z/
703
+ end
704
+ else
705
+ validates_each(attr_names,configuration) do |record, attr_name,value|
706
+ next if configuration[:allow_nil] and record.send("#{attr_name}_before_type_cast").nil?
707
+ begin
708
+ Kernel.Float(record.send("#{attr_name}_before_type_cast").to_s)
709
+ rescue ArgumentError, TypeError
710
+ record.errors.add(attr_name, configuration[:message])
711
+ end
712
+ end
713
+ end
714
+ end
715
+
716
+
717
+ # Creates an object just like Base.create but calls save! instead of save
718
+ # so an exception is raised if the record is invalid.
719
+ def create!(attributes = nil)
720
+ if attributes.is_a?(Array)
721
+ attributes.collect { |attr| create!(attr) }
722
+ else
723
+ attributes ||= {}
724
+ attributes.reverse_merge!(scope(:create)) if scoped?(:create)
725
+
726
+ object = new(attributes)
727
+ object.save!
728
+ object
729
+ end
730
+ end
731
+
732
+
733
+ private
734
+ def write_inheritable_set(key, methods)
735
+ existing_methods = read_inheritable_attribute(key) || []
736
+ write_inheritable_attribute(key, methods | existing_methods)
737
+ end
738
+
739
+ def validation_method(on)
740
+ case on
741
+ when :save then :validate
742
+ when :create then :validate_on_create
743
+ when :update then :validate_on_update
744
+ end
745
+ end
746
+ end
747
+
748
+ # The validation process on save can be skipped by passing false. The regular Base#save method is
749
+ # replaced with this when the validations module is mixed in, which it is by default.
750
+ def save_with_validation(perform_validation = true)
751
+ if perform_validation && valid? || !perform_validation
752
+ save_without_validation
753
+ else
754
+ false
755
+ end
756
+ end
757
+
758
+ # Attempts to save the record just like Base#save but will raise a RecordInvalid exception instead of returning false
759
+ # if the record is not valid.
760
+ def save_with_validation!
761
+ if valid?
762
+ save_without_validation!
763
+ else
764
+ raise RecordInvalid.new(self)
765
+ end
766
+ end
767
+
768
+ # Updates a single attribute and saves the record without going through the normal validation procedure.
769
+ # This is especially useful for boolean flags on existing records. The regular +update_attribute+ method
770
+ # in Base is replaced with this when the validations module is mixed in, which it is by default.
771
+ def update_attribute_with_validation_skipping(name, value)
772
+ send(name.to_s + '=', value)
773
+ save(false)
774
+ end
775
+
776
+ # Runs validate and validate_on_create or validate_on_update and returns true if no errors were added otherwise false.
777
+ def valid?
778
+ errors.clear
779
+
780
+ run_validations(:validate)
781
+ validate
782
+
783
+ if new_record?
784
+ run_validations(:validate_on_create)
785
+ validate_on_create
786
+ else
787
+ run_validations(:validate_on_update)
788
+ validate_on_update
789
+ end
790
+
791
+ errors.empty?
792
+ end
793
+
794
+ # Returns the Errors object that holds all information about attribute error messages.
795
+ def errors
796
+ @errors ||= Errors.new(self)
797
+ end
798
+
799
+ protected
800
+ # Overwrite this method for validation checks on all saves and use Errors.add(field, msg) for invalid attributes.
801
+ def validate #:doc:
802
+ end
803
+
804
+ # Overwrite this method for validation checks used only on creation.
805
+ def validate_on_create #:doc:
806
+ end
807
+
808
+ # Overwrite this method for validation checks used only on updates.
809
+ def validate_on_update # :doc:
810
+ end
811
+
812
+ private
813
+ def run_validations(validation_method)
814
+ validations = self.class.read_inheritable_attribute(validation_method.to_sym)
815
+ if validations.nil? then return end
816
+ validations.each do |validation|
817
+ if validation.is_a?(Symbol)
818
+ self.send(validation)
819
+ elsif validation.is_a?(String)
820
+ eval(validation, binding)
821
+ elsif validation_block?(validation)
822
+ validation.call(self)
823
+ elsif validation_class?(validation, validation_method)
824
+ validation.send(validation_method, self)
825
+ else
826
+ raise(
827
+ ActiveRecordError,
828
+ "Validations need to be either a symbol, string (to be eval'ed), proc/method, or " +
829
+ "class implementing a static validation method"
830
+ )
831
+ end
832
+ end
833
+ end
834
+
835
+ def validation_block?(validation)
836
+ validation.respond_to?("call") && (validation.arity == 1 || validation.arity == -1)
837
+ end
838
+
839
+ def validation_class?(validation, validation_method)
840
+ validation.respond_to?(validation_method)
841
+ end
842
+ end
843
+ end