activerecord 3.0.0 → 3.0.3

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 (46) hide show
  1. data/CHANGELOG +42 -0
  2. data/examples/performance.rb +18 -1
  3. data/lib/active_record.rb +3 -3
  4. data/lib/active_record/aggregations.rb +2 -2
  5. data/lib/active_record/association_preload.rb +1 -1
  6. data/lib/active_record/associations.rb +59 -26
  7. data/lib/active_record/associations/association_collection.rb +28 -18
  8. data/lib/active_record/associations/association_proxy.rb +4 -4
  9. data/lib/active_record/associations/belongs_to_association.rb +3 -3
  10. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +10 -13
  11. data/lib/active_record/associations/has_many_through_association.rb +2 -3
  12. data/lib/active_record/associations/has_one_association.rb +6 -6
  13. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  14. data/lib/active_record/attribute_methods/before_type_cast.rb +1 -4
  15. data/lib/active_record/attribute_methods/primary_key.rb +4 -3
  16. data/lib/active_record/autosave_association.rb +7 -7
  17. data/lib/active_record/base.rb +71 -47
  18. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +6 -8
  19. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +2 -2
  20. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +5 -9
  21. data/lib/active_record/connection_adapters/mysql_adapter.rb +7 -7
  22. data/lib/active_record/connection_adapters/sqlite_adapter.rb +2 -2
  23. data/lib/active_record/dynamic_finder_match.rb +20 -17
  24. data/lib/active_record/dynamic_scope_match.rb +6 -15
  25. data/lib/active_record/fixtures.rb +3 -5
  26. data/lib/active_record/locking/optimistic.rb +1 -1
  27. data/lib/active_record/locking/pessimistic.rb +4 -4
  28. data/lib/active_record/nested_attributes.rb +17 -13
  29. data/lib/active_record/persistence.rb +7 -8
  30. data/lib/active_record/railties/databases.rake +7 -7
  31. data/lib/active_record/relation.rb +16 -18
  32. data/lib/active_record/relation/batches.rb +1 -1
  33. data/lib/active_record/relation/calculations.rb +37 -28
  34. data/lib/active_record/relation/finder_methods.rb +19 -19
  35. data/lib/active_record/relation/predicate_builder.rb +8 -1
  36. data/lib/active_record/relation/query_methods.rb +100 -75
  37. data/lib/active_record/relation/spawn_methods.rb +50 -39
  38. data/lib/active_record/serialization.rb +1 -1
  39. data/lib/active_record/session_store.rb +4 -4
  40. data/lib/active_record/transactions.rb +6 -6
  41. data/lib/active_record/validations.rb +1 -1
  42. data/lib/active_record/validations/uniqueness.rb +6 -1
  43. data/lib/active_record/version.rb +2 -2
  44. data/lib/rails/generators/active_record.rb +2 -10
  45. data/lib/rails/generators/active_record/migration.rb +15 -0
  46. metadata +15 -14
data/CHANGELOG CHANGED
@@ -1,3 +1,45 @@
1
+ *Rails 3.0.3 (November 16, 2010)*
2
+
3
+ * Support find by class like this: Post.where(:name => Post)
4
+
5
+
6
+ *Rails 3.0.2 (November 15, 2010)*
7
+
8
+ * Dramatic speed increase (see: http://engineering.attinteractive.com/2010/10/arel-two-point-ohhhhh-yaaaaaa/) [Aaron Patterson]
9
+
10
+ * reorder is deprecated in favor of except(:order).order(...) [Santiago Pastorino]
11
+
12
+ * except is now AR public API
13
+
14
+ Model.order('name').except(:order).order('salary')
15
+
16
+ generates:
17
+
18
+ SELECT * FROM models ORDER BY salary
19
+
20
+ [Santiago Pastorino]
21
+
22
+ * The following code:
23
+
24
+ Model.limit(10).scoping { Model.count }
25
+
26
+ now generates the following SQL:
27
+
28
+ SELECT COUNT(*) FROM models LIMIT 10
29
+
30
+ This may not return what you want. Instead, you may with to do something
31
+ like this:
32
+
33
+ Model.limit(10).scoping { Model.all.size }
34
+
35
+ [Aaron Patterson]
36
+
37
+
38
+ *Rails 3.0.1 (October 15, 2010)*
39
+
40
+ * Introduce a fix for CVE-2010-3993
41
+
42
+
1
43
  *Rails 3.0.0 (August 29, 2010)*
2
44
 
3
45
  * Changed update_attribute to not run callbacks and update the record directly in the database [Neeraj Singh]
@@ -25,7 +25,7 @@ conn[:socket] = Pathname.glob(%w[
25
25
  /tmp/mysql.sock
26
26
  /var/mysql/mysql.sock
27
27
  /var/run/mysqld/mysqld.sock
28
- ]).find { |path| path.socket? }
28
+ ]).find { |path| path.socket? }.to_s
29
29
 
