activerecord 1.11.1 → 1.12.1

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 (102) hide show
  1. data/CHANGELOG +198 -0
  2. data/lib/active_record.rb +19 -14
  3. data/lib/active_record/acts/list.rb +8 -6
  4. data/lib/active_record/acts/tree.rb +33 -10
  5. data/lib/active_record/aggregations.rb +1 -7
  6. data/lib/active_record/associations.rb +151 -82
  7. data/lib/active_record/associations/association_collection.rb +25 -0
  8. data/lib/active_record/associations/association_proxy.rb +9 -8
  9. data/lib/active_record/associations/belongs_to_association.rb +19 -5
  10. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +44 -69
  11. data/lib/active_record/associations/has_many_association.rb +6 -14
  12. data/lib/active_record/associations/has_one_association.rb +5 -3
  13. data/lib/active_record/base.rb +344 -130
  14. data/lib/active_record/callbacks.rb +2 -2
  15. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +128 -0
  16. data/lib/active_record/connection_adapters/abstract/database_statements.rb +104 -0
  17. data/lib/active_record/connection_adapters/abstract/quoting.rb +51 -0
  18. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +249 -0
  19. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +245 -0
  20. data/lib/active_record/connection_adapters/abstract_adapter.rb +29 -464
  21. data/lib/active_record/connection_adapters/db2_adapter.rb +40 -10
  22. data/lib/active_record/connection_adapters/mysql_adapter.rb +131 -60
  23. data/lib/active_record/connection_adapters/oci_adapter.rb +106 -26
  24. data/lib/active_record/connection_adapters/postgresql_adapter.rb +211 -62
  25. data/lib/active_record/connection_adapters/sqlite_adapter.rb +193 -44
  26. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +24 -15
  27. data/lib/active_record/fixtures.rb +47 -24
  28. data/lib/active_record/migration.rb +34 -5
  29. data/lib/active_record/observer.rb +32 -2
  30. data/lib/active_record/query_cache.rb +12 -11
  31. data/lib/active_record/schema.rb +58 -0
  32. data/lib/active_record/schema_dumper.rb +84 -0
  33. data/lib/active_record/transactions.rb +1 -3
  34. data/lib/active_record/validations.rb +40 -26
  35. data/lib/active_record/vendor/mysql.rb +6 -0
  36. data/lib/active_record/version.rb +9 -0
  37. data/rakefile +5 -16
  38. data/test/abstract_unit.rb +6 -11
  39. data/test/adapter_test.rb +58 -0
  40. data/test/ar_schema_test.rb +33 -0
  41. data/test/association_callbacks_test.rb +14 -0
  42. data/test/associations_go_eager_test.rb +56 -14
  43. data/test/associations_test.rb +245 -25
  44. data/test/base_test.rb +205 -34
  45. data/test/binary_test.rb +25 -42
  46. data/test/callbacks_test.rb +75 -0
  47. data/test/conditions_scoping_test.rb +136 -0
  48. data/test/connections/native_mysql/connection.rb +0 -4
  49. data/test/connections/native_sqlite3/in_memory_connection.rb +17 -0
  50. data/test/copy_table_sqlite.rb +64 -0
  51. data/test/deprecated_associations_test.rb +7 -6
  52. data/test/deprecated_finder_test.rb +3 -3
  53. data/test/finder_test.rb +33 -3
  54. data/test/fixtures/accounts.yml +5 -0
  55. data/test/fixtures/categories_ordered.yml +7 -0
  56. data/test/fixtures/category.rb +11 -1
  57. data/test/fixtures/comment.rb +22 -2
  58. data/test/fixtures/comments.yml +6 -0
  59. data/test/fixtures/companies.yml +15 -0
  60. data/test/fixtures/company.rb +24 -1
  61. data/test/fixtures/db_definitions/db2.drop.sql +5 -1
  62. data/test/fixtures/db_definitions/db2.sql +15 -1
  63. data/test/fixtures/db_definitions/mysql.drop.sql +2 -0
  64. data/test/fixtures/db_definitions/mysql.sql +17 -2
  65. data/test/fixtures/db_definitions/oci.drop.sql +37 -5
  66. data/test/fixtures/db_definitions/oci.sql +47 -4
  67. data/test/fixtures/db_definitions/oci2.drop.sql +1 -1
  68. data/test/fixtures/db_definitions/oci2.sql +2 -2
  69. data/test/fixtures/db_definitions/postgresql.drop.sql +4 -0
  70. data/test/fixtures/db_definitions/postgresql.sql +33 -4
  71. data/test/fixtures/db_definitions/sqlite.drop.sql +2 -0
  72. data/test/fixtures/db_definitions/sqlite.sql +16 -2
  73. data/test/fixtures/db_definitions/sqlserver.drop.sql +2 -0
  74. data/test/fixtures/db_definitions/sqlserver.sql +16 -2
  75. data/test/fixtures/developer.rb +1 -1
  76. data/test/fixtures/flowers.jpg +0 -0
  77. data/test/fixtures/keyboard.rb +3 -0
  78. data/test/fixtures/mixins.yml +11 -1
  79. data/test/fixtures/order.rb +4 -0
  80. data/test/fixtures/post.rb +4 -0
  81. data/test/fixtures/posts.yml +7 -0
  82. data/test/fixtures/project.rb +1 -0
  83. data/test/fixtures/subject.rb +4 -0
  84. data/test/fixtures/subscriber.rb +2 -4
  85. data/test/fixtures/topics.yml +2 -2
  86. data/test/fixtures_test.rb +79 -7
  87. data/test/inheritance_test.rb +2 -2
  88. data/test/lifecycle_test.rb +14 -6
  89. data/test/migration_test.rb +164 -6
  90. data/test/mixin_test.rb +78 -2
  91. data/test/pk_test.rb +25 -1
  92. data/test/readonly_test.rb +31 -0
  93. data/test/reflection_test.rb +4 -1
  94. data/test/schema_dumper_test.rb +19 -0
  95. data/test/schema_test_postgresql.rb +3 -2
  96. data/test/synonym_test_oci.rb +17 -0
  97. data/test/threaded_connections_test.rb +2 -1
  98. data/test/transactions_test.rb +109 -10
  99. data/test/validations_test.rb +70 -42
  100. metadata +25 -5
  101. data/test/fixtures/associations.png +0 -0
  102. data/test/thread_safety_test.rb +0 -36
