feidee_utils 0.0.4.3 → 0.0.4.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fd2d6afd486620f91184ac15f846ae7fef857b7c
4
- data.tar.gz: d0b20dca231364ca6ee87392c9c57b69220c81d0
3
+ metadata.gz: 9cc0908a9717145ac9f863abad4f5d4ff910a898
4
+ data.tar.gz: 64d25e89e933a547ba71e6e412b64178dc7a9310
5
5
  SHA512:
6
- metadata.gz: addacf49613d3cfb3be9a830046046abc84ace8091a514589bdbd46c01f09437dbeee95c00f2ba147e1986096e64b077171f3f2105a782df9821e63de998860b
7
- data.tar.gz: 0fb69c197627fc5ddacf43bf956fe2649aaacd18159812fd8f0cb903be5de9755faf6489e141e4bdb06f5cc72d8ec84e5a8cd106d7f2540728f46e6f8e0a9e58
6
+ metadata.gz: 0fc21e8b91f1f7f44404163478533ffcdffc4c42455bdd0f81881758c1a5faa12e49193d500869d22cf52a416fc2ce4a5a38ad71d5f6552194f265fdbb4649dc
7
+ data.tar.gz: 3e78c29a78d14c2ca7e0ce2c5cab4c914ae02b2a39a13feb337726e859ba6de0a56cb42bab28c18c602f42c7b0bc828ce6d11c77b7a142e1135622caf23054fe
data/README.md CHANGED
@@ -2,7 +2,7 @@ Feidee Utils
2
2
  ============