30
30
  ActiveRecord::Base.establish_connection(conn)
31
31
 
@@ -155,6 +155,23 @@ RBench.run(TIMES) do
155
155
  ar { Exhibit.transaction { Exhibit.new } }
156
156
  end
157
157
 
158
+ report 'Model.find(id)' do
159
+ id = Exhibit.first.id
160
+ ar { Exhibit.find(id) }
161
+ end
162
+
163
+ report 'Model.find_by_sql' do
164
+ ar { Exhibit.find_by_sql("SELECT * FROM exhibits WHERE id = #{(rand * 1000 + 1).to_i}").first }
165
+ end
166
+
167
+ report 'Model.log', (TIMES * 10) do
168
+ ar { Exhibit.connection.send(:log, "hello", "world") {} }
169
+ end
170
+
171
+ report 'AR.execute(query)', (TIMES / 2) do
172
+ ar { ActiveRecord::Base.connection.execute("Select * from exhibits where id = #{(rand * 1000 + 1).to_i}") }
173
+ end
174
+
158
175
  summary 'Total'
159
176
  end
160
177
 
@@ -33,12 +33,12 @@ require 'active_support/i18n'
33
33
  require 'active_model'
34
34
  require 'arel'
35
35
 
36
+ require 'active_record/version'
37
+
36
38
  module ActiveRecord
37
39
  extend ActiveSupport::Autoload
38
40
 
39
41
  eager_autoload do
40
- autoload :VERSION
41
-
42
42
  autoload :ActiveRecordError, 'active_record/errors'
43
43
  autoload :ConnectionNotEstablished, 'active_record/errors'
44
44
 
@@ -118,7 +118,7 @@ module ActiveRecord
118
118
  end
119
119
 
120
120
  ActiveSupport.on_load(:active_record) do
121
- Arel::Table.engine = Arel::Sql::Engine.new(self)
121
+ Arel::Table.engine = self
122
122
  end
123
123
 
124
124
  I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml'
@@ -6,7 +6,7 @@ module ActiveRecord
6
6
  def clear_aggregation_cache #:nodoc:
7
7
  self.class.reflect_on_all_aggregations.to_a.each do |assoc|
8
8
  instance_variable_set "@#{assoc.name}", nil
9
- end unless self.new_record?
9
+ end if self.persisted?
10
10
  end
11
11
 
12
12
  # Active Record implements aggregation through a macro-like class method called +composed_of+
@@ -161,7 +161,7 @@ module ActiveRecord
161
161
  # by specifying an instance of the value object in the conditions hash. The following example
162
162
  # finds all customers with +balance_amount+ equal to 20 and +balance_currency+ equal to "USD":
163
163
  #
164
- # Customer.find(:all, :conditions => {:balance => Money.new(20, "USD")})
164
+ # Customer.where(:balance => Money.new(20, "USD")).all
165
165
  #
166
166
  module ClassMethods
167
167
  # Adds reader and writer methods for manipulating a value object:
@@ -284,7 +284,7 @@ module ActiveRecord
284
284
  end
285
285
  else
286
286
  options = {}