@@ -16,7 +16,7 @@ module ActiveRecord
16
16
  def self.sqlserver_connection(config) #:nodoc:
17
17
  require_library_or_gem 'dbi' unless self.class.const_defined?(:DBI)
18
18
 
19
- symbolize_strings_in_hash(config)
19
+ config = config.symbolize_keys
20
20
 
21
21
  mode = config[:mode] ? config[:mode].to_s.upcase : 'ADO'
22
22
  username = config[:username] ? config[:username].to_s : 'sa'
@@ -98,7 +98,7 @@ module ActiveRecord
98
98
 
99
99
  # These methods will only allow the adapter to insert binary data with a length of 7K or less
100
100
  # because of a SQL Server statement length policy.
101
- def string_to_binary(value)
101
+ def self.string_to_binary(value)
102
102
  value.gsub(/(\r|\n|\0|\x1a)/) do
103
103
  case $1
104
104
  when "\r" then "%00"
@@ -109,7 +109,7 @@ module ActiveRecord
109
109
  end
110
110
  end
111
111
 
112
- def binary_to_string(value)
112
+ def self.binary_to_string(value)
113
113
  value.gsub(/(%00|%01|%02|%03)/) do
114
114
  case $1
115
115
  when "%00" then "\r"
@@ -165,16 +165,16 @@ module ActiveRecord
165
165
  def native_database_types
166
166
  {
167
167
  :primary_key => "int NOT NULL IDENTITY(1, 1) PRIMARY KEY",
168
- :string => { :name => "varchar(255)" },
169
- :text => { :name => "text(16)" },
170
- :integer => { :name => "int(4)", :limit => 11 },
171
- :float => { :name => "float(8)" },
172
- :datetime => { :name => "datetime(8)" },
173
- :timestamp => { :name => "datetime(8)" },
174
- :time => { :name => "datetime(8)" },
175
- :date => { :name => "datetime(8)" },
176
- :binary => { :name => "image(16)" },
177
- :boolean => { :name => "bit(1)" }
168
+ :string => { :name => "varchar", :limit => 255 },
169
+ :text => { :name => "text" },
170
+ :integer => { :name => "int"},
171
+ :float => { :name => "float", :limit => 8 },
172
+ :datetime => { :name => "datetime" },
173
+ :timestamp => { :name => "datetime" },
174
+ :time => { :name => "datetime" },
175
+ :date => { :name => "datetime" },
176
+ :binary => { :name => "image"},
177
+ :boolean => { :name => "bit"}
178
178
  }
179
179
  end
180
180
 
@@ -204,7 +204,7 @@ module ActiveRecord
204
204
  columns
205
205
  end
206
206
 
207
- def insert(sql, name = nil, pk = nil, id_value = nil)
207
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
208
208
  begin
209
209
  table_name = get_table_name(sql)
210
210
  col = get_identity_column(table_name)
@@ -275,7 +275,7 @@ module ActiveRecord
275
275
  case value
276
276
  when String
277
277
  if column && column.type == :binary
278
- "'#{quote_string(column.string_to_binary(value))}'"
278
+ "'#{quote_string(column.class.string_to_binary(value))}'"
279
279
  else
280
280
  "'#{quote_string(value)}'"
281
281
  end
@@ -293,6 +293,14 @@ module ActiveRecord
293
293
  string.gsub(/\'/, "''")
294
294
  end
295
295
 
296
+ def quoted_true
297
+ "1"
298
+ end
299
+
300
+ def quoted_false
301
+ "0"
302
+ end
303
+
296
304
  def quote_column_name(name)
297
305
  "[#{name}]"
298
306
  end
@@ -332,6 +340,7 @@ module ActiveRecord
332
340
  record = {}
333
341
  row.column_names.each do |col|
334
342
  record[col] = row[col]
343
+ record[col] = record[col].to_time if record[col].is_a? DBI::Timestamp
335
344
  end
336
345
  rows << record
337
346
  end
@@ -2,6 +2,13 @@ require 'erb'
2
2
  require 'yaml'
3
3
  require 'csv'
4
4
 
5
+ module YAML #:nodoc:
6
+ class Omap #:nodoc:
7
+ def keys; map { |k, v| k } end
8
+ def values; map { |k, v| v } end
9
+ end
10
+ end
11
+
5
12
  # Fixtures are a way of organizing data that you want to test against; in short, sample data. They come in 3 flavours:
6
13
  #
7
14
  # 1. YAML fixtures
@@ -32,6 +39,20 @@ require 'csv'
32
39
  # indented list of key/value pairs in the "key: value" format. Records are separated by a blank line for your viewing
33
40
  # pleasure.
34
41
  #
42
+ # Note that YAML fixtures are unordered. If you want ordered fixtures, use the omap YAML type. See http://yaml.org/type/omap.html
43
+ # for the specification. You will need ordered fixtures when you have foreign key constraints on keys in the same table.
44
+ # This is commonly needed for tree structures. Example:
45
+ #
46
+ # --- !omap
47
+ # - parent:
48
+ # id: 1
49
+ # parent_id: NULL
50
+ # title: Parent
51
+ # - child:
52
+ # id: 2
53
+ # parent_id: 1
54
+ # title: Child
55
+ #
35
56
  # = CSV fixtures
36
57
  #
37
58
  # Fixtures can also be kept in the Comma Separated Value format. Akin to YAML fixtures, CSV fixtures are stored
@@ -191,7 +212,7 @@ require 'csv'
191
212
  # the results of your transaction until Active Record supports nested transactions or savepoints (in progress.)
192
213
  # 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM.
193
214
  # Use InnoDB, MaxDB, or NDB instead.
194
- class Fixtures < Hash
215
+ class Fixtures < YAML::Omap
195
216
  DEFAULT_FILTER_RE = /\.ya?ml$/
196
217
 
197
218
  def self.instantiate_fixtures(object, table_name, fixtures, load_instances=true)
@@ -232,12 +253,18 @@ class Fixtures < Hash
232
253
  end
233
254
  all_loaded_fixtures.merge! fixtures_map
234
255
 
256
+
235
257
  connection.transaction do
236
258
  fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures }
