double_entry 0.10.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +2 -1
  3. data/.rubocop.yml +55 -0
  4. data/.travis.yml +23 -12
  5. data/README.md +5 -1
  6. data/Rakefile +8 -3
  7. data/double_entry.gemspec +4 -3
  8. data/lib/active_record/locking_extensions.rb +28 -40
  9. data/lib/active_record/locking_extensions/log_subscriber.rb +4 -4
  10. data/lib/double_entry.rb +0 -2
  11. data/lib/double_entry/account.rb +13 -16
  12. data/lib/double_entry/account_balance.rb +0 -4
  13. data/lib/double_entry/balance_calculator.rb +4 -5
  14. data/lib/double_entry/configurable.rb +0 -2
  15. data/lib/double_entry/configuration.rb +2 -3
  16. data/lib/double_entry/errors.rb +2 -2
  17. data/lib/double_entry/line.rb +13 -16
  18. data/lib/double_entry/locking.rb +13 -18
  19. data/lib/double_entry/reporting.rb +2 -3
  20. data/lib/double_entry/reporting/aggregate.rb +90 -88
  21. data/lib/double_entry/reporting/aggregate_array.rb +58 -58
  22. data/lib/double_entry/reporting/day_range.rb +37 -35
  23. data/lib/double_entry/reporting/hour_range.rb +40 -37
  24. data/lib/double_entry/reporting/line_aggregate.rb +27 -28
  25. data/lib/double_entry/reporting/month_range.rb +67 -67
  26. data/lib/double_entry/reporting/time_range.rb +40 -38
  27. data/lib/double_entry/reporting/time_range_array.rb +3 -5
  28. data/lib/double_entry/reporting/week_range.rb +77 -78
  29. data/lib/double_entry/reporting/year_range.rb +27 -27
  30. data/lib/double_entry/transfer.rb +14 -15
  31. data/lib/double_entry/validation/line_check.rb +92 -86
  32. data/lib/double_entry/version.rb +1 -1
  33. data/lib/generators/double_entry/install/install_generator.rb +1 -2
  34. data/lib/generators/double_entry/install/templates/migration.rb +0 -2
  35. data/script/jack_hammer +1 -1
  36. data/spec/active_record/locking_extensions_spec.rb +45 -38
  37. data/spec/double_entry/account_balance_spec.rb +4 -5
  38. data/spec/double_entry/account_spec.rb +43 -44
  39. data/spec/double_entry/balance_calculator_spec.rb +6 -8
  40. data/spec/double_entry/configuration_spec.rb +14 -16
  41. data/spec/double_entry/line_spec.rb +25 -26
  42. data/spec/double_entry/locking_spec.rb +34 -39
  43. data/spec/double_entry/reporting/aggregate_array_spec.rb +8 -10
  44. data/spec/double_entry/reporting/aggregate_spec.rb +84 -44
  45. data/spec/double_entry/reporting/line_aggregate_spec.rb +7 -6
  46. data/spec/double_entry/reporting/month_range_spec.rb +109 -103
  47. data/spec/double_entry/reporting/time_range_array_spec.rb +145 -135
  48. data/spec/double_entry/reporting/time_range_spec.rb +36 -35
  49. data/spec/double_entry/reporting/week_range_spec.rb +82 -76
  50. data/spec/double_entry/reporting_spec.rb +9 -13
  51. data/spec/double_entry/transfer_spec.rb +13 -15
  52. data/spec/double_entry/validation/line_check_spec.rb +73 -79
  53. data/spec/double_entry_spec.rb +65 -68
  54. data/spec/generators/double_entry/install/install_generator_spec.rb +7 -10
  55. data/spec/spec_helper.rb +68 -10
  56. data/spec/support/accounts.rb +2 -4
  57. data/spec/support/double_entry_spec_helper.rb +4 -4
  58. data/spec/support/gemfiles/Gemfile.rails-3.2.x +1 -0
  59. metadata +31 -2
@@ -2,119 +2,125 @@
2
2
  require 'set'
3
3
 
4
4
  module DoubleEntry
5
- module Validation
6
- class LineCheck < ActiveRecord::Base
5
+ module Validation
6
+ class LineCheck < ActiveRecord::Base
7
+ default_scope -> { order('created_at') }
7
8
 
