sskirby-activerecord 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. data/CHANGELOG.md +6749 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +222 -0
  4. data/examples/associations.png +0 -0
  5. data/examples/performance.rb +177 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record.rb +147 -0
  8. data/lib/active_record/aggregations.rb +255 -0
  9. data/lib/active_record/associations.rb +1604 -0
  10. data/lib/active_record/associations/alias_tracker.rb +79 -0
  11. data/lib/active_record/associations/association.rb +239 -0
  12. data/lib/active_record/associations/association_scope.rb +119 -0
  13. data/lib/active_record/associations/belongs_to_association.rb +79 -0
  14. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +34 -0
  15. data/lib/active_record/associations/builder/association.rb +55 -0
  16. data/lib/active_record/associations/builder/belongs_to.rb +85 -0
  17. data/lib/active_record/associations/builder/collection_association.rb +75 -0
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +57 -0
  19. data/lib/active_record/associations/builder/has_many.rb +71 -0
  20. data/lib/active_record/associations/builder/has_one.rb +62 -0
  21. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  22. data/lib/active_record/associations/collection_association.rb +574 -0
  23. data/lib/active_record/associations/collection_proxy.rb +132 -0
  24. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +62 -0
  25. data/lib/active_record/associations/has_many_association.rb +108 -0
  26. data/lib/active_record/associations/has_many_through_association.rb +180 -0
  27. data/lib/active_record/associations/has_one_association.rb +73 -0
  28. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  29. data/lib/active_record/associations/join_dependency.rb +214 -0
  30. data/lib/active_record/associations/join_dependency/join_association.rb +154 -0
  31. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  32. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  33. data/lib/active_record/associations/join_helper.rb +55 -0
  34. data/lib/active_record/associations/preloader.rb +177 -0
  35. data/lib/active_record/associations/preloader/association.rb +127 -0
  36. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  37. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  38. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  39. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  40. data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
  41. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  42. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  43. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  44. data/lib/active_record/associations/preloader/through_association.rb +67 -0
  45. data/lib/active_record/associations/singular_association.rb +64 -0
  46. data/lib/active_record/associations/through_association.rb +83 -0
  47. data/lib/active_record/attribute_assignment.rb +221 -0
  48. data/lib/active_record/attribute_methods.rb +272 -0
  49. data/lib/active_record/attribute_methods/before_type_cast.rb +31 -0
  50. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  51. data/lib/active_record/attribute_methods/dirty.rb +101 -0
  52. data/lib/active_record/attribute_methods/primary_key.rb +114 -0
  53. data/lib/active_record/attribute_methods/query.rb +39 -0
  54. data/lib/active_record/attribute_methods/read.rb +135 -0
  55. data/lib/active_record/attribute_methods/serialization.rb +93 -0
  56. data/lib/active_record/attribute_methods/time_zone_conversion.rb +62 -0
  57. data/lib/active_record/attribute_methods/write.rb +69 -0
  58. data/lib/active_record/autosave_association.rb +422 -0
  59. data/lib/active_record/base.rb +716 -0
  60. data/lib/active_record/callbacks.rb +275 -0
  61. data/lib/active_record/coders/yaml_column.rb +41 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +188 -0
  64. data/lib/active_record/connection_adapters/abstract/database_limits.rb +58 -0
  65. data/lib/active_record/connection_adapters/abstract/database_statements.rb +388 -0
  66. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -0
  67. data/lib/active_record/connection_adapters/abstract/quoting.rb +115 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +492 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +598 -0
  70. data/lib/active_record/connection_adapters/abstract_adapter.rb +296 -0
  71. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
  72. data/lib/active_record/connection_adapters/column.rb +270 -0
  73. data/lib/active_record/connection_adapters/mysql2_adapter.rb +288 -0
  74. data/lib/active_record/connection_adapters/mysql_adapter.rb +426 -0
  75. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1261 -0
  76. data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
  77. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -0
  78. data/lib/active_record/connection_adapters/sqlite_adapter.rb +577 -0
  79. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  80. data/lib/active_record/counter_cache.rb +119 -0
  81. data/lib/active_record/dynamic_finder_match.rb +56 -0
  82. data/lib/active_record/dynamic_matchers.rb +79 -0
  83. data/lib/active_record/dynamic_scope_match.rb +23 -0
  84. data/lib/active_record/errors.rb +195 -0
  85. data/lib/active_record/explain.rb +85 -0
  86. data/lib/active_record/explain_subscriber.rb +21 -0
  87. data/lib/active_record/fixtures.rb +906 -0
  88. data/lib/active_record/fixtures/file.rb +65 -0
  89. data/lib/active_record/identity_map.rb +156 -0
  90. data/lib/active_record/inheritance.rb +167 -0
  91. data/lib/active_record/integration.rb +49 -0
  92. data/lib/active_record/locale/en.yml +40 -0
  93. data/lib/active_record/locking/optimistic.rb +183 -0
  94. data/lib/active_record/locking/pessimistic.rb +77 -0
  95. data/lib/active_record/log_subscriber.rb +68 -0
  96. data/lib/active_record/migration.rb +765 -0
  97. data/lib/active_record/migration/command_recorder.rb +105 -0
  98. data/lib/active_record/model_schema.rb +366 -0
  99. data/lib/active_record/nested_attributes.rb +469 -0
  100. data/lib/active_record/observer.rb +121 -0
  101. data/lib/active_record/persistence.rb +372 -0
  102. data/lib/active_record/query_cache.rb +74 -0
  103. data/lib/active_record/querying.rb +58 -0
  104. data/lib/active_record/railtie.rb +119 -0
  105. data/lib/active_record/railties/console_sandbox.rb +6 -0
  106. data/lib/active_record/railties/controller_runtime.rb +49 -0
  107. data/lib/active_record/railties/databases.rake +620 -0
  108. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  109. data/lib/active_record/readonly_attributes.rb +26 -0
  110. data/lib/active_record/reflection.rb +534 -0
  111. data/lib/active_record/relation.rb +534 -0
  112. data/lib/active_record/relation/batches.rb +90 -0
  113. data/lib/active_record/relation/calculations.rb +354 -0
  114. data/lib/active_record/relation/delegation.rb +49 -0
  115. data/lib/active_record/relation/finder_methods.rb +398 -0
  116. data/lib/active_record/relation/predicate_builder.rb +58 -0
  117. data/lib/active_record/relation/query_methods.rb +417 -0
  118. data/lib/active_record/relation/spawn_methods.rb +148 -0
  119. data/lib/active_record/result.rb +34 -0
  120. data/lib/active_record/sanitization.rb +194 -0
  121. data/lib/active_record/schema.rb +58 -0
  122. data/lib/active_record/schema_dumper.rb +204 -0
  123. data/lib/active_record/scoping.rb +152 -0
  124. data/lib/active_record/scoping/default.rb +142 -0
  125. data/lib/active_record/scoping/named.rb +202 -0
  126. data/lib/active_record/serialization.rb +18 -0
  127. data/lib/active_record/serializers/xml_serializer.rb +202 -0
  128. data/lib/active_record/session_store.rb +358 -0
  129. data/lib/active_record/store.rb +50 -0
  130. data/lib/active_record/test_case.rb +73 -0
  131. data/lib/active_record/timestamp.rb +113 -0
  132. data/lib/active_record/transactions.rb +360 -0
  133. data/lib/active_record/translation.rb +22 -0
  134. data/lib/active_record/validations.rb +83 -0
  135. data/lib/active_record/validations/associated.rb +43 -0
  136. data/lib/active_record/validations/uniqueness.rb +180 -0
  137. data/lib/active_record/version.rb +10 -0
  138. data/lib/rails/generators/active_record.rb +25 -0
  139. data/lib/rails/generators/active_record/migration.rb +15 -0
  140. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  141. data/lib/rails/generators/active_record/migration/templates/migration.rb +31 -0
  142. data/lib/rails/generators/active_record/model/model_generator.rb +43 -0
  143. data/lib/rails/generators/active_record/model/templates/migration.rb +15 -0
  144. data/lib/rails/generators/active_record/model/templates/model.rb +7 -0
  145. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  146. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  147. data/lib/rails/generators/active_record/observer/templates/observer.rb +4 -0
  148. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +25 -0
  149. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +12 -0
  150. metadata +242 -0
