Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save RobinDaugherty/480d39f8e43561684054 to your computer and use it in GitHub Desktop.
Save RobinDaugherty/480d39f8e43561684054 to your computer and use it in GitHub Desktop.
module Concerns
module HasOneAutoCreated
extend ActiveSupport::Concern
module ClassMethods
def has_one_auto_created(association_name, options = {})
has_one association_name, options
alias_method "existing_#{association_name}", association_name
# Returns the existing associated record, or a newly-created one.
define_method(association_name) do
transaction do
send("existing_#{association_name}") || send("create_#{association_name}")
end
end
# Yields with an existing or new association record, saving the record after the block completes.
# All of this happens inside of a single database transaction.
define_method("updating_#{association_name}") do |&block|
transaction do
record = send("existing_#{association_name}") || send("build_#{association_name}")
block.call(record)
record.save!
end
end
end
end
end
end
class Gadget < ActiveRecord::Base
include Concerns::HasOneAutoCreated
has_one_auto_created :gadget_status
end
require 'rails_helper'
describe Gadget, type: :model do
describe '#gadget_part' do
subject { FactoryGirl.create(:gadget) }
context 'with an existing record' do
let!(:gadget_part) { FactoryGirl.create(:gadget_part, gadget: subject) }
it 'returns the existing record' do
expect(subject.gadget_part).to eq(gadget_part)
end
end
context 'without existing record' do
it 'creates a new record' do
expect(subject.gadget_part).to be_kind_of(GadgetPart)
expect(subject.gadget_part).to be_persisted
expect(subject.gadget_part).to have_attributes(gadget_id: subject.id)
end
end
end
describe '#updating_gadget_part' do
subject { FactoryGirl.create(:gadget) }
context 'with an existing record' do
let!(:gadget_part) { FactoryGirl.create(:gadget_part, gadget: subject) }
it 'yields the existing record' do
expect { |b| subject.updating_gadget_part(&b) }
.to yield_with_args(gadget_part)
end
it 'saves the new record after yielding it' do
subject.updating_gadget_part do |gadget_part|
expect(gadget_part).to receive(:save!)
end
end
end
context 'without existing record' do
it 'yields a new record' do
fake_gadget_part = double('gadget_part', save!: true)
allow(subject).to receive(:build_gadget_part).and_return(fake_gadget_part)
expect { |b| subject.updating_gadget_part(&b) }
.to yield_with_args(fake_gadget_part)
end
it 'creates a correctly-formed GadgetPart' do
subject.updating_gadget_part do |gadget_part|
expect(gadget_part).to be_kind_of(GadgetPart)
expect(gadget_part).to_not be_persisted
expect(gadget_part).to have_attributes(gadget_id: subject.id)
end
end
it 'saves the new record after yielding it' do
subject.updating_gadget_part do |gadget_part|
expect(gadget_part).to receive(:save!)
end
end
end
end
end
class GadgetPart < ActiveRecord::Base
belongs_to :gadget
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment