Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

nodoc the first_or_create methods and document alternatives

  • Loading branch information...
commit 0096f53b25e68c3fc79429253f816fff4a4ee596 1 parent 45d585e
@jonleighton jonleighton authored
View
14 activerecord/CHANGELOG.md
@@ -24,9 +24,17 @@
Which obviously does not affect the scoping of queries within
callbacks.
- The `find_or_create_by` version also reads better, frankly. But note
- that it does not allow attributes to be specified for the `create`
- that are not included in the `find_by`.
+ The `find_or_create_by` version also reads better, frankly.
+
+ If you need to add extra attributes during create, you can do one of:
+
+ User.create_with(active: true).find_or_create_by(first_name: 'Jon')
+ User.find_or_create_by(first_name: 'Jon') { |u| u.active = true }
+
+ The `first_or_create` family of methods have been nodoc'ed in favour
+ of this API. They may be deprecated in the future but their
+ implementation is very small and it's probably not worth putting users
+ through lots of annoying deprecation warnings.
*Jon Leighton*
View
55 activerecord/lib/active_record/relation.rb
@@ -129,60 +129,41 @@ def create!(*args, &block)
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 +Base.create+.
- #
- # Note that the <tt>create</tt> will execute within the context of this scope, and that may for example
- # affect the result of queries within callbacks. If you don't want this, use the <tt>find_or_create_by</tt>
- # method.
+ def first_or_create(attributes = nil, &block) # :nodoc:
+ first || create(attributes, &block)
+ end
+
+ def first_or_create!(attributes = nil, &block) # :nodoc:
+ first || create!(attributes, &block)
+ end
+
+ def first_or_initialize(attributes = nil, &block) # :nodoc:
+ first || new(attributes, &block)
+ end
+
+ # Finds the first record with the given attributes, or creates a record with the attributes
+ # if one is not found.
#
# ==== Examples
# # Find the first user named Penélope or create a new one.
- # User.where(:first_name => 'Penélope').first_or_create
+ # User.find_or_create_by(first_name: 'Penélope')
# # => <User id: 1, first_name: 'Penélope', last_name: nil>
#
# # Find the first user named Penélope or create a new one.
# # We already have one so the existing record will be returned.
- # User.where(:first_name => 'Penélope').first_or_create
+ # User.find_or_create_by(first_name: 'Penélope')
# # => <User id: 1, first_name: 'Penélope', last_name: nil>
#
# # Find the first user named Scarlett or create a new one with a particular last name.
- # User.where(:first_name => 'Scarlett').first_or_create(:last_name => 'Johansson')
+ # User.create_with(last_name: 'Johansson').find_or_create_by(first_name: 'Scarlett')
# # => <User id: 2, first_name: 'Scarlett', last_name: 'Johansson'>
#
# # Find the first user named Scarlett or create a new one with a different last name.
# # We already have one so the existing record will be returned.
- # User.where(:first_name => 'Scarlett').first_or_create do |user|
+ # User.find_or_create_by(first_name: 'Scarlett') do |user|
# user.last_name = "O'Hara"
# end
# # => <User id: 2, first_name: 'Scarlett', last_name: 'Johansson'>
- def first_or_create(attributes = nil, &block)
- first || create(attributes, &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!(attributes = nil, &block)
- first || create!(attributes, &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_initialize(attributes = nil, &block)
- first || new(attributes, &block)
- end
-
- # Finds the first record with the given attributes, or creates it if one does not exist.
- #
- # See also <tt>first_or_create</tt>.
- #
- # ==== Examples
- # # Find the first user named Penélope or create a new one.
- # User.find_or_create_by(first_name: 'Penélope')
- # # => <User id: 1, first_name: 'Penélope', last_name: nil>
def find_or_create_by(attributes, &block)
find_by(attributes) || create(attributes, &block)
end
View
10 activerecord/test/cases/relations_test.rb
@@ -1067,6 +1067,16 @@ def test_find_or_create_by
assert_equal bird, Bird.find_or_create_by(name: 'bob')
end
+ def test_find_or_create_by_with_create_with
+ assert_nil Bird.find_by(name: 'bob')
+
+ bird = Bird.create_with(color: 'green').find_or_create_by(name: 'bob')
+ assert bird.persisted?
+ assert_equal 'green', bird.color
+
+ assert_equal bird, Bird.create_with(color: 'blue').find_or_create_by(name: 'bob')
+ end
+
def test_find_or_create_by!
assert_raises(ActiveRecord::RecordInvalid) { Bird.find_or_create_by!(color: 'green') }
end
View
39 guides/source/active_record_querying.md
@@ -1227,7 +1227,7 @@ Find or build a new object
It's common that you need to find a record or create it if it doesn't exist. You can do that with the `find_or_create_by` and `find_or_create_by!` methods.
-### `find_or_create_by` and `first_or_create`
+### `find_or_create_by`
The `find_or_create_by` method checks whether a record with the attributes exists. If it doesn't, then `create` is called. Let's see an example.
@@ -1251,13 +1251,18 @@ COMMIT
The new record might not be saved to the database; that depends on whether validations passed or not (just like `create`).
-Suppose we want to set the 'locked' attribute to true, if we're
+Suppose we want to set the 'locked' attribute to true if we're
creating a new record, but we don't want to include it in the query. So
we want to find the client named "Andy", or if that client doesn't
exist, create a client named "Andy" which is not locked.
-We can achive this in two ways. The first is passing a block to the
-`find_or_create_by` method:
+We can achive this in two ways. The first is to use `create_with`:
+
+```ruby
+Client.create_with(locked: false).find_or_create_by(first_name: 'Andy')
+```
+
+The second way is using a block:
```ruby
Client.find_or_create_by(first_name: 'Andy') do |c|
@@ -1268,23 +1273,7 @@ end
The block will only be executed if the client is being created. The
second time we run this code, the block will be ignored.
-The second way is using the `first_or_create` method:
-
-```ruby
-Client.where(first_name: 'Andy').first_or_create(locked: false)
-```
-
-In this version, we are building a scope to search for Andy, and getting
-the first record if it existed, or else creating it with `locked:
-false`.
-
-Note that these two are slightly different. In the second version, the
-scope that we build will affect any other queries that may happens while
-creating the record. For example, if we had a callback that ran
-another query, that would execute within the `Client.where(first_name:
-'Andy')` scope.
-
-### `find_or_create_by!` and `first_or_create!`
+### `find_or_create_by!`
You can also use `find_or_create_by!` to raise an exception if the new record is invalid. Validations are not covered on this guide, but let's assume for a moment that you temporarily add
@@ -1299,10 +1288,7 @@ Client.find_or_create_by!(first_name: 'Andy')
# => ActiveRecord::RecordInvalid: Validation failed: Orders count can't be blank
```
-There is also a `first_or_create!` method which does a similar thing for
-`first_or_create`.
-
-### `find_or_initialize_by` and `first_or_initialize`
+### `find_or_initialize_by`
The `find_or_initialize_by` method will work just like
`find_or_create_by` but it will call `new` instead of `create`. This
@@ -1334,9 +1320,6 @@ nick.save
# => true
```
-There is also a `first_or_initialize` method which does a similar thing
-for `first_or_create`.
-
Finding by SQL
--------------

3 comments on commit 0096f53

@rafaelfranca

@jonleighton what you think about deprecating first_or_initialize and friends?

@jonleighton
Collaborator

@rafaelfranca see the changelog:

    The `first_or_create` family of methods have been nodoc'ed in favour
    of this API. They may be deprecated in the future but their
    implementation is very small and it's probably not worth putting users
    through lots of annoying deprecation warnings.
@paukul

Foo.where(bar: 1).first_or_create(baz: 2) reads completely natural.
"Give me an item, matching the following criteria or create it with the following attributes if it doesn't exist."

The other solution reads like: "There's something that you would create with the following attributes. Give me one item that matches these attributes"

However. If you don't plan to deprecate it anyways, so it's just a matter of personal taste, please remove the nodoc because a lot of projects use the first_or_create syntax and developers new to rails won't find it in the docs anymore... @jonleighton

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