-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Rob
committed
Sep 6, 2011
1 parent
882d3b1
commit 6addaee
Showing
12 changed files
with
283 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
== Invoice number generating for ActiveRecord | ||
|
||
The problem this gem tries to solve is that for bookkeeping, an uninterrupted sequence of | ||
invoice numbers is needed. There are, however, situations in which the database's auto | ||
incrementing id field won't do. For example: | ||
|
||
* Orders for a webshop should only get an invoice number if the order is actually paid. | ||
* The order in which record are made is different from the order in which invoices are | ||
created, for example in case of a reservations system. | ||
|
||
This gem makes this stupid simple: | ||
|
||
# as soon as order is paid, invoice_nr will be set | ||
class Order < ActiveRecord::Base | ||
has_invoice_number :invoice_nr, assign_if: ->(order) { order.paid? } | ||
end | ||
|
||
# manually assign an invoice number when the user finishes the reservation | ||
class Reservation < ActiveRecord::Base | ||
has_invoice_number :invoice_nr | ||
|
||
def finish | ||
assign_invoice_number | ||
save | ||
end | ||
end | ||
|
||
# both funky order and boring order will share the same sequence of invoice numbers | ||
class FunkyOrder < ActiveRecord::Base | ||
has_invoice_number :invoice_nr, assign_if: ->(order) { order.paid? }, invoice_number_sequence: :order | ||
end | ||
class BoringOrder < ActiveRecord::Base | ||
has_invoice_number :invoice_nr, assign_if: ->(order) { order.paid? }, invoice_number_sequence: :order | ||
end | ||
|
||
That's it. This gem could use some more flexibility and support for NoSQL databases, but | ||
hopefully it is usefull to some people as is. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
require 'rubygems' | ||
require 'rake/testtask' | ||
require File.dirname( __FILE__ ) + '/test/database_configuration' | ||
require File.dirname( __FILE__ ) + '/test/database_schema' | ||
|
||
Rake::TestTask.new do |t| | ||
t.test_files = FileList['test/test*.rb'] | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
require 'rake' | ||
|
||
Gem::Specification.new do |s| | ||
s.name = 'invoice_numbers' | ||
s.version = '0.0.1' | ||
|
||
s.authors = [ 'Rob Scheepmaker' ] | ||
s.email = '[email protected]' | ||
s.date = '2011-09-06' | ||
s.description = 'Create sequences of uninterrupted invoice numbers' | ||
s.summary = 'Create sequences of uninterrupted invoice numbers' | ||
s.extra_rdoc_files = [ 'README' ] | ||
s.files = FileList['lib/**/*.rb', 'test/**/*'].to_a | ||
s.rdoc_options = ['--main', 'README'] | ||
s.require_paths = ['lib'] | ||
s.test_files = FileList['test/*.rb'].to_a | ||
s.add_dependency 'activerecord', ['>= 0'] | ||
s.add_development_dependency 'database_cleaner', ['>= 0'] | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
require 'rails/generators' | ||
require 'rails/generators/migration' | ||
|
||
class InvoiceNumbersGenerator < Rails::Generator::Base | ||
include Rails::Generators::Migration | ||
|
||
def self.source_root | ||
@source_root ||= File.join(File.dirname(__FILE__), 'templates') | ||
end | ||
|
||
# Implement the required interface for Rails::Generators::Migration. | ||
def self.next_migration_number(dirname) #:nodoc: | ||
next_migration_number = current_migration_number(dirname) + 1 | ||
if ActiveRecord::Base.timestamped_migrations | ||
[Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max | ||
else | ||
"%.3d" % next_migration_number | ||
end | ||
end | ||
|
||
def create_migration_file | ||
migration_template 'migration.rb', 'db/migrate/create_invoice_number_sequences.rb' | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
class CreateInvoiceNumberSequences < ActiveRecord::Migration | ||
def change | ||
create_table :invoice_number_sequences do |t| | ||
t.string :name | ||
t.integer :next_number, :default => 1 | ||
end | ||
|
||
add_index :invoice_number_sequences, [:name], :unique => true | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
require 'active_record/railtie' | ||
|
||
require File.dirname( __FILE__ ) + '/invoice_numbers/generator' | ||
require File.dirname( __FILE__ ) + '/invoice_numbers/active_record_extension' | ||
|
||
ActiveRecord::Base.send :include, InvoiceNumbers::ActiveRecordExtension |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
module InvoiceNumbers | ||
module ActiveRecordExtension | ||
def self.included( base ) | ||
base.send( :extend, ClassMethods ) | ||
end | ||
|
||
module ClassMethods | ||
def has_invoice_number( field_name, options = {} ) | ||
invoice_number_field = field_name | ||
invoice_number_sequence = options[:invoice_number_sequence] | ||
invoice_number_sequence ||= self.name.to_s.underscore | ||
invoice_number_assign_if = options[:assign_if] | ||
|
||
if invoice_number_assign_if | ||
before_save :assign_invoice_number | ||
end | ||
|
||
send(:define_method, :assign_invoice_number) do | ||
transaction do | ||
if read_attribute( invoice_number_field ).blank? | ||
if invoice_number_assign_if.nil? or invoice_number_assign_if.call(self) | ||
write_attribute( invoice_number_field, Generator.next_invoice_number( invoice_number_sequence ) ) | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
module InvoiceNumbers | ||
class InvoiceNumberSequence < ActiveRecord::Base | ||
def increment! | ||
transaction do | ||
lock! | ||
number = self.next_number | ||
self.next_number = number + 1 | ||
self.save! | ||
number | ||
end | ||
end | ||
end | ||
|
||
class Generator | ||
def self.next_invoice_number( sequence_name = :default ) | ||
sequence = InvoiceNumberSequence.find_or_create_by_name sequence_name | ||
sequence.increment! | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
require 'active_record' | ||
require 'sqlite3' | ||
|
||
ActiveRecord::Base.establish_connection( | ||
:adapter => 'sqlite3', | ||
:database => 'db/test.sqlite3', | ||
:pool => 5 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
ActiveRecord::Schema.define :version => 1 do | ||
create_table :invoice_number_sequences, :force => true do |t| | ||
t.string :name | ||
t.integer :next_number, :default => 1 | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
require 'test/unit' | ||
require 'database_cleaner' | ||
require 'minitest/spec' | ||
require 'invoice_numbers' | ||
require File.dirname( __FILE__ ) + '/database_configuration' | ||
|
||
ActiveRecord::Schema.define :version => 1 do | ||
create_table :orders, :force => true do |t| | ||
t.boolean :finished, :default => false | ||
t.integer :invoice_nr | ||
end | ||
create_table :reservations, :force => true do |t| | ||
t.boolean :finished, :default => false | ||
t.integer :invoice_nr | ||
end | ||
end | ||
|
||
class Order < ActiveRecord::Base | ||
has_invoice_number :invoice_nr, :assign_if => lambda { |order| order.finished? } | ||
end | ||
|
||
class Reservation < ActiveRecord::Base | ||
has_invoice_number :invoice_nr, :invoice_number_sequence => :shared | ||
end | ||
|
||
describe Reservation do | ||
before do | ||
@reservation = Reservation.new | ||
DatabaseCleaner.strategy = :truncation | ||
DatabaseCleaner.clean | ||
end | ||
|
||
it 'does not assign an invoice number' do | ||
@reservation.save | ||
@reservation.invoice_nr.must_be_nil | ||
end | ||
|
||
it 'assigns an invoice number when forced' do | ||
@reservation.assign_invoice_number | ||
@reservation.save | ||
@reservation.invoice_nr.must_equal 1 | ||
end | ||
|
||
it 'uses the shared sequence' do | ||
InvoiceNumbers::Generator.next_invoice_number(:shared) | ||
InvoiceNumbers::Generator.next_invoice_number(:shared) | ||
@reservation.assign_invoice_number | ||
@reservation.invoice_nr.must_equal 3 | ||
end | ||
end | ||
|
||
describe Order do | ||
before do | ||
@order = Order.new | ||
DatabaseCleaner.strategy = :truncation | ||
DatabaseCleaner.clean | ||
end | ||
|
||
it 'does not assign an invoice number' do | ||
@order.save | ||
@order.invoice_nr.must_be_nil | ||
end | ||
|
||
it 'assigns an invoice number when finished' do | ||
@order.finished = true | ||
@order.save | ||
@order.invoice_nr.must_equal 1 | ||
end | ||
|
||
it 'does not update an invoice number once assigned' do | ||
@order.finished = true | ||
@order.save | ||
@order.invoice_nr.must_equal 1 | ||
@order.invoice_nr_will_change! # force a new save | ||
@order.save | ||
@order.invoice_nr.must_equal 1 | ||
end | ||
|
||
it 'uses the order sequence' do | ||
InvoiceNumbers::Generator.next_invoice_number(:order) | ||
InvoiceNumbers::Generator.next_invoice_number(:order) | ||
@order.finished = true | ||
@order.assign_invoice_number | ||
@order.invoice_nr.must_equal 3 | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
require 'test/unit' | ||
require 'database_cleaner' | ||
require 'minitest/spec' | ||
require 'invoice_numbers' | ||
require File.dirname( __FILE__ ) + '/database_configuration' | ||
|
||
describe InvoiceNumbers::Generator do | ||
before do | ||
DatabaseCleaner.strategy = :truncation | ||
DatabaseCleaner.clean | ||
end | ||
|
||
describe 'next_invoice_number' do | ||
it 'should return 1 the first time we request a certain sequence' do | ||
InvoiceNumbers::Generator.next_invoice_number(:test).must_equal 1 | ||
end | ||
|
||
it 'should increment with one on future request of a certain sequence' do | ||
InvoiceNumbers::Generator.next_invoice_number(:test) | ||
InvoiceNumbers::Generator.next_invoice_number(:test).must_equal 2 | ||
end | ||
|
||
it 'should not increment other sequences' do | ||
InvoiceNumbers::Generator.next_invoice_number(:test) | ||
InvoiceNumbers::Generator.next_invoice_number(:other) | ||
InvoiceNumbers::Generator.next_invoice_number(:test).must_equal 2 | ||
end | ||
end | ||
end |