activerecord 3.1.10 → 4.2.11
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.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +6 -6
- data/CHANGELOG.md +1837 -338
- data/MIT-LICENSE +1 -1
- data/README.rdoc +39 -43
- data/examples/performance.rb +51 -20
- data/examples/simple.rb +4 -4
- data/lib/active_record/aggregations.rb +57 -43
- data/lib/active_record/association_relation.rb +35 -0
- data/lib/active_record/associations/alias_tracker.rb +47 -39
- data/lib/active_record/associations/association.rb +71 -85
- data/lib/active_record/associations/association_scope.rb +138 -89
- data/lib/active_record/associations/belongs_to_association.rb +65 -25
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -3
- data/lib/active_record/associations/builder/association.rb +125 -29
- data/lib/active_record/associations/builder/belongs_to.rb +91 -60
- data/lib/active_record/associations/builder/collection_association.rb +69 -49
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +113 -42
- data/lib/active_record/associations/builder/has_many.rb +8 -64
- data/lib/active_record/associations/builder/has_one.rb +12 -52
- data/lib/active_record/associations/builder/singular_association.rb +22 -29
- data/lib/active_record/associations/collection_association.rb +294 -187
- data/lib/active_record/associations/collection_proxy.rb +961 -94
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +118 -23
- data/lib/active_record/associations/has_many_through_association.rb +115 -45
- data/lib/active_record/associations/has_one_association.rb +57 -24
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +76 -102
- data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
- data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
- data/lib/active_record/associations/join_dependency.rb +230 -156
- data/lib/active_record/associations/preloader/association.rb +96 -55
- data/lib/active_record/associations/preloader/collection_association.rb +3 -3
- data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
- data/lib/active_record/associations/preloader/has_one.rb +1 -1
- data/lib/active_record/associations/preloader/singular_association.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +61 -32
- data/lib/active_record/associations/preloader.rb +113 -87
- data/lib/active_record/associations/singular_association.rb +29 -13
- data/lib/active_record/associations/through_association.rb +37 -19
- data/lib/active_record/associations.rb +505 -371
- data/lib/active_record/attribute.rb +163 -0
- data/lib/active_record/attribute_assignment.rb +212 -0
- data/lib/active_record/attribute_decorators.rb +66 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
- data/lib/active_record/attribute_methods/dirty.rb +141 -51
- data/lib/active_record/attribute_methods/primary_key.rb +87 -36
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +74 -117
- data/lib/active_record/attribute_methods/serialization.rb +70 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -47
- data/lib/active_record/attribute_methods/write.rb +60 -21
- data/lib/active_record/attribute_methods.rb +409 -48
- data/lib/active_record/attribute_set/builder.rb +106 -0
- data/lib/active_record/attribute_set.rb +81 -0
- data/lib/active_record/attributes.rb +147 -0
- data/lib/active_record/autosave_association.rb +279 -232
- data/lib/active_record/base.rb +84 -1969
- data/lib/active_record/callbacks.rb +66 -28
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/coders/yaml_column.rb +18 -21
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +422 -243
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +170 -194
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -19
- data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -57
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +273 -170
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +731 -254
- data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +339 -95
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +946 -0
- data/lib/active_record/connection_adapters/column.rb +33 -221
- data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +140 -602
- data/lib/active_record/connection_adapters/mysql_adapter.rb +254 -756
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +596 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +445 -902
- data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +578 -25
- data/lib/active_record/connection_handling.rb +132 -0
- data/lib/active_record/core.rb +579 -0
- data/lib/active_record/counter_cache.rb +159 -102
- data/lib/active_record/dynamic_matchers.rb +140 -0
- data/lib/active_record/enum.rb +197 -0
- data/lib/active_record/errors.rb +102 -34
- 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 +56 -0
- data/lib/active_record/fixtures.rb +318 -260
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +247 -0
- data/lib/active_record/integration.rb +113 -0
- data/lib/active_record/legacy_yaml_adapter.rb +30 -0
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +80 -52
- data/lib/active_record/locking/pessimistic.rb +27 -5
- data/lib/active_record/log_subscriber.rb +25 -18
- data/lib/active_record/migration/command_recorder.rb +130 -38
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +532 -201
- data/lib/active_record/model_schema.rb +342 -0
- data/lib/active_record/nested_attributes.rb +229 -139
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +81 -0
- data/lib/active_record/persistence.rb +304 -99
- data/lib/active_record/query_cache.rb +25 -43
- data/lib/active_record/querying.rb +68 -0
- data/lib/active_record/railtie.rb +86 -45
- data/lib/active_record/railties/console_sandbox.rb +3 -4
- data/lib/active_record/railties/controller_runtime.rb +7 -4
- data/lib/active_record/railties/databases.rake +198 -377
- data/lib/active_record/railties/jdbcmysql_error.rb +2 -2
- data/lib/active_record/readonly_attributes.rb +23 -0
- data/lib/active_record/reflection.rb +516 -165
- data/lib/active_record/relation/batches.rb +96 -45
- data/lib/active_record/relation/calculations.rb +221 -144
- data/lib/active_record/relation/delegation.rb +140 -0
- data/lib/active_record/relation/finder_methods.rb +362 -243
- data/lib/active_record/relation/merger.rb +193 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
- data/lib/active_record/relation/predicate_builder.rb +135 -41
- data/lib/active_record/relation/query_methods.rb +982 -155
- data/lib/active_record/relation/spawn_methods.rb +50 -110
- data/lib/active_record/relation.rb +371 -180
- data/lib/active_record/result.rb +109 -12
- data/lib/active_record/runtime_registry.rb +22 -0
- data/lib/active_record/sanitization.rb +191 -0
- data/lib/active_record/schema.rb +19 -13
- data/lib/active_record/schema_dumper.rb +111 -61
- data/lib/active_record/schema_migration.rb +53 -0
- data/lib/active_record/scoping/default.rb +135 -0
- data/lib/active_record/scoping/named.rb +164 -0
- data/lib/active_record/scoping.rb +87 -0
- data/lib/active_record/serialization.rb +7 -45
- data/lib/active_record/serializers/xml_serializer.rb +14 -65
- data/lib/active_record/statement_cache.rb +111 -0
- data/lib/active_record/store.rb +205 -0
- data/lib/active_record/tasks/database_tasks.rb +299 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +159 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +101 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
- data/lib/active_record/timestamp.rb +35 -14
- data/lib/active_record/transactions.rb +141 -74
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/type/big_integer.rb +13 -0
- data/lib/active_record/type/binary.rb +50 -0
- data/lib/active_record/type/boolean.rb +31 -0
- data/lib/active_record/type/date.rb +50 -0
- data/lib/active_record/type/date_time.rb +54 -0
- data/lib/active_record/type/decimal.rb +64 -0
- data/lib/active_record/type/decimal_without_scale.rb +11 -0
- data/lib/active_record/type/decorator.rb +14 -0
- data/lib/active_record/type/float.rb +19 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
- data/lib/active_record/type/integer.rb +59 -0
- data/lib/active_record/type/mutable.rb +16 -0
- data/lib/active_record/type/numeric.rb +36 -0
- data/lib/active_record/type/serialized.rb +62 -0
- data/lib/active_record/type/string.rb +40 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +26 -0
- data/lib/active_record/type/time_value.rb +38 -0
- data/lib/active_record/type/type_map.rb +64 -0
- data/lib/active_record/type/unsigned_integer.rb +15 -0
- data/lib/active_record/type/value.rb +110 -0
- data/lib/active_record/type.rb +23 -0
- data/lib/active_record/validations/associated.rb +27 -18
- data/lib/active_record/validations/presence.rb +67 -0
- data/lib/active_record/validations/uniqueness.rb +125 -66
- data/lib/active_record/validations.rb +37 -30
- data/lib/active_record/version.rb +5 -7
- data/lib/active_record.rb +80 -25
- data/lib/rails/generators/active_record/migration/migration_generator.rb +54 -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 +25 -11
- data/lib/rails/generators/active_record/migration.rb +11 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +17 -4
- data/lib/rails/generators/active_record/model/templates/model.rb +5 -2
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- data/lib/rails/generators/active_record.rb +3 -11
- metadata +132 -53
- data/examples/associations.png +0 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -62
- data/lib/active_record/associations/join_helper.rb +0 -55
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -135
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -556
- data/lib/active_record/dynamic_finder_match.rb +0 -56
- data/lib/active_record/dynamic_scope_match.rb +0 -23
- data/lib/active_record/identity_map.rb +0 -163
- data/lib/active_record/named_scope.rb +0 -200
- data/lib/active_record/observer.rb +0 -121
- data/lib/active_record/session_store.rb +0 -358
- data/lib/active_record/test_case.rb +0 -69
- data/lib/rails/generators/active_record/model/templates/migration.rb +0 -17
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
data/lib/active_record/result.rb
CHANGED
@@ -1,34 +1,131 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
###
|
3
3
|
# This class encapsulates a Result returned from calling +exec_query+ on any
|
4
|
-
# database connection adapter.
|
4
|
+
# database connection adapter. For example:
|
5
5
|
#
|
6
|
-
#
|
7
|
-
#
|
6
|
+
# result = ActiveRecord::Base.connection.exec_query('SELECT id, title, body FROM posts')
|
7
|
+
# result # => #<ActiveRecord::Result:0xdeadbeef>
|
8
|
+
#
|
9
|
+
# # Get the column names of the result:
|
10
|
+
# result.columns
|
11
|
+
# # => ["id", "title", "body"]
|
12
|
+
#
|
13
|
+
# # Get the record values of the result:
|
14
|
+
# result.rows
|
15
|
+
# # => [[1, "title_1", "body_1"],
|
16
|
+
# [2, "title_2", "body_2"],
|
17
|
+
# ...
|
18
|
+
# ]
|
19
|
+
#
|
20
|
+
# # Get an array of hashes representing the result (column => value):
|
21
|
+
# result.to_hash
|
22
|
+
# # => [{"id" => 1, "title" => "title_1", "body" => "body_1"},
|
23
|
+
# {"id" => 2, "title" => "title_2", "body" => "body_2"},
|
24
|
+
# ...
|
25
|
+
# ]
|
26
|
+
#
|
27
|
+
# # ActiveRecord::Result also includes Enumerable.
|
28
|
+
# result.each do |row|
|
29
|
+
# puts row['title'] + " " + row['body']
|
30
|
+
# end
|
8
31
|
class Result
|
9
32
|
include Enumerable
|
10
33
|
|
11
|
-
|
34
|
+
IDENTITY_TYPE = Type::Value.new # :nodoc:
|
35
|
+
|
36
|
+
attr_reader :columns, :rows, :column_types
|
37
|
+
|
38
|
+
def initialize(columns, rows, column_types = {})
|
39
|
+
@columns = columns
|
40
|
+
@rows = rows
|
41
|
+
@hash_rows = nil
|
42
|
+
@column_types = column_types
|
43
|
+
end
|
12
44
|
|
13
|
-
def
|
14
|
-
@
|
15
|
-
@rows = rows
|
16
|
-
@hash_rows = nil
|
45
|
+
def length
|
46
|
+
@rows.length
|
17
47
|
end
|
18
48
|
|
19
49
|
def each
|
20
|
-
|
50
|
+
if block_given?
|
51
|
+
hash_rows.each { |row| yield row }
|
52
|
+
else
|
53
|
+
hash_rows.to_enum { @rows.size }
|
54
|
+
end
|
21
55
|
end
|
22
56
|
|
23
57
|
def to_hash
|
24
58
|
hash_rows
|
25
59
|
end
|
26
60
|
|
61
|
+
alias :map! :map
|
62
|
+
alias :collect! :map
|
63
|
+
|
64
|
+
# Returns true if there are no records.
|
65
|
+
def empty?
|
66
|
+
rows.empty?
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_ary
|
70
|
+
hash_rows
|
71
|
+
end
|
72
|
+
|
73
|
+
def [](idx)
|
74
|
+
hash_rows[idx]
|
75
|
+
end
|
76
|
+
|
77
|
+
def last
|
78
|
+
hash_rows.last
|
79
|
+
end
|
80
|
+
|
81
|
+
def cast_values(type_overrides = {}) # :nodoc:
|
82
|
+
types = columns.map { |name| column_type(name, type_overrides) }
|
83
|
+
result = rows.map do |values|
|
84
|
+
types.zip(values).map { |type, value| type.type_cast_from_database(value) }
|
85
|
+
end
|
86
|
+
|
87
|
+
columns.one? ? result.map!(&:first) : result
|
88
|
+
end
|
89
|
+
|
90
|
+
def initialize_copy(other)
|
91
|
+
@columns = columns.dup
|
92
|
+
@rows = rows.dup
|
93
|
+
@column_types = column_types.dup
|
94
|
+
@hash_rows = nil
|
95
|
+
end
|
96
|
+
|
27
97
|
private
|
98
|
+
|
99
|
+
def column_type(name, type_overrides = {})
|
100
|
+
type_overrides.fetch(name) do
|
101
|
+
column_types.fetch(name, IDENTITY_TYPE)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
28
105
|
def hash_rows
|
29
|
-
@hash_rows ||=
|
30
|
-
|
31
|
-
|
106
|
+
@hash_rows ||=
|
107
|
+
begin
|
108
|
+
# We freeze the strings to prevent them getting duped when
|
109
|
+
# used as keys in ActiveRecord::Base's @attributes hash
|
110
|
+
columns = @columns.map { |c| c.dup.freeze }
|
111
|
+
@rows.map { |row|
|
112
|
+
# In the past we used Hash[columns.zip(row)]
|
113
|
+
# though elegant, the verbose way is much more efficient
|
114
|
+
# both time and memory wise cause it avoids a big array allocation
|
115
|
+
# this method is called a lot and needs to be micro optimised
|
116
|
+
hash = {}
|
117
|
+
|
118
|
+
index = 0
|
119
|
+
length = columns.length
|
120
|
+
|
121
|
+
while index < length
|
122
|
+
hash[columns[index]] = row[index]
|
123
|
+
index += 1
|
124
|
+
end
|
125
|
+
|
126
|
+
hash
|
127
|
+
}
|
128
|
+
end
|
32
129
|
end
|
33
130
|
end
|
34
131
|
end
|
@@ -0,0 +1,22 @@
|
|
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
|
+
|
17
|
+
[:connection_handler, :sql_runtime, :connection_id].each do |val|
|
18
|
+
class_eval %{ def self.#{val}; instance.#{val}; end }, __FILE__, __LINE__
|
19
|
+
class_eval %{ def self.#{val}=(x); instance.#{val}=x; end }, __FILE__, __LINE__
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Sanitization
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
def quote_value(value, column) #: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
|
+
alias_method :sanitize_conditions, :sanitize_sql
|
33
|
+
|
34
|
+
# Accepts an array, hash, or string of SQL conditions and sanitizes
|
35
|
+
# them into a valid SQL fragment for a SET clause.
|
36
|
+
# { name: nil, group_id: 4 } returns "name = NULL , group_id='4'"
|
37
|
+
def sanitize_sql_for_assignment(assignments, default_table_name = self.table_name)
|
38
|
+
case assignments
|
39
|
+
when Array; sanitize_sql_array(assignments)
|
40
|
+
when Hash; sanitize_sql_hash_for_assignment(assignments, default_table_name)
|
41
|
+
else assignments
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Accepts a hash of SQL conditions and replaces those attributes
|
46
|
+
# that correspond to a +composed_of+ relationship with their expanded
|
47
|
+
# aggregate attribute values.
|
48
|
+
# Given:
|
49
|
+
# class Person < ActiveRecord::Base
|
50
|
+
# composed_of :address, class_name: "Address",
|
51
|
+
# mapping: [%w(address_street street), %w(address_city city)]
|
52
|
+
# end
|
53
|
+
# Then:
|
54
|
+
# { address: Address.new("813 abc st.", "chicago") }
|
55
|
+
# # => { address_street: "813 abc st.", address_city: "chicago" }
|
56
|
+
def expand_hash_conditions_for_aggregates(attrs)
|
57
|
+
expanded_attrs = {}
|
58
|
+
attrs.each do |attr, value|
|
59
|
+
if aggregation = reflect_on_aggregation(attr.to_sym)
|
60
|
+
mapping = aggregation.mapping
|
61
|
+
mapping.each do |field_attr, aggregate_attr|
|
62
|
+
if mapping.size == 1 && !value.respond_to?(aggregate_attr)
|
63
|
+
expanded_attrs[field_attr] = value
|
64
|
+
else
|
65
|
+
expanded_attrs[field_attr] = value.send(aggregate_attr)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
else
|
69
|
+
expanded_attrs[attr] = value
|
70
|
+
end
|
71
|
+
end
|
72
|
+
expanded_attrs
|
73
|
+
end
|
74
|
+
|
75
|
+
# Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause.
|
76
|
+
# { name: "foo'bar", group_id: 4 }
|
77
|
+
# # => "name='foo''bar' and group_id= 4"
|
78
|
+
# { status: nil, group_id: [1,2,3] }
|
79
|
+
# # => "status IS NULL and group_id IN (1,2,3)"
|
80
|
+
# { age: 13..18 }
|
81
|
+
# # => "age BETWEEN 13 AND 18"
|
82
|
+
# { 'other_records.id' => 7 }
|
83
|
+
# # => "`other_records`.`id` = 7"
|
84
|
+
# { other_records: { id: 7 } }
|
85
|
+
# # => "`other_records`.`id` = 7"
|
86
|
+
# And for value objects on a composed_of relationship:
|
87
|
+
# { address: Address.new("123 abc st.", "chicago") }
|
88
|
+
# # => "address_street='123 abc st.' and address_city='chicago'"
|
89
|
+
def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
|
90
|
+
ActiveSupport::Deprecation.warn(<<-EOWARN)
|
91
|
+
sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
|
92
|
+
EOWARN
|
93
|
+
attrs = PredicateBuilder.resolve_column_aliases self, attrs
|
94
|
+
attrs = expand_hash_conditions_for_aggregates(attrs)
|
95
|
+
|
96
|
+
table = Arel::Table.new(table_name, arel_engine).alias(default_table_name)
|
97
|
+
PredicateBuilder.build_from_hash(self, attrs, table).map { |b|
|
98
|
+
connection.visitor.compile b
|
99
|
+
}.join(' AND ')
|
100
|
+
end
|
101
|
+
alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
|
102
|
+
|
103
|
+
# Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
|
104
|
+
# { status: nil, group_id: 1 }
|
105
|
+
# # => "status = NULL , group_id = 1"
|
106
|
+
def sanitize_sql_hash_for_assignment(attrs, table)
|
107
|
+
c = connection
|
108
|
+
attrs.map do |attr, value|
|
109
|
+
"#{c.quote_table_name_for_assignment(table, attr)} = #{quote_bound_value(value, c, columns_hash[attr.to_s])}"
|
110
|
+
end.join(', ')
|
111
|
+
end
|
112
|
+
|
113
|
+
# Sanitizes a +string+ so that it is safe to use within an SQL
|
114
|
+
# LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%"
|
115
|
+
def sanitize_sql_like(string, escape_character = "\\")
|
116
|
+
pattern = Regexp.union(escape_character, "%", "_")
|
117
|
+
string.gsub(pattern) { |x| [escape_character, x].join }
|
118
|
+
end
|
119
|
+
|
120
|
+
# Accepts an array of conditions. The array has each value
|
121
|
+
# sanitized and interpolated into the SQL statement.
|
122
|
+
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
|
123
|
+
def sanitize_sql_array(ary)
|
124
|
+
statement, *values = ary
|
125
|
+
if values.first.is_a?(Hash) && statement =~ /:\w+/
|
126
|
+
replace_named_bind_variables(statement, values.first)
|
127
|
+
elsif statement.include?('?')
|
128
|
+
replace_bind_variables(statement, values)
|
129
|
+
elsif statement.blank?
|
130
|
+
statement
|
131
|
+
else
|
132
|
+
statement % values.collect { |value| connection.quote_string(value.to_s) }
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def replace_bind_variables(statement, values) #:nodoc:
|
137
|
+
raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
|
138
|
+
bound = values.dup
|
139
|
+
c = connection
|
140
|
+
statement.gsub(/\?/) do
|
141
|
+
replace_bind_variable(bound.shift, c)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def replace_bind_variable(value, c = connection) #:nodoc:
|
146
|
+
if ActiveRecord::Relation === value
|
147
|
+
value.to_sql
|
148
|
+
else
|
149
|
+
quote_bound_value(value, c)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def replace_named_bind_variables(statement, bind_vars) #:nodoc:
|
154
|
+
statement.gsub(/(:?):([a-zA-Z]\w*)/) do
|
155
|
+
if $1 == ':' # skip postgresql casts
|
156
|
+
$& # return the whole match
|
157
|
+
elsif bind_vars.include?(match = $2.to_sym)
|
158
|
+
replace_bind_variable(bind_vars[match])
|
159
|
+
else
|
160
|
+
raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def quote_bound_value(value, c = connection, column = nil) #:nodoc:
|
166
|
+
if column
|
167
|
+
c.quote(value, column)
|
168
|
+
elsif value.respond_to?(:map) && !value.acts_like?(:string)
|
169
|
+
if value.respond_to?(:empty?) && value.empty?
|
170
|
+
c.quote(nil)
|
171
|
+
else
|
172
|
+
value.map { |v| c.quote(v) }.join(',')
|
173
|
+
end
|
174
|
+
else
|
175
|
+
c.quote(value)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def raise_if_bind_arity_mismatch(statement, expected, provided) #:nodoc:
|
180
|
+
unless expected == provided
|
181
|
+
raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}"
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# TODO: Deprecate this
|
187
|
+
def quoted_id
|
188
|
+
self.class.quote_value(id, column_for_attribute(self.class.primary_key))
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
data/lib/active_record/schema.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'active_support/core_ext/object/blank'
|
2
|
-
|
3
1
|
module ActiveRecord
|
4
2
|
# = Active Record Schema
|
5
3
|
#
|
@@ -12,16 +10,16 @@ module ActiveRecord
|
|
12
10
|
#
|
13
11
|
# ActiveRecord::Schema.define do
|
14
12
|
# create_table :authors do |t|
|
15
|
-
# t.string :name, :
|
13
|
+
# t.string :name, null: false
|
16
14
|
# end
|
17
15
|
#
|
18
16
|
# add_index :authors, :name, :unique
|
19
17
|
#
|
20
18
|
# create_table :posts do |t|
|
21
|
-
# t.integer :author_id, :
|
19
|
+
# t.integer :author_id, null: false
|
22
20
|
# t.string :subject
|
23
21
|
# t.text :body
|
24
|
-
# t.boolean :private, :
|
22
|
+
# t.boolean :private, default: false
|
25
23
|
# end
|
26
24
|
#
|
27
25
|
# add_index :posts, :author_id
|
@@ -30,10 +28,24 @@ module ActiveRecord
|
|
30
28
|
# ActiveRecord::Schema is only supported by database adapters that also
|
31
29
|
# support migrations, the two features being very similar.
|
32
30
|
class Schema < Migration
|
31
|
+
|
32
|
+
# Returns the migrations paths.
|
33
|
+
#
|
34
|
+
# ActiveRecord::Schema.new.migrations_paths
|
35
|
+
# # => ["db/migrate"] # Rails migration path by default.
|
33
36
|
def migrations_paths
|
34
37
|
ActiveRecord::Migrator.migrations_paths
|
35
38
|
end
|
36
39
|
|
40
|
+
def define(info, &block) # :nodoc:
|
41
|
+
instance_eval(&block)
|
42
|
+
|
43
|
+
unless info[:version].blank?
|
44
|
+
initialize_schema_migrations_table
|
45
|
+
connection.assume_migrated_upto_version(info[:version], migrations_paths)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
37
49
|
# Eval the given block. All methods available to the current connection
|
38
50
|
# adapter are available within the block, so you can easily use the
|
39
51
|
# database definition DSL to build up your schema (+create_table+,
|
@@ -42,17 +54,11 @@ module ActiveRecord
|
|
42
54
|
# The +info+ hash is optional, and if given is used to define metadata
|
43
55
|
# about the current schema (currently, only the schema's version):
|
44
56
|
#
|
45
|
-
# ActiveRecord::Schema.define(:
|
57
|
+
# ActiveRecord::Schema.define(version: 20380119000001) do
|
46
58
|
# ...
|
47
59
|
# end
|
48
60
|
def self.define(info={}, &block)
|
49
|
-
|
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
|
61
|
+
new.define(info, &block)
|
56
62
|
end
|
57
63
|
end
|
58
64
|
end
|
@@ -17,13 +17,24 @@ module ActiveRecord
|
|
17
17
|
cattr_accessor :ignore_tables
|
18
18
|
@@ignore_tables = []
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
class << self
|
21
|
+
def dump(connection=ActiveRecord::Base.connection, stream=STDOUT, config = ActiveRecord::Base)
|
22
|
+
new(connection, generate_options(config)).dump(stream)
|
23
|
+
stream
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
def generate_options(config)
|
28
|
+
{
|
29
|
+
table_name_prefix: config.table_name_prefix,
|
30
|
+
table_name_suffix: config.table_name_suffix
|
31
|
+
}
|
32
|
+
end
|
23
33
|
end
|
24
34
|
|
25
35
|
def dump(stream)
|
26
36
|
header(stream)
|
37
|
+
extensions(stream)
|
27
38
|
tables(stream)
|
28
39
|
trailer(stream)
|
29
40
|
stream
|
@@ -31,14 +42,15 @@ module ActiveRecord
|
|
31
42
|
|
32
43
|
private
|
33
44
|
|
34
|
-
def initialize(connection)
|
45
|
+
def initialize(connection, options = {})
|
35
46
|
@connection = connection
|
36
47
|
@types = @connection.native_database_types
|
37
48
|
@version = Migrator::current_version rescue nil
|
49
|
+
@options = options
|
38
50
|
end
|
39
51
|
|
40
52
|
def header(stream)
|
41
|
-
define_params = @version ? ":
|
53
|
+
define_params = @version ? "version: #{@version}" : ""
|
42
54
|
|
43
55
|
if stream.respond_to?(:external_encoding) && stream.external_encoding
|
44
56
|
stream.puts "# encoding: #{stream.external_encoding.name}"
|
@@ -55,7 +67,7 @@ module ActiveRecord
|
|
55
67
|
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
|
56
68
|
# you'll amass, the slower it'll run and the greater likelihood for issues).
|
57
69
|
#
|
58
|
-
# It's strongly recommended
|
70
|
+
# It's strongly recommended that you check this file into your version control system.
|
59
71
|
|
60
72
|
ActiveRecord::Schema.define(#{define_params}) do
|
61
73
|
|
@@ -66,17 +78,30 @@ HEADER
|
|
66
78
|
stream.puts "end"
|
67
79
|
end
|
68
80
|
|
81
|
+
def extensions(stream)
|
82
|
+
return unless @connection.supports_extensions?
|
83
|
+
extensions = @connection.extensions
|
84
|
+
if extensions.any?
|
85
|
+
stream.puts " # These are extensions that must be enabled in order to support this database"
|
86
|
+
extensions.each do |extension|
|
87
|
+
stream.puts " enable_extension #{extension.inspect}"
|
88
|
+
end
|
89
|
+
stream.puts
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
69
93
|
def tables(stream)
|
70
|
-
@connection.tables.sort
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
94
|
+
sorted_tables = @connection.tables.sort
|
95
|
+
|
96
|
+
sorted_tables.each do |table_name|
|
97
|
+
table(table_name, stream) unless ignored?(table_name)
|
98
|
+
end
|
99
|
+
|
100
|
+
# dump foreign keys at the end to make sure all dependent tables exist.
|
101
|
+
if @connection.supports_foreign_keys?
|
102
|
+
sorted_tables.each do |tbl|
|
103
|
+
foreign_keys(tbl, stream) unless ignored?(tbl)
|
78
104
|
end
|
79
|
-
table(tbl, stream)
|
80
105
|
end
|
81
106
|
end
|
82
107
|
|
@@ -86,51 +111,41 @@ HEADER
|
|
86
111
|
tbl = StringIO.new
|
87
112
|
|
88
113
|
# first dump primary key column
|
89
|
-
|
90
|
-
pk, _ = @connection.pk_and_sequence_for(table)
|
91
|
-
elsif @connection.respond_to?(:primary_key)
|
92
|
-
pk = @connection.primary_key(table)
|
93
|
-
end
|
114
|
+
pk = @connection.primary_key(table)
|
94
115
|
|
95
|
-
tbl.print " create_table #{table.inspect}"
|
96
|
-
|
116
|
+
tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
|
117
|
+
pkcol = columns.detect { |c| c.name == pk }
|
118
|
+
if pkcol
|
97
119
|
if pk != 'id'
|
98
|
-
tbl.print %Q(, :
|
120
|
+
tbl.print %Q(, primary_key: "#{pk}")
|
121
|
+
elsif pkcol.sql_type == 'bigint'
|
122
|
+
tbl.print ", id: :bigserial"
|
123
|
+
elsif pkcol.sql_type == 'uuid'
|
124
|
+
tbl.print ", id: :uuid"
|
125
|
+
tbl.print %Q(, default: #{pkcol.default_function.inspect})
|
99
126
|
end
|
100
127
|
else
|
101
|
-
tbl.print ", :
|
128
|
+
tbl.print ", id: false"
|
102
129
|
end
|
103
|
-
tbl.print ", :
|
130
|
+
tbl.print ", force: :cascade"
|
104
131
|
tbl.puts " do |t|"
|
105
132
|
|
106
133
|
# then dump all non-primary key columns
|
107
134
|
column_specs = columns.map do |column|
|
108
|
-
raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'"
|
135
|
+
raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type)
|
109
136
|
next if column.name == pk
|
110
|
-
|
111
|
-
spec[:name] = column.name.inspect
|
112
|
-
|
113
|
-
# AR has an optimisation 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
|
137
|
+
@connection.column_spec(column, @types)
|
127
138
|
end.compact
|
128
139
|
|
129
140
|
# find all migration keys used in this table
|
130
|
-
keys =
|
141
|
+
keys = @connection.migration_keys
|
131
142
|
|
132
143
|
# figure out the lengths for each column based on above keys
|
133
|
-
lengths = keys.map{ |key|
|
144
|
+
lengths = keys.map { |key|
|
145
|
+
column_specs.map { |spec|
|
146
|
+
spec[key] ? spec[key].length + 2 : 0
|
147
|
+
}.max
|
148
|
+
}
|
134
149
|
|
135
150
|
# the string we're going to sprintf our values against, with standardized column widths
|
136
151
|
format_string = lengths.map{ |len| "%-#{len}s" }
|
@@ -166,36 +181,71 @@ HEADER
|
|
166
181
|
stream
|
167
182
|
end
|
168
183
|
|
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
184
|
def indexes(table, stream)
|
181
185
|
if (indexes = @connection.indexes(table)).any?
|
182
186
|
add_index_statements = indexes.map do |index|
|
183
187
|
statement_parts = [
|
184
|
-
|
188
|
+
"add_index #{remove_prefix_and_suffix(index.table).inspect}",
|
185
189
|
index.columns.inspect,
|
186
|
-
|
190
|
+
"name: #{index.name.inspect}",
|
187
191
|
]
|
188
|
-
statement_parts << ':
|
192
|
+
statement_parts << 'unique: true' if index.unique
|
189
193
|
|
190
194
|
index_lengths = (index.lengths || []).compact
|
191
|
-
statement_parts <<
|
195
|
+
statement_parts << "length: #{Hash[index.columns.zip(index.lengths)].inspect}" if index_lengths.any?
|
196
|
+
|
197
|
+
index_orders = index.orders || {}
|
198
|
+
statement_parts << "order: #{index.orders.inspect}" if index_orders.any?
|
199
|
+
statement_parts << "where: #{index.where.inspect}" if index.where
|
200
|
+
statement_parts << "using: #{index.using.inspect}" if index.using
|
201
|
+
statement_parts << "type: #{index.type.inspect}" if index.type
|
192
202
|
|
193
|
-
|
203
|
+
" #{statement_parts.join(', ')}"
|
194
204
|
end
|
195
205
|
|
196
206
|
stream.puts add_index_statements.sort.join("\n")
|
197
207
|
stream.puts
|
198
208
|
end
|
199
209
|
end
|
210
|
+
|
211
|
+
def foreign_keys(table, stream)
|
212
|
+
if (foreign_keys = @connection.foreign_keys(table)).any?
|
213
|
+
add_foreign_key_statements = foreign_keys.map do |foreign_key|
|
214
|
+
parts = [
|
215
|
+
"add_foreign_key #{remove_prefix_and_suffix(foreign_key.from_table).inspect}",
|
216
|
+
remove_prefix_and_suffix(foreign_key.to_table).inspect,
|
217
|
+
]
|
218
|
+
|
219
|
+
if foreign_key.column != @connection.foreign_key_column_for(foreign_key.to_table)
|
220
|
+
parts << "column: #{foreign_key.column.inspect}"
|
221
|
+
end
|
222
|
+
|
223
|
+
if foreign_key.custom_primary_key?
|
224
|
+
parts << "primary_key: #{foreign_key.primary_key.inspect}"
|
225
|
+
end
|
226
|
+
|
227
|
+
if foreign_key.name !~ /^fk_rails_[0-9a-f]{10}$/
|
228
|
+
parts << "name: #{foreign_key.name.inspect}"
|
229
|
+
end
|
230
|
+
|
231
|
+
parts << "on_update: #{foreign_key.on_update.inspect}" if foreign_key.on_update
|
232
|
+
parts << "on_delete: #{foreign_key.on_delete.inspect}" if foreign_key.on_delete
|
233
|
+
|
234
|
+
" #{parts.join(', ')}"
|
235
|
+
end
|
236
|
+
|
237
|
+
stream.puts add_foreign_key_statements.sort.join("\n")
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def remove_prefix_and_suffix(table)
|
242
|
+
table.gsub(/^(#{@options[:table_name_prefix]})(.+)(#{@options[:table_name_suffix]})$/, "\\2")
|
243
|
+
end
|
244
|
+
|
245
|
+
def ignored?(table_name)
|
246
|
+
['schema_migrations', ignore_tables].flatten.any? do |ignored|
|
247
|
+
ignored === remove_prefix_and_suffix(table_name)
|
248
|
+
end
|
249
|
+
end
|
200
250
|
end
|
201
251
|
end
|