activerecord 4.1.0.beta2 → 4.1.0.rc1
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 +4 -4
- data/CHANGELOG.md +622 -9
- data/MIT-LICENSE +1 -1
- data/lib/active_record.rb +1 -1
- data/lib/active_record/associations.rb +10 -7
- data/lib/active_record/associations/alias_tracker.rb +39 -29
- data/lib/active_record/associations/association.rb +1 -1
- data/lib/active_record/associations/association_scope.rb +56 -31
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +5 -0
- data/lib/active_record/associations/builder/association.rb +6 -0
- data/lib/active_record/associations/builder/belongs_to.rb +1 -1
- data/lib/active_record/associations/collection_association.rb +33 -9
- data/lib/active_record/associations/collection_proxy.rb +53 -5
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +5 -5
- data/lib/active_record/associations/join_dependency/join_association.rb +8 -8
- data/lib/active_record/associations/preloader.rb +1 -1
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/attribute_methods.rb +28 -5
- data/lib/active_record/attribute_methods/dirty.rb +27 -4
- data/lib/active_record/attribute_methods/read.rb +1 -1
- data/lib/active_record/attribute_methods/serialization.rb +18 -0
- data/lib/active_record/autosave_association.rb +1 -1
- data/lib/active_record/base.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +16 -9
- data/lib/active_record/connection_adapters/abstract/quoting.rb +3 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +8 -8
- data/lib/active_record/connection_adapters/abstract/transaction.rb +4 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +15 -5
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +2 -6
- data/lib/active_record/connection_adapters/connection_specification.rb +200 -43
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +7 -1
- data/lib/active_record/connection_adapters/mysql_adapter.rb +8 -2
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/cast.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +32 -17
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +25 -3
- data/lib/active_record/connection_handling.rb +64 -3
- data/lib/active_record/core.rb +28 -24
- data/lib/active_record/dynamic_matchers.rb +6 -2
- data/lib/active_record/enum.rb +111 -17
- data/lib/active_record/errors.rb +12 -0
- data/lib/active_record/fixtures.rb +13 -15
- data/lib/active_record/inheritance.rb +29 -9
- data/lib/active_record/integration.rb +4 -2
- data/lib/active_record/migration.rb +20 -7
- data/lib/active_record/migration/command_recorder.rb +18 -6
- data/lib/active_record/persistence.rb +10 -5
- data/lib/active_record/querying.rb +1 -0
- data/lib/active_record/railtie.rb +11 -8
- data/lib/active_record/railties/databases.rake +24 -38
- data/lib/active_record/relation.rb +3 -2
- data/lib/active_record/relation/batches.rb +24 -9
- data/lib/active_record/relation/finder_methods.rb +100 -11
- data/lib/active_record/relation/query_methods.rb +39 -27
- data/lib/active_record/result.rb +1 -1
- data/lib/active_record/sanitization.rb +7 -5
- data/lib/active_record/scoping.rb +5 -0
- data/lib/active_record/scoping/named.rb +6 -0
- data/lib/active_record/store.rb +1 -1
- data/lib/active_record/tasks/database_tasks.rb +45 -23
- data/lib/active_record/timestamp.rb +2 -2
- data/lib/active_record/transactions.rb +7 -7
- data/lib/active_record/validations/presence.rb +1 -1
- data/lib/active_record/version.rb +1 -1
- metadata +5 -6
- data/lib/active_record/associations/join_helper.rb +0 -36
data/MIT-LICENSE
CHANGED
data/lib/active_record.rb
CHANGED
@@ -75,13 +75,13 @@ module ActiveRecord
|
|
75
75
|
|
76
76
|
class EagerLoadPolymorphicError < ActiveRecordError #:nodoc:
|
77
77
|
def initialize(reflection)
|
78
|
-
super("
|
78
|
+
super("Cannot eagerly load the polymorphic association #{reflection.name.inspect}")
|
79
79
|
end
|
80
80
|
end
|
81
81
|
|
82
82
|
class ReadOnlyAssociation < ActiveRecordError #:nodoc:
|
83
83
|
def initialize(reflection)
|
84
|
-
super("
|
84
|
+
super("Cannot add to a has_many :through association. Try adding to #{reflection.through_reflection.name.inspect}.")
|
85
85
|
end
|
86
86
|
end
|
87
87
|
|
@@ -130,7 +130,6 @@ module ActiveRecord
|
|
130
130
|
autoload :JoinDependency, 'active_record/associations/join_dependency'
|
131
131
|
autoload :AssociationScope, 'active_record/associations/association_scope'
|
132
132
|
autoload :AliasTracker, 'active_record/associations/alias_tracker'
|
133
|
-
autoload :JoinHelper, 'active_record/associations/join_helper'
|
134
133
|
end
|
135
134
|
|
136
135
|
# Clears out the association cache.
|
@@ -669,11 +668,14 @@ module ActiveRecord
|
|
669
668
|
# and member posts that use the posts table for STI. In this case, there must be a +type+
|
670
669
|
# column in the posts table.
|
671
670
|
#
|
671
|
+
# Note: The <tt>attachable_type=</tt> method is being called when assigning an +attachable+.
|
672
|
+
# The +class_name+ of the +attachable+ is passed as a String.
|
673
|
+
#
|
672
674
|
# class Asset < ActiveRecord::Base
|
673
675
|
# belongs_to :attachable, polymorphic: true
|
674
676
|
#
|
675
|
-
# def attachable_type=(
|
676
|
-
# super(
|
677
|
+
# def attachable_type=(class_name)
|
678
|
+
# super(class_name.constantize.base_class.to_s)
|
677
679
|
# end
|
678
680
|
# end
|
679
681
|
#
|
@@ -1213,7 +1215,8 @@ module ActiveRecord
|
|
1213
1215
|
# Returns the associated object. +nil+ is returned if none is found.
|
1214
1216
|
# [association=(associate)]
|
1215
1217
|
# Assigns the associate object, extracts the primary key, sets it as the foreign key,
|
1216
|
-
# and saves the associate object.
|
1218
|
+
# and saves the associate object. To avoid database inconsistencies, permanently deletes an existing
|
1219
|
+
# associated object when assigning a new one, even if the new one isn't saved to database.
|
1217
1220
|
# [build_association(attributes = {})]
|
1218
1221
|
# Returns a new object of the associated type that has been instantiated
|
1219
1222
|
# with +attributes+ and linked to this object through a foreign key, but has not
|
@@ -1581,7 +1584,7 @@ module ActiveRecord
|
|
1581
1584
|
hm_options[:through] = middle_reflection.name
|
1582
1585
|
hm_options[:source] = join_model.right_reflection.name
|
1583
1586
|
|
1584
|
-
[:before_add, :after_add, :before_remove, :after_remove].each do |k|
|
1587
|
+
[:before_add, :after_add, :before_remove, :after_remove, :autosave].each do |k|
|
1585
1588
|
hm_options[k] = options[k] if options.key? k
|
1586
1589
|
end
|
1587
1590
|
|
@@ -5,16 +5,48 @@ module ActiveRecord
|
|
5
5
|
# Keeps track of table aliases for ActiveRecord::Associations::ClassMethods::JoinDependency and
|
6
6
|
# ActiveRecord::Associations::ThroughAssociationScope
|
7
7
|
class AliasTracker # :nodoc:
|
8
|
-
attr_reader :aliases, :
|
8
|
+
attr_reader :aliases, :connection
|
9
|
+
|
10
|
+
def self.empty(connection)
|
11
|
+
new connection, Hash.new(0)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.create(connection, table_joins)
|
15
|
+
if table_joins.empty?
|
16
|
+
empty connection
|
17
|
+
else
|
18
|
+
aliases = Hash.new { |h,k|
|
19
|
+
h[k] = initial_count_for(connection, k, table_joins)
|
20
|
+
}
|
21
|
+
new connection, aliases
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.initial_count_for(connection, name, table_joins)
|
26
|
+
# quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
|
27
|
+
quoted_name = connection.quote_table_name(name).downcase
|
28
|
+
|
29
|
+
counts = table_joins.map do |join|
|
30
|
+
if join.is_a?(Arel::Nodes::StringJoin)
|
31
|
+
# Table names + table aliases
|
32
|
+
join.left.downcase.scan(
|
33
|
+
/join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
|
34
|
+
).size
|
35
|
+
else
|
36
|
+
join.left.table_name == name ? 1 : 0
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
counts.sum
|
41
|
+
end
|
9
42
|
|
10
43
|
# table_joins is an array of arel joins which might conflict with the aliases we assign here
|
11
|
-
def initialize(connection
|
12
|
-
@aliases
|
13
|
-
@
|
14
|
-
@connection = connection
|
44
|
+
def initialize(connection, aliases)
|
45
|
+
@aliases = aliases
|
46
|
+
@connection = connection
|
15
47
|
end
|
16
48
|
|
17
|
-
def aliased_table_for(table_name, aliased_name
|
49
|
+
def aliased_table_for(table_name, aliased_name)
|
18
50
|
table_alias = aliased_name_for(table_name, aliased_name)
|
19
51
|
|
20
52
|
if table_alias == table_name
|
@@ -24,9 +56,7 @@ module ActiveRecord
|
|
24
56
|
end
|
25
57
|
end
|
26
58
|
|
27
|
-
def aliased_name_for(table_name, aliased_name
|
28
|
-
aliased_name ||= table_name
|
29
|
-
|
59
|
+
def aliased_name_for(table_name, aliased_name)
|
30
60
|
if aliases[table_name].zero?
|
31
61
|
# If it's zero, we can have our table_name
|
32
62
|
aliases[table_name] = 1
|
@@ -48,26 +78,6 @@ module ActiveRecord
|
|
48
78
|
|
49
79
|
private
|
50
80
|
|
51
|
-
def initial_count_for(name)
|
52
|
-
return 0 if Arel::Table === table_joins
|
53
|
-
|
54
|
-
# quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
|
55
|
-
quoted_name = connection.quote_table_name(name).downcase
|
56
|
-
|
57
|
-
counts = table_joins.map do |join|
|
58
|
-
if join.is_a?(Arel::Nodes::StringJoin)
|
59
|
-
# Table names + table aliases
|
60
|
-
join.left.downcase.scan(
|
61
|
-
/join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
|
62
|
-
).size
|
63
|
-
else
|
64
|
-
join.left.table_name == name ? 1 : 0
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
counts.sum
|
69
|
-
end
|
70
|
-
|
71
81
|
def truncate(name)
|
72
82
|
name.slice(0, connection.table_alias_length - 2)
|
73
83
|
end
|
@@ -1,52 +1,77 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module Associations
|
3
3
|
class AssociationScope #:nodoc:
|
4
|
-
|
4
|
+
INSTANCE = new
|
5
5
|
|
6
|
-
|
6
|
+
def self.scope(association, connection)
|
7
|
+
INSTANCE.scope association, connection
|
8
|
+
end
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
+
def scope(association, connection)
|
11
|
+
klass = association.klass
|
12
|
+
reflection = association.reflection
|
13
|
+
scope = klass.unscoped
|
14
|
+
owner = association.owner
|
15
|
+
alias_tracker = AliasTracker.empty connection
|
10
16
|
|
11
|
-
|
12
|
-
|
13
|
-
@alias_tracker = AliasTracker.new klass.connection
|
17
|
+
scope.extending! Array(reflection.options[:extend])
|
18
|
+
add_constraints(scope, owner, klass, reflection, alias_tracker)
|
14
19
|
end
|
15
20
|
|
16
|
-
def
|
17
|
-
|
18
|
-
scope.extending! Array(options[:extend])
|
19
|
-
add_constraints(scope)
|
21
|
+
def join_type
|
22
|
+
Arel::Nodes::InnerJoin
|
20
23
|
end
|
21
24
|
|
22
25
|
private
|
23
26
|
|
24
|
-
def
|
27
|
+
def construct_tables(chain, klass, refl, alias_tracker)
|
28
|
+
chain.map do |reflection|
|
29
|
+
alias_tracker.aliased_table_for(
|
30
|
+
table_name_for(reflection, klass, refl),
|
31
|
+
table_alias_for(reflection, refl, reflection != refl)
|
32
|
+
)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def table_alias_for(reflection, refl, join = false)
|
37
|
+
name = "#{reflection.plural_name}_#{alias_suffix(refl)}"
|
38
|
+
name << "_join" if join
|
39
|
+
name
|
40
|
+
end
|
41
|
+
|
42
|
+
def join(table, constraint)
|
43
|
+
table.create_join(table, table.create_on(constraint), join_type)
|
44
|
+
end
|
45
|
+
|
46
|
+
def column_for(table_name, column_name, alias_tracker)
|
25
47
|
columns = alias_tracker.connection.schema_cache.columns_hash(table_name)
|
26
48
|
columns[column_name]
|
27
49
|
end
|
28
50
|
|
29
|
-
def bind_value(scope, column, value)
|
51
|
+
def bind_value(scope, column, value, alias_tracker)
|
30
52
|
substitute = alias_tracker.connection.substitute_at(
|
31
53
|
column, scope.bind_values.length)
|
32
54
|
scope.bind_values += [[column, value]]
|
33
55
|
substitute
|
34
56
|
end
|
35
57
|
|
36
|
-
def bind(scope, table_name, column_name, value)
|
37
|
-
column = column_for table_name, column_name
|
38
|
-
bind_value scope, column, value
|
58
|
+
def bind(scope, table_name, column_name, value, tracker)
|
59
|
+
column = column_for table_name, column_name, tracker
|
60
|
+
bind_value scope, column, value, tracker
|
39
61
|
end
|
40
62
|
|
41
|
-
def add_constraints(scope)
|
42
|
-
|
63
|
+
def add_constraints(scope, owner, assoc_klass, refl, tracker)
|
64
|
+
chain = refl.chain
|
65
|
+
scope_chain = refl.scope_chain
|
66
|
+
|
67
|
+
tables = construct_tables(chain, assoc_klass, refl, tracker)
|
43
68
|
|
44
69
|
chain.each_with_index do |reflection, i|
|
45
70
|
table, foreign_table = tables.shift, tables.first
|
46
71
|
|
47
72
|
if reflection.source_macro == :belongs_to
|
48
73
|
if reflection.options[:polymorphic]
|
49
|
-
key = reflection.association_primary_key(
|
74
|
+
key = reflection.association_primary_key(assoc_klass)
|
50
75
|
else
|
51
76
|
key = reflection.association_primary_key
|
52
77
|
end
|
@@ -58,12 +83,12 @@ module ActiveRecord
|
|
58
83
|
end
|
59
84
|
|
60
85
|
if reflection == chain.last
|
61
|
-
bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key]
|
86
|
+
bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], tracker
|
62
87
|
scope = scope.where(table[key].eq(bind_val))
|
63
88
|
|
64
89
|
if reflection.type
|
65
90
|
value = owner.class.base_class.name
|
66
|
-
bind_val = bind scope, table.table_name, reflection.type.to_s, value
|
91
|
+
bind_val = bind scope, table.table_name, reflection.type.to_s, value, tracker
|
67
92
|
scope = scope.where(table[reflection.type].eq(bind_val))
|
68
93
|
end
|
69
94
|
else
|
@@ -71,7 +96,7 @@ module ActiveRecord
|
|
71
96
|
|
72
97
|
if reflection.type
|
73
98
|
value = chain[i + 1].klass.base_class.name
|
74
|
-
bind_val = bind scope, table.table_name, reflection.type.to_s, value
|
99
|
+
bind_val = bind scope, table.table_name, reflection.type.to_s, value, tracker
|
75
100
|
scope = scope.where(table[reflection.type].eq(bind_val))
|
76
101
|
end
|
77
102
|
|
@@ -79,14 +104,14 @@ module ActiveRecord
|
|
79
104
|
end
|
80
105
|
|
81
106
|
is_first_chain = i == 0
|
82
|
-
klass = is_first_chain ?
|
107
|
+
klass = is_first_chain ? assoc_klass : reflection.klass
|
83
108
|
|
84
109
|
# Exclude the scope of the association itself, because that
|
85
110
|
# was already merged in the #scope method.
|
86
111
|
scope_chain[i].each do |scope_chain_item|
|
87
|
-
item = eval_scope(klass, scope_chain_item)
|
112
|
+
item = eval_scope(klass, scope_chain_item, owner)
|
88
113
|
|
89
|
-
if scope_chain_item ==
|
114
|
+
if scope_chain_item == refl.scope
|
90
115
|
scope.merge! item.except(:where, :includes, :bind)
|
91
116
|
end
|
92
117
|
|
@@ -102,22 +127,22 @@ module ActiveRecord
|
|
102
127
|
scope
|
103
128
|
end
|
104
129
|
|
105
|
-
def alias_suffix
|
106
|
-
|
130
|
+
def alias_suffix(refl)
|
131
|
+
refl.name
|
107
132
|
end
|
108
133
|
|
109
|
-
def table_name_for(reflection)
|
110
|
-
if reflection ==
|
134
|
+
def table_name_for(reflection, klass, refl)
|
135
|
+
if reflection == refl
|
111
136
|
# If this is a polymorphic belongs_to, we want to get the klass from the
|
112
137
|
# association because it depends on the polymorphic_type attribute of
|
113
138
|
# the owner
|
114
139
|
klass.table_name
|
115
140
|
else
|
116
|
-
|
141
|
+
reflection.table_name
|
117
142
|
end
|
118
143
|
end
|
119
144
|
|
120
|
-
def eval_scope(klass, scope)
|
145
|
+
def eval_scope(klass, scope, owner)
|
121
146
|
if scope.is_a?(Relation)
|
122
147
|
scope
|
123
148
|
else
|
@@ -26,6 +26,12 @@ module ActiveRecord::Associations::Builder
|
|
26
26
|
attr_reader :name, :scope, :options
|
27
27
|
|
28
28
|
def self.build(model, name, scope, options, &block)
|
29
|
+
if model.dangerous_attribute_method?(name)
|
30
|
+
raise ArgumentError, "You tried to define an association named #{name} on the model #{model.name}, but " \
|
31
|
+
"this will conflict with a method #{name} already defined by Active Record. " \
|
32
|
+
"Please choose a different association name."
|
33
|
+
end
|
34
|
+
|
29
35
|
builder = create_builder model, name, scope, options, &block
|
30
36
|
reflection = builder.build(model)
|
31
37
|
define_accessors model, reflection
|
@@ -4,7 +4,7 @@ module ActiveRecord
|
|
4
4
|
#
|
5
5
|
# CollectionAssociation is an abstract class that provides common stuff to
|
6
6
|
# ease the implementation of association proxies that represent
|
7
|
-
# collections. See the class hierarchy in
|
7
|
+
# collections. See the class hierarchy in Association.
|
8
8
|
#
|
9
9
|
# CollectionAssociation:
|
10
10
|
# HasManyAssociation => has_many
|
@@ -24,6 +24,10 @@ module ActiveRecord
|
|
24
24
|
# If you need to work on all current children, new and existing records,
|
25
25
|
# +load_target+ and the +loaded+ flag are your friends.
|
26
26
|
class CollectionAssociation < Association #:nodoc:
|
27
|
+
def initialize(owner, reflection)
|
28
|
+
super
|
29
|
+
@proxy = CollectionProxy.create(klass, self)
|
30
|
+
end
|
27
31
|
|
28
32
|
# Implements the reader method, e.g. foo.items for Foo.has_many :items
|
29
33
|
def reader(force_reload = false)
|
@@ -33,7 +37,7 @@ module ActiveRecord
|
|
33
37
|
reload
|
34
38
|
end
|
35
39
|
|
36
|
-
@proxy
|
40
|
+
@proxy
|
37
41
|
end
|
38
42
|
|
39
43
|
# Implements the writer method, e.g. foo.items= for Foo.has_many :items
|
@@ -66,11 +70,11 @@ module ActiveRecord
|
|
66
70
|
@target = []
|
67
71
|
end
|
68
72
|
|
69
|
-
def select(
|
73
|
+
def select(*fields)
|
70
74
|
if block_given?
|
71
75
|
load_target.select.each { |e| yield e }
|
72
76
|
else
|
73
|
-
scope.select(
|
77
|
+
scope.select(*fields)
|
74
78
|
end
|
75
79
|
end
|
76
80
|
|
@@ -96,11 +100,31 @@ module ActiveRecord
|
|
96
100
|
end
|
97
101
|
|
98
102
|
def first(*args)
|
99
|
-
|
103
|
+
first_nth_or_last(:first, *args)
|
104
|
+
end
|
105
|
+
|
106
|
+
def second(*args)
|
107
|
+
first_nth_or_last(:second, *args)
|
108
|
+
end
|
109
|
+
|
110
|
+
def third(*args)
|
111
|
+
first_nth_or_last(:third, *args)
|
112
|
+
end
|
113
|
+
|
114
|
+
def fourth(*args)
|
115
|
+
first_nth_or_last(:fourth, *args)
|
116
|
+
end
|
117
|
+
|
118
|
+
def fifth(*args)
|
119
|
+
first_nth_or_last(:fifth, *args)
|
120
|
+
end
|
121
|
+
|
122
|
+
def forty_two(*args)
|
123
|
+
first_nth_or_last(:forty_two, *args)
|
100
124
|
end
|
101
125
|
|
102
126
|
def last(*args)
|
103
|
-
|
127
|
+
first_nth_or_last(:last, *args)
|
104
128
|
end
|
105
129
|
|
106
130
|
def build(attributes = {}, &block)
|
@@ -526,7 +550,7 @@ module ActiveRecord
|
|
526
550
|
# * target already loaded
|
527
551
|
# * owner is new record
|
528
552
|
# * target contains new or changed record(s)
|
529
|
-
def
|
553
|
+
def fetch_first_nth_or_last_using_find?(args)
|
530
554
|
if args.first.is_a?(Hash)
|
531
555
|
true
|
532
556
|
else
|
@@ -564,10 +588,10 @@ module ActiveRecord
|
|
564
588
|
end
|
565
589
|
|
566
590
|
# Fetches the first/last using SQL if possible, otherwise from the target array.
|
567
|
-
def
|
591
|
+
def first_nth_or_last(type, *args)
|
568
592
|
args.shift if args.first.is_a?(Hash) && args.first.empty?
|
569
593
|
|
570
|
-
collection =
|
594
|
+
collection = fetch_first_nth_or_last_using_find?(args) ? scope : load_target
|
571
595
|
collection.send(type, *args).tap do |record|
|
572
596
|
set_inverse_instance record if record.is_a? ActiveRecord::Base
|
573
597
|
end
|