237
259
  fixtures.each { |fixture| fixture.insert_fixtures }
238
260
  end
239
-
240
- reset_sequences(connection, table_names) if connection.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
261
+
262
+ # Cap primary key sequences to max(pk).
263
+ if connection.respond_to?(:reset_pk_sequence!)
264
+ table_names.flatten.each do |table_name|
265
+ connection.reset_pk_sequence!(table_name)
266
+ end
267
+ end
241
268
 
242
269
  return fixtures.size > 1 ? fixtures : fixtures.first
243
270
  ensure
@@ -245,28 +272,14 @@ class Fixtures < Hash
245
272
  end
246
273
  end
247
274
 
248
- # Work around for PostgreSQL to have new fixtures created from id 1 and running.
249
- def self.reset_sequences(connection, table_names)
250
- table_names.flatten.each do |table|
251
- table_class = Inflector.classify(table.to_s)
252
- if Object.const_defined?(table_class)
253
- pk = eval("#{table_class}::primary_key")
254
- if pk == 'id'
255
- connection.execute(
256
- "SELECT setval('#{table.to_s}_id_seq', (SELECT MAX(id) FROM #{table.to_s}), true)",
257
- 'Setting Sequence'
258
- )
259
- end
260
- end
261
- end
262
- end
263
275
 
264
276
  attr_reader :table_name
265
277
 
266
278
  def initialize(connection, table_name, fixture_path, file_filter = DEFAULT_FILTER_RE)
267
279
  @connection, @table_name, @fixture_path, @file_filter = connection, table_name, fixture_path, file_filter
268
- @class_name = Inflector.classify(@table_name)
269
280
 
281
+ @class_name = Inflector.classify(@table_name)
282
+ @table_name = ActiveRecord::Base.table_name_prefix + @table_name + ActiveRecord::Base.table_name_suffix
270
283
  read_fixture_files
271
284
  end
272
285
 
@@ -285,8 +298,12 @@ class Fixtures < Hash
285
298
  if File.file?(yaml_file_path)
286
299
  # YAML fixtures
287
300
  begin
288
- yaml = YAML::load(erb_render(IO.read(yaml_file_path)))
289
- yaml.each { |name, data| self[name] = Fixture.new(data, @class_name) } if yaml
301
+ if yaml = YAML::load(erb_render(IO.read(yaml_file_path)))
302
+ yaml = yaml.value if yaml.respond_to?(:type_id) and yaml.respond_to?(:value)
303
+ yaml.each do |name, data|
304
+ self[name] = Fixture.new(data, @class_name)
305
+ end
306
+ end
290
307
  rescue Exception=>boom
291
308
  raise Fixture::FormatError, "a YAML error occured parsing #{yaml_file_path}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{boom.class}: #{boom}"
292
309
  end
@@ -342,7 +359,15 @@ class Fixture #:nodoc:
342
359
  end
343
360
 
344
361
  def initialize(fixture, class_name)
345
- @fixture = fixture.is_a?(Hash) ? fixture : read_fixture_file(fixture)
362
+ case fixture
363
+ when Hash, YAML::Omap
364
+ @fixture = fixture
365
+ when String
366
+ @fixture = read_fixture_file(fixture)
367
+ else
368
+ raise ArgumentError, "Bad fixture argument #{fixture.inspect}"
369
+ end
370
+
346
371
  @class_name = class_name
347
372
  end
348
373
 
@@ -397,8 +422,6 @@ end
397
422
  module Test #:nodoc:
398
423
  module Unit #:nodoc:
399
424
  class TestCase #:nodoc:
400
- include ClassInheritableAttributes
401
-
402
425
  cattr_accessor :fixture_path
403
426
  class_inheritable_accessor :fixture_table_names
404
427
  class_inheritable_accessor :use_transactional_fixtures
@@ -63,7 +63,7 @@ module ActiveRecord
63
63
  # * <tt>change_column(table_name, column_name, type, options)</tt>: Changes the column to a different type using the same
64
64
  # parameters as add_column.
