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 +2 -0
- data/Rakefile +1 -1
- data/lib/active_record/associations/association_collection.rb +30 -18
- data/lib/active_record/calculations.rb +19 -12
- data/lib/active_record/version.rb +1 -1
- data/test/cases/associations/has_many_associations_test.rb +34 -0
- data/test/cases/associations_test.rb +0 -19
- data/test/cases/calculations_test.rb +13 -0
- data/test/cases/nested_attributes_test.rb +1 -7
- data/test/fixtures/fixture_database.sqlite3 +0 -0
- data/test/fixtures/fixture_database_2.sqlite3 +0 -0
- data/test/models/company.rb +2 -0
- metadata +7 -7
data/CHANGELOG
CHANGED
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.
|
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
|
-
|
358
|
-
|
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
|
-
|
385
|
-
|
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
|
-
|
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[:
|
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' ? :
|
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
|
243
|
-
association
|
244
|
-
associated
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
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[
|
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],
|
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)
|
@@ -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
|
Binary file
|
Binary file
|
data/test/models/company.rb
CHANGED
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:
|
4
|
+
hash: 21
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 2
|
8
8
|
- 3
|
9
|
-
-
|
10
|
-
version: 2.3.
|
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:
|
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:
|
29
|
+
hash: 21
|
30
30
|
segments:
|
31
31
|
- 2
|
32
32
|
- 3
|
33
|
-
-
|
34
|
-
version: 2.3.
|
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.
|