287
- options[:include] = reflection.options[:include] || reflection.options[:source] if reflection.options[:conditions]
287
+ options[:include] = reflection.options[:include] || reflection.options[:source] if reflection.options[:conditions] || reflection.options[:order]
288
288
  options[:order] = reflection.options[:order]
289
289
  options[:conditions] = reflection.options[:conditions]
290
290
  records.first.class.preload_associations(records, through_association, options)
@@ -118,7 +118,7 @@ module ActiveRecord
118
118
  def clear_association_cache #:nodoc:
119
119
  self.class.reflect_on_all_associations.to_a.each do |assoc|
120
120
  instance_variable_set "@#{assoc.name}", nil
121
- end unless self.new_record?
121
+ end if self.persisted?
122
122
  end
123
123
 
124
124
  private
@@ -602,7 +602,7 @@ module ActiveRecord
602
602
  # other than the main one. If this is the case Active Record falls back to the previously
603
603
  # used LEFT OUTER JOIN based strategy. For example
604
604
  #
605
- # Post.find(:all, :include => [ :author, :comments ], :conditions => ['comments.approved = ?', true])
605
+ # Post.includes([:author, :comments]).where(['comments.approved = ?', true]).all
606
606
  #
607
607
  # This will result in a single SQL query with joins along the lines of:
608
608
  # <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt> and
@@ -1416,8 +1416,8 @@ module ActiveRecord
1416
1416
  include Module.new {
1417
1417
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
1418
1418
  def destroy # def destroy
1419
- super # super
1420
1419
  #{reflection.name}.clear # posts.clear
1420
+ super # super
1421
1421
  end # end
1422
1422
  RUBY
1423
1423
  }
@@ -1840,7 +1840,7 @@ module ActiveRecord
1840
1840
 
1841
1841
  def initialize(base, associations, joins)
1842
1842
  @joins = [JoinBase.new(base, joins)]
1843
- @associations = associations
1843
+ @associations = {}
1844
1844
  @reflections = []
1845
1845
  @base_records_hash = {}
1846
1846
  @base_records_in_order = []
@@ -1852,7 +1852,7 @@ module ActiveRecord
1852
1852
  def graft(*associations)
1853
1853
  associations.each do |association|
1854
1854
  join_associations.detect {|a| association == a} ||
1855
- build(association.reflection.name, association.find_parent_in(self) || join_base, association.join_class)
1855
+ build(association.reflection.name, association.find_parent_in(self) || join_base, association.join_type)
1856
1856
  end
1857
1857
  self
1858
1858
  end
@@ -1920,28 +1920,57 @@ module ActiveRecord
1920
1920
 
1921
1921
  protected
1922
1922
 
1923
- def build(associations, parent = nil, join_class = Arel::InnerJoin)
1923
+ def cache_joined_association(association)
1924
+ associations = []
1925
+ parent = association.parent
1926
+ while parent != join_base
1927
+ associations.unshift(parent.reflection.name)
1928
+ parent = parent.parent
1929
+ end
1930
+ ref = @associations
1931
+ associations.each do |key|
1932
+ ref = ref[key]
1933
+ end
1934
+ ref[association.reflection.name] ||= {}
1935
+ end
1936
+
1937
+ def build(associations, parent = nil, join_type = Arel::InnerJoin)
1924
1938
  parent ||= @joins.last
1925
1939
  case associations
1926
1940
  when Symbol, String
1927
1941
  reflection = parent.reflections[associations.to_s.intern] or
1928
1942
  raise ConfigurationError, "Association named '#{ associations }' was not found; perhaps you misspelled it?"
1929
- @reflections << reflection
1930
- @joins << build_join_association(reflection, parent).with_join_class(join_class)
1943
+ unless join_association = find_join_association(reflection, parent)
1944
+ @reflections << reflection
1945
+ join_association = build_join_association(reflection, parent)
1946
+ join_association.join_type = join_type
1947
+ @joins << join_association
1948
+ cache_joined_association(join_association)
1949
+ end
1950
+ join_association
1931
1951
  when Array
1932
1952
  associations.each do |association|
1933
- build(association, parent, join_class)
1953
+ build(association, parent, join_type)
1934
1954
  end
