diff options
author | Andrés Mejía <andmej@gmail.com> | 2011-08-27 23:10:25 -0500 |
---|---|---|
committer | Andrés Mejía <andmej@gmail.com> | 2011-08-30 16:33:37 -0500 |
commit | 84dad446c6a23a15f67b9d558e8039891a008bff (patch) | |
tree | 6c0560bca9644384c59f21a3807561d6a4ed102b /activerecord | |
parent | 2350fecd2251584d770afc4bd1764b3fe526ff70 (diff) | |
download | rails-84dad446c6a23a15f67b9d558e8039891a008bff.tar.gz rails-84dad446c6a23a15f67b9d558e8039891a008bff.tar.bz2 rails-84dad446c6a23a15f67b9d558e8039891a008bff.zip |
Adding first_or_create, first_or_create!, first_or_new and first_or_build to Active Record.
This let's you write things like:
User.where(:first_name => "Scarlett").first_or_create!(:last_name => "Johansson", :hot => true)
Related to #2420.
Diffstat (limited to 'activerecord')
-rw-r--r-- | activerecord/CHANGELOG | 6 | ||||
-rw-r--r-- | activerecord/lib/active_record/base.rb | 1 | ||||
-rw-r--r-- | activerecord/lib/active_record/relation.rb | 43 | ||||
-rw-r--r-- | activerecord/test/cases/base_test.rb | 31 | ||||
-rw-r--r-- | activerecord/test/cases/relations_test.rb | 128 |
5 files changed, 209 insertions, 0 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 700e11ff94..57e4fcf9cf 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,11 @@ *Rails 3.2.0 (unreleased)* +* Add first_or_create, first_or_create!, first_or_build and first_or_new methods to Active Record. This is a better approach over the old find_or_create_by dynamic methods because it's clearer which arguments are used to find the record and which are used to create it: + + User.where(:first_name => "Scarlett").first_or_create!(:last_name => "Johansson", :hot => true) + + [Andrés Mejía] + * Support bulk change_table in mysql2 adapter, as well as the mysql one. [Jon Leighton] * If multiple parameters are sent representing a date, and some are blank, the diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 374791deb1..2979ad1cb3 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -442,6 +442,7 @@ module ActiveRecord #:nodoc: class << self # Class methods delegate :find, :first, :first!, :last, :last!, :all, :exists?, :any?, :many?, :to => :scoped + delegate :first_or_create, :first_or_create!, :first_or_new, :first_or_build, :to => :scoped delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to => :scoped delegate :find_each, :find_in_batches, :to => :scoped delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 15fd1a58c8..461237c7ad 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -94,6 +94,49 @@ module ActiveRecord scoping { @klass.create!(*args, &block) } end + # Tries to load the first record; if it fails, then <tt>create</tt> is called with the same arguments as this method. + # + # Expects arguments in the same format as <tt>Base.create</tt>. + # + # ==== Examples + # # Find the first user named Scarlett or create a new one. + # User.where(:first_name => 'Scarlett').first_or_create(:last_name => 'Johansson') + # # => <User id: 1, first_name: 'Scarlett', last_name: 'Johansson'> + # + # # Find the first user named Scarlett or create one with a different last name. + # # We already have one Scarlett, so she'll be returned. + # User.where(:first_name => 'Scarlett').first_or_create do |user| + # user.last_name = "O'Hara" + # end + # # => <User id: 1, first_name: 'Scarlett', last_name: 'Johansson'> + # + # # Find the first user named Andy or create several at a time. + # User.where(:first_name => 'Andy').first_or_create([{:last_name => 'García'}, {:last_name => 'Mejía'}]) + # # => [#<User id: 2, first_name: 'Andy', last_name: 'García'>, + # #<User id: 3, first_name: 'Andy', last_name: 'Mejía'>] + # + # # Find the first user with last name García or create several at a time. + # User.where(:last_name => 'García').first_or_create([{:first_name => 'Jorge'}, {:first_name => 'Andy'}]) + # # => <User id: 2, first_name: 'Andy', last_name: 'García'> + def first_or_create(*args, &block) + first || create(*args, &block) + end + + # Like <tt>first_or_create</tt> but calls <tt>create!</tt> so an exception is raised if the created record is invalid. + # + # Expects arguments in the same format as <tt>Base.create</tt>. + def first_or_create!(*args, &block) + first || create!(*args, &block) + end + + # Like <tt>first_or_create</tt> but calls <tt>new</tt> instead of <tt>create</tt>. + # + # Expects arguments in the same format as <tt>Base.new</tt>. + def first_or_new(*args, &block) + first || new(*args, &block) + end + alias :first_or_build :first_or_new + def respond_to?(method, include_private = false) arel.respond_to?(method, include_private) || Array.method_defined?(method) || diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 1e647b5970..02a4644cd2 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -277,6 +277,37 @@ class BasicsTest < ActiveRecord::TestCase assert_equal(true, cb.frickinawesome) end + def test_first_or_create + parrot = Bird.first_or_create(:color => 'green', :name => 'parrot') + assert parrot.persisted? + the_same_parrot = Bird.first_or_create(:color => 'yellow', :name => 'macaw') + assert_equal parrot, the_same_parrot + end + + def test_first_or_create_bang + assert_raises(ActiveRecord::RecordInvalid) { Bird.first_or_create! } + parrot = Bird.first_or_create!(:color => 'green', :name => 'parrot') + assert parrot.persisted? + the_same_parrot = Bird.first_or_create!(:color => 'yellow', :name => 'macaw') + assert_equal parrot, the_same_parrot + end + + def test_first_or_new + parrot = Bird.first_or_new(:color => 'green', :name => 'parrot') + assert_kind_of Bird, parrot + assert !parrot.persisted? + assert parrot.new_record? + assert parrot.valid? + end + + def test_first_or_build + parrot = Bird.first_or_build(:color => 'green', :name => 'parrot') + assert_kind_of Bird, parrot + assert !parrot.persisted? + assert parrot.new_record? + assert parrot.valid? + end + def test_load topics = Topic.find(:all, :order => 'id') assert_equal(4, topics.size) diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 615551a279..9f129cf9fc 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -863,6 +863,134 @@ class RelationTest < ActiveRecord::TestCase assert_equal 'hen', hen.name end + def test_first_or_create + parrot = Bird.where(:color => 'green').first_or_create(:name => 'parrot') + assert_kind_of Bird, parrot + assert parrot.persisted? + assert_equal 'parrot', parrot.name + assert_equal 'green', parrot.color + + same_parrot = Bird.where(:color => 'green').first_or_create(:name => 'parakeet') + assert_kind_of Bird, same_parrot + assert same_parrot.persisted? + assert_equal parrot, same_parrot + end + + def test_first_or_create_with_no_parameters + parrot = Bird.where(:color => 'green').first_or_create + assert_kind_of Bird, parrot + assert !parrot.persisted? + assert_equal 'green', parrot.color + end + + def test_first_or_create_with_block + parrot = Bird.where(:color => 'green').first_or_create { |bird| bird.name = 'parrot' } + assert_kind_of Bird, parrot + assert parrot.persisted? + assert_equal 'green', parrot.color + assert_equal 'parrot', parrot.name + + same_parrot = Bird.where(:color => 'green').first_or_create { |bird| bird.name = 'parakeet' } + assert_equal parrot, same_parrot + end + + def test_first_or_create_with_array + several_green_birds = Bird.where(:color => 'green').first_or_create([{:name => 'parrot'}, {:name => 'parakeet'}]) + assert_kind_of Array, several_green_birds + several_green_birds.each { |bird| assert bird.persisted? } + + same_parrot = Bird.where(:color => 'green').first_or_create([{:name => 'hummingbird'}, {:name => 'macaw'}]) + assert_kind_of Bird, same_parrot + assert_equal several_green_birds.first, same_parrot + end + + def test_first_or_create_bang_with_valid_options + parrot = Bird.where(:color => 'green').first_or_create!(:name => 'parrot') + assert_kind_of Bird, parrot + assert parrot.persisted? + assert_equal 'parrot', parrot.name + assert_equal 'green', parrot.color + + same_parrot = Bird.where(:color => 'green').first_or_create!(:name => 'parakeet') + assert_kind_of Bird, same_parrot + assert same_parrot.persisted? + assert_equal parrot, same_parrot + end + + def test_first_or_create_bang_with_invalid_options + assert_raises(ActiveRecord::RecordInvalid) { Bird.where(:color => 'green').first_or_create!(:pirate_id => 1) } + end + + def test_first_or_create_bang_with_no_parameters + assert_raises(ActiveRecord::RecordInvalid) { Bird.where(:color => 'green').first_or_create! } + end + + def test_first_or_create_bang_with_valid_block + parrot = Bird.where(:color => 'green').first_or_create! { |bird| bird.name = 'parrot' } + assert_kind_of Bird, parrot + assert parrot.persisted? + assert_equal 'green', parrot.color + assert_equal 'parrot', parrot.name + + same_parrot = Bird.where(:color => 'green').first_or_create! { |bird| bird.name = 'parakeet' } + assert_equal parrot, same_parrot + end + + def test_first_or_create_bang_with_invalid_block + assert_raise(ActiveRecord::RecordInvalid) do + parrot = Bird.where(:color => 'green').first_or_create! { |bird| bird.pirate_id = 1 } + end + end + + def test_first_or_create_with_valid_array + several_green_birds = Bird.where(:color => 'green').first_or_create!([{:name => 'parrot'}, {:name => 'parakeet'}]) + assert_kind_of Array, several_green_birds + several_green_birds.each { |bird| assert bird.persisted? } + + same_parrot = Bird.where(:color => 'green').first_or_create!([{:name => 'hummingbird'}, {:name => 'macaw'}]) + assert_kind_of Bird, same_parrot + assert_equal several_green_birds.first, same_parrot + end + + def test_first_or_create_with_invalid_array + assert_raises(ActiveRecord::RecordInvalid) { Bird.where(:color => 'green').first_or_create!([ {:name => 'parrot'}, {:pirate_id => 1} ]) } + end + + def test_first_or_new + parrot = Bird.where(:color => 'green').first_or_new(:name => 'parrot') + assert_kind_of Bird, parrot + assert !parrot.persisted? + assert parrot.valid? + assert parrot.new_record? + assert_equal 'parrot', parrot.name + assert_equal 'green', parrot.color + end + + def test_first_or_new_with_no_parameters + parrot = Bird.where(:color => 'green').first_or_new + assert_kind_of Bird, parrot + assert !parrot.persisted? + assert !parrot.valid? + assert parrot.new_record? + assert_equal 'green', parrot.color + end + + def test_first_or_new_with_block + parrot = Bird.where(:color => 'green').first_or_new { |bird| bird.name = 'parrot' } + assert_kind_of Bird, parrot + assert !parrot.persisted? + assert parrot.valid? + assert parrot.new_record? + assert_equal 'green', parrot.color + assert_equal 'parrot', parrot.name + end + + def test_first_or_build_is_alias_for_first_or_new + birds = Bird.scoped + assert birds.respond_to?(:first_or_build) + assert_equal birds.method(:first_or_new), birds.method(:first_or_build) + end + def test_explicit_create_scope hens = Bird.where(:name => 'hen') assert_equal 'hen', hens.new.name |