activerecord 2.3.10 → 2.3.11

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.

data/CHANGELOG CHANGED
@@ -1,3 +1,5 @@
1
+ *2.3.11 (February 9, 2011)*
2
+
1
3
  *2.3.10 (October 15, 2010)*
2
4
 
3
5
  * Security Release to fix CVE-2010-3933
data/Rakefile CHANGED
@@ -192,7 +192,7 @@ spec = Gem::Specification.new do |s|
192
192
  s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
193
193
  end
194
194
 
195
- s.add_dependency('activesupport', '= 2.3.10' + PKG_BUILD)
195
+ s.add_dependency('activesupport', '= 2.3.11' + PKG_BUILD)
196
196
 
197
197
  s.files.delete FIXTURES_ROOT + "/fixture_database.sqlite"
198
198
  s.files.delete FIXTURES_ROOT + "/fixture_database_2.sqlite"
@@ -353,15 +353,14 @@ module ActiveRecord
353
353
  if @target.is_a?(Array) && @target.any?
354
354
  @target = find_target.map do |f|
355
355
  i = @target.index(f)
356
- if i
357
- @target.delete_at(i).tap do |t|
358
- keys = ["id"] + t.changes.keys + (f.attribute_names - t.attribute_names)
359
- t.attributes = f.attributes.except(*keys)
360
- end
356
+ t = @target.delete_at(i) if i
357
+ if t && t.changed?
358
+ t
361
359
  else
360
+ f.mark_for_destruction if t && t.marked_for_destruction?
362
361
  f
363
362
  end
364
- end + @target
363
+ end + @target.find_all {|t| t.new_record?}
365
364
  else
366
365
  @target = find_target
367
366
  end
@@ -375,16 +374,17 @@ module ActiveRecord
375
374
  target
376
375
  end
377
376
 
378
- def method_missing(method, *args)
377
+ def method_missing(method, *args, &block)
379
378
  case method.to_s
380
379
  when 'find_or_create'
381
380
  return find(:first, :conditions => args.first) || create(args.first)
382
381
  when /^find_or_create_by_(.*)$/
383
382
  rest = $1
384
- return send("find_by_#{rest}", *args) ||
385
- method_missing("create_by_#{rest}", *args)
383
+ find_args = pull_finder_args_from(DynamicFinderMatch.match(method).attribute_names, *args)
384
+ return send("find_by_#{rest}", find_args) ||
385
+ method_missing("create_by_#{rest}", *args, &block)
386
386
  when /^create_by_(.*)$/
387
- return create($1.split('_and_').zip(args).inject({}) { |h,kv| k,v=kv ; h[k] = v ; h })
387
+ return create($1.split('_and_').zip(args).inject({}) { |h,kv| k,v=kv ; h[k] = v ; h }, &block)
388
388
  end
389
389
 
390
390
  if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
@@ -434,20 +434,32 @@ module ActiveRecord
434
434
  callback(:before_add, record)
435
435
  yield(record) if block_given?
436
436
  @target ||= [] unless loaded?
437
- index = @target.index(record)
438
- unless @reflection.options[:uniq] && index
439
- if index
440
- @target[index] = record
441
- else
442
- @target << record
443
- end
444
- end
437
+ @target << record unless @reflection.options[:uniq] && @target.include?(record)
445
438
  callback(:after_add, record)
446
439
  set_inverse_instance(record, @owner)
447
440
  record
448
441
  end
449
442
 
450
443
  private
444
+ # Separate the "finder" args from the "create" args given to a
445
+ # find_or_create_by_ call. Returns an array with the
446
+ # parameter values in the same order as the keys in the
447
+ # "names" array. This code was based on code in base.rb's
448
+ # method_missing method.
449
+ def pull_finder_args_from(names, *args)
450
+ attributes = names.collect { |name| name.intern }
451
+ attribute_hash = {}
452
+ args.each_with_index do |arg, i|
453
+ if arg.is_a?(Hash)
454
+ attribute_hash.merge! arg
455
+ else
456
+ attribute_hash[attributes[i]] = arg
457
+ end
458
+ end
459
+ attribute_hash = attribute_hash.with_indifferent_access
460
+ attributes.collect { |attr| attribute_hash[attr] }
461
+ end
462
+
451
463
  def create_record(attrs)
