subledger 0.7.7
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.
- data/.gitignore +9 -0
- data/LICENSE.txt +12 -0
- data/README.md +136 -0
- data/Rakefile +5 -0
- data/bin/subledger +6 -0
- data/lib/subledger/actor.rb +32 -0
- data/lib/subledger/collection_name.rb +25 -0
- data/lib/subledger/domain/account.rb +168 -0
- data/lib/subledger/domain/balance.rb +102 -0
- data/lib/subledger/domain/book.rb +111 -0
- data/lib/subledger/domain/category.rb +157 -0
- data/lib/subledger/domain/control.rb +180 -0
- data/lib/subledger/domain/formatters.rb +31 -0
- data/lib/subledger/domain/identity.rb +159 -0
- data/lib/subledger/domain/journal_entry.rb +293 -0
- data/lib/subledger/domain/key.rb +113 -0
- data/lib/subledger/domain/line.rb +272 -0
- data/lib/subledger/domain/org.rb +110 -0
- data/lib/subledger/domain/report.rb +247 -0
- data/lib/subledger/domain/report_rendering.rb +233 -0
- data/lib/subledger/domain/roles/activatable.rb +11 -0
- data/lib/subledger/domain/roles/archivable.rb +11 -0
- data/lib/subledger/domain/roles/attributable.rb +14 -0
- data/lib/subledger/domain/roles/collectable.rb +175 -0
- data/lib/subledger/domain/roles/creatable.rb +58 -0
- data/lib/subledger/domain/roles/describable.rb +33 -0
- data/lib/subledger/domain/roles/describable_report_rendering.rb +50 -0
- data/lib/subledger/domain/roles/identifiable.rb +15 -0
- data/lib/subledger/domain/roles/postable.rb +54 -0
- data/lib/subledger/domain/roles/progressable.rb +11 -0
- data/lib/subledger/domain/roles/readable.rb +34 -0
- data/lib/subledger/domain/roles/restable.rb +69 -0
- data/lib/subledger/domain/roles/storable.rb +30 -0
- data/lib/subledger/domain/roles/timeable.rb +18 -0
- data/lib/subledger/domain/roles/updatable.rb +35 -0
- data/lib/subledger/domain/roles/versionable.rb +35 -0
- data/lib/subledger/domain/roles.rb +16 -0
- data/lib/subledger/domain/value/credit.rb +16 -0
- data/lib/subledger/domain/value/debit.rb +16 -0
- data/lib/subledger/domain/value/zero.rb +24 -0
- data/lib/subledger/domain/value.rb +111 -0
- data/lib/subledger/domain.rb +95 -0
- data/lib/subledger/exception_handler.rb +65 -0
- data/lib/subledger/interface/client.rb +295 -0
- data/lib/subledger/interface/dispatcher.rb +20 -0
- data/lib/subledger/interface.rb +2 -0
- data/lib/subledger/path.rb +106 -0
- data/lib/subledger/rest.rb +128 -0
- data/lib/subledger/server.rb +3 -0
- data/lib/subledger/store/api/errors.rb +95 -0
- data/lib/subledger/store/api/roles/activate.rb +21 -0
- data/lib/subledger/store/api/roles/archive.rb +21 -0
- data/lib/subledger/store/api/roles/balance.rb +39 -0
- data/lib/subledger/store/api/roles/categories.rb +51 -0
- data/lib/subledger/store/api/roles/collect.rb +58 -0
- data/lib/subledger/store/api/roles/create.rb +26 -0
- data/lib/subledger/store/api/roles/create_and_post.rb +35 -0
- data/lib/subledger/store/api/roles/create_identity.rb +39 -0
- data/lib/subledger/store/api/roles/create_line.rb +24 -0
- data/lib/subledger/store/api/roles/first_and_last_line.rb +31 -0
- data/lib/subledger/store/api/roles/post.rb +25 -0
- data/lib/subledger/store/api/roles/progress.rb +21 -0
- data/lib/subledger/store/api/roles/read.rb +19 -0
- data/lib/subledger/store/api/roles/reports.rb +77 -0
- data/lib/subledger/store/api/roles/update.rb +24 -0
- data/lib/subledger/store/api/store.rb +103 -0
- data/lib/subledger/store/api.rb +20 -0
- data/lib/subledger/store.rb +236 -0
- data/lib/subledger/supervisor.rb +21 -0
- data/lib/subledger/uuid.rb +52 -0
- data/lib/subledger/version.rb +4 -0
- data/lib/subledger.rb +234 -0
- data/spec/spec_helper.rb +77 -0
- data/spec/subledger_account_spec.rb +354 -0
- data/spec/subledger_book_spec.rb +130 -0
- data/spec/subledger_category_spec.rb +203 -0
- data/spec/subledger_control_spec.rb +43 -0
- data/spec/subledger_identity_spec.rb +47 -0
- data/spec/subledger_journal_entry_spec.rb +417 -0
- data/spec/subledger_key_spec.rb +43 -0
- data/spec/subledger_org_spec.rb +68 -0
- data/spec/subledger_report_spec.rb +295 -0
- data/spec/subledger_spec.rb +101 -0
- data/subledger.gemspec +52 -0
- metadata +205 -0
| @@ -0,0 +1,25 @@ | |
| 1 | 
            +
            module Subledger
         | 