8
- default_scope -> { order('created_at') }
9
+ def self.perform!
10
+ new.perform
11
+ end
9
12
 
10
- def self.perform!
11
- new.perform
12
- end
13
+ def perform
14
+ log = ''
15
+ current_line_id = nil
13
16
 
14
- def perform
15
- log = ''
16
- current_line_id = nil
17
+ active_accounts = Set.new
18
+ incorrect_accounts = Set.new
17
19
 
18
- active_accounts = Set.new
19
- incorrect_accounts = Set.new
20
+ new_lines_since_last_run.find_each do |line|
21
+ incorrect_accounts << line.account unless running_balance_correct?(line, log)
22
+ active_accounts << line.account
23
+ current_line_id = line.id
24
+ end
20
25
 
21
- new_lines_since_last_run.find_each do |line|
22
- incorrect_accounts << line.account unless running_balance_correct?(line, log)
23
- active_accounts << line.account
24
- current_line_id = line.id
25
- end
26
+ active_accounts.each do |account|
27
+ incorrect_accounts << account unless cached_balance_correct?(account)
28
+ end
29
+
30
+ incorrect_accounts.each { |account| recalculate_account(account) }
26
31
 
27
- active_accounts.each do |account|
28
- incorrect_accounts << account unless cached_balance_correct?(account)
32
+ unless active_accounts.empty?
33
+ LineCheck.create!(
34
+ :errors_found => incorrect_accounts.any?,
35
+ :last_line_id => current_line_id,
36
+ :log => log,
37
+ )
38
+ end
29
39
  end
30
40
 
31
- incorrect_accounts.each { |account| recalculate_account(account) }
41
+ private
32
42
 
33
- unless active_accounts.empty?
34
- LineCheck.create!(
35
- :errors_found => incorrect_accounts.any?,
36
- :last_line_id => current_line_id,
37
- :log => log,
38
- )
43
+ def last_run_line_id
44
+ latest = LineCheck.last
45
+ latest ? latest.last_line_id : 0
39
46
  end
40
- end
41
-
42
- private
43
47
 
44
- def last_run_line_id
45
- latest = LineCheck.last
46
- latest ? latest.last_line_id : 0
47
- end
48
+ def new_lines_since_last_run
49
+ Line.where('id > ?', last_run_line_id)
50
+ end
48
51
 
49
- def new_lines_since_last_run
50
- Line.where('id > ?', last_run_line_id)
51
- end
52
+ def running_balance_correct?(line, log)
53
+ # Another work around for the MySQL 5.1 query optimiser bug that causes the ORDER BY
54
+ # on the query to fail in some circumstances, resulting in an old balance being
55
+ # returned. This was biting us intermittently in spec runs.
56
+ # See http://bugs.mysql.com/bug.php?id=51431
57
+ force_index = if Line.connection.adapter_name.match(/mysql/i)
58
+ 'FORCE INDEX (lines_scope_account_id_idx)'
59
+ else
60
+ ''
61
+ end
62
+
63
+ # yes, it needs to be find_by_sql, because any other find will be affected
64
+ # by the find_each call in perform!
65
+ previous_line = Line.find_by_sql([<<-SQL, line.account.identifier.to_s, line.scope, line.id])
66
+ SELECT * FROM #{Line.quoted_table_name} #{force_index}
67
+ WHERE account = ?
68
+ AND scope = ?
69
+ AND id < ?
70
+ ORDER BY id DESC
71
+ LIMIT 1
72
+ SQL
73
+
74
+ previous_balance = previous_line.length == 1 ? previous_line[0].balance : Money.zero(line.account.currency)
75
+
76
+ if line.balance != (line.amount + previous_balance)
77
+ log << line_error_message(line, previous_line, previous_balance)
78
+ end
52
79
 