1935
1955
  when Hash
1936
1956
  associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name|
1937
- build(name, parent, join_class)
1938
- build(associations[name], nil, join_class)
1957
+ join_association = build(name, parent, join_type)
1958
+ build(associations[name], join_association, join_type)
1939
1959
  end
1940
1960
  else
1941
1961
  raise ConfigurationError, associations.inspect
1942
1962
  end
1943
1963
  end
1944
1964
 
1965
+ def find_join_association(name_or_reflection, parent)
1966
+ case name_or_reflection
1967
+ when Symbol, String
1968
+ join_associations.detect {|j| (j.reflection.name == name_or_reflection.to_s.intern) && (j.parent == parent)}
1969
+ else
1970
+ join_associations.detect {|j| (j.reflection == name_or_reflection) && (j.parent == parent)}
1971
+ end
1972
+ end
1973
+
1945
1974
  def remove_uniq_by_reflection(reflection, records)
1946
1975
  if reflection && reflection.collection?
1947
1976
  records.each { |record| record.send(reflection.name).target.uniq! }
@@ -2020,8 +2049,7 @@ module ActiveRecord
2020
2049
 
2021
2050
  def ==(other)
2022
2051
  other.class == self.class &&
2023
- other.active_record == active_record &&
2024
- other.table_joins == table_joins
2052
+ other.active_record == active_record
2025
2053
  end
2026
2054
 
2027
2055
  def aliased_prefix
@@ -2049,7 +2077,7 @@ module ActiveRecord
2049
2077
  end
2050
2078
 
2051
2079
  def extract_record(row)
2052
- column_names_with_alias.inject({}){|record, (cn, an)| record[cn] = row[an]; record}
2080
+ Hash[column_names_with_alias.map{|cn, an| [cn, row[an]]}]
2053
2081
  end
2054
2082
 
2055
2083
  def record_id(row)
@@ -2062,7 +2090,9 @@ module ActiveRecord
2062
2090
  end
2063
2091
 
2064
2092
  class JoinAssociation < JoinBase # :nodoc:
2065
- attr_reader :reflection, :parent, :aliased_table_name, :aliased_prefix, :aliased_join_table_name, :parent_table_name, :join_class
2093
+ attr_reader :reflection, :parent, :aliased_table_name, :aliased_prefix, :aliased_join_table_name, :parent_table_name
2094
+ # What type of join will be generated, either Arel::InnerJoin (default) or Arel::OuterJoin
2095
+ attr_accessor :join_type
2066
2096
  delegate :options, :klass, :through_reflection, :source_reflection, :to => :reflection
2067
2097
 
2068
2098
  def initialize(reflection, join_dependency, parent = nil)
@@ -2079,7 +2109,7 @@ module ActiveRecord
2079
2109
  @parent_table_name = parent.active_record.table_name
2080
2110
  @aliased_table_name = aliased_table_name_for(table_name)
2081
2111
  @join = nil
2082
- @join_class = Arel::InnerJoin
2112
+ @join_type = Arel::InnerJoin
2083
2113
 
2084
2114
  if reflection.macro == :has_and_belongs_to_many
2085
2115
  @aliased_join_table_name = aliased_table_name_for(reflection.options[:join_table], "_join")
@@ -2102,16 +2132,16 @@ module ActiveRecord
2102
2132
  end
2103
2133
  end
2104
2134
 
2105
- def with_join_class(join_class)
2106
- @join_class = join_class
2107
- self
2108
- end
2109
-
2110
2135
  def association_join
2111
2136
  return @join if @join
2112
2137
 
2113
- aliased_table = Arel::Table.new(table_name, :as => @aliased_table_name, :engine => arel_engine)
2114
- parent_table = Arel::Table.new(parent.table_name, :as => parent.aliased_table_name, :engine => arel_engine)
2138
+ aliased_table = Arel::Table.new(table_name, :as => @aliased_table_name,
2139
+ :engine => arel_engine,
2140
+ :columns => klass.columns)
2141
+
2142
+ parent_table = Arel::Table.new(parent.table_name, :as => parent.aliased_table_name,
2143
+ :engine => arel_engine,
2144
+ :columns => parent.active_record.columns)
2115
2145
 