| 2 | 
            +
              module Store
         | 
| 3 | 
            +
                module Api
         | 
| 4 | 
            +
                  module Post
         | 
| 5 | 
            +
                    def post postable_journal_entry
         | 
| 6 | 
            +
                      path = Path.for_entity :anchor => postable_journal_entry
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                      begin
         | 
| 9 | 
            +
                        json_body = http.post do |req|
         | 
| 10 | 
            +
                                      req.url path
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                                      unless postable_journal_entry.post_delay.zero?
         | 
| 13 | 
            +
                                        req.headers['X-Subledger-Post-Slowly'] = 'true'
         | 
| 14 | 
            +
                                      end
         | 
| 15 | 
            +
                                    end.body
         | 
| 16 | 
            +
                      rescue Exception => e
         | 
| 17 | 
            +
                        raise PostError, "Cannot post #{postable_journal_entry}: #{e}"
         | 
| 18 | 
            +
                      end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                      new_or_initialize json_body, postable_journal_entry
         | 
| 21 | 
            +
                    end
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
            end
         | 
| @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            module Subledger
         | 
| 2 | 
            +
              module Store
         | 
| 3 | 
            +
                module Api
         | 
| 4 | 
            +
                  module Progress
         | 
| 5 | 
            +
                    def progress progressable
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                      path = Path.for_entity( :anchor => progressable ) + '/progress'
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                      begin
         | 
| 10 | 
            +
                        response_hash = parse_json(
         | 
| 11 | 
            +
                                          http.get( path ).body )
         | 
| 12 | 
            +
                      rescue Exception => e
         | 
| 13 | 
            +
                        raise ProgressError, "Cannot progress #{progressable}: #{e}"
         | 
| 14 | 
            +
                      end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                      response_hash['progress']['percentage'].to_i
         | 
| 17 | 
            +
                    end
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
            end
         | 
| @@ -0,0 +1,19 @@ | |
| 1 | 
            +
            module Subledger
         | 
| 2 | 
            +
              module Store
         | 
| 3 | 
            +
                module Api
         | 
| 4 | 
            +
                  module Read
         | 
| 5 | 
            +
                    def read readable
         | 
| 6 | 
            +
                      path = Path.for_entity :anchor => readable
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                      begin
         | 
| 9 | 
            +
                        json_body = http.get( path ).body
         | 
| 10 | 
            +
                      rescue Exception => e
         | 
| 11 | 
            +
                        raise ReadError, "Cannot read #{readable}: #{e}"
         | 
| 12 | 
            +
                      end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                      new_or_initialize json_body, readable
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
            end
         | 
| @@ -0,0 +1,77 @@ | |
| 1 | 
            +
            module Subledger
         | 
| 2 | 
            +
              module Store
         | 
| 3 | 
            +
                module Api
         | 
| 4 | 
            +
                  module Reports
         | 
| 5 | 
            +
                    def attach_category_to_report args
         | 
| 6 | 
            +
                      report   = args[:report]
         | 
| 7 | 
            +
                      category = args[:category]
         | 
| 8 | 
            +
                      parent   = args[:parent]
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                      path = Path.for_entity( :anchor => report ) + '/attach'
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                      attach_post_hash = { 'category' => category.id }
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                      attach_post_hash['parent'] = parent.id unless parent.nil?
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                      begin
         | 
| 17 | 
            +
                        json_body = http.post do |req|
         | 
| 18 | 
            +
                                      req.url    path
         | 
| 19 | 
            +
                                      req.body = attach_post_hash
         | 
| 20 | 
            +
                                    end.body
         | 
| 21 | 
            +
                      rescue Exception => e
         | 
| 22 | 
            +
                        raise AttachError, "Cannot attach #{category}: #{e}"
         | 
| 23 | 
            +
                      end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                      category
         | 
| 26 | 
            +
                    end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                    def detach_category_from_report args
         | 
| 29 | 
            +
                      category = args[:category]
         | 
| 30 | 
            +
                      report   = args[:report]
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                      path = Path.for_entity( :anchor => report ) + '/detach'
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                      detach_post_hash = { 'category' => category.id }
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                      begin
         | 
| 37 | 
            +
                        json_body = http.post do |req|
         | 
| 38 | 
            +
                                      req.url    path
         | 
| 39 | 
            +
                                      req.body = detach_post_hash
         | 
| 40 | 
            +
                                    end.body
         | 
| 41 | 
            +
                      rescue Exception => e
         | 
| 42 | 
            +
                        raise DetachError, "Cannot detach #{category}: #{e}"
         | 
| 43 | 
            +
                      end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                      category
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                    def render args
         | 
| 49 | 
            +
                      at = args[:at].iso8601
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                      building_report_rendering = args[:building_report_rendering]
         | 
| 52 | 
            +
                      report                    = building_report_rendering.report
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                      path = Path.for_entity( :anchor => report ) + "/render?at=#{at}"
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                      begin
         | 
| 57 | 
            +
                        json_body = http.post do |req|
         | 
| 58 | 
            +
                                                req.url path
         | 
| 59 | 
            +
                                              end.body
         | 
| 60 | 
            +
                      rescue Exception => e
         | 
| 61 | 
            +
                        raise ReportError, "Cannot render #{report}: #{e}"
         | 
| 62 | 
            +
                      end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                      new_or_initialize json_body, building_report_rendering
         | 
| 65 | 
            +
                    end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                    def collect_categories_for_report report, &block
         | 
| 68 | 
            +
                      raise ReportError, 'report#categories is not yet implemented'
         | 
| 69 | 
            +
                    end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                    def report_structure_for report, &block
         | 
| 72 | 
            +
                      raise ReportError, 'report#structure is not yet implemented'
         | 
| 73 | 
            +
                    end
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
              end
         | 
| 77 | 
            +
            end
         | 
| @@ -0,0 +1,24 @@ | |
| 1 | 
            +
            module Subledger
         | 
| 2 | 
            +
              module Store
         | 
| 3 | 
            +
                module Api
         | 
| 4 | 
            +
                  module Update
         | 
| 5 | 
            +
                    def update updatable
         | 
| 6 | 
            +
                      path = Path.for_entity :anchor => updatable
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                      begin
         | 
| 9 | 
            +
                        json_body = http.patch do |req|
         | 
| 10 | 
            +
                                      req.url    path
         | 
| 11 | 
            +
                                      req.body = updatable.patch_hash
         | 