53
- def running_balance_correct?(line, log)
54
- # Another work around for the MySQL 5.1 query optimiser bug that causes the ORDER BY
55
- # on the query to fail in some circumstances, resulting in an old balance being
56
- # returned. This was biting us intermittently in spec runs.
57
- # See http://bugs.mysql.com/bug.php?id=51431
58
- force_index = if Line.connection.adapter_name.match /mysql/i
59
- "FORCE INDEX (lines_scope_account_id_idx)"
60
- else
61
- ""
62
- end
63
-
64
- # yes, it needs to be find_by_sql, because any other find will be affected
65
- # by the find_each call in perform!
66
- previous_line = Line.find_by_sql(["SELECT * FROM #{Line.quoted_table_name} #{force_index} WHERE account = ? AND scope = ? AND id < ? ORDER BY id DESC LIMIT 1", line.account.identifier.to_s, line.scope, line.id])
67
-
68
- previous_balance = previous_line.length == 1 ? previous_line[0].balance : Money.zero(line.account.currency)
69
-
70
- if line.balance != (line.amount + previous_balance)
71
- log << line_error_message(line, previous_line, previous_balance)
80
+ line.balance == previous_balance + line.amount
72
81
  end
73
82
 
74
- line.balance == previous_balance + line.amount
75
- end
76
-
77
- def line_error_message(line, previous_line, previous_balance)
78
- <<-END_OF_MESSAGE.strip_heredoc
83
+ def line_error_message(line, previous_line, previous_balance)
84
+ <<-END_OF_MESSAGE.strip_heredoc
79
85
  *********************************
80
86
  Error on line ##{line.id}: balance:#{line.balance} != #{previous_balance} + #{line.amount}
81
87
  *********************************
82
- #{previous_line.inspect}
83
- #{line.inspect}
88
+ #{previous_line.inspect}
89
+ #{line.inspect}
84
90
 
85
- END_OF_MESSAGE
86
- end
91
+ END_OF_MESSAGE
92
+ end
87
93
 
88
- def cached_balance_correct?(account)
89
- DoubleEntry.lock_accounts(account) do
90
- return AccountBalance.find_by_account(account).balance == account.balance
94
+ def cached_balance_correct?(account)
95
+ DoubleEntry.lock_accounts(account) do
96
+ return AccountBalance.find_by_account(account).balance == account.balance
97
+ end
91
98
  end
92
- end
93
99
 
94
- def recalculate_account(account)
95
- DoubleEntry.lock_accounts(account) do
96
- recalculated_balance = Money.zero(account.currency)
100
+ def recalculate_account(account)
101
+ DoubleEntry.lock_accounts(account) do
102
+ recalculated_balance = Money.zero(account.currency)
97
103
 
98
- lines_for_account(account).each do |line|
99
- recalculated_balance += line.amount
100
- line.update_attribute(:balance, recalculated_balance) if line.balance != recalculated_balance
101
- end
104
+ lines_for_account(account).each do |line|
105
+ recalculated_balance += line.amount
106
+ line.update_attribute(:balance, recalculated_balance) if line.balance != recalculated_balance
107
+ end
102
108
 
103
- update_balance_for_account(account, recalculated_balance)
109
+ update_balance_for_account(account, recalculated_balance)
110
+ end
104
111
  end
105
- end
106
112
 
107
- def lines_for_account(account)
108
- Line.where(
109
- :account => account.identifier.to_s,
110
- :scope => account.scope_identity.to_s
111
- ).order(:id)
112
- end
113
+ def lines_for_account(account)
114
+ Line.where(
115
+ :account => account.identifier.to_s,
116
+ :scope => account.scope_identity.to_s,
117
+ ).order(:id)
118
+ end
113
119
 
114
- def update_balance_for_account(account, balance)
115
- account_balance = Locking.balance_for_locked_account(account)
116
- account_balance.update_attribute(:balance, balance)
120
+ def update_balance_for_account(account, balance)
121
+ account_balance = Locking.balance_for_locked_account(account)
122
+ account_balance.update_attribute(:balance, balance)
123
+ end
117
124
  end
118
125
  end
119
- end
120
126
  end
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module DoubleEntry
4
- VERSION = "0.10.0"
4
+ VERSION = '0.10.1'
5
5
  end
@@ -14,9 +14,8 @@ module DoubleEntry
14
14
  end
15
15
 
16
16
  def copy_migrations
17
- migration_template "migration.rb", "db/migrate/create_double_entry_tables.rb"
17
+ migration_template 'migration.rb', 'db/migrate/create_double_entry_tables.rb'
18
18
  end
19
-
20
19
  end
21
20
  end
