hws-transactions 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1205ba882568d97a1a6443a6a3d3dbcf885e7449c985658e29da4b81d2ac8527
4
+ data.tar.gz: 5e28853389dc197feb0fdf12d417e82792f3f91f971e8d4734e7b686882802f0
5
+ SHA512:
6
+ metadata.gz: a9d4888fd58c53a6e7cdb3a56f073b1cf47f2a7e84b2e88ba1dee7a5e07be83924b445540250f32da552fafc3a489e180c422570a4fbc61b18abb97c0524eafd
7
+ data.tar.gz: bbd70d2ffcd3302bdb34177ecb813a0d7b151db7251deb74a73700bee172e5a32e84f14cedf3eace725aee769e0ec743c8609fc36759b79049da76adfc50088d
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,21 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.5
3
+ NewCops: enable
4
+
5
+ Style/StringLiterals:
6
+ Enabled: true
7
+ EnforcedStyle: single_quotes
8
+
9
+ Style/StringLiteralsInInterpolation:
10
+ Enabled: true
11
+ EnforcedStyle: double_quotes
12
+
13
+ Style/ClassAndModuleChildren:
14
+ Enabled: true
15
+ EnforcedStyle: compact
16
+
17
+ Style/RedundantSelf:
18
+ Enabled: false
19
+
20
+ Layout/LineLength:
21
+ Max: 120
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2021-11-18
4
+
5
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in hws-tansactions.gemspec
6
+ gemspec
7
+
8
+ gem 'rake', '~> 13.0'
9
+
10
+ gem 'rspec', '~> 3.0'
11
+
12
+ gem 'rubocop', '~> 1.7'
13
+
14
+ gem 'lsuuid'
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Karthick
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # Hws::Transactions
2
+
3
+ **Financial Primitive - Transaction**
4
+
5
+ Any financial usecase will have the need to maintain the list of transactions performed. This is the primitive library to manage them.
6
+ There are two entities - TransactionGroup and TransactionEntry.
7
+
8
+ - TransactionGroup - This can be used to dictate the type of transaction and provide the scheme for the entries.
9
+ - TransactionEntry - These are the actual transactions that occur in the usecase.
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ ```ruby
16
+ gem 'hws-transactions'
17
+ ```
18
+
19
+ And then execute:
20
+
21
+ $ bundle install
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install hws-transactions
26
+
27
+ ## Usage
28
+
29
+ ### Generate migration file:
30
+ The gem needs two tables transaction_groups and transaction_entries. They can be created by running the following commands.
31
+ ```
32
+ $ bundle exec rails generate hws:transaction:install
33
+ $ bundle exec rake db:migrate
34
+ ```
35
+
36
+ ### Create a transaction group
37
+ ```ruby
38
+ Hws::Transactions.create_group('Vendor Payments', 'Track Vendor Payments Sent', ['status', 'vendor_acknowledged'], ['vendor_name', 'invoice_number', 'payment_date'])
39
+ => #<Hws::Transactions::Models::TransactionGroup id: "619f676d-085a-f890-9f73-6b6821ea1cad", name: "Vendor Payments", description: "Track Vendor Payments Sent", tags: {"mutable_tags"=>["status", "vendor_acknowledged"], "immutable_tags"=>["vendor_name", "invoice_number", "payment_date"]}, created_at: "2021-11-25 10:37:33", updated_at: "2021-11-25 10:37:33"> # returns the TransactionGroup object which was created
40
+ ```
41
+
42
+ ### Get a transaction group
43
+ ```ruby
44
+ Hws::Transactions.get_group("619f676d-085a-f890-9f73-6b6821ea1cad")
45
+ => #<Hws::Transactions::Models::TransactionGroup id: "619f676d-085a-f890-9f73-6b6821ea1cad", name: "Vendor Payments", description: "Track Vendor Payments Sent", tags: {"mutable_tags"=>["status", "vendor_acknowledged"], "immutable_tags"=>["vendor_name", "invoice_number", "payment_date"]}, created_at: "2021-11-25 10:37:33", updated_at: "2021-11-25 10:37:33">
46
+ ```
47
+
48
+ ### Add a transaction entry
49
+ ```ruby
50
+ Hws::Transactions.add_entry("619f676d-085a-f890-9f73-6b6821ea1cad", 15.25, Time.now, { mutable_tags: {status: 'SENT', 'vendor_acknowledged': true}, immutable_tags: {vendor_name: 'ACME Inc', invoice_number: 'DEMO123', payment_date: '2021-11-25'}})
51
+ => #<Hws::Transactions::Models::TransactionEntry id: "619f6e3d-2090-e0a0-668d-d4da170b77c0", transaction_group_id: "619f676d-085a-f890-9f73-6b6821ea1cad", value: 15, txn_time: "2021-11-25 11:06:37", immutable_tags: {"vendor_name"=>"ACME Inc", "invoice_number"=>"DEMO123", "payment_date"=>"2021-11-25"}, mutable_tags: {"status"=>"SENT", "vendor_acknowledged"=>false}, created_at: "2021-11-25 11:06:37", updated_at: "2021-11-25 11:06:37">
52
+ ```
53
+
54
+ ### Update a transaction entry
55
+ ```ruby
56
+ Hws::Transactions.update_entry("619f676d-085a-f890-9f73-6b6821ea1cad", "619f6e3d-2090-e0a0-668d-d4da170b77c0", {status: 'COMPLETED', 'vendor_acknowledged': true})
57
+ => true # Update successful
58
+ ```
59
+
60
+ ### Fetch a transaction entry list
61
+ ```ruby
62
+ Hws::Transactions.list_entries("619f676d-085a-f890-9f73-6b6821ea1cad", {immutable_tags: {invoice_number: 'DEMO123'}}, {page_size: 10})
63
+ => #<ActiveRecord::AssociationRelation [#<Hws::Transactions::Models::TransactionEntry id: "619f6e3d-2090-e0a0-668d-d4da170b77c0", transaction_group_id: "619f676d-085a-f890-9f73-6b6821ea1cad", value: 15, txn_time: "2021-11-25 11:06:37", immutable_tags: {"vendor_name"=>"ACME Inc", "payment_date"=>"2021-11-25", "invoice_number"=>"DEMO123"}, mutable_tags: {"status"=>"COMPLETED", "vendor_acknowledged"=>true}, created_at: "2021-11-25 11:06:37", updated_at: "2021-11-25 11:07:04">]>
64
+
65
+ ```
66
+
67
+ ## Contributing
68
+
69
+ Bug reports and pull requests are welcome on GitHub at https://github.com/hwslabs/hws-transactions-ruby.
70
+
71
+ ## License
72
+
73
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require 'rubocop/rake_task'
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "hws/tansactions"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Adds necessary ActiveRecord migrations to the rails application
3
+
4
+ Example:
5
+ bundle exec rails generate hws:transactions:install
6
+
7
+ This will create:
8
+ db/migrate/<migration_number>_create_transaction_group_and_entries.rb
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators/migration'
4
+ require 'rails/generators/active_record'
5
+
6
+ module Hws
7
+ module Transactions # :nodoc:
8
+ module Generators # :nodoc:
9
+ class InstallGenerator < ::Rails::Generators::Base # :nodoc:
10
+ include Rails::Generators::Migration
11
+ source_root File.expand_path('templates', __dir__)
12
+
13
+ def self.next_migration_number(path)
14
+ ActiveRecord::Generators::Base.next_migration_number(path)
15
+ end
16
+
17
+ desc 'copying migrations to db/migrate'
18
+ def copy_migrations
19
+ migration_template 'create_transaction_group_and_entries.rb.erb',
20
+ 'db/migrate/create_hws_transaction_tables.rb'
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,30 @@
1
+ class <%= @migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def self.up
3
+ enable_extension 'pgcrypto' unless extensions.include?('pgcrypto')
4
+ create_table :transaction_groups, id: :uuid, force: true do |t|
5
+ t.string :name
6
+ t.text :description
7
+ t.jsonb :tags
8
+ t.timestamps
9
+ end
10
+
11
+ create_table :transaction_entries, id: :uuid, force: true do |t|
12
+ t.uuid :transaction_group_id
13
+ t.integer :value
14
+ t.datetime :txn_time
15
+ t.jsonb :immutable_tags
16
+ t.jsonb :mutable_tags
17
+
18
+ t.timestamps
19
+ end
20
+
21
+ add_index :transaction_entries, :immutable_tags, using: :gin
22
+ add_index :transaction_entries, :mutable_tags, using: :gin
23
+ add_index :transaction_entries, :txn_time
24
+ end
25
+
26
+ def self.down
27
+ drop_table :transaction_groups
28
+ drop_table :transaction_entries
29
+ end
30
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hws::Helpers # :nodoc:
4
+ class Query # :nodoc:
5
+ class << self
6
+ def construct_query(base, filter_hash)
7
+ mut = filter_hash.key?('mutable_tags') ? filter_hash.delete('mutable_tags') : {}
8
+ immut = filter_hash.key?('immutable_tags') ? filter_hash.delete('immutable_tags') : {}
9
+
10
+ query = base.where(filter_hash)
11
+ query = query_jsonb(query, 'mutable_tags', mut)
12
+ query_jsonb(query, 'immutable_tags', immut)
13
+ end
14
+
15
+ def query_jsonb(base, column_name, filter_hash)
16
+ return base if filter_hash.empty?
17
+
18
+ base.where("#{column_name} @> ?", filter_hash.to_json)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hws::Transactions::Models
4
+ class Base < ActiveRecord::Base # :nodoc:
5
+ self.abstract_class = true
6
+
7
+ before_create :set_uuid
8
+
9
+ def set_uuid
10
+ self.id = LSUUID.generate if self.id.blank?
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'hws-transactions/models/base'
4
+
5
+ module Hws::Transactions::Models
6
+ class TransactionEntry < Base
7
+ belongs_to :transaction_group, class_name: 'TransactionGroup', foreign_key: 'transaction_group_id'
8
+ end
9
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'hws-transactions/models/base'
4
+ require 'hws-transactions/helpers/query'
5
+
6
+ module Hws::Transactions::Models
7
+ class TransactionGroup < Base
8
+ has_many :transaction_entries, foreign_key: 'transaction_group_id'
9
+
10
+ DEFAULT_LIMIT = 10
11
+
12
+ # tags: hash of the form mentioned below.
13
+ # {
14
+ # 'mutable_tags': {mutable_tag_key1: m_tag_val1, mutable_tag_key2: m_tag_val2},
15
+ # 'immutable_tags': {immutable_tag_key1: i_tag_val1, immutable_tag_key2: i_tag_val2}
16
+ # }
17
+ def add_entry(value, txn_time, tags = {})
18
+ tags = validate_and_sanitize_tags(tags)
19
+
20
+ self.transaction_entries.create(
21
+ value: value,
22
+ txn_time: txn_time,
23
+ mutable_tags: tags['mutable_tags'],
24
+ immutable_tags: tags['immutable_tags']
25
+ )
26
+ end
27
+
28
+ # only mutable tags are allowed to be updated
29
+ # TODO: Optimistic locking?
30
+ def update_entry(entry_id, mutable_tags)
31
+ entry = self.transaction_entries.find(entry_id)
32
+
33
+ unless mutable_tags.keys.all? { |key| self.mutable_tags.include?(key) }
34
+ raise 'Invalid mutable tags present in update_entry request.'
35
+ end
36
+
37
+ entry.update!(mutable_tags: entry.mutable_tags.merge(mutable_tags))
38
+ end
39
+
40
+ # filters struture:
41
+ # {
42
+ # 'col1' => 'val1',
43
+ # 'col2' => 'val2',
44
+ # 'mut1' => {
45
+ # 'jsonf1' => 'val1',
46
+ # 'jsonf2' => {'nested_jsonf3' => 'val2'}
47
+ # }
48
+ # }
49
+ # pagination_context structure:
50
+ # {
51
+ # 'last_entry': '',
52
+ # 'page_size': 10
53
+ # }
54
+ def list_entries(filters = {}, page_context = {})
55
+ filtered_query = if filters.empty?
56
+ self.transaction_entries
57
+ else
58
+ Hws::Helpers::Query.construct_query(self.transaction_entries, filters)
59
+ end
60
+
61
+ unless page_context['last_entry'].nil?
62
+ filtered_query = filtered_query.where('id > ?', page_context['last_entry'])
63
+ end
64
+ filtered_query.order('id').limit(page_context['page_size'] || DEFAULT_LIMIT)
65
+ end
66
+
67
+ def mutable_tags
68
+ @m_tags ||= self.tags['mutable_tags'].to_set
69
+ @m_tags
70
+ end
71
+
72
+ def immutable_tags
73
+ @i_tags ||= self.tags['immutable_tags'].to_set
74
+ @i_tags
75
+ end
76
+
77
+ private
78
+
79
+ def validate_and_sanitize_tags(tags)
80
+ tags['mutable_tags'] = {} unless tags.key? 'mutable_tags'
81
+ tags['immutable_tags'] = {} unless tags.key? 'immutable_tags'
82
+
83
+ unless tags['mutable_tags'].keys.all? { |key| self.mutable_tags.include?(key) }
84
+ raise 'Invalid mutable tags present in add_entry request.'
85
+ end
86
+
87
+ unless tags['immutable_tags'].keys.all? { |key| self.immutable_tags.include?(key) }
88
+ raise 'Invalid immutable_tags tags present in add_entry request.'
89
+ end
90
+
91
+ tags
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,62 @@
1
+ # rubocop:disable Naming/FileName
2
+ # frozen_string_literal: true
3
+
4
+ module Hws # :nodoc:
5
+ module Transactions
6
+ # name: name of the journal
7
+ # description: description of the journal
8
+ # mutable_tags: []
9
+ # immutable_tags: []
10
+ def self.create_group(name, description, mutable_tags, immutable_tags)
11
+ ::Hws::Transactions::Models::TransactionGroup.create(name: name, description: description,
12
+ tags: { mutable_tags: mutable_tags, immutable_tags: immutable_tags }.with_indifferent_access)
13
+ end
14
+
15
+ def self.get_group(id)
16
+ ::Hws::Transactions::Models::TransactionGroup.find(id)
17
+ end
18
+
19
+ # tags: hash of the form mentioned below.
20
+ # {
21
+ # 'mutable_tags' => {mutable_tag_key1 => m_tag_val1, mutable_tag_key2 => m_tag_val2},
22
+ # 'immutable_tags' => {immutable_tag_key1 => i_tag_val1, immutable_tag_key2 => i_tag_val2}
23
+ # }
24
+ def self.add_entry(transaction_group_id, value, txn_time, tags)
25
+ self.get_group(transaction_group_id).try(:add_entry, value, txn_time, tags.with_indifferent_access)
26
+ end
27
+
28
+ def self.update_entry(transaction_group_id, entry_id, mutable_tags)
29
+ self.get_group(transaction_group_id).try(:update_entry, entry_id, mutable_tags.with_indifferent_access)
30
+ end
31
+
32
+ # filters: hash of the form mentioned below.
33
+ # {
34
+ # 'col1' => 'val1',
35
+ # 'col2' => 'val2',
36
+ # 'mutable_tags' => {
37
+ # 'jsonf1' => 'val1',
38
+ # 'jsonf2' => {'nested_jsonf3' => 'val2'}
39
+ # }
40
+ # 'immutable_tags' => {
41
+ # 'jsonf1' => 'val1',
42
+ # 'jsonf2' => {'nested_jsonf3' => 'val2'}
43
+ # }
44
+ # }
45
+ # pagination_context: hash of the form mentioned below.
46
+ # {
47
+ # 'last_entry': '',
48
+ # 'page_size': 10
49
+ # }
50
+ def self.list_entries(transaction_group_id, filters, page_context)
51
+ self.get_group(transaction_group_id).try(:list_entries, filters.with_indifferent_access, page_context.with_indifferent_access)
52
+ end
53
+
54
+ module Models # :nodoc:
55
+ end
56
+ end
57
+ end
58
+
59
+ require 'hws-transactions/models/transaction_entry'
60
+ require 'hws-transactions/models/transaction_group'
61
+
62
+ # rubocop:enable Naming/FileName
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hws-transactions
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Hypto Engineering Team
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-11-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: lsuuid
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Experimental transaction primitive
28
+ email:
29
+ - engineering@hypto.in
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".rspec"
35
+ - ".rubocop.yml"
36
+ - CHANGELOG.md
37
+ - Gemfile
38
+ - LICENSE.txt
39
+ - README.md
40
+ - Rakefile
41
+ - bin/console
42
+ - bin/setup
43
+ - lib/generators/hws/transactions/USAGE
44
+ - lib/generators/hws/transactions/install_generator.rb
45
+ - lib/generators/hws/transactions/templates/create_transaction_group_and_entries.rb.erb
46
+ - lib/hws-transactions.rb
47
+ - lib/hws-transactions/helpers/query.rb
48
+ - lib/hws-transactions/models/base.rb
49
+ - lib/hws-transactions/models/transaction_entry.rb
50
+ - lib/hws-transactions/models/transaction_group.rb
51
+ homepage: https://github.com/hwslabs/hws-transactions-ruby
52
+ licenses:
53
+ - MIT
54
+ metadata:
55
+ homepage_uri: https://github.com/hwslabs/hws-transactions-ruby
56
+ source_code_uri: https://github.com/hwslabs/hws-transactions-ruby
57
+ changelog_uri: https://github.com/hwslabs/hws-transactions-ruby/blob/main/CHANGELOG.md
58
+ post_install_message:
59
+ rdoc_options: []
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: 2.5.0
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ requirements: []
73
+ rubygems_version: 3.0.3
74
+ signing_key:
75
+ specification_version: 4
76
+ summary: Experimental transaction primitive
77
+ test_files: []