| 12 | 
            +
                                    end.body
         | 
| 13 | 
            +
                      rescue UpdateConflictError => e
         | 
| 14 | 
            +
                        raise e
         | 
| 15 | 
            +
                      rescue Exception => e
         | 
| 16 | 
            +
                        raise UpdateError, "Cannot update #{updatable}: #{e}"
         | 
| 17 | 
            +
                      end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                      new_or_initialize json_body, updatable
         | 
| 20 | 
            +
                    end
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
            end
         | 
| @@ -0,0 +1,103 @@ | |
| 1 | 
            +
            module Subledger
         | 
| 2 | 
            +
              module Store
         | 
| 3 | 
            +
                module Api
         | 
| 4 | 
            +
                  class Store
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                    include Subledger::Store
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                    include Balance
         | 
| 9 | 
            +
                    include Categories
         | 
| 10 | 
            +
                    include Collect
         | 
| 11 | 
            +
                    include Create
         | 
| 12 | 
            +
                    include CreateAndPost
         | 
| 13 | 
            +
                    include CreateLine
         | 
| 14 | 
            +
                    include FirstAndLastLine
         | 
| 15 | 
            +
                    include Post
         | 
| 16 | 
            +
                    include Progress
         | 
| 17 | 
            +
                    include Read
         | 
| 18 | 
            +
                    include Reports
         | 
| 19 | 
            +
                    include Update
         | 
| 20 | 
            +
                    include Activate
         | 
| 21 | 
            +
                    include Archive
         | 
| 22 | 
            +
                    include CreateIdentity
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    def add_initial_controls_for org
         | 
| 25 | 
            +
                      # Server handles this functionality
         | 
| 26 | 
            +
                    end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                    def add_initial_controls_to identity
         | 
| 29 | 
            +
                      # Server handles this functionality
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                    def raise_unless_bucket_name_valid args
         | 
| 33 | 
            +
                      # No endpoints for this
         | 
| 34 | 
            +
                    end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                    def create_backup anchor
         | 
| 37 | 
            +
                      # No endpoints for this
         | 
| 38 | 
            +
                    end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                    def backup_exists? anchor
         | 
| 41 | 
            +
                      # No endpoints for this
         | 
| 42 | 
            +
                    end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                    private
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                    attr_reader :http
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                    def base_url
         | 
| 49 | 
            +
                      "#{SCHEME}#{DOMAIN}"
         | 
| 50 | 
            +
                    end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                    def setup
         | 
| 53 | 
            +
                      @http = Faraday.new :url => base_url do |conn|
         | 
| 54 | 
            +
                                unless auth_key.nil?
         | 
| 55 | 
            +
                                  conn.basic_auth auth_key.id, auth_key.secret
         | 
| 56 | 
            +
                                end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                                conn.request    :json
         | 
| 59 | 
            +
                                conn.response   :json, :content_type => /^json$/
         | 
| 60 | 
            +
                                conn.use        Subledger::Store::Api::Errors::ResponseError
         | 
| 61 | 
            +
                                conn.adapter    Faraday.default_adapter
         | 
| 62 | 
            +
                              end
         | 
| 63 | 
            +
                    end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                    def parse_json json_body
         | 
| 66 | 
            +
                      MultiJson.load json_body
         | 
| 67 | 
            +
                    end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                    def klass_method_from parsed_json
         | 
| 70 | 
            +
                      parsed_json.keys.first
         | 
| 71 | 
            +
                    end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                    def new_or_initialize json_body, initializable
         | 
| 74 | 
            +
                      client = initializable.client
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                      parsed_json = parse_json json_body
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                      klass_method = klass_method_from parsed_json
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                      response_hash = parsed_json[klass_method]
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                      args = Rest.to_args response_hash, client
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                      args.merge! :json => MultiJson.dump( response_hash )
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                      if initializable.respond_to? :post_delay
         | 
| 87 | 
            +
                        args.merge! :post_delay => initializable.post_delay
         | 