22
21
  end
@@ -54,7 +54,6 @@ class CreateDoubleEntryTables < ActiveRecord::Migration
54
54
  t.text "log"
55
55
  t.timestamps :null => false
56
56
  end
57
-
58
57
  end
59
58
 
60
59
  def self.down
@@ -63,5 +62,4 @@ class CreateDoubleEntryTables < ActiveRecord::Migration
63
62
  drop_table "double_entry_lines"
64
63
  drop_table "double_entry_account_balances"
65
64
  end
66
-
67
65
  end
data/script/jack_hammer CHANGED
@@ -68,7 +68,7 @@ end
68
68
  def clean_out_database
69
69
  puts "Cleaning out the database..."
70
70
 
71
- DatabaseCleaner.clean_with(:deletion)
71
+ DatabaseCleaner.clean_with(:truncation)
72
72
  end
73
73
 
74
74
  def create_accounts_and_transfers
@@ -1,12 +1,11 @@
1
1
  # encoding: utf-8
2
- require 'spec_helper'
3
2
 
4
- describe ActiveRecord::LockingExtensions do
5
- PG_DEADLOCK = ActiveRecord::StatementInvalid.new("PG::Error: ERROR: deadlock detected")
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...")
3
+ RSpec.describe ActiveRecord::LockingExtensions do
4
+ PG_DEADLOCK = ActiveRecord::StatementInvalid.new('PG::Error: ERROR: deadlock detected')
5
+ MYSQL_DEADLOCK = ActiveRecord::StatementInvalid.new('Mysql::Error: Deadlock found when trying to get lock')
6
+ SQLITE3_LOCK = ActiveRecord::StatementInvalid.new('SQLite3::BusyException: database is locked: UPDATE...')
8
7
 
9
- context "#restartable_transaction" do
8
+ context '#restartable_transaction' do
10
9
  it "keeps running the lock until a ActiveRecord::RestartTransaction isn't raised" do
11
10
  expect(User).to receive(:create!).ordered.and_raise(ActiveRecord::RestartTransaction)
12
11
  expect(User).to receive(:create!).ordered.and_raise(ActiveRecord::RestartTransaction)
@@ -16,88 +15,96 @@ describe ActiveRecord::LockingExtensions do
16
15
  end
17
16
  end
18
17
 
19
- context "#with_restart_on_deadlock" do
20
- shared_examples "abstract adapter" do
21
- it "raises a ActiveRecord::RestartTransaction error if a deadlock occurs" do
22
- expect { User.with_restart_on_deadlock { raise exception } }.to raise_error(ActiveRecord::RestartTransaction)
18
+ context '#with_restart_on_deadlock' do
19
+ shared_examples 'abstract adapter' do
20
+ it 'raises a ActiveRecord::RestartTransaction error if a deadlock occurs' do
21
+ expect { User.with_restart_on_deadlock { fail exception } }.
22
+ to raise_error(ActiveRecord::RestartTransaction)
23
23
  end
24
24
 
25
- it "publishes a notification" do
26
- expect(ActiveSupport::Notifications).to receive(:publish).with("deadlock_restart.active_record", hash_including(:exception => exception))
27
- expect { User.with_restart_on_deadlock { raise exception } }.to raise_error
25
+ it 'publishes a notification' do
26
+ expect(ActiveSupport::Notifications).
27
+ to receive(:publish).
28
+ with('deadlock_restart.active_record', hash_including(:exception => exception))
29
+ expect { User.with_restart_on_deadlock { fail exception } }.to raise_error
28
30
  end
29
31
  end
30
32
 
31
- context "mysql" do
33
+ context 'mysql' do
32
34
  let(:exception) { MYSQL_DEADLOCK }
33
35
 
34
- it_behaves_like "abstract adapter"
36
+ it_behaves_like 'abstract adapter'
35
37
  end
36
38
 
37
- context "postgres" do
39
+ context 'postgres' do
38
40
  let(:exception) { PG_DEADLOCK }
39
41
 
40
- it_behaves_like "abstract adapter"
42
+ it_behaves_like 'abstract adapter'
41
43
  end
42
44
 
43
- context "sqlite" do
45
+ context 'sqlite' do
44
46
  let(:exception) { SQLITE3_LOCK }