@@ -0,0 +1,34 @@
1
+ module ActiveRecord
2
+ ###
3
+ # This class encapsulates a Result returned from calling +exec_query+ on any
4
+ # database connection adapter. For example:
5
+ #
6
+ # x = ActiveRecord::Base.connection.exec_query('SELECT * FROM foo')
7
+ # x # => #<ActiveRecord::Result:0xdeadbeef>
8
+ class Result
9
+ include Enumerable
10
+
11
+ attr_reader :columns, :rows
12
+
13
+ def initialize(columns, rows)
14
+ @columns = columns
15
+ @rows = rows
16
+ @hash_rows = nil
17
+ end
18
+
19
+ def each
20
+ hash_rows.each { |row| yield row }
21
+ end
22
+
23
+ def to_hash
24
+ hash_rows
25
+ end
26
+
27
+ private
28
+ def hash_rows
29
+ @hash_rows ||= @rows.map { |row|
30
+ Hash[@columns.zip(row)]
31
+ }
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,194 @@
1
+ require 'active_support/concern'
2
+
3
+ module ActiveRecord
4
+ module Sanitization
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ def quote_value(value, column = nil) #:nodoc:
9
+ connection.quote(value,column)
10
+ end
11
+
12
+ # Used to sanitize objects before they're used in an SQL SELECT statement. Delegates to <tt>connection.quote</tt>.
13
+ def sanitize(object) #:nodoc:
14
+ connection.quote(object)
15
+ end
16
+
17
+ protected
18
+
19
+ # Accepts an array, hash, or string of SQL conditions and sanitizes
20
+ # them into a valid SQL fragment for a WHERE clause.
21
+ # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
22
+ # { :name => "foo'bar", :group_id => 4 } returns "name='foo''bar' and group_id='4'"
23
+ # "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'"
24
+ def sanitize_sql_for_conditions(condition, table_name = self.table_name)
25
+ return nil if condition.blank?
26
+
27
+ case condition
28
+ when Array; sanitize_sql_array(condition)
29
+ when Hash; sanitize_sql_hash_for_conditions(condition, table_name)
30
+ else condition
31
+ end
32
+ end
33
+ alias_method :sanitize_sql, :sanitize_sql_for_conditions
34
+
35
+ # Accepts an array, hash, or string of SQL conditions and sanitizes
36
+ # them into a valid SQL fragment for a SET clause.
37
+ # { :name => nil, :group_id => 4 } returns "name = NULL , group_id='4'"
38
+ def sanitize_sql_for_assignment(assignments)
39
+ case assignments
40
+ when Array; sanitize_sql_array(assignments)
41
+ when Hash; sanitize_sql_hash_for_assignment(assignments)
42
+ else assignments
43
+ end
44
+ end
45
+
46
+ # Accepts a hash of SQL conditions and replaces those attributes
47
+ # that correspond to a +composed_of+ relationship with their expanded
48
+ # aggregate attribute values.
49
+ # Given:
50
+ # class Person < ActiveRecord::Base
51
+ # composed_of :address, :class_name => "Address",
52
+ # :mapping => [%w(address_street street), %w(address_city city)]
53
+ # end
54
+ # Then:
55
+ # { :address => Address.new("813 abc st.", "chicago") }
56
+ # # => { :address_street => "813 abc st.", :address_city => "chicago" }
57
+ def expand_hash_conditions_for_aggregates(attrs)
58
+ expanded_attrs = {}
59
+ attrs.each do |attr, value|
60
+ unless (aggregation = reflect_on_aggregation(attr.to_sym)).nil?
61
+ mapping = aggregate_mapping(aggregation)
62
+ mapping.each do |field_attr, aggregate_attr|
63
+ if mapping.size == 1 && !value.respond_to?(aggregate_attr)
64
+ expanded_attrs[field_attr] = value
65
+ else
66
+ expanded_attrs[field_attr] = value.send(aggregate_attr)
67
+ end
68
+ end
69
+ else
70
+ expanded_attrs[attr] = value
71
+ end
72
+ end
73
+ expanded_attrs
74
+ end
75
+
76
+ # Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause.
77
+ # { :name => "foo'bar", :group_id => 4 }
78
+ # # => "name='foo''bar' and group_id= 4"
79
+ # { :status => nil, :group_id => [1,2,3] }
80
+ # # => "status IS NULL and group_id IN (1,2,3)"
81
+ # { :age => 13..18 }
82
+ # # => "age BETWEEN 13 AND 18"
83
+ # { 'other_records.id' => 7 }
84
+ # # => "`other_records`.`id` = 7"
85
+ # { :other_records => { :id => 7 } }
86
+ # # => "`other_records`.`id` = 7"
87
+ # And for value objects on a composed_of relationship:
88
+ # { :address => Address.new("123 abc st.", "chicago") }
89
+ # # => "address_street='123 abc st.' and address_city='chicago'"
90
+ def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
91
+ attrs = expand_hash_conditions_for_aggregates(attrs)
92
+
93
+ table = Arel::Table.new(table_name).alias(default_table_name)
94
+ PredicateBuilder.build_from_hash(arel_engine, attrs, table).map { |b|
95
+ connection.visitor.accept b
96
+ }.join(' AND ')
97
+ end
98
+ alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
99
+
100
+ # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
101
+ # { :status => nil, :group_id => 1 }
102
+ # # => "status = NULL , group_id = 1"
103
+ def sanitize_sql_hash_for_assignment(attrs)
104
+ attrs.map do |attr, value|
105
+ "#{connection.quote_column_name(attr)} = #{quote_bound_value(value)}"
106
+ end.join(', ')
107
+ end
108
+
109
+ # Accepts an array of conditions. The array has each value
110
+ # sanitized and interpolated into the SQL statement.
111
+ # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
112
+ def sanitize_sql_array(ary)
113
+ statement, *values = ary
114
+ if values.first.is_a?(Hash) && statement =~ /:\w+/
115
+ replace_named_bind_variables(statement, values.first)
116
+ elsif statement.include?('?')
117
+ replace_bind_variables(statement, values)
118
+ elsif statement.blank?
119
+ statement
120
+ else
121
+ statement % values.collect { |value| connection.quote_string(value.to_s) }
122
+ end
123
+ end
124
+
125
+ alias_method :sanitize_conditions, :sanitize_sql
126
+
127
+ def replace_bind_variables(statement, values) #:nodoc:
128
+ raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
129
+ bound = values.dup
130
+ c = connection
131
+ statement.gsub('?') { quote_bound_value(bound.shift, c) }
132
+ end
133
+
134
+ def replace_named_bind_variables(statement, bind_vars) #:nodoc:
135
+ statement.gsub(/(:?):([a-zA-Z]\w*)/) do
136
+ if $1 == ':' # skip postgresql casts
137
+ $& # return the whole match
138
+ elsif bind_vars.include?(match = $2.to_sym)
139
+ quote_bound_value(bind_vars[match])
140
+ else
141
+ raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
142
+ end
143
+ end
144
+ end
145
+
146
+ def expand_range_bind_variables(bind_vars) #:nodoc:
147
+ expanded = []
148
+
149
+ bind_vars.each do |var|
150
+ next if var.is_a?(Hash)
151
+
152
+ if var.is_a?(Range)
153
+ expanded << var.first
154
+ expanded << var.last
155
+ else
156
+ expanded << var
157
+ end
158
+ end
159
+
160
+ expanded
161
+ end
162
+
163
+ def quote_bound_value(value, c = connection) #:nodoc:
164
+ if value.respond_to?(:map) && !value.acts_like?(:string)
165
+ if value.respond_to?(:empty?) && value.empty?
166
+ c.quote(nil)
167
+ else
168
+ value.map { |v| c.quote(v) }.join(',')
169
+ end
170
+ else
171
+ c.quote(value)
172
+ end
173
+ end
174
+
175
+ def raise_if_bind_arity_mismatch(statement, expected, provided) #:nodoc:
176
+ unless expected == provided
177
+ raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}"
178
+ end
179
+ end
180
+ end
181
+
182
+ # TODO: Deprecate this
183
+ def quoted_id #:nodoc:
184
+ quote_value(id, column_for_attribute(self.class.primary_key))
185
+ end
186
+
187
+ private
188
+
189
+ # Quote strings appropriately for SQL statements.
190
+ def quote_value(value, column = nil)
191
+ self.class.connection.quote(value, column)
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,58 @@
1
+ require 'active_support/core_ext/object/blank'
2
+
3
+ module ActiveRecord
4
+ # = Active Record Schema
5
+ #
6
+ # Allows programmers to programmatically define a schema in a portable
7
+ # DSL. This means you can define tables, indexes, etc. without using SQL
8
+ # directly, so your applications can more easily support multiple
9
+ # databases.
10
+ #
11
+ # Usage:
12
+ #
13
+ # ActiveRecord::Schema.define do
14
+ # create_table :authors do |t|
15
+ # t.string :name, :null => false
16
+ # end
17
+ #
18
+ # add_index :authors, :name, :unique
19
+ #
20
+ # create_table :posts do |t|
21
+ # t.integer :author_id, :null => false
22
+ # t.string :subject
23
+ # t.text :body
24
+ # t.boolean :private, :default => false
25
+ # end
26
+ #
27
+ # add_index :posts, :author_id
28
+ # end
29
+ #
30
+ # ActiveRecord::Schema is only supported by database adapters that also
31
+ # support migrations, the two features being very similar.
32
+ class Schema < Migration
33
+ def migrations_paths
34
+ ActiveRecord::Migrator.migrations_paths
35
+ end
36
+
37
+ # Eval the given block. All methods available to the current connection
38
+ # adapter are available within the block, so you can easily use the
39
+ # database definition DSL to build up your schema (+create_table+,
40
+ # +add_index+, etc.).
41
+ #
42
+ # The +info+ hash is optional, and if given is used to define metadata
43
+ # about the current schema (currently, only the schema's version):
44
+ #
45
+ # ActiveRecord::Schema.define(:version => 20380119000001) do
46
+ # ...
47
+ # end
48
+ def self.define(info={}, &block)
49
+ schema = new
50
+ schema.instance_eval(&block)
51
+
52
+ unless info[:version].blank?
53
+ initialize_schema_migrations_table
54
+ assume_migrated_upto_version(info[:version], schema.migrations_paths)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,204 @@
1
+ require 'stringio'
2
+ require 'active_support/core_ext/big_decimal'
3
+
4
+ module ActiveRecord
5
+ # = Active Record Schema Dumper
6
+ #
7
+ # This class is used to dump the database schema for some connection to some
8
+ # output format (i.e., ActiveRecord::Schema).
9
+ class SchemaDumper #:nodoc:
10
+ private_class_method :new
11
+
12
+ ##
13
+ # :singleton-method:
14
+ # A list of tables which should not be dumped to the schema.
15
+ # Acceptable values are strings as well as regexp.
16
+ # This setting is only used if ActiveRecord::Base.schema_format == :ruby
17
+ cattr_accessor :ignore_tables
18
+ @@ignore_tables = []
19
+
20
+ def self.dump(connection=ActiveRecord::Base.connection, stream=STDOUT)
21
+ new(connection).dump(stream)
22
+ stream
23
+ end
24
+
25
+ def dump(stream)
26
+ header(stream)
27
+ tables(stream)
28
+ trailer(stream)
29
+ stream
30
+ end
31
+
32
+ private
33
+
34
+ def initialize(connection)
35
+ @connection = connection
36
+ @types = @connection.native_database_types
37
+ @version = Migrator::current_version rescue nil
38
+ end
39
+
40
+ def header(stream)
41
+ define_params = @version ? ":version => #{@version}" : ""
42
+
43
+ if stream.respond_to?(:external_encoding)
44
+ stream.puts "# encoding: #{stream.external_encoding.name}"
45
+ end
46
+
47
+ stream.puts <<HEADER
48
+ # This file is auto-generated from the current state of the database. Instead
49
+ # of editing this file, please use the migrations feature of Active Record to
50
+ # incrementally modify your database, and then regenerate this schema definition.
51
+ #
52
+ # Note that this schema.rb definition is the authoritative source for your
53
+ # database schema. If you need to create the application database on another
54
+ # system, you should be using db:schema:load, not running all the migrations
55
+ # from scratch. The latter is a flawed and unsustainable approach (the more migrations
56
+ # you'll amass, the slower it'll run and the greater likelihood for issues).
57
+ #
58
+ # It's strongly recommended to check this file into your version control system.
59
+
60
+ ActiveRecord::Schema.define(#{define_params}) do
61
+
62
+ HEADER
63
+ end
64
+
65
+ def trailer(stream)
66
+ stream.puts "end"
67
+ end
68
+
69
+ def tables(stream)
70
+ @connection.tables.sort.each do |tbl|
71
+ next if ['schema_migrations', ignore_tables].flatten.any? do |ignored|
72
+ case ignored
73
+ when String; tbl == ignored
74
+ when Regexp; tbl =~ ignored
75
+ else
76
+ raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
77
+ end
78
+ end
79
+ table(tbl, stream)
80
+ end
81
+ end
82
+
83
+ def table(table, stream)
84
+ columns = @connection.columns(table)
85
+ begin
86
+ tbl = StringIO.new
87
+
88
+ # first dump primary key column
89
+ if @connection.respond_to?(:pk_and_sequence_for)
90
+ pk, _ = @connection.pk_and_sequence_for(table)
91
+ elsif @connection.respond_to?(:primary_key)
92
+ pk = @connection.primary_key(table)
93
+ end
94
+
95
+ tbl.print " create_table #{table.inspect}"
96
+ if columns.detect { |c| c.name == pk }
97
+ if pk != 'id'
98
+ tbl.print %Q(, :primary_key => "#{pk}")
99
+ end
100
+ else
101
+ tbl.print ", :id => false"
102
+ end
103
+ tbl.print ", :force => true"
104
+ tbl.puts " do |t|"
105
+
106
+ # then dump all non-primary key columns
107
+ column_specs = columns.map do |column|
108
+ raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" if @types[column.type].nil?
109
+ next if column.name == pk
110
+ spec = {}
111
+ spec[:name] = column.name.inspect
112
+
113
+ # AR has an optimization which handles zero-scale decimals as integers. This
114
+ # code ensures that the dumper still dumps the column as a decimal.
115
+ spec[:type] = if column.type == :integer && [/^numeric/, /^decimal/].any? { |e| e.match(column.sql_type) }
116
+ 'decimal'
117
+ else
118
+ column.type.to_s
119
+ end
120
+ spec[:limit] = column.limit.inspect if column.limit != @types[column.type][:limit] && spec[:type] != 'decimal'
121
+ spec[:precision] = column.precision.inspect if column.precision
122
+ spec[:scale] = column.scale.inspect if column.scale
123
+ spec[:null] = 'false' unless column.null
124
+ spec[:default] = default_string(column.default) if column.has_default?
125
+ (spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k.inspect} => ")}
126
+ spec
127
+ end.compact
128
+
129
+ # find all migration keys used in this table
130
+ keys = [:name, :limit, :precision, :scale, :default, :null] & column_specs.map{ |k| k.keys }.flatten
131
+
132
+ # figure out the lengths for each column based on above keys
133
+ lengths = keys.map{ |key| column_specs.map{ |spec| spec[key] ? spec[key].length + 2 : 0 }.max }
134
+
135
+ # the string we're going to sprintf our values against, with standardized column widths
136
+ format_string = lengths.map{ |len| "%-#{len}s" }
137
+
138
+ # find the max length for the 'type' column, which is special
139
+ type_length = column_specs.map{ |column| column[:type].length }.max
140
+
141
+ # add column type definition to our format string
142
+ format_string.unshift " t.%-#{type_length}s "
143
+
144
+ format_string *= ''
145
+
146
+ column_specs.each do |colspec|
147
+ values = keys.zip(lengths).map{ |key, len| colspec.key?(key) ? colspec[key] + ", " : " " * len }
148
+ values.unshift colspec[:type]
149
+ tbl.print((format_string % values).gsub(/,\s*$/, ''))
150
+ tbl.puts
151
+ end
152
+
153
+ tbl.puts " end"
154
+ tbl.puts
155
+
156
+ indexes(table, tbl)
157
+
158
+ tbl.rewind
159
+ stream.print tbl.read
160
+ rescue => e
161
+ stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
162
+ stream.puts "# #{e.message}"
163
+ stream.puts
164
+ end
165
+
166
+ stream
167
+ end
168
+
169
+ def default_string(value)
170
+ case value
171
+ when BigDecimal
172
+ value.to_s
173
+ when Date, DateTime, Time
174
+ "'" + value.to_s(:db) + "'"
175
+ else
176
+ value.inspect
177
+ end
178
+ end
179
+
180
+ def indexes(table, stream)
181
+ if (indexes = @connection.indexes(table)).any?
182
+ add_index_statements = indexes.map do |index|
183
+ statement_parts = [
184
+ ('add_index ' + index.table.inspect),
185
+ index.columns.inspect,
186
+ (':name => ' + index.name.inspect),
187
+ ]
188
+ statement_parts << ':unique => true' if index.unique
189
+
190
+ index_lengths = (index.lengths || []).compact
191
+ statement_parts << (':length => ' + Hash[index.columns.zip(index.lengths)].inspect) unless index_lengths.empty?
192
+
193
+ index_orders = (index.orders || {})
194
+ statement_parts << (':order => ' + index.orders.inspect) unless index_orders.empty?
195
+
196
+ ' ' + statement_parts.join(', ')
197
+ end
198
+
199
+ stream.puts add_index_statements.sort.join("\n")
200
+ stream.puts
201
+ end
202
+ end
203
+ end
204
+ end