| 88 | 
            +
                      end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                      new_item = client.send klass_method, args
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                      if initializable.entity_name == klass_method.to_sym
         | 
| 93 | 
            +
                        initializable.send :initialize, new_item.attributes
         | 
| 94 | 
            +
                      else
         | 
| 95 | 
            +
                        initializable = new_item
         | 
| 96 | 
            +
                      end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                      initializable
         | 
| 99 | 
            +
                    end
         | 
| 100 | 
            +
                  end
         | 
| 101 | 
            +
                end
         | 
| 102 | 
            +
              end
         | 
| 103 | 
            +
            end
         | 
| @@ -0,0 +1,20 @@ | |
| 1 | 
            +
            require 'faraday_middleware'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'subledger/store/api/roles/activate'
         | 
| 4 | 
            +
            require 'subledger/store/api/roles/archive'
         | 
| 5 | 
            +
            require 'subledger/store/api/roles/balance'
         | 
| 6 | 
            +
            require 'subledger/store/api/roles/categories'
         | 
| 7 | 
            +
            require 'subledger/store/api/roles/collect'
         | 
| 8 | 
            +
            require 'subledger/store/api/roles/create'
         | 
| 9 | 
            +
            require 'subledger/store/api/roles/create_and_post'
         | 
| 10 | 
            +
            require 'subledger/store/api/roles/create_line'
         | 
| 11 | 
            +
            require 'subledger/store/api/roles/first_and_last_line'
         | 
| 12 | 
            +
            require 'subledger/store/api/roles/post'
         | 
| 13 | 
            +
            require 'subledger/store/api/roles/progress'
         | 
| 14 | 
            +
            require 'subledger/store/api/roles/read'
         | 
| 15 | 
            +
            require 'subledger/store/api/roles/reports'
         | 
| 16 | 
            +
            require 'subledger/store/api/roles/update'
         | 
| 17 | 
            +
            require 'subledger/store/api/roles/create_identity'
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            require 'subledger/store/api/errors'
         | 
| 20 | 
            +
            require 'subledger/store/api/store'
         | 
| @@ -0,0 +1,236 @@ | |
| 1 | 
            +
            module Subledger
         | 
| 2 | 
            +
              module Store
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                class ActivateError         < Error; end
         | 
| 5 | 
            +
                class ArchiveError          < Error; end
         | 
| 6 | 
            +
                class AttachError           < Error; end
         | 
| 7 | 
            +
                class BalanceError          < Error; end
         | 
| 8 | 
            +
                class CategoryError         < Error; end
         | 
| 9 | 
            +
                class CollectError          < Error; end
         | 
| 10 | 
            +
                class CreateError           < Error; end
         | 
| 11 | 
            +
                class CreateLineError       < Error; end
         | 
| 12 | 
            +
                class DeleteError           < Error; end
         | 
| 13 | 
            +
                class DetachError           < Error; end
         | 
| 14 | 
            +
                class FirstAndLastLineError < Error; end
         | 
| 15 | 
            +
                class PostError             < Error; end
         | 
| 16 | 
            +
                class ProgressError         < Error; end
         | 
| 17 | 
            +
                class ReadError             < Error; end
         | 
| 18 | 
            +
                class ReportError           < Error; end
         | 
| 19 | 
            +
                class UpdateError           < Error; end
         | 
| 20 | 
            +
                class UpdateNotFoundError   < Error; end
         | 
| 21 | 
            +
                class UpdateConflictError   < Error; end
         | 
| 22 | 
            +
                class BucketValidatorError  < Error; end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                attr_reader :auth_key
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def initialize args
         | 
| 27 | 
            +
                  @auth_key = args[:auth_key]
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  setup
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def attributes
         | 
| 33 | 
            +
                  { :auth_key => auth_key }
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                def collect args
         | 
| 37 | 
            +
                  collection_name = args[:collection_name]
         | 