45
47
 
46
- it_behaves_like "abstract adapter"
48
+ it_behaves_like 'abstract adapter'
47
49
  end
48
50
  end
49
51
 
50
- context "#create_ignoring_duplicates" do
51
- it "does not raise an error if a duplicate index error is raised in the database" do
52
- User.make! :username => "keith"
52
+ context '#create_ignoring_duplicates' do
53
+ it 'does not raise an error if a duplicate index error is raised in the database' do
54
+ User.make! :username => 'keith'
53
55
 
54
- expect { User.make! :username => "keith" }.to raise_error
55
- expect { User.create_ignoring_duplicates! :username => "keith" }.to_not raise_error
56
+ expect { User.make! :username => 'keith' }.to raise_error
57
+ expect { User.create_ignoring_duplicates! :username => 'keith' }.to_not raise_error
56
58
  end
57
59
 
58
- it "publishes a notification when a duplicate is encountered" do
59
- User.make! :username => "keith"
60
+ it 'publishes a notification when a duplicate is encountered' do
61
+ User.make! :username => 'keith'
60
62
 
61
- expect(ActiveSupport::Notifications).to receive(:publish).with("duplicate_ignore.active_record", hash_including(:exception => kind_of(ActiveRecord::RecordNotUnique)))
63
+ expect(ActiveSupport::Notifications).
64
+ to receive(:publish).
65
+ with('duplicate_ignore.active_record', hash_including(:exception => kind_of(ActiveRecord::RecordNotUnique)))
62
66
 
63
- expect { User.create_ignoring_duplicates! :username => "keith" }.to_not raise_error
67
+ expect { User.create_ignoring_duplicates! :username => 'keith' }.to_not raise_error
64
68
  end
65
69
 
66
- shared_examples "abstract adapter" do
67
- it "retries the creation if a deadlock error is raised from the database" do
70
+ shared_examples 'abstract adapter' do
71
+ it 'retries the creation if a deadlock error is raised from the database' do
68
72
  expect(User).to receive(:create!).ordered.and_raise(exception)
69
73
  expect(User).to receive(:create!).ordered.and_return(true)
70
74
 
71
75
  expect { User.create_ignoring_duplicates! }.to_not raise_error
72
76
  end
73
77
 
74
- it "publishes a notification on each retry" do
78
+ it 'publishes a notification on each retry' do
75
79
  expect(User).to receive(:create!).ordered.and_raise(exception)
76
80
  expect(User).to receive(:create!).ordered.and_raise(exception)
77
81
  expect(User).to receive(:create!).ordered.and_return(true)
78
82
 
79
- expect(ActiveSupport::Notifications).to receive(:publish).with("deadlock_retry.active_record", hash_including(:exception => exception)).twice
83
+ expect(ActiveSupport::Notifications).
84
+ to receive(:publish).
85
+ with('deadlock_retry.active_record', hash_including(:exception => exception)).
86
+ twice
80
87
 
81
88
  expect { User.create_ignoring_duplicates! }.to_not raise_error
82
89
  end
83
90
  end
84
91
 
85
- context "mysql" do
92
+ context 'mysql' do
86
93
  let(:exception) { MYSQL_DEADLOCK }
87
94
 
88
- it_behaves_like "abstract adapter"
95
+ it_behaves_like 'abstract adapter'
89
96
  end
90
97
 
91
- context "postgres" do
98
+ context 'postgres' do
92
99
  let(:exception) { PG_DEADLOCK }
93
100
 
94
- it_behaves_like "abstract adapter"
101
+ it_behaves_like 'abstract adapter'
95
102
  end
96
103
 
97
- context "sqlite" do
104
+ context 'sqlite' do
98
105
  let(:exception) { SQLITE3_LOCK }
99
106
 
100
- it_behaves_like "abstract adapter"
107
+ it_behaves_like 'abstract adapter'
101
108
  end
102
109
  end
103
110
  end
@@ -1,8 +1,7 @@
1
1
  # encoding: utf-8