2116
2146
  @join = case reflection.macro
2117
2147
  when :has_and_belongs_to_many
@@ -2194,7 +2224,9 @@ module ActiveRecord
2194
2224
  end
2195
2225
 
2196
2226
  def relation
2197
- aliased = Arel::Table.new(table_name, :as => @aliased_table_name, :engine => arel_engine)
2227
+ aliased = Arel::Table.new(table_name, :as => @aliased_table_name,
2228
+ :engine => arel_engine,
2229
+ :columns => klass.columns)
2198
2230
 
2199
2231
  if reflection.macro == :has_and_belongs_to_many
2200
2232
  [Arel::Table.new(options[:join_table], :as => aliased_join_table_name, :engine => arel_engine), aliased]
@@ -2205,8 +2237,9 @@ module ActiveRecord
2205
2237
  end
2206
2238
  end
2207
2239
 
2208
- def join_relation(joining_relation, join = nil)
2209
- joining_relation.joins(self.with_join_class(Arel::OuterJoin))
2240
+ def join_relation(joining_relation)
2241
+ self.join_type = Arel::OuterJoin
2242
+ joining_relation.joins(self)
2210
2243
  end
2211
2244
 
2212
2245
  protected
@@ -127,13 +127,13 @@ module ActiveRecord
127
127
  # Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically.
128
128
  def <<(*records)
129
129
  result = true
130
- load_target if @owner.new_record?
130
+ load_target unless @owner.persisted?
131
131
 
132
132
  transaction do
133
133
  flatten_deeper(records).each do |record|
134
134
  raise_on_type_mismatch(record)
135
135
  add_record_to_target_with_callbacks(record) do |r|
136
- result &&= insert_record(record) unless @owner.new_record?
136
+ result &&= insert_record(record) if @owner.persisted?
137
137
  end
138
138
  end
139
139
  end
@@ -291,12 +291,12 @@ module ActiveRecord
291
291
  # This method is abstract in the sense that it relies on
292
292
  # +count_records+, which is a method descendants have to provide.
293
293
  def size
294
- if @owner.new_record? || (loaded? && !@reflection.options[:uniq])
294
+ if !@owner.persisted? || (loaded? && !@reflection.options[:uniq])
295
295
  @target.size
296
296
  elsif !loaded? && @reflection.options[:group]
297
297
  load_target.size
298
298
  elsif !loaded? && !@reflection.options[:uniq] && @target.is_a?(Array)
299
- unsaved_records = @target.select { |r| r.new_record? }
299
+ unsaved_records = @target.reject { |r| r.persisted? }
300
300
  unsaved_records.size + count_records
301
301
  else
302
302
  count_records
@@ -338,13 +338,12 @@ module ActiveRecord
338
338
 
339
339
  def uniq(collection = self)
340
340
  seen = Set.new
341
- collection.inject([]) do |kept, record|
341
+ collection.map do |record|
342
342
  unless seen.include?(record.id)
343
- kept << record
344
343
  seen << record.id
344
+ record
345
345
  end
346
- kept
347
- end
346
+ end.compact
348
347
  end
349
348
 
350
349
  # Replace this collection with +other_array+
@@ -364,6 +363,7 @@ module ActiveRecord
364
363
 
365
364
  def include?(record)
366
365
  return false unless record.is_a?(@reflection.klass)
366
+ return include_in_memory?(record) unless record.persisted?
367
367
  load_target if @reflection.options[:finder_sql] && !loaded?
368
368
  return @target.include?(record) if loaded?
369
369
  exists?(record)
@@ -390,7 +390,7 @@ module ActiveRecord
390
390
  end
391
391
 
392
392
  def load_target
393
- if !@owner.new_record? || foreign_key_present
393
+ if @owner.persisted? || foreign_key_present
394
394
  begin
395
395
  if !loaded?
396
396
  if @target.is_a?(Array) && @target.any?
@@ -493,11 +493,9 @@ module ActiveRecord
493
493
  attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