| 38 | 
            +
                  action          = args[:action]
         | 
| 39 | 
            +
                  limit           = args[:limit]
         | 
| 40 | 
            +
                  no_balances     = args[:no_balances]
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  begin
         | 
| 43 | 
            +
                    if limit.nil? or limit < 1 or limit > 100
         | 
| 44 | 
            +
                      raise CollectError, ':limit must be 1-100'
         | 
| 45 | 
            +
                    end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                    collected = send action, args
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                    if collection_name == :account_lines and not collected.empty?
         | 
| 50 | 
            +
                      if no_balances
         | 
| 51 | 
            +
                        collected.each do |line|
         | 
| 52 | 
            +
                          line.send :balance=, nil
         | 
| 53 | 
            +
                        end
         | 
| 54 | 
            +
                      else
         | 
| 55 | 
            +
                        args.merge! :account_lines => collected
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                        set_account_line_balances args
         | 
| 58 | 
            +
                      end
         | 
| 59 | 
            +
                    end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                    collected
         | 
| 62 | 
            +
                  rescue Exception => e
         | 
| 63 | 
            +
                    raise CollectError, "Unable to collect #{collection_name}: #{e}"
         | 
| 64 | 
            +
                  end
         | 
| 65 | 
            +
                end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                def raise_unless_bucket_name_valid args
         | 
| 68 | 
            +
                  bucket_name = args[:bucket_name]
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                  unless bucket_name.nil?
         | 
| 71 | 
            +
                    Celluloid::Actor[:backup_bucket_validators].validate args
         | 
| 72 | 
            +
                  end
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                def create_backup anchor
         | 
| 76 | 
            +
                  Celluloid::Actor[:backup_creators].async.create :anchor => anchor
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                  anchor
         | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                def backup_exists? anchor
         | 
| 82 | 
            +
                  Celluloid::Actor[:backup_creators].exists? :anchor => anchor
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                def create_identity identity
         | 
| 86 | 
            +
                  create identity
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                  key = add_key_to identity
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                  add_initial_controls_to identity
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                  return identity, key
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                def add_initial_controls_for org
         | 
| 96 | 
            +
                  client = org.client
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                  key = read client.keys :id => auth_key.id
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                  identity = key.identity
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                  # TODO OrgIdentities table is not being updated yet
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                  create client.active_controls :org      => org,
         | 
| 105 | 
            +
                                                :identity => identity,
         | 
| 106 | 
            +
                                                :verbs    => 'GET|PATCH',
         | 
| 107 | 
            +
                                                :path     => "/#{API_VERSION}/orgs/#{org.id}",
         | 
| 108 | 
            +
                                                :mode     => :allow
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                  create client.active_controls :org      => org,
         | 
| 111 | 
            +
                                                :identity => identity,
         | 
| 112 | 
            +
                                                :verbs    => 'POST|GET|PATCH',
         | 
| 113 | 
            +
                                                :path     => "/#{API_VERSION}/orgs/#{org.id}/*",
         | 
| 114 | 
            +
                                                :mode     => :allow
         | 
| 115 | 
            +
                end
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                def add_key_to identity
         | 
| 118 | 
            +
                  key = identity.client.active_keys :identity => identity
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                  create key
         | 
| 121 | 
            +
                end
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                def add_initial_controls_to identity
         | 
| 124 | 
            +
                  client = identity.client
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                  client.active_controls.create :identity => identity,
         | 
| 127 | 
            +
                                                :verbs    => 'GET|PATCH',
         | 
| 128 | 
            +
                                                :path     => "/#{API_VERSION}/identities/#{identity.id}",
         | 
| 129 | 
            +
                                                :mode     => :allow
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                  client.active_controls.create :identity => identity,
         | 
| 132 | 
            +
                                                :verbs    => 'GET|POST',
         | 
| 133 | 
            +
                                                :path     => "/#{API_VERSION}/identities/#{identity.id}/*",
         | 