2
- require 'spec_helper'
3
- describe DoubleEntry::AccountBalance do
4
-
5
- it "has a table name prefixed with double_entry_" do
6
- expect(DoubleEntry::AccountBalance.table_name).to eq "double_entry_account_balances"
2
+ RSpec.describe DoubleEntry::AccountBalance do
3
+ describe '.table_name' do
4
+ subject { DoubleEntry::AccountBalance.table_name }
5
+ it { should eq('double_entry_account_balances') }
7
6
  end
8
7
  end
@@ -1,19 +1,18 @@
1
1
  # encoding: utf-8
2
- require 'spec_helper'
3
2
  module DoubleEntry
4
- describe Account do
3
+ RSpec.describe Account do
5
4
  let(:identity_scope) { ->(value) { value } }
6
5
 
7
- describe "::new" do
8
- context "given an identifier 31 characters in length" do
9
- let(:identifier) { "xxxxxxxx 31 characters xxxxxxxx" }
6
+ describe '::new' do
7
+ context 'given an identifier 31 characters in length' do
8
+ let(:identifier) { 'xxxxxxxx 31 characters xxxxxxxx' }
10
9
  specify do
11
10
  expect { Account.new(:identifier => identifier) }.to_not raise_error
12
11
  end
13
12
  end
14
13
 
15
- context "given an identifier 32 characters in length" do
16
- let(:identifier) { "xxxxxxxx 32 characters xxxxxxxxx" }
14
+ context 'given an identifier 32 characters in length' do
15
+ let(:identifier) { 'xxxxxxxx 32 characters xxxxxxxxx' }
17
16
  specify do
18
17
  expect { Account.new(:identifier => identifier) }.to raise_error AccountIdentifierTooLongError, /'#{identifier}'/
19
18
  end
@@ -21,64 +20,64 @@ module DoubleEntry
21
20
  end
22
21
 
23
22
  describe Account::Instance do
24
- it "is sortable" do
25
- account = Account.new(:identifier => "savings", :scope_identifier => identity_scope)
26
- a = Account::Instance.new(:account => account, :scope => "123")
27
- b = Account::Instance.new(:account => account, :scope => "456")
23
+ it 'is sortable' do
24
+ account = Account.new(:identifier => 'savings', :scope_identifier => identity_scope)
25
+ a = Account::Instance.new(:account => account, :scope => '123')
26
+ b = Account::Instance.new(:account => account, :scope => '456')
28
27
  expect([b, a].sort).to eq [a, b]
29
28
  end
30
29
 
31
- it "is hashable" do
32
- account = Account.new(:identifier => "savings", :scope_identifier => identity_scope)
33
- a1 = Account::Instance.new(:account => account, :scope => "123")
34
- a2 = Account::Instance.new(:account => account, :scope => "123")
35
- b = Account::Instance.new(:account => account, :scope => "456")
30
+ it 'is hashable' do
31
+ account = Account.new(:identifier => 'savings', :scope_identifier => identity_scope)
32
+ a1 = Account::Instance.new(:account => account, :scope => '123')
33
+ a2 = Account::Instance.new(:account => account, :scope => '123')
34
+ b = Account::Instance.new(:account => account, :scope => '456')
36
35
 
37
36
  expect(a1.hash).to eq a2.hash
38
37
  expect(a1.hash).to_not eq b.hash
39
38
  end
40
39
 
41
- describe "::new" do
42
- let(:account) { Account.new(:identifier => "x", :scope_identifier => identity_scope) }
40
+ describe '::new' do
41
+ let(:account) { Account.new(:identifier => 'x', :scope_identifier => identity_scope) }
43
42
  subject(:initialize_account_instance) { Account::Instance.new(:account => account, :scope => scope) }
44
43
 
45
- context "given a scope identifier 23 characters in length" do
46
- let(:scope) { "xxxx 23 characters xxxx" }
44
+ context 'given a scope identifier 23 characters in length' do
45
+ let(:scope) { 'xxxx 23 characters xxxx' }
47
46
  specify { expect { initialize_account_instance }.to_not raise_error }
48
47
  end
49
48
 
50
- context "given a scope identifier 24 characters in length" do
51
- let(:scope) { "xxxx 24 characters xxxxx" }
49
+ context 'given a scope identifier 24 characters in length' do
50
+ let(:scope) { 'xxxx 24 characters xxxxx' }
52
51
  specify { expect { initialize_account_instance }.to raise_error ScopeIdentifierTooLongError, /'#{scope}'/ }
