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 CHANGED
@@ -16,6 +16,7 @@ spec/support/database.yml
16
16
  test/tmp
17
17
  test/version_tmp
18
18
  tmp
19
+ db/*.sqlite3
19
20
  bin/
20
21
  log/
21
22
  /Gemfile.lock
data/.travis.yml CHANGED
@@ -6,6 +6,7 @@ rvm:
6
6
  env:
7
7
  - DB=mysql
8
8
  - DB=postgres
9
+ - DB=sqlite
9
10
  before_script:
10
11
  - cp spec/support/database.travis.yml spec/support/database.yml
11
12
  - mysql -e 'create database double_entry_test;'
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 = lambda do |user_identifier|
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
@@ -6,7 +6,7 @@ RSpec::Core::RakeTask.new(:spec) do |t|
6
6
  end
7
7
 
8
8
  task :default do
9
- %w(mysql postgres).each do |db|
9
+ %w(mysql postgres sqlite).each do |db|
10
10
  puts "Running tests with `DB=#{db}`"
11
11
  ENV['DB'] = db
12
12
  Rake::Task["spec"].execute
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
- create!(*args)
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
- elsif exception.message =~ /deadlock/i
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.
@@ -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
@@ -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}", :scope => "#{account.scope}")
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]
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module DoubleEntry
4
- VERSION = "0.4.0"
4
+ VERSION = "0.5.0"
5
5
  end
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'] ||= 'mysql'
22
- ActiveRecord::Base.establish_connection YAML.load_file(File.join(support, "database.yml"))[ENV['DB']]
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
- describe DoubleEntry::Account do
4
- let(:empty_scope) { lambda {|value| value } }
5
-
6
- it "instances should be sortable" do
7
- account = DoubleEntry::Account.new(:identifier => "savings", :scope_identifier => empty_scope)
8
- a = DoubleEntry::Account::Instance.new(:account => account, :scope => "123")
9
- b = DoubleEntry::Account::Instance.new(:account => account, :scope => "456")
10
- expect([b, a].sort).to eq [a, b]
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
- it "instances should be hashable" do
14
- account = DoubleEntry::Account.new(:identifier => "savings", :scope_identifier => empty_scope)
15
- a1 = DoubleEntry::Account::Instance.new(:account => account, :scope => "123")
16
- a2 = DoubleEntry::Account::Instance.new(:account => account, :scope => "123")
17
- b = DoubleEntry::Account::Instance.new(:account => account, :scope => "456")
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
- expect(a1.hash).to eq a2.hash
20
- expect(a1.hash).to_not eq b.hash
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 DoubleEntry::Account::Set do
25
- describe "#define" do
26
- context "given a 'savings' account is defined" do
27
- before { subject.define(:identifier => "savings") }
28
- its(:first) { should be_a DoubleEntry::Account }
29
- its("first.identifier") { should eq "savings" }
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 "should create missing account balance records" do
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 "should take the balance for new account balance records from the lines table" do
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 "should not allow locking inside a regular transaction" do
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 "should not allow a transfer inside a regular transaction" do
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 "should allow a transfer inside a lock if we've locked the transaction accounts" do
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 "should not allow a transfer inside a lock if the right locks aren't held" do
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 "should allow nested locks if the outer lock locks all the accounts" do
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 "should not allow nested locks if the out lock doesn't lock all the accounts" do
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 "should roll back a locking transaction" do
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 "should roll back a locking transaction if there's an exception" do
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
- it "should allow multiple threads to lock at the same time" do
134
- threads = Array.new
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
- expect do
137
- threads << Thread.new do
138
- sleep 0.05
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
- threads << Thread.new do
145
- DoubleEntry::Locking.lock_accounts(@account_c, @account_d) do
146
- sleep 0.1
147
- DoubleEntry.transfer(Money.new(10_00), :from => @account_c, :to => @account_d, :code => :test)
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
- threads.each(&:join)
152
- end.to_not raise_error
153
- end
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
- it "should allow multiple threads to lock accounts without balances at the same time" do
156
- threads = Array.new
154
+ threads.each(&:join)
155
+ end.to_not raise_error
156
+ end
157
157
 
158
- expect do
159
- threads << Thread.new { DoubleEntry::Locking.lock_accounts(@account_a, @account_b) { } }
160
- threads << Thread.new { DoubleEntry::Locking.lock_accounts(@account_c, @account_d) { } }
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
- threads.each(&:join)
163
- end.to_not raise_error
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'] ||= 'mysql'
7
- ActiveRecord::Base.establish_connection YAML.load_file(File.expand_path("../support/database.yml", __FILE__))[ENV['DB']]
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
@@ -1,16 +1,10 @@
1
1
  # encoding: utf-8
2
- user_scope = lambda do |user_identifier|
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)
@@ -14,3 +14,8 @@ mysql:
14
14
  pool: 100
15
15
  username: root
16
16
  password:
17
+ sqlite:
18
+ adapter: sqlite3
19
+ encoding: utf8
20
+ database: db/double_entry_test.sqlite3
21
+ pool: 100
@@ -16,3 +16,9 @@ postgres:
16
16
  pool: 100
17
17
  timeout: 5000
18
18
  host: localhost
19
+
20
+ sqlite:
21
+ adapter: sqlite3
22
+ encoding: utf8
23
+ database: db/double_entry_test.sqlite3
24
+ pool: 100
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.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-07-17 00:00:00.000000000 Z
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