| 134 | 
            +
                                                :mode     => :allow
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                  client.active_controls.create :identity => identity,
         | 
| 137 | 
            +
                                                :verbs    => 'POST',
         | 
| 138 | 
            +
                                                :path     => "/#{API_VERSION}/orgs",
         | 
| 139 | 
            +
                                                :mode     => :allow
         | 
| 140 | 
            +
                end
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                def first_and_last_line args
         | 
| 143 | 
            +
                  account = args[:account]
         | 
| 144 | 
            +
             | 
| 145 | 
            +
                  # eventually consistent means we could get one, but not two...
         | 
| 146 | 
            +
                  # zero is fine, it means the account has no lines!
         | 
| 147 | 
            +
             | 
| 148 | 
            +
                  begin
         | 
| 149 | 
            +
                    lines = [ ]
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                    lines += first_or_last_line( args.merge! :effective_at => first_second,
         | 
| 152 | 
            +
                                                             :action       => :starting )
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                    lines += first_or_last_line( args.merge! :effective_at => last_second,
         | 
| 155 | 
            +
                                                             :action       => :ending )
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                  end while lines.length == 1 and sleep 0.025
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                  lines
         | 
| 160 | 
            +
                end
         | 
| 161 | 
            +
             | 
| 162 | 
            +
                private
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                def first_or_last_line args
         | 
| 165 | 
            +
                  account = args[:account]
         | 
| 166 | 
            +
                  client  = args[:client] ||= account.client
         | 
| 167 | 
            +
             | 
| 168 | 
            +
                  anchor = account.line :effective_at => args[:effective_at]
         | 
| 169 | 
            +
             | 
| 170 | 
            +
                  collect args.merge! :collection_name => :account_lines,
         | 
| 171 | 
            +
                                      :anchor          => anchor,
         | 
| 172 | 
            +
                                      :limit           => 1
         | 
| 173 | 
            +
                end
         | 
| 174 | 
            +
             | 
| 175 | 
            +
                def set_account_line_balances args
         | 
| 176 | 
            +
                  if [ :before, :ending, :preceding ].include? args[:action]
         | 
| 177 | 
            +
                    account_lines = args[:account_lines].reverse
         | 
| 178 | 
            +
                  else
         | 
| 179 | 
            +
                    account_lines = args[:account_lines]
         | 
| 180 | 
            +
                  end
         | 
| 181 | 
            +
             | 
| 182 | 
            +
                  first_line = account_lines.first
         | 
| 183 | 
            +
             | 
| 184 | 
            +
                  balance = balance_for first_line
         | 
| 185 | 
            +
             | 
| 186 | 
            +
                  first_line.send :balance=, balance
         | 
| 187 | 
            +
             | 
| 188 | 
            +
                  # Beware insane Ruby array indexing on line below
         | 
| 189 | 
            +
             | 
| 190 | 
            +
                  account_lines[1..-1].each do |account_line|
         | 
| 191 | 
            +
                    balance += account_line
         | 
| 192 | 
            +
                    account_line.send :balance=, balance
         | 
| 193 | 
            +
                  end
         | 
| 194 | 
            +
                end
         | 
| 195 | 
            +
             | 
| 196 | 
            +
                def set_account_line_balance account_line
         | 
| 197 | 
            +
                  account_line.send :balance=, balance_for( account_line )
         | 
| 198 | 
            +
                end
         | 
| 199 | 
            +
             | 
| 200 | 
            +
                def balance_for account_line
         | 
| 201 | 
            +
                  client       = account_line.client
         | 
| 202 | 
            +
                  account      = account_line.account
         | 
| 203 | 
            +
                  effective_at = account_line.effective_at
         | 
| 204 | 
            +
             | 
| 205 | 
            +
                  previous_ms = previous_ms_for effective_at
         | 
| 206 | 
            +
             | 
| 207 | 
            +
                  balance = account_balance :store       => self,
         | 
| 208 | 
            +
                                            :client      => client,
         | 