53
52
  end
54
53
  end
55
54
  end
56
55
 
57
- describe "currency" do
58
- it "defaults to USD currency" do
59
- account = DoubleEntry::Account.new(:identifier => "savings", :scope_identifier => identity_scope)
60
- expect(DoubleEntry::Account::Instance.new(:account => account).currency).to eq("USD")
56
+ describe 'currency' do
57
+ it 'defaults to USD currency' do
58
+ account = DoubleEntry::Account.new(:identifier => 'savings', :scope_identifier => identity_scope)
59
+ expect(DoubleEntry::Account::Instance.new(:account => account).currency).to eq('USD')
61
60
  end
62
61
 
63
- it "allows the currency to be set" do
64
- account = DoubleEntry::Account.new(:identifier => "savings", :scope_identifier => identity_scope, :currency => "AUD")
65
- expect(DoubleEntry::Account::Instance.new(:account => account).currency).to eq("AUD")
62
+ it 'allows the currency to be set' do
63
+ account = DoubleEntry::Account.new(:identifier => 'savings', :scope_identifier => identity_scope, :currency => 'AUD')
64
+ expect(DoubleEntry::Account::Instance.new(:account => account).currency).to eq('AUD')
66
65
  end
67
66
  end
68
67
 
69
68
  describe Account::Set do
70
- describe "#define" do
69
+ describe '#define' do
71
70
  context "given a 'savings' account is defined" do
72
- before { subject.define(:identifier => "savings") }
71
+ before { subject.define(:identifier => 'savings') }
73
72
  its(:first) { should be_an Account }
74
- its("first.identifier") { should eq "savings" }
73
+ its('first.identifier') { should eq 'savings' }
75
74
  end
76
75
  end
77
76
 
78
- describe "#active_record_scope_identifier" do
77
+ describe '#active_record_scope_identifier' do
79
78
  subject(:scope) { Account::Set.new.active_record_scope_identifier(ar_class) }
80
79
 
81
- context "given ActiveRecordScopeFactory is stubbed" do
80
+ context 'given ActiveRecordScopeFactory is stubbed' do
82
81
  let(:scope_identifier) { double(:scope_identifier) }
83
82
  let(:scope_factory) { double(:scope_factory, :scope_identifier => scope_identifier) }
84
83
  let(:ar_class) { double(:ar_class) }
@@ -90,36 +89,36 @@ module DoubleEntry
90
89
  end
91
90
  end
92
91
 
93
- describe Account::ActiveRecordScopeFactory do
94
- context "given the class User" do
92
+ RSpec.describe Account::ActiveRecordScopeFactory do
93
+ context 'given the class User' do
95
94
  subject(:factory) { Account::ActiveRecordScopeFactory.new(User) }
96
95
 
97
- describe "#scope_identifier" do
96
+ describe '#scope_identifier' do
98
97
  subject(:scope_identifier) { factory.scope_identifier }
99
98
 
100
- describe "#call" do
99
+ describe '#call' do
101
100
  subject(:scope) { scope_identifier.call(value) }
102
101
 
103
- context "given a User instance with ID 32" do
102
+ context 'given a User instance with ID 32' do
104
103
  let(:value) { User.make(:id => 32) }
105
104
 
106
105
  it { should eq 32 }
107
106
  end
108
107
 
109
- context "given differing model instance with ID 32" do
108
+ context 'given differing model instance with ID 32' do
110
109
  let(:value) { double(:id => 32) }
111
- it "raises an error" do
110
+ it 'raises an error' do
112
111
  expect { scope_identifier.call(value) }.to raise_error DoubleEntry::AccountScopeMismatchError
113
112
  end
114
113
  end
115
114
 
116
115
  context "given the String 'I am a bearded lady'" do
117
- let(:value) { "I am a bearded lady" }
116
+ let(:value) { 'I am a bearded lady' }
118
117
 
119
- it { should eq "I am a bearded lady" }
118
+ it { should eq 'I am a bearded lady' }
120
119
  end
121
120
 
122
- context "given the Integer 42" do
121
+ context 'given the Integer 42' do
123
122
  let(:value) { 42 }
124
123
 
125
124
  it { should eq 42 }