activerecord 3.0.0 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/CHANGELOG.md +2102 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +35 -44
- data/examples/performance.rb +110 -100
- data/lib/active_record/aggregations.rb +59 -75
- data/lib/active_record/associations/alias_tracker.rb +76 -0
- data/lib/active_record/associations/association.rb +248 -0
- data/lib/active_record/associations/association_scope.rb +135 -0
- data/lib/active_record/associations/belongs_to_association.rb +60 -59
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -59
- data/lib/active_record/associations/builder/association.rb +108 -0
- data/lib/active_record/associations/builder/belongs_to.rb +98 -0
- data/lib/active_record/associations/builder/collection_association.rb +89 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
- data/lib/active_record/associations/builder/has_many.rb +15 -0
- data/lib/active_record/associations/builder/has_one.rb +25 -0
- data/lib/active_record/associations/builder/singular_association.rb +32 -0
- data/lib/active_record/associations/collection_association.rb +608 -0
- data/lib/active_record/associations/collection_proxy.rb +986 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +40 -112
- data/lib/active_record/associations/has_many_association.rb +83 -76
- data/lib/active_record/associations/has_many_through_association.rb +147 -66
- data/lib/active_record/associations/has_one_association.rb +67 -108
- data/lib/active_record/associations/has_one_through_association.rb +21 -25
- data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
- data/lib/active_record/associations/join_dependency.rb +235 -0
- data/lib/active_record/associations/join_helper.rb +45 -0
- data/lib/active_record/associations/preloader/association.rb +121 -0
- data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
- data/lib/active_record/associations/preloader/collection_association.rb +24 -0
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
- data/lib/active_record/associations/preloader/has_many.rb +17 -0
- data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
- data/lib/active_record/associations/preloader/has_one.rb +23 -0
- data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
- data/lib/active_record/associations/preloader/singular_association.rb +21 -0
- data/lib/active_record/associations/preloader/through_association.rb +63 -0
- data/lib/active_record/associations/preloader.rb +178 -0
- data/lib/active_record/associations/singular_association.rb +64 -0
- data/lib/active_record/associations/through_association.rb +87 -0
- data/lib/active_record/associations.rb +512 -1224
- data/lib/active_record/attribute_assignment.rb +201 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +49 -12
- data/lib/active_record/attribute_methods/dirty.rb +51 -28
- data/lib/active_record/attribute_methods/primary_key.rb +94 -22
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +63 -72
- data/lib/active_record/attribute_methods/serialization.rb +162 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -41
- data/lib/active_record/attribute_methods/write.rb +39 -13
- data/lib/active_record/attribute_methods.rb +362 -29
- data/lib/active_record/autosave_association.rb +132 -75
- data/lib/active_record/base.rb +83 -1627
- data/lib/active_record/callbacks.rb +69 -47
- data/lib/active_record/coders/yaml_column.rb +38 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +411 -138
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +21 -11
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +234 -173
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -22
- data/lib/active_record/connection_adapters/abstract/quoting.rb +82 -25
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +176 -414
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +562 -232
- data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +281 -53
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
- data/lib/active_record/connection_adapters/column.rb +318 -0
- data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +365 -450
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +672 -752
- data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +588 -17
- data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
- data/lib/active_record/connection_handling.rb +98 -0
- data/lib/active_record/core.rb +463 -0
- data/lib/active_record/counter_cache.rb +108 -101
- data/lib/active_record/dynamic_matchers.rb +131 -0
- data/lib/active_record/errors.rb +54 -13
- data/lib/active_record/explain.rb +38 -0
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +29 -0
- data/lib/active_record/fixture_set/file.rb +55 -0
- data/lib/active_record/fixtures.rb +703 -785
- data/lib/active_record/inheritance.rb +200 -0
- data/lib/active_record/integration.rb +60 -0
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +69 -60
- data/lib/active_record/locking/pessimistic.rb +34 -12
- data/lib/active_record/log_subscriber.rb +40 -6
- data/lib/active_record/migration/command_recorder.rb +164 -0
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +614 -216
- data/lib/active_record/model_schema.rb +345 -0
- data/lib/active_record/nested_attributes.rb +248 -119
- data/lib/active_record/null_relation.rb +65 -0
- data/lib/active_record/persistence.rb +275 -57
- data/lib/active_record/query_cache.rb +29 -9
- data/lib/active_record/querying.rb +62 -0
- data/lib/active_record/railtie.rb +135 -21
- data/lib/active_record/railties/console_sandbox.rb +5 -0
- data/lib/active_record/railties/controller_runtime.rb +17 -5
- data/lib/active_record/railties/databases.rake +249 -359
- data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
- data/lib/active_record/readonly_attributes.rb +30 -0
- data/lib/active_record/reflection.rb +283 -103
- data/lib/active_record/relation/batches.rb +38 -34
- data/lib/active_record/relation/calculations.rb +252 -139
- data/lib/active_record/relation/delegation.rb +125 -0
- data/lib/active_record/relation/finder_methods.rb +182 -188
- data/lib/active_record/relation/merger.rb +161 -0
- data/lib/active_record/relation/predicate_builder.rb +86 -21
- data/lib/active_record/relation/query_methods.rb +917 -134
- data/lib/active_record/relation/spawn_methods.rb +53 -92
- data/lib/active_record/relation.rb +405 -143
- data/lib/active_record/result.rb +67 -0
- data/lib/active_record/runtime_registry.rb +17 -0
- data/lib/active_record/sanitization.rb +168 -0
- data/lib/active_record/schema.rb +20 -14
- data/lib/active_record/schema_dumper.rb +55 -46
- data/lib/active_record/schema_migration.rb +39 -0
- data/lib/active_record/scoping/default.rb +146 -0
- data/lib/active_record/scoping/named.rb +175 -0
- data/lib/active_record/scoping.rb +82 -0
- data/lib/active_record/serialization.rb +8 -46
- data/lib/active_record/serializers/xml_serializer.rb +21 -68
- data/lib/active_record/statement_cache.rb +26 -0
- data/lib/active_record/store.rb +156 -0
- data/lib/active_record/tasks/database_tasks.rb +203 -0
- data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
- data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
- data/lib/active_record/test_case.rb +57 -28
- data/lib/active_record/timestamp.rb +49 -18
- data/lib/active_record/transactions.rb +106 -63
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations/associated.rb +25 -24
- data/lib/active_record/validations/presence.rb +65 -0
- data/lib/active_record/validations/uniqueness.rb +123 -83
- data/lib/active_record/validations.rb +29 -29
- data/lib/active_record/version.rb +7 -5
- data/lib/active_record.rb +83 -34
- data/lib/rails/generators/active_record/migration/migration_generator.rb +46 -9
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +30 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -5
- data/lib/rails/generators/active_record/model/templates/model.rb +7 -2
- data/lib/rails/generators/active_record/model/templates/module.rb +3 -1
- data/lib/rails/generators/active_record.rb +4 -8
- metadata +163 -121
- data/CHANGELOG +0 -6023
- data/examples/associations.png +0 -0
- data/lib/active_record/association_preload.rb +0 -403
- data/lib/active_record/associations/association_collection.rb +0 -562
- data/lib/active_record/associations/association_proxy.rb +0 -295
- data/lib/active_record/associations/through_association_scope.rb +0 -154
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -113
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -401
- data/lib/active_record/dynamic_finder_match.rb +0 -53
- data/lib/active_record/dynamic_scope_match.rb +0 -32
- data/lib/active_record/named_scope.rb +0 -138
- data/lib/active_record/observer.rb +0 -140
- data/lib/active_record/session_store.rb +0 -340
- data/lib/rails/generators/active_record/model/templates/migration.rb +0 -16
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -2
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -24
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -0,0 +1,67 @@
|
|
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, :column_types
|
12
|
+
|
13
|
+
def initialize(columns, rows, column_types = {})
|
14
|
+
@columns = columns
|
15
|
+
@rows = rows
|
16
|
+
@hash_rows = nil
|
17
|
+
@column_types = column_types
|
18
|
+
end
|
19
|
+
|
20
|
+
def each
|
21
|
+
hash_rows.each { |row| yield row }
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_hash
|
25
|
+
hash_rows
|
26
|
+
end
|
27
|
+
|
28
|
+
alias :map! :map
|
29
|
+
alias :collect! :map
|
30
|
+
|
31
|
+
# Returns true if there are no records.
|
32
|
+
def empty?
|
33
|
+
rows.empty?
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_ary
|
37
|
+
hash_rows
|
38
|
+
end
|
39
|
+
|
40
|
+
def [](idx)
|
41
|
+
hash_rows[idx]
|
42
|
+
end
|
43
|
+
|
44
|
+
def last
|
45
|
+
hash_rows.last
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize_copy(other)
|
49
|
+
@columns = columns.dup
|
50
|
+
@rows = rows.dup
|
51
|
+
@hash_rows = nil
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
def hash_rows
|
56
|
+
@hash_rows ||=
|
57
|
+
begin
|
58
|
+
# We freeze the strings to prevent them getting duped when
|
59
|
+
# used as keys in ActiveRecord::Base's @attributes hash
|
60
|
+
columns = @columns.map { |c| c.dup.freeze }
|
61
|
+
@rows.map { |row|
|
62
|
+
Hash[columns.zip(row)]
|
63
|
+
}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'active_support/per_thread_registry'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
# This is a thread locals registry for Active Record. For example:
|
5
|
+
#
|
6
|
+
# ActiveRecord::RuntimeRegistry.connection_handler
|
7
|
+
#
|
8
|
+
# returns the connection handler local to the current thread.
|
9
|
+
#
|
10
|
+
# See the documentation of <tt>ActiveSupport::PerThreadRegistry</tt>
|
11
|
+
# for further details.
|
12
|
+
class RuntimeRegistry # :nodoc:
|
13
|
+
extend ActiveSupport::PerThreadRegistry
|
14
|
+
|
15
|
+
attr_accessor :connection_handler, :sql_runtime, :connection_id
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Sanitization
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
def quote_value(value, column = nil) #:nodoc:
|
7
|
+
connection.quote(value,column)
|
8
|
+
end
|
9
|
+
|
10
|
+
# Used to sanitize objects before they're used in an SQL SELECT statement. Delegates to <tt>connection.quote</tt>.
|
11
|
+
def sanitize(object) #:nodoc:
|
12
|
+
connection.quote(object)
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
# Accepts an array, hash, or string of SQL conditions and sanitizes
|
18
|
+
# them into a valid SQL fragment for a WHERE clause.
|
19
|
+
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
|
20
|
+
# { name: "foo'bar", group_id: 4 } returns "name='foo''bar' and group_id='4'"
|
21
|
+
# "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'"
|
22
|
+
def sanitize_sql_for_conditions(condition, table_name = self.table_name)
|
23
|
+
return nil if condition.blank?
|
24
|
+
|
25
|
+
case condition
|
26
|
+
when Array; sanitize_sql_array(condition)
|
27
|
+
when Hash; sanitize_sql_hash_for_conditions(condition, table_name)
|
28
|
+
else condition
|
29
|
+
end
|
30
|
+
end
|
31
|
+
alias_method :sanitize_sql, :sanitize_sql_for_conditions
|
32
|
+
|
33
|
+
# Accepts an array, hash, or string of SQL conditions and sanitizes
|
34
|
+
# them into a valid SQL fragment for a SET clause.
|
35
|
+
# { name: nil, group_id: 4 } returns "name = NULL , group_id='4'"
|
36
|
+
def sanitize_sql_for_assignment(assignments, default_table_name = self.table_name)
|
37
|
+
case assignments
|
38
|
+
when Array; sanitize_sql_array(assignments)
|
39
|
+
when Hash; sanitize_sql_hash_for_assignment(assignments, default_table_name)
|
40
|
+
else assignments
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Accepts a hash of SQL conditions and replaces those attributes
|
45
|
+
# that correspond to a +composed_of+ relationship with their expanded
|
46
|
+
# aggregate attribute values.
|
47
|
+
# Given:
|
48
|
+
# class Person < ActiveRecord::Base
|
49
|
+
# composed_of :address, class_name: "Address",
|
50
|
+
# mapping: [%w(address_street street), %w(address_city city)]
|
51
|
+
# end
|
52
|
+
# Then:
|
53
|
+
# { address: Address.new("813 abc st.", "chicago") }
|
54
|
+
# # => { address_street: "813 abc st.", address_city: "chicago" }
|
55
|
+
def expand_hash_conditions_for_aggregates(attrs)
|
56
|
+
expanded_attrs = {}
|
57
|
+
attrs.each do |attr, value|
|
58
|
+
if aggregation = reflect_on_aggregation(attr.to_sym)
|
59
|
+
mapping = aggregation.mapping
|
60
|
+
mapping.each do |field_attr, aggregate_attr|
|
61
|
+
if mapping.size == 1 && !value.respond_to?(aggregate_attr)
|
62
|
+
expanded_attrs[field_attr] = value
|
63
|
+
else
|
64
|
+
expanded_attrs[field_attr] = value.send(aggregate_attr)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
else
|
68
|
+
expanded_attrs[attr] = value
|
69
|
+
end
|
70
|
+
end
|
71
|
+
expanded_attrs
|
72
|
+
end
|
73
|
+
|
74
|
+
# Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause.
|
75
|
+
# { name: "foo'bar", group_id: 4 }
|
76
|
+
# # => "name='foo''bar' and group_id= 4"
|
77
|
+
# { status: nil, group_id: [1,2,3] }
|
78
|
+
# # => "status IS NULL and group_id IN (1,2,3)"
|
79
|
+
# { age: 13..18 }
|
80
|
+
# # => "age BETWEEN 13 AND 18"
|
81
|
+
# { 'other_records.id' => 7 }
|
82
|
+
# # => "`other_records`.`id` = 7"
|
83
|
+
# { other_records: { id: 7 } }
|
84
|
+
# # => "`other_records`.`id` = 7"
|
85
|
+
# And for value objects on a composed_of relationship:
|
86
|
+
# { address: Address.new("123 abc st.", "chicago") }
|
87
|
+
# # => "address_street='123 abc st.' and address_city='chicago'"
|
88
|
+
def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
|
89
|
+
attrs = expand_hash_conditions_for_aggregates(attrs)
|
90
|
+
|
91
|
+
table = Arel::Table.new(table_name, arel_engine).alias(default_table_name)
|
92
|
+
PredicateBuilder.build_from_hash(self.class, attrs, table).map { |b|
|
93
|
+
connection.visitor.accept b
|
94
|
+
}.join(' AND ')
|
95
|
+
end
|
96
|
+
alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
|
97
|
+
|
98
|
+
# Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
|
99
|
+
# { status: nil, group_id: 1 }
|
100
|
+
# # => "status = NULL , group_id = 1"
|
101
|
+
def sanitize_sql_hash_for_assignment(attrs, table)
|
102
|
+
attrs.map do |attr, value|
|
103
|
+
"#{connection.quote_table_name_for_assignment(table, attr)} = #{quote_bound_value(value)}"
|
104
|
+
end.join(', ')
|
105
|
+
end
|
106
|
+
|
107
|
+
# Accepts an array of conditions. The array has each value
|
108
|
+
# sanitized and interpolated into the SQL statement.
|
109
|
+
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
|
110
|
+
def sanitize_sql_array(ary)
|
111
|
+
statement, *values = ary
|
112
|
+
if values.first.is_a?(Hash) && statement =~ /:\w+/
|
113
|
+
replace_named_bind_variables(statement, values.first)
|
114
|
+
elsif statement.include?('?')
|
115
|
+
replace_bind_variables(statement, values)
|
116
|
+
elsif statement.blank?
|
117
|
+
statement
|
118
|
+
else
|
119
|
+
statement % values.collect { |value| connection.quote_string(value.to_s) }
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
alias_method :sanitize_conditions, :sanitize_sql
|
124
|
+
|
125
|
+
def replace_bind_variables(statement, values) #:nodoc:
|
126
|
+
raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
|
127
|
+
bound = values.dup
|
128
|
+
c = connection
|
129
|
+
statement.gsub('?') { quote_bound_value(bound.shift, c) }
|
130
|
+
end
|
131
|
+
|
132
|
+
def replace_named_bind_variables(statement, bind_vars) #:nodoc:
|
133
|
+
statement.gsub(/(:?):([a-zA-Z]\w*)/) do
|
134
|
+
if $1 == ':' # skip postgresql casts
|
135
|
+
$& # return the whole match
|
136
|
+
elsif bind_vars.include?(match = $2.to_sym)
|
137
|
+
quote_bound_value(bind_vars[match])
|
138
|
+
else
|
139
|
+
raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def quote_bound_value(value, c = connection) #:nodoc:
|
145
|
+
if value.respond_to?(:map) && !value.acts_like?(:string)
|
146
|
+
if value.respond_to?(:empty?) && value.empty?
|
147
|
+
c.quote(nil)
|
148
|
+
else
|
149
|
+
value.map { |v| c.quote(v) }.join(',')
|
150
|
+
end
|
151
|
+
else
|
152
|
+
c.quote(value)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def raise_if_bind_arity_mismatch(statement, expected, provided) #:nodoc:
|
157
|
+
unless expected == provided
|
158
|
+
raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# TODO: Deprecate this
|
164
|
+
def quoted_id
|
165
|
+
self.class.quote_value(id, column_for_attribute(self.class.primary_key))
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
data/lib/active_record/schema.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'active_support/core_ext/object/blank'
|
2
1
|
|
3
2
|
module ActiveRecord
|
4
3
|
# = Active Record Schema
|
@@ -12,16 +11,16 @@ module ActiveRecord
|
|
12
11
|
#
|
13
12
|
# ActiveRecord::Schema.define do
|
14
13
|
# create_table :authors do |t|
|
15
|
-
# t.string :name, :
|
14
|
+
# t.string :name, null: false
|
16
15
|
# end
|
17
16
|
#
|
18
17
|
# add_index :authors, :name, :unique
|
19
18
|
#
|
20
19
|
# create_table :posts do |t|
|
21
|
-
# t.integer :author_id, :
|
20
|
+
# t.integer :author_id, null: false
|
22
21
|
# t.string :subject
|
23
22
|
# t.text :body
|
24
|
-
# t.boolean :private, :
|
23
|
+
# t.boolean :private, default: false
|
25
24
|
# end
|
26
25
|
#
|
27
26
|
# add_index :posts, :author_id
|
@@ -30,10 +29,22 @@ module ActiveRecord
|
|
30
29
|
# ActiveRecord::Schema is only supported by database adapters that also
|
31
30
|
# support migrations, the two features being very similar.
|
32
31
|
class Schema < Migration
|
33
|
-
private_class_method :new
|
34
32
|
|
35
|
-
|
36
|
-
|
33
|
+
# Returns the migrations paths.
|
34
|
+
#
|
35
|
+
# ActiveRecord::Schema.new.migrations_paths
|
36
|
+
# # => ["db/migrate"] # Rails migration path by default.
|
37
|
+
def migrations_paths
|
38
|
+
ActiveRecord::Migrator.migrations_paths
|
39
|
+
end
|
40
|
+
|
41
|
+
def define(info, &block) # :nodoc:
|
42
|
+
instance_eval(&block)
|
43
|
+
|
44
|
+
unless info[:version].blank?
|
45
|
+
initialize_schema_migrations_table
|
46
|
+
connection.assume_migrated_upto_version(info[:version], migrations_paths)
|
47
|
+
end
|
37
48
|
end
|
38
49
|
|
39
50
|
# Eval the given block. All methods available to the current connection
|
@@ -44,16 +55,11 @@ module ActiveRecord
|
|
44
55
|
# The +info+ hash is optional, and if given is used to define metadata
|
45
56
|
# about the current schema (currently, only the schema's version):
|
46
57
|
#
|
47
|
-
# ActiveRecord::Schema.define(:
|
58
|
+
# ActiveRecord::Schema.define(version: 20380119000001) do
|
48
59
|
# ...
|
49
60
|
# end
|
50
61
|
def self.define(info={}, &block)
|
51
|
-
|
52
|
-
|
53
|
-
unless info[:version].blank?
|
54
|
-
initialize_schema_migrations_table
|
55
|
-
assume_migrated_upto_version(info[:version], migrations_path)
|
56
|
-
end
|
62
|
+
new.define(info, &block)
|
57
63
|
end
|
58
64
|
end
|
59
65
|
end
|
@@ -24,6 +24,7 @@ module ActiveRecord
|
|
24
24
|
|
25
25
|
def dump(stream)
|
26
26
|
header(stream)
|
27
|
+
extensions(stream)
|
27
28
|
tables(stream)
|
28
29
|
trailer(stream)
|
29
30
|
stream
|
@@ -38,7 +39,11 @@ module ActiveRecord
|
|
38
39
|
end
|
39
40
|
|
40
41
|
def header(stream)
|
41
|
-
define_params = @version ? ":
|
42
|
+
define_params = @version ? "version: #{@version}" : ""
|
43
|
+
|
44
|
+
if stream.respond_to?(:external_encoding) && stream.external_encoding
|
45
|
+
stream.puts "# encoding: #{stream.external_encoding.name}"
|
46
|
+
end
|
42
47
|
|
43
48
|
stream.puts <<HEADER
|
44
49
|
# This file is auto-generated from the current state of the database. Instead
|
@@ -51,7 +56,7 @@ module ActiveRecord
|
|
51
56
|
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
|
52
57
|
# you'll amass, the slower it'll run and the greater likelihood for issues).
|
53
58
|
#
|
54
|
-
# It's strongly recommended
|
59
|
+
# It's strongly recommended that you check this file into your version control system.
|
55
60
|
|
56
61
|
ActiveRecord::Schema.define(#{define_params}) do
|
57
62
|
|
@@ -62,12 +67,24 @@ HEADER
|
|
62
67
|
stream.puts "end"
|
63
68
|
end
|
64
69
|
|
70
|
+
def extensions(stream)
|
71
|
+
return unless @connection.supports_extensions?
|
72
|
+
extensions = @connection.extensions
|
73
|
+
if extensions.any?
|
74
|
+
stream.puts " # These are extensions that must be enabled in order to support this database"
|
75
|
+
extensions.each do |extension|
|
76
|
+
stream.puts " enable_extension #{extension.inspect}"
|
77
|
+
end
|
78
|
+
stream.puts
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
65
82
|
def tables(stream)
|
66
83
|
@connection.tables.sort.each do |tbl|
|
67
84
|
next if ['schema_migrations', ignore_tables].flatten.any? do |ignored|
|
68
85
|
case ignored
|
69
|
-
when String; tbl == ignored
|
70
|
-
when Regexp; tbl =~ ignored
|
86
|
+
when String; remove_prefix_and_suffix(tbl) == ignored
|
87
|
+
when Regexp; remove_prefix_and_suffix(tbl) =~ ignored
|
71
88
|
else
|
72
89
|
raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
|
73
90
|
end
|
@@ -83,50 +100,38 @@ HEADER
|
|
83
100
|
|
84
101
|
# first dump primary key column
|
85
102
|
if @connection.respond_to?(:pk_and_sequence_for)
|
86
|
-
pk,
|
103
|
+
pk, _ = @connection.pk_and_sequence_for(table)
|
87
104
|
elsif @connection.respond_to?(:primary_key)
|
88
105
|
pk = @connection.primary_key(table)
|
89
106
|
end
|
90
107
|
|
91
|
-
tbl.print " create_table #{table.inspect}"
|
108
|
+
tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
|
92
109
|
if columns.detect { |c| c.name == pk }
|
93
110
|
if pk != 'id'
|
94
|
-
tbl.print %Q(, :
|
111
|
+
tbl.print %Q(, primary_key: "#{pk}")
|
95
112
|
end
|
96
113
|
else
|
97
|
-
tbl.print ", :
|
114
|
+
tbl.print ", id: false"
|
98
115
|
end
|
99
|
-
tbl.print ", :
|
116
|
+
tbl.print ", force: true"
|
100
117
|
tbl.puts " do |t|"
|
101
118
|
|
102
119
|
# then dump all non-primary key columns
|
103
120
|
column_specs = columns.map do |column|
|
104
|
-
raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'"
|
121
|
+
raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type)
|
105
122
|
next if column.name == pk
|
106
|
-
|
107
|
-
spec[:name] = column.name.inspect
|
108
|
-
|
109
|
-
# AR has an optimisation which handles zero-scale decimals as integers. This
|
110
|
-
# code ensures that the dumper still dumps the column as a decimal.
|
111
|
-
spec[:type] = if column.type == :integer && [/^numeric/, /^decimal/].any? { |e| e.match(column.sql_type) }
|
112
|
-
'decimal'
|
113
|
-
else
|
114
|
-
column.type.to_s
|
115
|
-
end
|
116
|
-
spec[:limit] = column.limit.inspect if column.limit != @types[column.type][:limit] && spec[:type] != 'decimal'
|
117
|
-
spec[:precision] = column.precision.inspect if !column.precision.nil?
|
118
|
-
spec[:scale] = column.scale.inspect if !column.scale.nil?
|
119
|
-
spec[:null] = 'false' if !column.null
|
120
|
-
spec[:default] = default_string(column.default) if column.has_default?
|
121
|
-
(spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k.inspect} => ")}
|
122
|
-
spec
|
123
|
+
@connection.column_spec(column, @types)
|
123
124
|
end.compact
|
124
125
|
|
125
126
|
# find all migration keys used in this table
|
126
|
-
keys =
|
127
|
+
keys = @connection.migration_keys
|
127
128
|
|
128
129
|
# figure out the lengths for each column based on above keys
|
129
|
-
lengths = keys.map{ |key|
|
130
|
+
lengths = keys.map { |key|
|
131
|
+
column_specs.map { |spec|
|
132
|
+
spec[key] ? spec[key].length + 2 : 0
|
133
|
+
}.max
|
134
|
+
}
|
130
135
|
|
131
136
|
# the string we're going to sprintf our values against, with standardized column widths
|
132
137
|
format_string = lengths.map{ |len| "%-#{len}s" }
|
@@ -162,27 +167,27 @@ HEADER
|
|
162
167
|
stream
|
163
168
|
end
|
164
169
|
|
165
|
-
def default_string(value)
|
166
|
-
case value
|
167
|
-
when BigDecimal
|
168
|
-
value.to_s
|
169
|
-
when Date, DateTime, Time
|
170
|
-
"'" + value.to_s(:db) + "'"
|
171
|
-
else
|
172
|
-
value.inspect
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
170
|
def indexes(table, stream)
|
177
171
|
if (indexes = @connection.indexes(table)).any?
|
178
172
|
add_index_statements = indexes.map do |index|
|
179
|
-
statement_parts = [
|
180
|
-
|
181
|
-
|
182
|
-
|
173
|
+
statement_parts = [
|
174
|
+
('add_index ' + remove_prefix_and_suffix(index.table).inspect),
|
175
|
+
index.columns.inspect,
|
176
|
+
('name: ' + index.name.inspect),
|
177
|
+
]
|
178
|
+
statement_parts << 'unique: true' if index.unique
|
179
|
+
|
180
|
+
index_lengths = (index.lengths || []).compact
|
181
|
+
statement_parts << ('length: ' + Hash[index.columns.zip(index.lengths)].inspect) unless index_lengths.empty?
|
182
|
+
|
183
|
+
index_orders = (index.orders || {})
|
184
|
+
statement_parts << ('order: ' + index.orders.inspect) unless index_orders.empty?
|
183
185
|
|
184
|
-
|
185
|
-
|
186
|
+
statement_parts << ('where: ' + index.where.inspect) if index.where
|
187
|
+
|
188
|
+
statement_parts << ('using: ' + index.using.inspect) if index.using
|
189
|
+
|
190
|
+
statement_parts << ('type: ' + index.type.inspect) if index.type
|
186
191
|
|
187
192
|
' ' + statement_parts.join(', ')
|
188
193
|
end
|
@@ -191,5 +196,9 @@ HEADER
|
|
191
196
|
stream.puts
|
192
197
|
end
|
193
198
|
end
|
199
|
+
|
200
|
+
def remove_prefix_and_suffix(table)
|
201
|
+
table.gsub(/^(#{ActiveRecord::Base.table_name_prefix})(.+)(#{ActiveRecord::Base.table_name_suffix})$/, "\\2")
|
202
|
+
end
|
194
203
|
end
|
195
204
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'active_record/scoping/default'
|
2
|
+
require 'active_record/scoping/named'
|
3
|
+
require 'active_record/base'
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
class SchemaMigration < ActiveRecord::Base
|
7
|
+
|
8
|
+
def self.table_name
|
9
|
+
"#{Base.table_name_prefix}schema_migrations#{Base.table_name_suffix}"
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.index_name
|
13
|
+
"#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.create_table(limit=nil)
|
17
|
+
unless connection.table_exists?(table_name)
|
18
|
+
version_options = {null: false}
|
19
|
+
version_options[:limit] = limit if limit
|
20
|
+
|
21
|
+
connection.create_table(table_name, id: false) do |t|
|
22
|
+
t.column :version, :string, version_options
|
23
|
+
end
|
24
|
+
connection.add_index table_name, :version, unique: true, name: index_name
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.drop_table
|
29
|
+
if connection.table_exists?(table_name)
|
30
|
+
connection.remove_index table_name, name: index_name
|
31
|
+
connection.drop_table(table_name)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def version
|
36
|
+
super.to_i
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Scoping
|
3
|
+
module Default
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
# Stores the default scope for the class.
|
8
|
+
class_attribute :default_scopes, instance_writer: false, instance_predicate: false
|
9
|
+
|
10
|
+
self.default_scopes = []
|
11
|
+
|
12
|
+
def self.default_scopes?
|
13
|
+
ActiveSupport::Deprecation.warn(
|
14
|
+
"#default_scopes? is deprecated. Do something like #default_scopes.empty? instead."
|
15
|
+
)
|
16
|
+
|
17
|
+
!!self.default_scopes
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
# Returns a scope for the model without the +default_scope+.
|
23
|
+
#
|
24
|
+
# class Post < ActiveRecord::Base
|
25
|
+
# def self.default_scope
|
26
|
+
# where published: true
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# Post.all # Fires "SELECT * FROM posts WHERE published = true"
|
31
|
+
# Post.unscoped.all # Fires "SELECT * FROM posts"
|
32
|
+
#
|
33
|
+
# This method also accepts a block. All queries inside the block will
|
34
|
+
# not use the +default_scope+:
|
35
|
+
#
|
36
|
+
# Post.unscoped {
|
37
|
+
# Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
|
38
|
+
# }
|
39
|
+
def unscoped
|
40
|
+
block_given? ? relation.scoping { yield } : relation
|
41
|
+
end
|
42
|
+
|
43
|
+
def before_remove_const #:nodoc:
|
44
|
+
self.current_scope = nil
|
45
|
+
end
|
46
|
+
|
47
|
+
protected
|
48
|
+
|
49
|
+
# Use this macro in your model to set a default scope for all operations on
|
50
|
+
# the model.
|
51
|
+
#
|
52
|
+
# class Article < ActiveRecord::Base
|
53
|
+
# default_scope { where(published: true) }
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# Article.all # => SELECT * FROM articles WHERE published = true
|
57
|
+
#
|
58
|
+
# The +default_scope+ is also applied while creating/building a record.
|
59
|
+
# It is not applied while updating a record.
|
60
|
+
#
|
61
|
+
# Article.new.published # => true
|
62
|
+
# Article.create.published # => true
|
63
|
+
#
|
64
|
+
# (You can also pass any object which responds to +call+ to the
|
65
|
+
# +default_scope+ macro, and it will be called when building the
|
66
|
+
# default scope.)
|
67
|
+
#
|
68
|
+
# If you use multiple +default_scope+ declarations in your model then
|
69
|
+
# they will be merged together:
|
70
|
+
#
|
71
|
+
# class Article < ActiveRecord::Base
|
72
|
+
# default_scope { where(published: true) }
|
73
|
+
# default_scope { where(rating: 'G') }
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G'
|
77
|
+
#
|
78
|
+
# This is also the case with inheritance and module includes where the
|
79
|
+
# parent or module defines a +default_scope+ and the child or including
|
80
|
+
# class defines a second one.
|
81
|
+
#
|
82
|
+
# If you need to do more complex things with a default scope, you can
|
83
|
+
# alternatively define it as a class method:
|
84
|
+
#
|
85
|
+
# class Article < ActiveRecord::Base
|
86
|
+
# def self.default_scope
|
87
|
+
# # Should return a scope, you can call 'super' here etc.
|
88
|
+
# end
|
89
|
+
# end
|
90
|
+
def default_scope(scope = nil)
|
91
|
+
scope = Proc.new if block_given?
|
92
|
+
|
93
|
+
if scope.is_a?(Relation) || !scope.respond_to?(:call)
|
94
|
+
ActiveSupport::Deprecation.warn(
|
95
|
+
"Calling #default_scope without a block is deprecated. For example instead " \
|
96
|
+
"of `default_scope where(color: 'red')`, please use " \
|
97
|
+
"`default_scope { where(color: 'red') }`. (Alternatively you can just redefine " \
|
98
|
+
"self.default_scope.)"
|
99
|
+
)
|
100
|
+
end
|
101
|
+
|
102
|
+
self.default_scopes += [scope]
|
103
|
+
end
|
104
|
+
|
105
|
+
def build_default_scope # :nodoc:
|
106
|
+
if !Base.is_a?(method(:default_scope).owner)
|
107
|
+
# The user has defined their own default scope method, so call that
|
108
|
+
evaluate_default_scope { default_scope }
|
109
|
+
elsif default_scopes.any?
|
110
|
+
evaluate_default_scope do
|
111
|
+
default_scopes.inject(relation) do |default_scope, scope|
|
112
|
+
if !scope.is_a?(Relation) && scope.respond_to?(:call)
|
113
|
+
default_scope.merge(unscoped { scope.call })
|
114
|
+
else
|
115
|
+
default_scope.merge(scope)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def ignore_default_scope? # :nodoc:
|
123
|
+
ScopeRegistry.value_for(:ignore_default_scope, self)
|
124
|
+
end
|
125
|
+
|
126
|
+
def ignore_default_scope=(ignore) # :nodoc:
|
127
|
+
ScopeRegistry.set_value_for(:ignore_default_scope, self, ignore)
|
128
|
+
end
|
129
|
+
|
130
|
+
# The ignore_default_scope flag is used to prevent an infinite recursion
|
131
|
+
# situation where a default scope references a scope which has a default
|
132
|
+
# scope which references a scope...
|
133
|
+
def evaluate_default_scope # :nodoc:
|
134
|
+
return if ignore_default_scope?
|
135
|
+
|
136
|
+
begin
|
137
|
+
self.ignore_default_scope = true
|
138
|
+
yield
|
139
|
+
ensure
|
140
|
+
self.ignore_default_scope = false
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|