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.

Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +622 -9
  3. data/MIT-LICENSE +1 -1
  4. data/lib/active_record.rb +1 -1
  5. data/lib/active_record/associations.rb +10 -7
  6. data/lib/active_record/associations/alias_tracker.rb +39 -29
  7. data/lib/active_record/associations/association.rb +1 -1
  8. data/lib/active_record/associations/association_scope.rb +56 -31
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +5 -0
  10. data/lib/active_record/associations/builder/association.rb +6 -0
  11. data/lib/active_record/associations/builder/belongs_to.rb +1 -1
  12. data/lib/active_record/associations/collection_association.rb +33 -9
  13. data/lib/active_record/associations/collection_proxy.rb +53 -5
  14. data/lib/active_record/associations/has_many_association.rb +1 -1
  15. data/lib/active_record/associations/join_dependency.rb +5 -5
  16. data/lib/active_record/associations/join_dependency/join_association.rb +8 -8
  17. data/lib/active_record/associations/preloader.rb +1 -1
  18. data/lib/active_record/associations/singular_association.rb +1 -1
  19. data/lib/active_record/attribute_methods.rb +28 -5
  20. data/lib/active_record/attribute_methods/dirty.rb +27 -4
  21. data/lib/active_record/attribute_methods/read.rb +1 -1
  22. data/lib/active_record/attribute_methods/serialization.rb +18 -0
  23. data/lib/active_record/autosave_association.rb +1 -1
  24. data/lib/active_record/base.rb +1 -1
  25. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +2 -2
  26. data/lib/active_record/connection_adapters/abstract/database_statements.rb +16 -9
  27. data/lib/active_record/connection_adapters/abstract/quoting.rb +3 -1
  28. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +8 -8
  29. data/lib/active_record/connection_adapters/abstract/transaction.rb +4 -0
  30. data/lib/active_record/connection_adapters/abstract_adapter.rb +15 -5
  31. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +2 -6
  32. data/lib/active_record/connection_adapters/connection_specification.rb +200 -43
  33. data/lib/active_record/connection_adapters/mysql2_adapter.rb +7 -1
  34. data/lib/active_record/connection_adapters/mysql_adapter.rb +8 -2
  35. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +3 -2
  36. data/lib/active_record/connection_adapters/postgresql/cast.rb +7 -7
  37. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +2 -2
  38. data/lib/active_record/connection_adapters/postgresql/quoting.rb +1 -1
  39. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +13 -0
  40. data/lib/active_record/connection_adapters/postgresql_adapter.rb +32 -17
  41. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +25 -3
  42. data/lib/active_record/connection_handling.rb +64 -3
  43. data/lib/active_record/core.rb +28 -24
  44. data/lib/active_record/dynamic_matchers.rb +6 -2
  45. data/lib/active_record/enum.rb +111 -17
  46. data/lib/active_record/errors.rb +12 -0
  47. data/lib/active_record/fixtures.rb +13 -15
  48. data/lib/active_record/inheritance.rb +29 -9
  49. data/lib/active_record/integration.rb +4 -2
  50. data/lib/active_record/migration.rb +20 -7
  51. data/lib/active_record/migration/command_recorder.rb +18 -6
  52. data/lib/active_record/persistence.rb +10 -5
  53. data/lib/active_record/querying.rb +1 -0
  54. data/lib/active_record/railtie.rb +11 -8
  55. data/lib/active_record/railties/databases.rake +24 -38
  56. data/lib/active_record/relation.rb +3 -2
  57. data/lib/active_record/relation/batches.rb +24 -9
  58. data/lib/active_record/relation/finder_methods.rb +100 -11
  59. data/lib/active_record/relation/query_methods.rb +39 -27
  60. data/lib/active_record/result.rb +1 -1
  61. data/lib/active_record/sanitization.rb +7 -5
  62. data/lib/active_record/scoping.rb +5 -0
  63. data/lib/active_record/scoping/named.rb +6 -0
  64. data/lib/active_record/store.rb +1 -1
  65. data/lib/active_record/tasks/database_tasks.rb +45 -23
  66. data/lib/active_record/timestamp.rb +2 -2
  67. data/lib/active_record/transactions.rb +7 -7
  68. data/lib/active_record/validations/presence.rb +1 -1
  69. data/lib/active_record/version.rb +1 -1
  70. metadata +5 -6
  71. data/lib/active_record/associations/join_helper.rb +0 -36