3
3
  [![Build Status](https://travis-ci.org/muyiliqing/feidee_utils.svg?branch=master)](https://travis-ci.org/muyiliqing/feidee_utils)
4
4
 
5
- Free users' data from [Feidee](http://www.feidee.com) private backups (.kbf).
5
+ Free user data from [Feidee](http://www.feidee.com) private backups (.kbf).
6
6
 
7
7
  Feidee And The KBF Format
8
8
  -----------
@@ -26,9 +26,9 @@ A set of ActiveRecord-like classes are provided to access the information in the
26
26
  require 'feidee_utils'
27
27
 
28
28
  kbf = FeideeUtils::Kbf.open_file(path_to_kbf_file)
29
- database = kbf.sqlite_db
30
- all_accounts = database.namespaced::Account.all
31
- all_transactions = database.namespaced::Transaction.all
29
+ database = kbf.db
30
+ all_accounts = database.ledger::Account.all
31
+ all_transactions = database.ledger::Transaction.all
32
32
  ```
33
33
 
34
34
  For more examples see ```examples/``` (To be added).
@@ -5,17 +5,34 @@ require 'bigdecimal'
5
5
  module FeideeUtils
6
6
  class Account < Record
7
7
  def validate_integrity
8
- raise "Account type should always be 0, but it's #{field["type"]}.\n" + inspect unless not field["type"] or field["type"] == 0
9
- raise "Account usedCount should always be 0, but it's #{field["usedCount"]}.\n" + inspect unless field["usedCount"] == 0
10
- raise "Account uuid should always be empty, but it's #{field["uuid"]}.\n" + inspect unless field["uuid"].to_s.empty?
11
- raise "Account hierachy contains more than 2 levels.\n" + inspect unless flat_parent_hierachy?
12
- raise "Account hidden should be either 0 or 1, but it's #{raw_hidden}.\n" + inspect unless (raw_hidden == 1 or raw_hidden == 0)
8
+ unless not field["type"] or field["type"] == 0
9
+ raise "Account type should always be 0, but it's #{field["type"]}.\n" +
10
+ inspect
11
+ end
12
+ unless field["usedCount"] == 0
13
+ raise "Account usedCount should always be 0," +
14
+ " but it's #{field["usedCount"]}.\n"+
15
+ inspect
16
+ end
17
+ unless field["uuid"].to_s.empty?
18
+ raise "Account uuid should always be empty,"+
19
+ " but it's #{field["uuid"]}.\n" +
20
+ inspect
21
+ end
22
+ unless flat_parent_hierachy?
23
+ raise "Account hierachy contains more than 2 levels.\n" + inspect
24
+ end
25
+ unless (raw_hidden == 1 or raw_hidden == 0)
26
+ raise "Account hidden should be either 0 or 1," +
27
+ " but it's #{raw_hidden}.\n" +
28
+ inspect
29
+ end
13
30
  end
14
31
 
15
32
  def self.validate_global_integrity
16
33
  if self.find_by_id(-1) != nil
17
- raise "-1 is used as the parent POID placeholder of a parent account. " +
18
- "Account of POID -1 should not exist."
34
+ raise "-1 is used as the parent POID placeholder of a parent account." +
35
+ " Account of POID -1 should not exist."
19
36
  end
20
37
  end
21
38
 
@@ -36,15 +53,12 @@ module FeideeUtils
36
53
  }.freeze
37
54
 
38
55
  IgnoredFields = [
39
- "tradingEntityPOID",
40
- # TODO: Field type is removed from database_version 73. Not sure about
41
- # earlier versions.
42
- "type", # Always 0
56
+ "tradingEntityPOID", # Foreign key to t_user or maybe t_tradingEntity.
57
+ "type", # Always 0, removed since database version 73.
43
58
  "usedCount", # Always 0
44
59
  "uuid", # Always empty.
45
- # TODO: code is also removed.
46
- "code", # WTF
47
- "clientID", # WTF
60
+ "code", # Always 0, removed since database version 73.
61
+ "clientID", # Always equal to poid.
48
62
  ].freeze
49
63
 
50
64
  define_accessors(FieldMappings)
@@ -96,7 +110,9 @@ module FeideeUtils
96
110
 
97
111
  def children
98
112
  arr = []
99
- self.class.database.query("SELECT * FROM #{self.class.table_name} WHERE parent = ?", poid) do |result|
113
+ self.class.database.query(
114
+ "SELECT * FROM #{self.class.table_name} WHERE parent = ?", poid
115
+ ) do |result|
100
116
  result.each do |raw_row|
101
117
  arr << self.class.new(result.columns, result.types, raw_row)
102
118
  end
@@ -104,6 +120,10 @@ module FeideeUtils
104
120
  arr
105
121
  end
106
122
 
123
+ def to_s
124
+ "#{name} (Account/#{poid})"
125
+ end
126
+
107
127
  class ModifiedAccount < Record::ModifiedRecord
108
128
  define_custom_methods([
109
129
  :balance,
@@ -26,9 +26,9 @@ module FeideeUtils
26
26
  }.freeze
27
27
 
28
28
  IgnoredFields = [
29
- "userTradingEntityPOID", # WTF
29
+ "userTradingEntityPOID", # Foreign key to t_user.
30
30
  "_tempIconName", # Icon name in the app
31
- "clientID", # WTF
31
+ "clientID", # Always equal to poid.
32
32
  ].freeze
33
33
 
34
34
  define_accessors(FieldMappings)
@@ -39,6 +39,10 @@ module FeideeUtils
39
39
  2 => :claim,
40
40
  })
41
41
 
42
+ def to_s
43
+ "#{name} (AccountGroup/#{poid})"
44
+ end
45
+
42
46
  # Schema
43
47
  # accountGroupPOID long not null
44
48
  # name varchar(100) not null
@@ -10,13 +10,18 @@ module FeideeUtils
10
10
  def validate_integrity
11
11
  validate_depth_integrity
12
12
  validate_one_level_path_integrity
13
- raise "Category usedCount should always be 0, but it's #{field["usedCount"]}.\n" + inspect unless field["usedCount"] == 0
13
+ unless field["usedCount"] == 0
14
+ raise "Category usedCount should always be 0, " +
15
+ "but it's #{field["usedCount"]}.\n" +
16
+ inspect
17
+ end
14
18
  end
15
19
 
16
20
  def self.validate_global_integrity
17
21
  project_root_code = 2
18
22
  if TypeEnum[project_root_code] != :project_root
19
- raise "The type code of project root has been changed, please update the code."
23
+ raise "The type code of project root has been changed," +
24
+ " please update the code."
20
25
  end
21
26
 
22
27
  rows = self.database.execute <<-SQL
@@ -26,11 +31,13 @@ module FeideeUtils
26
31
 
27
32
  if rows.length > 1
28
33
  poids = rows.map do |row| row[0] end
29
- raise "More than one category have type project_root. IDs are #{poids.inspect}."
34
+ raise "More than one category have type project_root." +
35
+ " IDs are #{poids.inspect}."
30
36
  elsif rows.length == 1
31
37
  category_name = rows[0][1]
32
38
  if category_name != "projectRoot" and category_name != "root"
33
- raise "Category #{category_name} has type project_root. ID: #{rows[0][0]}."
39
+ raise "Category #{category_name} has type project_root." +
40
+ " ID: #{rows[0][0]}."
34
41
  end
35
42
  end
36
43
  end
@@ -45,10 +52,10 @@ module FeideeUtils
45
52
  }.freeze
46
53
 
47
54
  IgnoredFields = [
48
- "userTradingEntityPOID", # WTF
55
+ "userTradingEntityPOID", # Foreign key to t_user.
49
56
  "_tempIconName", # Icon name in the app
50
57
  "usedCount", # Always 0.
51
- "clientID", # WTF
58
+ "clientID", # Always equal to poid.
52
59
  ].freeze
53
60
 
54
61
  define_accessors(FieldMappings)
@@ -59,6 +66,10 @@ module FeideeUtils
59
66
  2 => :project_root, # unkown
60
67
  })
61
68
 
69
+ def to_s
70
+ "#{name} (Category/#{poid})"
71
+ end
72
+
62
73
  # Schema
63
74
  # categoryPOID LONG NOT NULL
64
75
  # name varchar(100) NOT NULL
@@ -22,12 +22,14 @@ module FeideeUtils
22
22
  t_fund_trans
23
23
  t_module_stock_holding
24
24
  t_module_stock_tran
25
+ t_tradingEntity
26
+ t_user
25
27
  ).freeze
26
28
 
27
29
  attr_reader :sqlite_file
28
- attr_reader :platform, :sqlite_name, :sqlite_timestamp
30
+ attr_reader :platform, :ledger_name, :last_modified_at
29
31
  attr_reader :missing_tables
30
- attr_reader :namespaced
32
+ attr_reader :ledger
31
33
 
32
34
  def initialize(private_sqlite, strip = false)
33
35
  @sqlite_file = Database.feidee_to_sqlite(private_sqlite)
@@ -37,7 +39,8 @@ module FeideeUtils
37
39
  extract_metadata
38
40
  drop_unused_tables if strip
39
41
 
40
- @namespaced = Record.generate_namespaced_record_classes(self)
42
+ # TODO: make Ledger a first class object.
43
+ @ledger = Record.generate_namespaced_record_classes(self)
41
44
  end
42
45
 
43
46
  def sqlite_backup(dest_file_path)
@@ -51,8 +54,8 @@ module FeideeUtils
51
54
  end
52
55
 
53
56
  def validate_global_integrity
54
- @namespaced.constants.each do |const|
55
- @namespaced.const_get(const).validate_global_integrity if const != :Database
57
+ @ledger.constants.each do |const|
58
+ @ledger.const_get(const).validate_global_integrity if const != :Database
56
59
  end
57
60
  end
58
61
 
@@ -71,7 +74,10 @@ module FeideeUtils
71
74
 
72
75
  def drop_unused_tables
73
76
  useful_tables = Tables.values + PotentialUsefulTables
74
- useful_tables = (useful_tables + useful_tables.map do |x| self.class.trash_table_name(x) end).sort
77
+ useful_tables += useful_tables.map do |x|
78
+ self.class.trash_table_name(x)
79
+ end
80
+ useful_tables.sort!
75
81
 
76
82
  # TODO: Record all tables droped.
77
83
  (all_tables - useful_tables).each do |table|
@@ -85,14 +91,20 @@ module FeideeUtils
85
91
  end
86
92
 
87
93
  def extract_metadata
88
- @platform = self.execute("SELECT platform from #{Tables[:metadata]}")[0][0];
89
-
90
- @sqlite_name = self.get_first_row("SELECT accountBookName FROM #{Tables[:profile]};")[0];
91
-
92
- # This is not recorded in the database, so the lastest lastUpdateTime of all
93
- # transactions is chosen.
94
- timestamp = self.get_first_row("SELECT max(lastUpdateTime) FROM #{Tables[:transactions]};")[0]
95
- @sqlite_timestamp = timestamp == nil ? Time.at(0) : Time.at(timestamp / 1000)
94
+ @platform = self.execute(
95
+ "SELECT platform from #{Tables[:metadata]}"
96
+ )[0][0];
97
+
98
+ @ledger_name = self.get_first_row(
99
+ "SELECT accountBookName FROM #{Tables[:profile]};"
100
+ )[0];
101
+
102
+ # This is not recorded in the database, so the lastest lastUpdateTime of
103
+ # all transactions is chosen.
104
+ timestamp = self.get_first_row(
105
+ "SELECT max(lastUpdateTime) FROM #{Tables[:transactions]};"
106
+ )[0]
107
+ @last_modified_at = Time.at((timestamp or 0.0) / 1000)
96
108
  end
97
109
 
98
110
  class << self
@@ -103,13 +115,14 @@ module FeideeUtils
103
115
  Header = "SQLite format 3\0".force_encoding("binary")
104
116
  FeideeHeader_iOS = "%$^#&!@_@- -!F\xff\0".force_encoding('binary')
105
117
  FeideeHeader_Android = ("\0" * 13 + "F\xff\0").force_encoding("binary")
118
+ AllHeaders = [FeideeHeader_iOS, FeideeHeader_Android, Header]
106
119
 
107
120
  def feidee_to_sqlite(private_sqlite, sqlite_file = nil)
108
121
  # Discard the first a few bytes content.
109
122
  private_header = private_sqlite.read(Header.length)
110
123
 
111
- unless [FeideeHeader_iOS, FeideeHeader_Android, Header].include? private_header
112
- raise "Unexpected header #{private_header.inspect} in private sqlite file."
124
+ unless AllHeaders.include? private_header
125
+ raise "Unexpected private sqlite header #{private_header.inspect}."
113
126
  end
114
127
 
115
128
  # Write the rest to a tempfile.
@@ -123,7 +136,10 @@ module FeideeUtils
123
136
  end
124
137
 
125
138
  class << self
126
- NoDeleteSuffixTables = %w(account category tag tradingEntity transaction transaction_template)
139
+ NoDeleteSuffixTables = %w(
140
+ account category tag tradingEntity
141
+ transaction transaction_template
142
+ )
127
143
 
128
144
  def trash_table_name name
129
145
  NoDeleteSuffixTables.each do |core_name|
@@ -9,22 +9,25 @@ module FeideeUtils
9
9
  class Kbf
10
10
  DatabaseName = 'mymoney.sqlite'
11
11
 
12
- attr_reader :zipfile, :sqlite_db
12
+ attr_reader :zipfile, :db
13
13
 
14
14
  def initialize(input_stream)
15
- @zipfile = Zip::File.open_buffer(input_stream) do |zipfile|
15
+ Zip::File.open_buffer(input_stream) do |zipfile|
16
16
  zipfile.each do |entry|
17
17
  if entry.name == DatabaseName
18
18
  # Each call to get_input_stream will create a new stream
19
19
  @original_sqlite_db_entry = entry
20
- @sqlite_db = FeideeUtils::Database.new(entry.get_input_stream, true)
20
+ @db = FeideeUtils::Database.new(entry.get_input_stream, true)
21
21
  end
22
22
  end
23
23
  end
24
24
  end
25
25
 
26
26
  def extract_original_sqlite(dest_file_path = nil)
27
- FeideeUtils::Database.feidee_to_sqlite(@original_sqlite_db_entry.get_input_stream, dest_file_path)
27
+ FeideeUtils::Database.feidee_to_sqlite(
28
+ @original_sqlite_db_entry.get_input_stream,
29
+ dest_file_path
30
+ )
28
31
  end
29
32
 
30
33
  class << self
@@ -12,7 +12,9 @@ module FeideeUtils
12
12
  module ClassMethods
13
13
  def define_accessors field_mappings
14
14
  field_mappings.each do |name, key|
15
- raise "Accessor #{name} already exists in #{self.name}." if method_defined? name
15
+ if method_defined? name
16
+ raise "Accessor #{name} already exists in #{self.name}."
17
+ end
16
18
  define_method name do field[key] end
17
19
  end
18
20
  end
@@ -12,7 +12,8 @@ module FeideeUtils
12
12
  @child_classes.add(child_class)
13
13
  end
14
14
 
15
- # To use Record with different databases, generate a set of classes for each db
15
+ # To use Record with different databases, generate a set of classes for
16
+ # each db
16
17
  def generate_namespaced_record_classes(db)
17
18
  @child_classes ||= Set.new
18
19
  this = self
@@ -36,13 +36,17 @@ module FeideeUtils
36
36
  end
37
37
 
38
38
  def find_by_id(id)
39
- raw_result = database.query("SELECT * FROM #{self.table_name} WHERE #{self.id_field_name} = ?", id)
39
+ raw_result = database.query(
40
+ "SELECT * FROM #{self.table_name} WHERE #{self.id_field_name} = ?",
41
+ id
42
+ )
40
43
 
41
44
  raw_row = raw_result.next
42
45
  return nil if raw_row == nil
43
46
 
44
47
  if raw_result.next != nil
45
- raise "Getting more than one result with the same ID #{id} in table #{self.table_name}."
48
+ raise "Getting more than one result with the same ID #{id} " +
49
+ "in table #{self.table_name}."
46
50
  end
47
51
 
48
52
  self.new(raw_result.columns, raw_result.types, raw_row)
@@ -6,18 +6,21 @@ require 'feidee_utils/record/modified_record'
6
6
 
7
7
  module FeideeUtils
8
8
  # The implementation here is wired.
9
- # The goal is to create a class hierachy similar to ActiveRecord, where every table is represented by a
10
- # subclass of ActiveRecord::Base. Class methods, attribute accessors and almost all other functionalities
11
- # are provided by ActiveRecord::Base. For example, Base.all(), Base.find_by_id() are tied to a specific
12
- # table in a specific database.
13
- # The problem we are solving here is not the same as ActiveRecord. In ActiveRecord, the databases
14
- # are static, i.e. they won't be changed at runtime. Meanwhile, in our case, new databases can be created
15
- # at runtime, when a new KBF backup file is uploaded. Furthermore, multiple instances of different databases
16
- # can co-exist at the same time. To provide the same syntax as ActiveRecord, a standalone "Base" class has
17
- # to be created for each database.
18
- # In our implementation, when a new database is created, a subclass of Record is created in a new namepsace.
19
- # For each subclass of Record, a new subclass is copied to the new namespace, with it's database method
20
- # overloaded.
9
+ # The goal is to create a class hierachy similar to ActiveRecord, where every
10
+ # table is represented by a subclass of ActiveRecord::Base. Class methods,
11
+ # attribute accessors and almost all other functionalities are provided by
12
+ # ActiveRecord::Base. For example, Base.all(), Base.find_by_id() are tied to
13
+ # a specific table in a specific database.
14
+ # The problem we are solving here is not the same as ActiveRecord. In
15
+ # ActiveRecord, the databases are static, i.e. they won't be changed at
16
+ # runtime. Meanwhile, in our case, new databases can be created at runtime,
17
+ # when a new KBF backup file is uploaded. Furthermore, multiple instances of
18
+ # different databases can co-exist at the same time. To provide the same
19
+ # syntax as ActiveRecord, a standalone "Base" class has to be created for each
20
+ # database.
21
+ # In our implementation, when a new database is created, a subclass of Record
22
+ # is created in a new namepsace. For each subclass of Record, a new subclass
23
+ # is copied to the new namespace, with it's database method overloaded.
21
24
  class Record
22
25
  attr_reader :field, :field_type
23
26
 
@@ -25,30 +25,37 @@ module FeideeUtils
25
25
  unless buyer_account_poid != 0 and seller_account_poid != 0
26
26
  raise TransferLackBuyerOrSellerException,
27
27
  "Both buyer and seller should be set in a transfer. " +
28
- "Buyer account POID: #{buyer_account_poid}. Seller account POID: #{seller_account_poid}.\n" +
28
+ "Buyer account POID: #{buyer_account_poid}. " +
29
+ "Seller account POID: #{seller_account_poid}.\n" +
29
30
  inspect
30
31
  end
31
32
  unless buyer_category_poid == 0 and seller_category_poid == 0
32
33
  raise TransferWithCategoryException,
33
34
  "Neither buyer or seller category should be set in a transfer. " +
34
- "Buyer category POID: #{buyer_category_poid}. Seller category POID: #{seller_category_poid}.\n" +
35
+ "Buyer category POID: #{buyer_category_poid}. " +
36
+ "Seller category POID: #{seller_category_poid}.\n" +
35
37
  inspect
36
38
  end
37
39
  else
38
40
  unless (buyer_account_poid == 0) ^ (seller_account_poid == 0)
39
41
  raise InconsistentBuyerAndSellerException,
40
- "Exactly one of buyer and seller should be set in a non-transfer transaction. " +
41
- "Buyer account POID: #{buyer_account_poid}. Seller account POID: #{seller_account_poid}.\n" +
42
+ "Exactly one of buyer and seller should be set in a non-transfer " +
43
+ "transaction. " +
44
+ "Buyer account POID: #{buyer_account_poid}. " +
45
+ "Seller account POID: #{seller_account_poid}.\n" +
42
46
  inspect
43
47
  end
44
48
 
45
- # We could enforce that category is set to the matching party (buyer or seller) of account.
46
- # However the implementation could handle all situations, as long as only one of them is set.
47
- # Thus no extra check is done here.
49
+ # We could enforce that category is set to the matching party (buyer or
50
+ # seller) of account. However the implementation could handle all
51
+ # situations, as long as only one of them is set. Thus no extra check is
52
+ # done here.
48
53
  unless buyer_category_poid == 0 or seller_category_poid == 0
49
54
  raise InconsistentCategoryException,
50
- "Only one of buyer and seller category should be set in a non-transfer transaction. "
51
- "Buyer category POID: #{buyer_category_poid}. Seller category POID: #{seller_category_poid}.\n" +
55
+ "Only one of buyer and seller category should be set in a " +
56
+ "non-transfer transaction. " +
57
+ "Buyer category POID: #{buyer_category_poid}. " +
58
+ "Seller category POID: #{seller_category_poid}.\n" +
52
59
  inspect
53
60
  end
54
61
  end
@@ -56,7 +63,8 @@ module FeideeUtils
56
63
  unless raw_buyer_deduction == raw_seller_addition
57
64
  raise InconsistentAmountException,
58
65
  "Buyer and seller should have the same amount set. " +
59
- "Buyer deduction: #{buyer_deduction}, seller_addition: #{seller_addition}.\n" +
66
+ "Buyer deduction: #{buyer_deduction}. "+
67
+ "Seller_addition: #{seller_addition}.\n" +
60
68
  inspect
61
69
  end
62
70
  end
@@ -78,8 +86,10 @@ module FeideeUtils
78
86
  valid = true
79
87
  valid &&= transfers[0] != nil
80
88
  valid &&= transfers[1] != nil
81
- valid &&= transfers[0].buyer_account_poid == transfers[1].buyer_account_poid
82
- valid &&= transfers[0].seller_account_poid == transfers[1].seller_account_poid
89
+ valid &&=
90
+ transfers[0].buyer_account_poid == transfers[1].buyer_account_poid
91
+ valid &&=
92
+ transfers[0].seller_account_poid == transfers[1].seller_account_poid
83
93
  raise TransfersNotPaired.new([uuid] + transfers) unless valid
84
94
  end
85
95
  end
@@ -102,13 +112,13 @@ module FeideeUtils
102
112
  define_entity_accessor :seller_account_poid, :account
103
113
 
104
114
  IgnoredFields = [
105
- "creatorTradingEntityPOID",
106
- "modifierTradingEntityPOID",
107
- "ffrom", # The signature of the App writting this transaction.
115
+ "creatorTradingEntityPOID", # Foreign key to to_user.
116
+ "modifierTradingEntityPOID", # Foreign key to to_user.
117
+ "ffrom", # The signature of the App.
108
118
  "photoName", # To be added
109
119
  "photoNeedUpload", # To be added
110
- "relationUnitPOID", # WTF
111
- "clientID", # WTF
120
+ "relationUnitPOID", # Foreign key to t_tradingEntity: Merchant.
121
+ "clientID", # Always 0 for ealier versions, or equal to poid.
112
122
  "FSourceKey", # WTF
113
123
  ].freeze
114
124
 
@@ -190,6 +200,17 @@ module FeideeUtils
190
200
  end
191
201
  end
192
202
 
203
+ def to_s
204
+ if is_transfer?
205
+ "Transfer #{amount.to_f} from #{buyer_account} to #{seller_account}"
206
+ elsif is_initial_balance?
207
+ "Balance of #{revised_account} set to #{revised_amount.to_f}"
208
+ else
209
+ "Transaction of #{revised_amount.to_f} on #{revised_account} in " +
210
+ "#{category}"
211
+ end
212
+ end
213
+
193
214
  class ModifiedTransaction < ModifiedRecord
194
215
  define_custom_methods([
195
216
  :created_at,
@@ -1,3 +1,3 @@
1
1
  module FeideeUtils
2
- VERSION = '0.0.4.3'
2
+ VERSION = '0.0.4.4'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: feidee_utils
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4.3
4
+ version: 0.0.4.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Liqing Muyi