Skip to content
This repository
Browse code

bug 1108: fix a bug with find_or_create_by and additional values

There was a bug with find_or_create_by_x introduced in 2.3.9 - if you
included extra parameters for the create() then those parameters would
confuse the find() so you'd never get to the create().  This patch
filters the parameters so we only pass to find() the subset that it's
interested in.  The code for the filtering was modelled on the code in
base.rb's method_missing().
  • Loading branch information...
commit fdfc8e3b9c4905057677fd009f463a377be60b93 1 parent f5ed5c3
Toby Cabot authored September 29, 2010 tenderlove committed October 20, 2010
22  activerecord/lib/active_record/associations/association_collection.rb
@@ -381,7 +381,8 @@ def method_missing(method, *args)
381 381
             return find(:first, :conditions => args.first) || create(args.first)
382 382
           when /^find_or_create_by_(.*)$/
383 383
             rest = $1
384  
-            return  send("find_by_#{rest}", *args) ||
  384
+            find_args = pull_finder_args_from(DynamicFinderMatch.match(method).attribute_names, *args)
  385
+            return  send("find_by_#{rest}", find_args) ||
385 386
                     method_missing("create_by_#{rest}", *args)
386 387
           when /^create_by_(.*)$/
387 388
             return create($1.split('_and_').zip(args).inject({}) { |h,kv| k,v=kv ; h[k] = v ; h })
@@ -448,6 +449,25 @@ def add_record_to_target_with_callbacks(record)
448 449
         end
449 450
 
450 451
       private
  452
+        # Separate the "finder" args from the "create" args given to a
  453
+        # find_or_create_by_ call.  Returns an array with the
  454
+        # parameter values in the same order as the keys in the
  455
+        # "names" array.  This code was based on code in base.rb's
  456
+        # method_missing method.
  457
+        def pull_finder_args_from(names, *args)
  458
+          attributes = names.collect { |name| name.intern }
  459
+          attribute_hash = {}
  460
+          args.each_with_index do |arg, i|
  461
+            if arg.is_a?(Hash)
  462
+              attribute_hash.merge! arg
  463
+            else
  464
+              attribute_hash[attributes[i]] = arg
  465
+            end
  466
+          end
  467
+          attribute_hash = attribute_hash.with_indifferent_access
  468
+          attributes.collect { |attr| attribute_hash[attr] }
  469
+        end
  470
+
451 471
         def create_record(attrs)
452 472
           attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
453 473
           ensure_owner_is_not_new
17  activerecord/test/cases/associations/has_many_associations_test.rb
@@ -65,6 +65,23 @@ def test_find_or_create_by
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
+
68 85
   def test_find_or_create
69 86
     person = Person.create! :first_name => 'tenderlove'
70 87
     post   = Post.find :first

0 notes on commit fdfc8e3

Jérémy FRERE

This test is not sufficient, and allowed a bug to pop in.

Run this twice and you get a duplicate :
post.comments.find_or_create_by_body_and_type('3rd test comment body', 'test')
post.comments.find_or_create_by_body_and_type('3rd test comment body', 'test')
assert_equal 4, post.comments.count
assert_equal 4, post.comments.length

Actually your change broke the "find" part of the "find_or_create", as a new entry is created each time.

Please sign in to comment.
Something went wrong with that request. Please try again.