65
65
  # * <tt>remove_column(table_name, column_name)</tt>: Removes the column named +column_name+ from the table called +table_name+.
66
- # * <tt>add_index(table_name, column_name)</tt>: Add a new index with the name of the column on the column.
66
+ # * <tt>add_index(table_name, column_name, index_type)</tt>: Add a new index with the name of the column on the column. Specify an optional index_type (e.g. UNIQUE).
67
67
  # * <tt>remove_index(table_name, column_name)</tt>: Remove the index called the same as the column.
68
68
  #
69
69
  # == Irreversible transformations
@@ -120,6 +120,21 @@ module ActiveRecord
120
120
  # execute "ALTER TABLE `pages_linked_pages` DROP INDEX `page_id_linked_page_id`"
121
121
  # end
122
122
  # end
123
+ #
124
+ # == Using the class after changing table
125
+ #
126
+ # Some times you'll want to add a column in a migration and populate it immediately after. In that case, you'll need
127
+ # to make a call to Base#reset_column_information in order to ensure that the class has the latest column data from
128
+ # after the new column was added. Example:
129
+ #
130
+ # class MakeJoinUnique < ActiveRecord::Migration
131
+ # def self.up
132
+ # add_column :people, :salary, :integer
133
+ # Person.find(:all).each do |p|
134
+ # p.salary = SalaryCalculator.compute(p)
135
+ # end
136
+ # end
137
+ # end
123
138
  class Migration
124
139
  class << self
125
140
  def up() end
@@ -127,6 +142,7 @@ module ActiveRecord
127
142
 
128
143
  private
129
144
  def method_missing(method, *arguments, &block)
145
+ arguments[0] = Migrator.proper_table_name(arguments.first) unless arguments.empty?
130
146
  ActiveRecord::Base.connection.send(method, *arguments, &block)
131
147
  end
132
148
  end
@@ -135,6 +151,7 @@ module ActiveRecord
135
151
  class Migrator#:nodoc:
136
152
  class << self
137
153
  def migrate(migrations_path, target_version = nil)
154
+ Base.connection.initialize_schema_information
138
155
  case
139
156
  when target_version.nil?, current_version < target_version
140
157
  up(migrations_path, target_version)
@@ -153,9 +170,19 @@ module ActiveRecord
153
170
  self.new(:down, migrations_path, target_version).migrate
154
171
  end
155
172
 
173
+ def schema_info_table_name
174
+ Base.table_name_prefix + "schema_info" + Base.table_name_suffix
175
+ end
176
+
156
177
  def current_version
157
- Base.connection.select_one("SELECT version FROM schema_info")["version"].to_i
178
+ (Base.connection.select_one("SELECT version FROM #{schema_info_table_name}") || {"version" => 0})["version"].to_i
179
+ end
180
+
181
+ def proper_table_name(name)
182
+ # Use the ActiveRecord objects own table_name, or pre/suffix from ActiveRecord::Base if name is a symbol/string
183
+ name.table_name rescue "#{ActiveRecord::Base.table_name_prefix}#{name}#{ActiveRecord::Base.table_name_suffix}"
158
184
  end
185
+
159
186
  end
160
187
 
161
188
  def initialize(direction, migrations_path, target_version = nil)
@@ -191,10 +218,12 @@ module ActiveRecord
191
218
  end
192
219
 
193
220
  def migration_files
194
- files = Dir["#{@migrations_path}/[0-9]*_*.rb"].sort
221
+ files = Dir["#{@migrations_path}/[0-9]*_*.rb"].sort_by do |f|
222
+ migration_version_and_name(f).first.to_i
223
+ end
195
224
  down? ? files.reverse : files
196
225
  end
197
-
226
+
198
227
  def migration_class(migration_name)
199
228
  migration_name.camelize.constantize
200
229
  end
@@ -204,7 +233,7 @@ module ActiveRecord
204
233
  end
205
234
 
206
235
  def set_schema_version(version)
207
- Base.connection.update("UPDATE schema_info SET version = #{down? ? version.to_i - 1 : version.to_i}")
236
+ Base.connection.update("UPDATE #{self.class.schema_info_table_name} SET version = #{down? ? version.to_i - 1 : version.to_i}")
208
237
  end