| 209 | 
            +
                                            :account     => account,
         | 
| 210 | 
            +
                                            :at          => previous_ms
         | 
| 211 | 
            +
             | 
| 212 | 
            +
                  account.lines( :action       => :after,
         | 
| 213 | 
            +
                                 :effective_at => previous_ms,
         | 
| 214 | 
            +
                                 :no_balances  => true ) do |line|
         | 
| 215 | 
            +
             | 
| 216 | 
            +
                     balance += line
         | 
| 217 | 
            +
             | 
| 218 | 
            +
                     break if line == account_line
         | 
| 219 | 
            +
                  end
         | 
| 220 | 
            +
             | 
| 221 | 
            +
                  balance
         | 
| 222 | 
            +
                end
         | 
| 223 | 
            +
             | 
| 224 | 
            +
                def previous_ms_for effective_at
         | 
| 225 | 
            +
                  effective_at_sec  = BigDecimal effective_at.tv_sec
         | 
| 226 | 
            +
                  effective_at_usec = BigDecimal effective_at.tv_usec
         | 
| 227 | 
            +
                  effective_at_frac = effective_at_usec / 1_000_000
         | 
| 228 | 
            +
             | 
| 229 | 
            +
                  effective_at_bd   = effective_at_sec + effective_at_frac
         | 
| 230 | 
            +
             | 
| 231 | 
            +
                  previous_ms_bd    = effective_at_bd - BigDecimal( '.001' )
         | 
| 232 | 
            +
             | 
| 233 | 
            +
                  Time.at( previous_ms_bd ).utc
         | 
| 234 | 
            +
                end
         | 
| 235 | 
            +
              end
         | 
| 236 | 
            +
            end
         | 
| @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            module Subledger
         | 
| 2 | 
            +
              class Supervisor < Celluloid::SupervisionGroup
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                DEFAULT_SIZE = 1
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                SMALL  = 16
         | 
| 7 | 
            +
                MEDIUM = 32
         | 
| 8 | 
            +
                LARGE  = 48
         | 
| 9 | 
            +
                GRANDE = 80
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def self.manage klass, args
         | 
| 12 | 
            +
                  args[:size] ||= DEFAULT_SIZE
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  if args[:size] == 1
         | 
| 15 | 
            +
                    supervise klass, args
         | 
| 16 | 
            +
                  else
         | 
| 17 | 
            +
                    pool klass, args
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
            end
         | 
| @@ -0,0 +1,52 @@ | |
| 1 | 
            +
            require 'securerandom'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Subledger
         | 
| 4 | 
            +
              module UUID
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                SPACE   = 62 ** 22
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                CHARS = ('0'..'9').to_a + ('a'..'z').to_a + ('A'..'Z').to_a
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                VALID = /^[0-9A-Za-z]{22}$/
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def self.valid? uuid
         | 
| 13 | 
            +
                  !uuid.nil? and uuid =~ VALID
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def self.invalid? uuid
         | 
| 17 | 
            +
                  uuid.nil? or uuid !~ VALID
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                def self.as_string
         | 
| 21 | 
            +
                  to_string as_bignum
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                def self.as_bignum
         | 
| 25 | 
            +
                  SecureRandom.random_number SPACE
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                def self.to_string bignum
         | 
| 29 | 
            +
                  base_62 = ''
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  22.times do
         | 
| 32 | 
            +
                    base_62 << CHARS[ bignum.modulo 62 ]
         | 
| 33 | 
            +
                    bignum /= 62
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  base_62.reverse
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                def self.to_bignum uuid
         | 
| 40 | 
            +
                  bignum = 0
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  power = 0
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  uuid.split('').reverse.each do |character|
         | 
| 45 | 
            +
                    bignum += CHARS.index( character ) * ( 62 ** power )
         | 
| 46 | 
            +
                    power += 1
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                  bignum
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
              end
         | 
| 52 | 
            +
            end
         |