double_entry 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.travis.yml +1 -0
- data/README.md +1 -8
- data/Rakefile +1 -1
- data/db/.gitkeep +0 -0
- data/double_entry.gemspec +1 -0
- data/lib/active_record/locking_extensions.rb +30 -8
- data/lib/double_entry/account.rb +15 -8
- data/lib/double_entry/errors.rb +0 -2
- data/lib/double_entry/reporting.rb +2 -1
- data/lib/double_entry/version.rb +1 -1
- data/script/jack_hammer +8 -2
- data/spec/active_record/locking_extensions_spec.rb +8 -0
- data/spec/double_entry/account_spec.rb +69 -22
- data/spec/double_entry/locking_spec.rb +37 -34
- data/spec/spec_helper.rb +3 -2
- data/spec/support/accounts.rb +2 -8
- data/spec/support/database.example.yml +5 -0
- data/spec/support/database.travis.yml +6 -0
- metadata +19 -2
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -169,14 +169,7 @@ require 'double_entry'
|
|
169
169
|
|
170
170
|
DoubleEntry.configure do |config|
|
171
171
|
config.define_accounts do |accounts|
|
172
|
-
user_scope =
|
173
|
-
if user_identifier.is_a?(User)
|
174
|
-
user_identifier.id
|
175
|
-
else
|
176
|
-
user_identifier
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
172
|
+
user_scope = accounts.active_record_scope_identifier(User)
|
180
173
|
accounts.define(:identifier => :savings, :scope_identifier => user_scope, :positive_only => true)
|
181
174
|
accounts.define(:identifier => :checking, :scope_identifier => user_scope)
|
182
175
|
end
|
data/Rakefile
CHANGED
data/db/.gitkeep
ADDED
File without changes
|
data/double_entry.gemspec
CHANGED
@@ -28,6 +28,7 @@ Gem::Specification.new do |gem|
|
|
28
28
|
gem.add_development_dependency 'rake'
|
29
29
|
gem.add_development_dependency 'mysql2'
|
30
30
|
gem.add_development_dependency 'pg'
|
31
|
+
gem.add_development_dependency 'sqlite3'
|
31
32
|
gem.add_development_dependency 'rspec'
|
32
33
|
gem.add_development_dependency 'rspec-its'
|
33
34
|
gem.add_development_dependency 'database_cleaner'
|
@@ -20,7 +20,7 @@ module ActiveRecord
|
|
20
20
|
begin
|
21
21
|
yield
|
22
22
|
rescue ActiveRecord::StatementInvalid => exception
|
23
|
-
if exception.message =~ /deadlock/i
|
23
|
+
if exception.message =~ /deadlock/i || exception.message =~ /database is locked/i
|
24
24
|
raise ActiveRecord::RestartTransaction
|
25
25
|
else
|
26
26
|
raise
|
@@ -29,18 +29,41 @@ module ActiveRecord
|
|
29
29
|
end
|
30
30
|
|
31
31
|
# Create the record, but ignore the exception if there's a duplicate.
|
32
|
+
# if there is a deadlock, retry
|
32
33
|
def create_ignoring_duplicates!(*args)
|
34
|
+
retry_deadlocks do
|
35
|
+
ignoring_duplicates do
|
36
|
+
create!(*args)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def ignoring_duplicates
|
33
44
|
# Error examples:
|
34
|
-
# PG::Error: ERROR: deadlock detected
|
35
|
-
# Mysql::Error: Deadlock found when trying to get lock
|
36
45
|
# PG::Error: ERROR: duplicate key value violates unique constraint
|
37
46
|
# Mysql2::Error: Duplicate entry 'keith' for key 'index_users_on_username': INSERT INTO `users...
|
47
|
+
# ActiveRecord::RecordNotUnique SQLite3::ConstraintException: column username is not unique: INSERT INTO "users"...
|
38
48
|
begin
|
39
|
-
|
40
|
-
rescue ActiveRecord::StatementInvalid => exception
|
41
|
-
if exception.message =~ /duplicate/i
|
49
|
+
yield
|
50
|
+
rescue ActiveRecord::StatementInvalid, ActiveRecord::RecordNotUnique => exception
|
51
|
+
if exception.message =~ /duplicate/i || exception.message =~ /(is|are) not unique/i
|
42
52
|
# Just ignore it...someone else has already created the record.
|
43
|
-
|
53
|
+
else
|
54
|
+
raise
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def retry_deadlocks
|
60
|
+
# Error examples:
|
61
|
+
# PG::Error: ERROR: deadlock detected
|
62
|
+
# Mysql::Error: Deadlock found when trying to get lock
|
63
|
+
begin
|
64
|
+
yield
|
65
|
+
rescue ActiveRecord::StatementInvalid, ActiveRecord::RecordNotUnique => exception
|
66
|
+
if exception.message =~ /deadlock/i || exception.message =~ /database is locked/i
|
44
67
|
# Somebody else is in the midst of creating the record. We'd better
|
45
68
|
# retry, so we ensure they're done before we move on.
|
46
69
|
retry
|
@@ -49,7 +72,6 @@ module ActiveRecord
|
|
49
72
|
end
|
50
73
|
end
|
51
74
|
end
|
52
|
-
|
53
75
|
end
|
54
76
|
|
55
77
|
# Raise this inside a restartable_transaction to retry the transaction from the beginning.
|
data/lib/double_entry/account.rb
CHANGED
@@ -29,23 +29,30 @@ module DoubleEntry
|
|
29
29
|
super(account)
|
30
30
|
end
|
31
31
|
end
|
32
|
+
|
33
|
+
def active_record_scope_identifier(active_record_class)
|
34
|
+
ActiveRecordScopeFactory.new(active_record_class).scope_identifier
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class ActiveRecordScopeFactory
|
39
|
+
def initialize(active_record_class)
|
40
|
+
@active_record_class = active_record_class
|
41
|
+
end
|
42
|
+
|
43
|
+
def scope_identifier
|
44
|
+
->(value) { value.is_a?(@active_record_class) ? value.id : value }
|
45
|
+
end
|
32
46
|
end
|
33
47
|
|
34
48
|
class Instance
|
35
49
|
attr_accessor :account, :scope
|
50
|
+
delegate :identifier, :scope_identifier, :scoped?, :positive_only, :to => :account
|
36
51
|
|
37
52
|
def initialize(attributes)
|
38
53
|
attributes.each { |name, value| send("#{name}=", value) }
|
39
54
|
end
|
40
55
|
|
41
|
-
def method_missing(method, *args)
|
42
|
-
if block_given?
|
43
|
-
account.send(method, *args, &Proc.new)
|
44
|
-
else
|
45
|
-
account.send(method, *args)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
56
|
def scope_identity
|
50
57
|
scope_identifier.call(scope).to_s if scoped?
|
51
58
|
end
|
data/lib/double_entry/errors.rb
CHANGED
@@ -4,10 +4,8 @@ module DoubleEntry
|
|
4
4
|
class UnknownAccount < RuntimeError; end
|
5
5
|
class TransferNotAllowed < RuntimeError; end
|
6
6
|
class TransferIsNegative < RuntimeError; end
|
7
|
-
class RequiredMetaMissing < RuntimeError; end
|
8
7
|
class DuplicateAccount < RuntimeError; end
|
9
8
|
class DuplicateTransfer < RuntimeError; end
|
10
|
-
class UserAccountNotLocked < RuntimeError; end
|
11
9
|
class AccountWouldBeSentNegative < RuntimeError; end
|
12
10
|
|
13
11
|
end
|
@@ -150,7 +150,8 @@ module DoubleEntry
|
|
150
150
|
# which they always should.
|
151
151
|
#
|
152
152
|
def reconciled?(account)
|
153
|
-
scoped_lines = Line.where(:account => "#{account.identifier}"
|
153
|
+
scoped_lines = Line.where(:account => "#{account.identifier}")
|
154
|
+
scoped_lines = scoped_lines.where(:scope => "#{account.scope_identity}") if account.scoped?
|
154
155
|
sum_of_amounts = scoped_lines.sum(:amount)
|
155
156
|
final_balance = scoped_lines.order(:id).last[:balance]
|
156
157
|
cached_balance = AccountBalance.find_by_account(account)[:balance]
|
data/lib/double_entry/version.rb
CHANGED
data/script/jack_hammer
CHANGED
@@ -18,8 +18,14 @@ require 'database_cleaner'
|
|
18
18
|
|
19
19
|
support = File.expand_path("../../spec/support/", __FILE__)
|
20
20
|
|
21
|
-
ENV['DB']
|
22
|
-
|
21
|
+
db_engine = ENV['DB'] || 'mysql'
|
22
|
+
|
23
|
+
if db_engine == 'sqlite'
|
24
|
+
puts "Skipping jackhammer for SQLITE..."
|
25
|
+
exit 0
|
26
|
+
end
|
27
|
+
|
28
|
+
ActiveRecord::Base.establish_connection YAML.load_file(File.join(support, "database.yml"))[db_engine]
|
23
29
|
require "#{support}/schema"
|
24
30
|
|
25
31
|
lib = File.expand_path('../../lib', __FILE__)
|
@@ -4,6 +4,7 @@ require 'spec_helper'
|
|
4
4
|
describe ActiveRecord::LockingExtensions do
|
5
5
|
PG_DEADLOCK = ActiveRecord::StatementInvalid.new("PG::Error: ERROR: deadlock detected")
|
6
6
|
MYSQL_DEADLOCK = ActiveRecord::StatementInvalid.new("Mysql::Error: Deadlock found when trying to get lock")
|
7
|
+
SQLITE3_LOCK = ActiveRecord::StatementInvalid.new("SQLite3::BusyException: database is locked: UPDATE...")
|
7
8
|
|
8
9
|
context "#restartable_transaction" do
|
9
10
|
it "keeps running the lock until a ActiveRecord::RestartTransaction isn't raised" do
|
@@ -49,6 +50,13 @@ describe ActiveRecord::LockingExtensions do
|
|
49
50
|
|
50
51
|
expect { User.create_ignoring_duplicates! }.to_not raise_error
|
51
52
|
end
|
53
|
+
|
54
|
+
it "in sqlite3" do
|
55
|
+
expect(User).to receive(:create!).ordered.and_raise(SQLITE3_LOCK)
|
56
|
+
expect(User).to receive(:create!).ordered.and_return(true)
|
57
|
+
|
58
|
+
expect { User.create_ignoring_duplicates! }.to_not raise_error
|
59
|
+
end
|
52
60
|
end
|
53
61
|
end
|
54
62
|
end
|
@@ -1,32 +1,79 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require 'spec_helper'
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
3
|
+
module DoubleEntry
|
4
|
+
describe Account do
|
5
|
+
let(:identity_scope) { ->(value) { value } }
|
6
|
+
|
7
|
+
it "instances should be sortable" do
|
8
|
+
account = Account.new(:identifier => "savings", :scope_identifier => identity_scope)
|
9
|
+
a = Account::Instance.new(:account => account, :scope => "123")
|
10
|
+
b = Account::Instance.new(:account => account, :scope => "456")
|
11
|
+
expect([b, a].sort).to eq [a, b]
|
12
|
+
end
|
13
|
+
|
14
|
+
it "instances should be hashable" do
|
15
|
+
account = Account.new(:identifier => "savings", :scope_identifier => identity_scope)
|
16
|
+
a1 = Account::Instance.new(:account => account, :scope => "123")
|
17
|
+
a2 = Account::Instance.new(:account => account, :scope => "123")
|
18
|
+
b = Account::Instance.new(:account => account, :scope => "456")
|
19
|
+
|
20
|
+
expect(a1.hash).to eq a2.hash
|
21
|
+
expect(a1.hash).to_not eq b.hash
|
22
|
+
end
|
11
23
|
end
|
12
24
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
25
|
+
describe Account::Set do
|
26
|
+
describe "#define" do
|
27
|
+
context "given a 'savings' account is defined" do
|
28
|
+
before { subject.define(:identifier => "savings") }
|
29
|
+
its(:first) { should be_a Account }
|
30
|
+
its("first.identifier") { should eq "savings" }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#active_record_scope_identifier" do
|
35
|
+
subject(:scope) { Account::Set.new.active_record_scope_identifier(ar_class) }
|
18
36
|
|
19
|
-
|
20
|
-
|
37
|
+
context "given ActiveRecordScopeFactory is stubbed" do
|
38
|
+
let(:scope_identifier) { double(:scope_identifier) }
|
39
|
+
let(:scope_factory) { double(:scope_factory, :scope_identifier => scope_identifier) }
|
40
|
+
let(:ar_class) { double(:ar_class) }
|
41
|
+
before { allow(Account::ActiveRecordScopeFactory).to receive(:new).with(ar_class).and_return(scope_factory) }
|
42
|
+
|
43
|
+
it { should eq scope_identifier }
|
44
|
+
end
|
45
|
+
end
|
21
46
|
end
|
22
|
-
end
|
23
47
|
|
24
|
-
describe
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
48
|
+
describe Account::ActiveRecordScopeFactory do
|
49
|
+
context "given the class User" do
|
50
|
+
subject(:factory) { Account::ActiveRecordScopeFactory.new(User) }
|
51
|
+
|
52
|
+
describe "#scope_identifier" do
|
53
|
+
subject(:scope_identifier) { factory.scope_identifier }
|
54
|
+
|
55
|
+
describe "#call" do
|
56
|
+
subject(:scope) { scope_identifier.call(value) }
|
57
|
+
|
58
|
+
context "given a User instance with ID 32" do
|
59
|
+
let(:value) { User.make(:id => 32) }
|
60
|
+
|
61
|
+
it { should eq 32 }
|
62
|
+
end
|
63
|
+
|
64
|
+
context "given the String 'I am a bearded lady'" do
|
65
|
+
let(:value) { "I am a bearded lady" }
|
66
|
+
|
67
|
+
it { should eq "I am a bearded lady" }
|
68
|
+
end
|
69
|
+
|
70
|
+
context "given the Integer 42" do
|
71
|
+
let(:value) { 42 }
|
72
|
+
|
73
|
+
it { should eq 42 }
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
30
77
|
end
|
31
78
|
end
|
32
79
|
end
|
@@ -38,7 +38,7 @@ describe DoubleEntry::Locking do
|
|
38
38
|
@account_d = DoubleEntry.account(:account_d, :scope => "4")
|
39
39
|
end
|
40
40
|
|
41
|
-
it "
|
41
|
+
it "creates missing account balance records" do
|
42
42
|
expect do
|
43
43
|
DoubleEntry::Locking.lock_accounts(@account_a) { }
|
44
44
|
end.to change(DoubleEntry::AccountBalance, :count).by(1)
|
@@ -48,7 +48,7 @@ describe DoubleEntry::Locking do
|
|
48
48
|
expect(account_balance.balance).to eq Money.new(0)
|
49
49
|
end
|
50
50
|
|
51
|
-
it "
|
51
|
+
it "takes the balance for new account balance records from the lines table" do
|
52
52
|
DoubleEntry::Line.create!(:account => @account_a, :amount => Money.new(3_00), :balance => Money.new( 3_00), :code => :test)
|
53
53
|
DoubleEntry::Line.create!(:account => @account_a, :amount => Money.new(7_00), :balance => Money.new(10_00), :code => :test)
|
54
54
|
|
@@ -61,7 +61,7 @@ describe DoubleEntry::Locking do
|
|
61
61
|
expect(account_balance.balance).to eq Money.new(10_00)
|
62
62
|
end
|
63
63
|
|
64
|
-
it "
|
64
|
+
it "prohibits locking inside a regular transaction" do
|
65
65
|
expect {
|
66
66
|
DoubleEntry::AccountBalance.transaction do
|
67
67
|
DoubleEntry::Locking.lock_accounts(@account_a, @account_b) do
|
@@ -70,7 +70,7 @@ describe DoubleEntry::Locking do
|
|
70
70
|
}.to raise_error(DoubleEntry::Locking::LockMustBeOutermostTransaction)
|
71
71
|
end
|
72
72
|
|
73
|
-
it "
|
73
|
+
it "prohibits a transfer inside a regular transaction" do
|
74
74
|
expect {
|
75
75
|
DoubleEntry::AccountBalance.transaction do
|
76
76
|
DoubleEntry.transfer(Money.new(10_00), :from => @account_a, :to => @account_b, :code => :test)
|
@@ -78,7 +78,7 @@ describe DoubleEntry::Locking do
|
|
78
78
|
}.to raise_error(DoubleEntry::Locking::LockMustBeOutermostTransaction)
|
79
79
|
end
|
80
80
|
|
81
|
-
it "
|
81
|
+
it "allows a transfer inside a lock if we've locked the transaction accounts" do
|
82
82
|
expect {
|
83
83
|
DoubleEntry::Locking.lock_accounts(@account_a, @account_b) do
|
84
84
|
DoubleEntry.transfer(Money.new(10_00), :from => @account_a, :to => @account_b, :code => :test)
|
@@ -86,7 +86,7 @@ describe DoubleEntry::Locking do
|
|
86
86
|
}.to_not raise_error
|
87
87
|
end
|
88
88
|
|
89
|
-
it "
|
89
|
+
it "does not allow a transfer inside a lock if the right locks aren't held" do
|
90
90
|
expect {
|
91
91
|
DoubleEntry::Locking.lock_accounts(@account_a, @account_c) do
|
92
92
|
DoubleEntry.transfer(Money.new(10_00), :from => @account_a, :to => @account_b, :code => :test)
|
@@ -94,7 +94,7 @@ describe DoubleEntry::Locking do
|
|
94
94
|
}.to raise_error(DoubleEntry::Locking::LockNotHeld, "No lock held for account: account_b, scope 2")
|
95
95
|
end
|
96
96
|
|
97
|
-
it "
|
97
|
+
it "allows nested locks if the outer lock locks all the accounts" do
|
98
98
|
expect do
|
99
99
|
DoubleEntry::Locking.lock_accounts(@account_a, @account_b) do
|
100
100
|
DoubleEntry::Locking.lock_accounts(@account_a, @account_b) { }
|
@@ -102,7 +102,7 @@ describe DoubleEntry::Locking do
|
|
102
102
|
end.to_not raise_error
|
103
103
|
end
|
104
104
|
|
105
|
-
it "
|
105
|
+
it "prohibits nested locks if the out lock doesn't lock all the accounts" do
|
106
106
|
expect do
|
107
107
|
DoubleEntry::Locking.lock_accounts(@account_a) do
|
108
108
|
DoubleEntry::Locking.lock_accounts(@account_a, @account_b) { }
|
@@ -110,7 +110,7 @@ describe DoubleEntry::Locking do
|
|
110
110
|
end.to raise_error(DoubleEntry::Locking::LockNotHeld, "No lock held for account: account_b, scope 2")
|
111
111
|
end
|
112
112
|
|
113
|
-
it "
|
113
|
+
it "rolls back a locking transaction" do
|
114
114
|
DoubleEntry::Locking.lock_accounts(@account_a, @account_b) do
|
115
115
|
DoubleEntry.transfer(Money.new(10_00), :from => @account_a, :to => @account_b, :code => :test)
|
116
116
|
raise ActiveRecord::Rollback
|
@@ -119,7 +119,7 @@ describe DoubleEntry::Locking do
|
|
119
119
|
expect(DoubleEntry.balance(@account_b)).to eq Money.new(0)
|
120
120
|
end
|
121
121
|
|
122
|
-
it "
|
122
|
+
it "rolls back a locking transaction if there's an exception" do
|
123
123
|
expect do
|
124
124
|
DoubleEntry::Locking.lock_accounts(@account_a, @account_b) do
|
125
125
|
DoubleEntry.transfer(Money.new(10_00), :from => @account_a, :to => @account_b, :code => :test)
|
@@ -130,36 +130,39 @@ describe DoubleEntry::Locking do
|
|
130
130
|
expect(DoubleEntry.balance(@account_b)).to eq Money.new(0)
|
131
131
|
end
|
132
132
|
|
133
|
-
|
134
|
-
|
133
|
+
# sqlite cannot handle these cases so they don't run when DB=sqlite
|
134
|
+
describe "concurrent locking", :unless => ENV['DB'] == 'sqlite' do
|
135
135
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
DoubleEntry::Locking.lock_accounts(@account_a, @account_b) do
|
140
|
-
DoubleEntry.transfer(Money.new(10_00), :from => @account_a, :to => @account_b, :code => :test)
|
141
|
-
end
|
142
|
-
end
|
136
|
+
it "allows multiple threads to lock at the same time" do
|
137
|
+
expect do
|
138
|
+
threads = Array.new
|
143
139
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
140
|
+
threads << Thread.new do
|
141
|
+
sleep 0.05
|
142
|
+
DoubleEntry::Locking.lock_accounts(@account_a, @account_b) do
|
143
|
+
DoubleEntry.transfer(Money.new(10_00), :from => @account_a, :to => @account_b, :code => :test)
|
144
|
+
end
|
148
145
|
end
|
149
|
-
end
|
150
146
|
|
151
|
-
|
152
|
-
|
153
|
-
|
147
|
+
threads << Thread.new do
|
148
|
+
DoubleEntry::Locking.lock_accounts(@account_c, @account_d) do
|
149
|
+
sleep 0.1
|
150
|
+
DoubleEntry.transfer(Money.new(10_00), :from => @account_c, :to => @account_d, :code => :test)
|
151
|
+
end
|
152
|
+
end
|
154
153
|
|
155
|
-
|
156
|
-
|
154
|
+
threads.each(&:join)
|
155
|
+
end.to_not raise_error
|
156
|
+
end
|
157
157
|
|
158
|
-
|
159
|
-
threads
|
160
|
-
|
158
|
+
it "allows multiple threads to lock accounts without balances at the same time" do
|
159
|
+
threads = Array.new
|
160
|
+
expect do
|
161
|
+
threads << Thread.new { DoubleEntry::Locking.lock_accounts(@account_a, @account_b) { sleep 0.1 } }
|
162
|
+
threads << Thread.new { DoubleEntry::Locking.lock_accounts(@account_c, @account_d) { sleep 0.1 } }
|
161
163
|
|
162
|
-
|
163
|
-
|
164
|
+
threads.each(&:join)
|
165
|
+
end.to_not raise_error
|
166
|
+
end
|
164
167
|
end
|
165
168
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -3,8 +3,9 @@ require 'bundler/setup'
|
|
3
3
|
require 'active_record'
|
4
4
|
require 'active_support'
|
5
5
|
|
6
|
-
ENV['DB']
|
7
|
-
|
6
|
+
db_engine = ENV['DB'] || 'mysql'
|
7
|
+
|
8
|
+
ActiveRecord::Base.establish_connection YAML.load_file(File.expand_path("../support/database.yml", __FILE__))[db_engine]
|
8
9
|
|
9
10
|
FileUtils.mkdir_p 'log'
|
10
11
|
FileUtils.rm 'log/test.log', :force => true
|
data/spec/support/accounts.rb
CHANGED
@@ -1,16 +1,10 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
|
3
|
-
if user_identifier.is_a?(User)
|
4
|
-
user_identifier.id
|
5
|
-
else
|
6
|
-
user_identifier
|
7
|
-
end
|
8
|
-
end
|
9
|
-
|
2
|
+
require_relative 'blueprints'
|
10
3
|
DoubleEntry.configure do |config|
|
11
4
|
|
12
5
|
# A set of accounts to test with
|
13
6
|
config.define_accounts do |accounts|
|
7
|
+
user_scope = accounts.active_record_scope_identifier(User)
|
14
8
|
accounts.define(:identifier => :savings, :scope_identifier => user_scope, :positive_only => true)
|
15
9
|
accounts.define(:identifier => :checking, :scope_identifier => user_scope, :positive_only => true)
|
16
10
|
accounts.define(:identifier => :test, :scope_identifier => user_scope)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: double_entry
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -16,7 +16,7 @@ authors:
|
|
16
16
|
autorequire:
|
17
17
|
bindir: bin
|
18
18
|
cert_chain: []
|
19
|
-
date: 2014-
|
19
|
+
date: 2014-08-01 00:00:00.000000000 Z
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
22
22
|
name: money
|
@@ -146,6 +146,22 @@ dependencies:
|
|
146
146
|
- - ! '>='
|
147
147
|
- !ruby/object:Gem::Version
|
148
148
|
version: '0'
|
149
|
+
- !ruby/object:Gem::Dependency
|
150
|
+
name: sqlite3
|
151
|
+
requirement: !ruby/object:Gem::Requirement
|
152
|
+
none: false
|
153
|
+
requirements:
|
154
|
+
- - ! '>='
|
155
|
+
- !ruby/object:Gem::Version
|
156
|
+
version: '0'
|
157
|
+
type: :development
|
158
|
+
prerelease: false
|
159
|
+
version_requirements: !ruby/object:Gem::Requirement
|
160
|
+
none: false
|
161
|
+
requirements:
|
162
|
+
- - ! '>='
|
163
|
+
- !ruby/object:Gem::Version
|
164
|
+
version: '0'
|
149
165
|
- !ruby/object:Gem::Dependency
|
150
166
|
name: rspec
|
151
167
|
requirement: !ruby/object:Gem::Requirement
|
@@ -264,6 +280,7 @@ files:
|
|
264
280
|
- LICENSE.md
|
265
281
|
- README.md
|
266
282
|
- Rakefile
|
283
|
+
- db/.gitkeep
|
267
284
|
- double_entry.gemspec
|
268
285
|
- gemfiles/Gemfile.rails-3.2.0
|
269
286
|
- gemfiles/Gemfile.rails-4.0.0
|