452
464
  attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
453
465
  ensure_owner_is_not_new
@@ -187,7 +187,7 @@ module ActiveRecord
187
187
  # A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT.
188
188
  sql = "SELECT COUNT(*) AS #{aggregate_alias}" if use_workaround
189
189
 
190
- sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group]
190
+ options[:group_fields].each_index{|i| sql << ", #{options[:group_fields][i]} AS #{options[:group_aliases][i]}" } if options[:group]
191
191
  if options[:from]
192
192
  sql << " FROM #{options[:from]} "
193
193
  elsif scope && scope[:from] && !use_workaround
@@ -211,8 +211,8 @@ module ActiveRecord
211
211
  add_limited_ids_condition!(sql, options, join_dependency) if join_dependency && !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
212
212
 
213
213
  if options[:group]
214
- group_key = connection.adapter_name == 'FrontBase' ? :group_alias : :group_field
215
- sql << " GROUP BY #{options[group_key]} "
214
+ group_key = connection.adapter_name == 'FrontBase' ? :group_aliases : :group_fields
215
+ sql << " GROUP BY #{options[group_key].join(',')} "
216
216
  end
217
217
 
218
218
  if options[:group] && options[:having]
@@ -239,24 +239,31 @@ module ActiveRecord
239
239
  end
240
240
 
241
241
  def execute_grouped_calculation(operation, column_name, column, options) #:nodoc:
242
- group_attr = options[:group].to_s
243
- association = reflect_on_association(group_attr.to_sym)
244
- associated = association && association.macro == :belongs_to # only count belongs_to associations
245
- group_field = associated ? association.primary_key_name : group_attr
246
- group_alias = column_alias_for(group_field)
247
- group_column = column_for group_field
248
- sql = construct_calculation_sql(operation, column_name, options.merge(:group_field => group_field, :group_alias => group_alias))
242
+ group_attr = options[:group]
243
+ association = reflect_on_association(group_attr.to_s.to_sym)
244
+ associated = association && association.macro == :belongs_to # only count belongs_to associations
245
+ group_fields = Array(associated ? association.primary_key_name : group_attr)
246
+ group_aliases = []
247
+ group_columns = {}
248
+
249
+ group_fields.each do |field|
250
+ group_aliases << column_alias_for(field)
251
+ group_columns[column_alias_for(field)] = column_for(field)
252
+ end
253
+
254
+ sql = construct_calculation_sql(operation, column_name, options.merge(:group_fields => group_fields, :group_aliases => group_aliases))
249
255
  calculated_data = connection.select_all(sql)
250
256
  aggregate_alias = column_alias_for(operation, column_name)
251
257
 
252
258
  if association
253
- key_ids = calculated_data.collect { |row| row[group_alias] }
259
+ key_ids = calculated_data.collect { |row| row[group_aliases.first] }
254
260
  key_records = association.klass.base_class.find(key_ids)
255
261
  key_records = key_records.inject({}) { |hsh, r| hsh.merge(r.id => r) }
256
262
  end
257
263
 
258
264
  calculated_data.inject(ActiveSupport::OrderedHash.new) do |all, row|
259
- key = type_cast_calculated_value(row[group_alias], group_column)
265
+ key = group_aliases.map{|group_alias| type_cast_calculated_value(row[group_alias], group_columns[group_alias])}
266
+ key = key.first if key.size == 1
260
267
  key = key_records[key] if associated
261
268
  value = row[aggregate_alias]
262
269
  all[key] = type_cast_calculated_value(value, column, operation)
@@ -2,7 +2,7 @@ module ActiveRecord
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 2
4
4
  MINOR = 3