@@ -1,4 +1,4 @@
1
- Copyright (c) 2004-2013 David Heinemeier Hansson
1
+ Copyright (c) 2004-2014 David Heinemeier Hansson
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2004-2013 David Heinemeier Hansson
2
+ # Copyright (c) 2004-2014 David Heinemeier Hansson
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining
5
5
  # a copy of this software and associated documentation files (the
@@ -75,13 +75,13 @@ module ActiveRecord
75
75
 
76
76
  class EagerLoadPolymorphicError < ActiveRecordError #:nodoc:
77
77
  def initialize(reflection)
78
- super("Can not eagerly load the polymorphic association #{reflection.name.inspect}")
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("Can not add to a has_many :through association. Try adding to #{reflection.through_reflection.name.inspect}.")
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=(sType)
676
- # super(sType.to_s.classify.constantize.base_class.to_s)
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, :table_joins, :connection
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 = Base.connection, table_joins = [])
12
- @aliases = Hash.new { |h,k| h[k] = initial_count_for(k) }
13
- @table_joins = table_joins
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 = nil)
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 = nil)
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
@@ -94,7 +94,7 @@ module ActiveRecord
94
94
  # actually gets built.
95
95
  def association_scope
96
96
  if klass
97
- @association_scope ||= AssociationScope.new(self).scope
97
+ @association_scope ||= AssociationScope.scope(self, klass.connection)
98
98
  end
99
99
  end
100
100
 
@@ -1,52 +1,77 @@
1
1
  module ActiveRecord
2
2
  module Associations
3
3
  class AssociationScope #:nodoc:
4
- include JoinHelper
4
+ INSTANCE = new
5
5
 
6
- attr_reader :association, :alias_tracker
6
+ def self.scope(association, connection)
7
+ INSTANCE.scope association, connection
8
+ end
7
9
 
8
- delegate :klass, :owner, :reflection, :interpolate, :to => :association
9
- delegate :chain, :scope_chain, :options, :source_options, :active_record, :to => :reflection
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
- def initialize(association)
12
- @association = association
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 scope
17
- scope = klass.unscoped
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 column_for(table_name, column_name)
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
- tables = construct_tables
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(self.klass)
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 ? self.klass : reflection.klass
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 == self.reflection.scope
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
- reflection.name
130
+ def alias_suffix(refl)
131
+ refl.name
107
132
  end
108
133
 
109
- def table_name_for(reflection)
110
- if reflection == self.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
- super
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
@@ -14,6 +14,11 @@ module ActiveRecord
14
14
  owner[reflection.foreign_type] = record.class.base_class.name
15
15
  end
16
16
 
17
+ def remove_keys
18
+ super
19
+ owner[reflection.foreign_type] = nil
20
+ end
21
+
17
22
  def different_target?(record)
18
23
  super || record.class != klass
19
24
  end
@@ -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
@@ -112,7 +112,7 @@ module ActiveRecord::Associations::Builder
112
112
  end
113
113
 
114
114
  record = o.send name
115
- unless record.nil? || record.new_record?
115
+ if record && record.persisted?
116
116
  if touch != true
117
117
  record.touch touch
118
118
  else
@@ -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 AssociationProxy.
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 ||= CollectionProxy.create(klass, self)
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(select = nil)
73
+ def select(*fields)
70
74
  if block_given?
71
75
  load_target.select.each { |e| yield e }
72
76
  else
73
- scope.select(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
- first_or_last(:first, *args)
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
- first_or_last(:last, *args)
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 fetch_first_or_last_using_find?(args)
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 first_or_last(type, *args)
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 = fetch_first_or_last_using_find?(args) ? scope : load_target
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