Skip to content

Commit

Permalink
Fix find_or_(create|initialize)_by on scoped relations.
Browse files Browse the repository at this point in the history
[ fix #2707 ]
  • Loading branch information
durran committed Jan 19, 2013
1 parent e9e19e9 commit 5ac8c45
Show file tree
Hide file tree
Showing 6 changed files with 289 additions and 239 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,10 @@ For instructions on upgrading to newer versions, visit
* \#2714 Overriding sessions when the new session has a different database will
now properly switch the database at runtime as well.

* \#2707 Calling `find_or_create_by` or `find_by_initialize_by` off a relation
with a chained criteria or scope now properly keeps the relations intact on
the new or found document.

* \#2699 Resetting a field now removes the name from the changed attributes
list. (Subhash Bhushan)

Expand Down
43 changes: 43 additions & 0 deletions lib/mongoid/criterion/modifiable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,32 @@ def create!(attrs = {}, &block)
create_document(:create!, attrs, &block)
end

# Find the first +Document+ given the conditions, or creates a new document
# with the conditions that were supplied.
#
# @example Find or create the document.
# Person.find_or_create_by(:attribute => "value")
#
# @param [ Hash ] attrs The attributes to check.
#
# @return [ Document ] A matching or newly created document.
def find_or_create_by(attrs = {}, &block)
find_or(:create, attrs, &block)
end

# Find the first +Document+ given the conditions, or initializes a new document
# with the conditions that were supplied.
#
# @example Find or initialize the document.
# Person.find_or_initialize_by(:attribute => "value")
#
# @param [ Hash ] attrs The attributes to check.
#
# @return [ Document ] A matching or newly initialized document.
def find_or_initialize_by(attrs = {}, &block)
find_or(:new, attrs, &block)
end

# Find the first +Document+, or creates a new document
# with the conditions that were supplied plus attributes.
#
Expand Down Expand Up @@ -127,8 +153,25 @@ def create_document(method, attrs = nil, &block)
end, &block)
end

# Find the first object or create/initialize it.
#
# @api private
#
# @example Find or perform an action.
# Person.find_or(:create, :name => "Dev")
#
# @param [ Symbol ] method The method to invoke.
# @param [ Hash ] attrs The attributes to query or set.
#
# @return [ Document ] The first or new document.
def find_or(method, attrs = {}, &block)
where(attrs).first || send(method, attrs, &block)
end

# Find the first document or create/initialize it.
#
# @api private
#
# @example First or perform an action.
# Person.first_or(:create, :name => "Dev")
#
Expand Down
43 changes: 2 additions & 41 deletions lib/mongoid/finders.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ module Finders
:each_with_index,
:extras,
:find_and_modify,
:find_or_create_by,
:find_or_initialize_by,
:first_or_create,
:first_or_create!,
:first_or_initialize,
Expand Down Expand Up @@ -81,32 +83,6 @@ def find(*args)
with_default_scope.find(*args)
end

# Find the first +Document+ given the conditions, or creates a new document
# with the conditions that were supplied.
#
# @example Find or create the document.
# Person.find_or_create_by(:attribute => "value")
#
# @param [ Hash ] attrs The attributes to check.
#
# @return [ Document ] A matching or newly created document.
def find_or_create_by(attrs = {}, &block)
find_or(:create, attrs, &block)
end

# Find the first +Document+ given the conditions, or initializes a new document
# with the conditions that were supplied.
#
# @example Find or initialize the document.
# Person.find_or_initialize_by(:attribute => "value")
#
# @param [ Hash ] attrs The attributes to check.
#
# @return [ Document ] A matching or newly initialized document.
def find_or_initialize_by(attrs = {}, &block)
find_or(:new, attrs, &block)
end

# Find the first +Document+ given the conditions, or raises
# Mongoid::Errors::DocumentNotFound
#
Expand Down Expand Up @@ -148,20 +124,5 @@ def first
def last
with_default_scope.last
end

protected

# Find the first object or create/initialize it.
#
# @example Find or perform an action.
# Person.find_or(:create, :name => "Dev")
#
# @param [ Symbol ] method The method to invoke.
# @param [ Hash ] attrs The attributes to query or set.
#
# @return [ Document ] The first or new document.
def find_or(method, attrs = {}, &block)
where(attrs).first || send(method, attrs, &block)
end
end
end
191 changes: 191 additions & 0 deletions spec/mongoid/criterion/modifiable_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
require "spec_helper"

describe Mongoid::Criterion::Modifiable do

describe ".find_or_create_by" do

context "when the document is found" do

context "when providing an attribute" do

let!(:person) do
Person.create(title: "Senior")
end

it "returns the document" do
Person.find_or_create_by(title: "Senior").should eq(person)
end
end

context "when providing a document" do

context "with an owner with a BSON identity type" do

let!(:person) do
Person.create
end

let!(:game) do
Game.create(person: person)
end

context "when providing the object directly" do

let(:from_db) do
Game.find_or_create_by(person: person)
end

it "returns the document" do
from_db.should eq(game)
end
end

context "when providing the proxy relation" do

let(:from_db) do
Game.find_or_create_by(person: game.person)
end

it "returns the document" do
from_db.should eq(game)
end
end
end

context "with an owner with an Integer identity type" do

let!(:jar) do
Jar.create
end

let!(:cookie) do
Cookie.create(jar: jar)
end

let(:from_db) do
Cookie.find_or_create_by(jar: jar)
end

it "returns the document" do
from_db.should eq(cookie)
end
end
end
end

context "when the document is not found" do

context "when providing a document" do

let!(:person) do
Person.create
end

let!(:game) do
Game.create
end

let(:from_db) do
Game.find_or_create_by(person: person)
end

it "returns the new document" do
from_db.person.should eq(person)
end

it "does not return an existing false document" do
from_db.should_not eq(game)
end
end

context "when not providing a block" do

let!(:person) do
Person.find_or_create_by(title: "Senorita")
end

it "creates a persisted document" do
person.should be_persisted
end

it "sets the attributes" do
person.title.should eq("Senorita")
end
end

context "when providing a block" do

let!(:person) do
Person.find_or_create_by(title: "Senorita") do |person|
person.pets = true
end
end

it "creates a persisted document" do
person.should be_persisted
end

it "sets the attributes" do
person.title.should eq("Senorita")
end

it "calls the block" do
person.pets.should be_true
end
end
end
end

describe ".find_or_initialize_by" do

context "when the document is found" do

let!(:person) do
Person.create(title: "Senior")
end

it "returns the document" do
Person.find_or_initialize_by(title: "Senior").should eq(person)
end
end

context "when the document is not found" do

context "when not providing a block" do

let!(:person) do
Person.find_or_initialize_by(title: "Senorita")
end

it "creates a new document" do
person.should be_new_record
end

it "sets the attributes" do
person.title.should eq("Senorita")
end
end

context "when providing a block" do

let!(:person) do
Person.find_or_initialize_by(title: "Senorita") do |person|
person.pets = true
end
end

it "creates a new document" do
person.should be_new_record
end

it "sets the attributes" do
person.title.should eq("Senorita")
end

it "calls the block" do
person.pets.should be_true
end
end
end
end
end
Loading

0 comments on commit 5ac8c45

Please sign in to comment.