5
- TINY = 10
5
+ TINY = 11
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -65,6 +65,29 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
65
65
  assert_equal person, person.readers.first.person
66
66
  end
67
67
 
68
+ def test_find_or_create_by_with_additional_parameters
69
+ post = Post.create! :title => 'test_find_or_create_by_with_additional_parameters', :body => 'this is the body'
70
+ comment = post.comments.create! :body => 'test comment body', :type => 'test'
71
+
72
+ assert_equal comment, post.comments.find_or_create_by_body('test comment body')
73
+
74
+ post.comments.find_or_create_by_body(:body => 'other test comment body', :type => 'test')
75
+ assert_equal 2, post.comments.count
76
+ assert_equal 2, post.comments.length
77
+ post.comments.find_or_create_by_body('other other test comment body', :type => 'test')
78
+ assert_equal 3, post.comments.count
79
+ assert_equal 3, post.comments.length
80
+ post.comments.find_or_create_by_body_and_type('3rd test comment body', 'test')
81
+ assert_equal 4, post.comments.count
82
+ assert_equal 4, post.comments.length
83
+ end
84
+
85
+ def test_find_or_create_by_with_block
86
+ post = Post.create! :title => 'test_find_or_create_by_with_additional_parameters', :body => 'this is the body'
87
+ comment = post.comments.find_or_create_by_body('other test comment body') { |comment| comment.type = 'test' }
88
+ assert_equal 'test', comment.type
89
+ end
90
+
68
91
  def test_find_or_create
69
92
  person = Person.create! :first_name => 'tenderlove'
70
93
  post = Post.find :first
@@ -843,6 +866,17 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
843
866
  assert companies(:first_firm).clients_of_firm(true).empty?, "37signals has no clients after destroy all and refresh"
844
867
  end
845
868
 
869
+ def test_destroy_all_with_creates_and_scope_that_doesnt_match_created_records
870
+ company = companies(:first_firm)
871
+ unloaded_client_matching_scope = companies(:second_client)
872
+ created_client_matching_scope = company.clients_of_firm.create!(:name => "Somesoft")
873
+ created_client_not_matching_scope = company.clients_of_firm.create!(:name => "OtherCo")
874
+ destroyed = company.clients_of_firm.with_oft_in_name.destroy_all
875
+ assert destroyed.include?(unloaded_client_matching_scope), "unloaded clients matching the scope destroy_all on should have been destroyed"
876
+ assert destroyed.include?(created_client_matching_scope), "loaded clients matching the scope destroy_all on should have been destroyed"
877
+ assert !destroyed.include?(created_client_not_matching_scope), "loaded clients not matching the scope destroy_all on should not have been destroyed"
878
+ end
879
+
846
880
  def test_dependence
847
881
  firm = companies(:first_firm)
848
882
  assert_equal 2, firm.clients.size
@@ -18,8 +18,6 @@ require 'models/person'
18
18
  require 'models/reader'
19
19
  require 'models/parrot'
20
20
  require 'models/pirate'
21
- require 'models/ship'
22
- require 'models/ship_part'
23
21
  require 'models/treasure'
24
22
  require 'models/price_estimate'
25
23
  require 'models/club'
@@ -31,23 +29,6 @@ class AssociationsTest < ActiveRecord::TestCase
31
29
  fixtures :accounts, :companies, :developers, :projects, :developers_projects,
32
30
  :computers, :people, :readers
33
31
 
34
- def test_loading_the_association_target_should_keep_child_records_marked_for_destruction
35
- ship = Ship.create!(:name => "The good ship Dollypop")
36
- part = ship.parts.create!(:name => "Mast")
37
- part.mark_for_destruction
38
- ship.parts.send(:load_target)
39
- assert ship.parts[0].marked_for_destruction?
40
- end
41
-
42
- def test_loading_the_association_target_should_load_most_recent_attributes_for_child_records_marked_for_destruction
43
- ship = Ship.create!(:name => "The good ship Dollypop")
44
- part = ship.parts.create!(:name => "Mast")
45
- part.mark_for_destruction
46
- ShipPart.find(part.id).update_attribute(:name, 'Deck')
47
- ship.parts.send(:load_target)
48
- assert_equal 'Deck', ship.parts[0].name
49
- end
50
-
51
32
  def test_include_with_order_works