209
238
 
210
239
  def up?
@@ -1,6 +1,33 @@
1
1
  require 'singleton'
2
2
 
3
3
  module ActiveRecord
4
+ module Observing # :nodoc:
5
+ def self.append_features(base)
6
+ super
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+ # Activates the observers assigned. Examples:
12
+ #
13
+ # # Calls PersonObserver.instance
14
+ # ActiveRecord::Base.observers = :person_observer
15
+ #
16
+ # # Calls Cacher.instance and GarbageCollector.instance
17
+ # ActiveRecord::Base.observers = :cacher, :garbage_collector
18
+ #
19
+ # # Same as above, just using explicit class references
20
+ # ActiveRecord::Base.observers = Cacher, GarbageCollector
21
+ def observers=(*observers)
22
+ observers = [ observers ].flatten.each do |observer|
23
+ observer.is_a?(Symbol) ?
24
+ observer.to_s.camelize.constantize.instance :
25
+ observer.instance
26
+ end
27
+ end
28
+ end
29
+ end
30
+
4
31
  # Observer classes respond to lifecycle callbacks to implement trigger-like
5
32
  # behavior outside the original class. This is a great way to reduce the
6
33
  # clutter that normally comes when the model class is burdened with
@@ -48,7 +75,8 @@ module ActiveRecord
48
75
  # == Triggering Observers
49
76
  #
50
77
  # In order to activate an observer, you need to call Observer.instance. In Rails, this can be done in controllers
51
- # using the short-hand of for example observer :comment_observer.
78
+ # using the short-hand of for example observer :comment_observer. Or directly from Active Record, with
79
+ # ActiveRecord::Base.observer(:comment_observer).
52
80
  class Observer
53
81
  include Singleton
54
82
 
@@ -58,7 +86,9 @@ module ActiveRecord
58
86
  end
59
87
 
60
88
  def initialize
61
- [ observed_class ].flatten.each do |klass|
89
+ observed_classes = [ observed_class ].flatten
90
+ observed_subclasses_class = observed_classes.collect {|c| c.send(:subclasses) }.flatten!
91
+ (observed_classes + observed_subclasses_class).each do |klass|
62
92
  klass.add_observer(self)
63
93
  klass.send(:define_method, :after_find) unless klass.respond_to?(:after_find)
64
94
  end
@@ -10,7 +10,7 @@ module ActiveRecord
10
10
  end
11
11
 
12
12
  def select_all(sql, name = nil)
13
- @query_cache[sql] ||= @connection.select_all(sql, name)
13
+ (@query_cache[sql] ||= @connection.select_all(sql, name)).dup
14
14
  end
15
15
 
16
16
  def select_one(sql, name = nil)
@@ -37,21 +37,22 @@ module ActiveRecord
37
37
  end
38
38
 
39
39
  private
40
- def method_missing(method, *arguments)
41
- @connection.send(method, *arguments)
40
+ def method_missing(method, *arguments, &proc)
41
+ @connection.send(method, *arguments, &proc)
42
42
  end
43
43
  end
44
44
 
45
45
  class Base
46
46
  # Set the connection for the class with caching on
47
- def self.connection=(spec)
48
- raise ConnectionNotEstablished unless spec
49
-
50
- conn = spec.config[:query_cache] ?
51
- QueryCache.new(self.send(spec.adapter_method, spec.config)) :
52
- self.send(spec.adapter_method, spec.config)
53
-
54
- active_connections[self] = conn
47
+ class << self
48
+ alias_method :connection_without_query_cache=, :connection=
49
+
50
+ def connection=(spec)
51
+ if spec.is_a?(ConnectionSpecification) and spec.config[:query_cache]
52
+ spec = QueryCache.new(self.send(spec.adapter_method, spec.config))
53
+ end
54
+ self.connection_without_query_cache = spec
55
+ end
55
56
  end
56
57
  end
57
58
 
@@ -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 = quote(v, cols.detect { |c| c.name == k.to_s })
51
+ "#{k} = #{v}"
52
+ end
53
+
54
+ update "UPDATE schema_info SET #{info.join(", ")}"
55
+ end
56
+ end
57
+ end
58
+ end