494
494
  ensure_owner_is_not_new
495
495
 
496
- _scope = self.construct_scope[:create]
497
- csm = @reflection.klass.send(:current_scoped_methods)
498
- options = (csm.blank? || !_scope.is_a?(Hash)) ? _scope : _scope.merge(csm.where_values_hash)
499
-
500
- record = @reflection.klass.send(:with_scope, :create => options) do
496
+ scoped_where = scoped.where_values_hash
497
+ create_scope = scoped_where ? construct_scope[:create].merge(scoped_where) : construct_scope[:create]
498
+ record = @reflection.klass.send(:with_scope, :create => create_scope) do
501
499
  @reflection.build_association(attrs)
502
500
  end
503
501
  if block_given?
@@ -523,7 +521,7 @@ module ActiveRecord
523
521
 
524
522
  transaction do
525
523
  records.each { |record| callback(:before_remove, record) }
526
- old_records = records.reject { |r| r.new_record? }
524
+ old_records = records.select { |r| r.persisted? }
527
525
  yield(records, old_records)
528
526
  records.each { |record| callback(:after_remove, record) }
529
527
  end
@@ -548,14 +546,26 @@ module ActiveRecord
548
546
  end
549
547
 
550
548
  def ensure_owner_is_not_new
551
- if @owner.new_record?
549
+ unless @owner.persisted?
552
550
  raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
553
551
  end
554
552
  end
555
553
 
556
554
  def fetch_first_or_last_using_find?(args)
557
- args.first.kind_of?(Hash) || !(loaded? || @owner.new_record? || @reflection.options[:finder_sql] ||
558
- @target.any? { |record| record.new_record? } || args.first.kind_of?(Integer))
555
+ args.first.kind_of?(Hash) || !(loaded? || !@owner.persisted? || @reflection.options[:finder_sql] ||
556
+ !@target.all? { |record| record.persisted? } || args.first.kind_of?(Integer))
557
+ end
558
+
559
+ def include_in_memory?(record)
560
+ if @reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
561
+ @owner.send(proxy_reflection.through_reflection.name.to_sym).map do |source|
562
+ source_reflection_target = source.send(proxy_reflection.source_reflection.name)
563
+ return true if source_reflection_target.respond_to?(:include?) ? source_reflection_target.include?(record) : source_reflection_target == record
564
+ end
565
+ false
566
+ else
567
+ @target.include?(record)
568
+ end
559
569
  end
560
570
  end
561
571
  end
@@ -53,7 +53,7 @@ module ActiveRecord
53
53
  alias_method :proxy_respond_to?, :respond_to?
54
54
  alias_method :proxy_extend, :extend
55
55
  delegate :to_param, :to => :proxy_target
56
- instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|proxy_/ }
56
+ instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|^respond_to_missing|proxy_/ }
57
57
 
58
58
  def initialize(owner, reflection)
59
59
  @owner, @reflection = owner, reflection
@@ -174,10 +174,10 @@ module ActiveRecord
174
174
  # If the association is polymorphic the type of the owner is also set.
175
175
  def set_belongs_to_association_for(record)
176
176
  if @reflection.options[:as]
177
- record["#{@reflection.options[:as]}_id"] = @owner.id unless @owner.new_record?
177
+ record["#{@reflection.options[:as]}_id"] = @owner.id if @owner.persisted?
178
178
  record["#{@reflection.options[:as]}_type"] = @owner.class.base_class.name.to_s
179
179
  else
180
- unless @owner.new_record?
180
+ if @owner.persisted?
181
181
  primary_key = @reflection.options[:primary_key] || :id
182
182
  record[@reflection.primary_key_name] = @owner.send(primary_key)
183
183
  end
@@ -233,7 +233,7 @@ module ActiveRecord
233
233
  def load_target
234
234
  return nil unless defined?(@loaded)
235
235
 
236
- if !loaded? and (!@owner.new_record? || foreign_key_present)
236
+ if !loaded? and (@owner.persisted? || foreign_key_present)
237
237
  @target = find_target
238
238
  end
239
239