52
33
  assert_nothing_raised {Account.find(:first, :order => 'id', :include => :firm)}
53
34
  assert_nothing_raised {Account.find(:first, :order => :id, :include => :firm)}
@@ -58,6 +58,19 @@ class CalculationsTest < ActiveRecord::TestCase
58
58
  [1,6,2].each { |firm_id| assert c.keys.include?(firm_id) }
59
59
  end
60
60
 
61
+ def test_should_group_by_multiple_fields
62
+ c = Account.count(:all, :group => ['firm_id', :credit_limit])
63
+ [ [nil, 50], [1, 50], [6, 50], [6, 55], [9, 53], [2, 60] ].each { |firm_and_limit| assert c.keys.include?(firm_and_limit) }
64
+ end
65
+
66
+ def test_should_group_by_multiple_fields_having_functions
67
+ c = Topic.count(:all, :group => [:author_name, 'COALESCE(type, title)'])
68
+ assert_equal 1, c[["Nick", "The Third Topic of the day"]]
69
+ assert_equal 1, c[["Mary", "Reply"]]
70
+ assert_equal 1, c[["David", "The First Topic"]]
71
+ assert_equal 1, c[["Carl", "Reply"]]
72
+ end
73
+
61
74
  def test_should_group_by_summed_field
62
75
  c = Account.sum(:credit_limit, :group => :firm_id)
63
76
  assert_equal 50, c[1]
@@ -808,13 +808,7 @@ class TestHasManyAutosaveAssoictaionWhichItselfHasAutosaveAssociations < ActiveR
808
808
  @part = @ship.parts.create!(:name => "Mast")
809
809
  @trinket = @part.trinkets.create!(:name => "Necklace")
810
810
  end
811
-
812
- test "if association is not loaded and association record is saved and then in memory record attributes should be saved" do
813
- @ship.parts_attributes=[{:id => @part.id,:name =>'Deck'}]
814
- assert_equal 1, @ship.parts.proxy_target.size
815
- assert_equal 'Deck', @ship.parts[0].name
816
- end
817
-
811
+
818
812
  test "when grandchild changed in memory, saving parent should save grandchild" do
819
813
  @trinket.name = "changed"
820
814
  @ship.save
@@ -12,6 +12,8 @@ class Company < AbstractCompany
12
12
  has_many :contracts
13
13
  has_many :developers, :through => :contracts
14
14
 
15
+ named_scope :with_oft_in_name, :conditions => "name LIKE '%oft%'"
16
+
15
17
  def arbitrary_method
16
18
  "I am Jack's profound disappointment"
17
19
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 21
5
5
  prerelease: false
6
6
  segments:
7
7
  - 2
8
8
  - 3
9
- - 10
10
- version: 2.3.10
9
+ - 11
10
+ version: 2.3.11
11
11
  platform: ruby
12
12
  authors:
13
13
  - David Heinemeier Hansson
@@ -15,7 +15,7 @@ autorequire: active_record
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-10-15 00:00:00 +13:00
18
+ date: 2011-02-09 00:00:00 +13:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -26,12 +26,12 @@ dependencies:
26
26
  requirements:
27
27
  - - "="
28
28
  - !ruby/object:Gem::Version
29
- hash: 23
29
+ hash: 21
30
30
  segments:
31
31
  - 2
32
32
  - 3
33
- - 10
34
- version: 2.3.10
33
+ - 11
34
+ version: 2.3.11
35
35
  type: :runtime
36
36
  version_requirements: *id001
37
37
  description: Implements the ActiveRecord pattern (Fowler, PoEAA) for ORM. It ties database tables and classes together for business objects, like Customer or Subscription, that can find, save, and destroy themselves without resorting to manual SQL.