Find or create by and its usages in Rails 3

Oct 20, 2012

One method that I frequently use in Rails is the `find_or_create_by_*` method ( If you can call it **one** method since it comes in so many variations ). As it implies, this method will try to find a record from one or many attributes and create it if it does not find it. But like so many other functions, it has undergone some changes along the way. **Using one attribute** This will try to find a User by its name and create one if not found. ```language-ruby User.all => [#<User id: 1, name: "Jake", age: 30>] User.find_or_create_by_name("Jake") => #<User id: 1, name: "Jake", age: 30> User.find_or_create_by_name("Joe") => #<User id: 2, name: "Joe", age: nil> User.all => [#<User id: 1, name: "Jake", age: 30>, #<User id: 2, name: "Joe", age: nil>] ``` **Using Multiple attributes** ```language-ruby User.all => [#<User id: 1, name: "Jake", age: 30>] User.find_or_create_by_name_and_age("Jake", 30) => #<User id: 1, name: "Jake", age: 30> User.find_or_create_by_name_and_age("Jake", 31) => #<User id: 2, name: "Jake", age: 31> User.find_or_create_by_name_and_age("Joe", 40) => #<User id: 3, name: "Joe", age: 40> User.all => [#<User id: 1, name: "Jake", age: 30>, #<User id: 2, name: "Jake", age: 31>, #<User id: 3, name: "Joe", age: 40>] ``` One recent addition to this set of methods is with an exclamation mark at the end, for example `find_or_create_by_name!`. What this means is that if there was no record that existed and there was an error while trying to create the new record (maybe because of validation) then an error will be raised. That does not happen without the exclamation mark. The reason I like that addition is that it makes it offers a better way to use it within transations. Say for example the scenario of creating a Blog Post record and want to add Tags to that new record. Then a very simple way of doing it in rails is to handle the Tag records in an `after_save` callback. But if there is a problem in the callback that causes the associated record not to be saved/created, then you might not want the first Post to be saved either. So by raising an error upon failed save, then the entire transation towards the database will be rolled back. That way you can be certain that there are no _half-saved_ records in the database. Here is an example of how that could look: ```language-ruby class Post < ActiveRecord::Base has_many :post_tags, dependent: :destroy has_many :tags, through: :post_tags attr_writer: tag_names after_save do self.tags = tag_names.split(' ').map { |tag_name| Tag.find_or_create_by_name!(tag_name) } end ... end ```