From fd7af043368e9b5f4060c9966b06f9b33a47b7ac Mon Sep 17 00:00:00 2001 From: juanca sardin Date: Tue, 11 Jul 2023 00:06:02 -0400 Subject: [PATCH 1/6] Update style --- lib/fintoc/client.rb | 3 +-- lib/fintoc/constants.rb | 1 + lib/fintoc/utils.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/fintoc/client.rb b/lib/fintoc/client.rb index 465e36d..151b4ca 100644 --- a/lib/fintoc/client.rb +++ b/lib/fintoc/client.rb @@ -14,7 +14,6 @@ def initialize(api_key) @user_agent = "fintoc-ruby/#{Fintoc::VERSION}" @headers = { "Authorization": @api_key, "User-Agent": @user_agent } @link_headers = nil - @link_header_pattern = '<(?.*)>;\s*rel="(?.*)"' @default_params = {} end @@ -82,7 +81,7 @@ def client end def parse_headers(dict, link) - matches = link.strip.match(@link_header_pattern) + matches = link.strip.match(Fintoc::Constants::PAGINATION_REGEX) dict[matches[:rel]] = matches[:url] dict end diff --git a/lib/fintoc/constants.rb b/lib/fintoc/constants.rb index 11d2b2e..585b8ec 100644 --- a/lib/fintoc/constants.rb +++ b/lib/fintoc/constants.rb @@ -4,5 +4,6 @@ module Constants GENERAL_DOC_URL = "https://fintoc.com/docs" SCHEME = "https://" BASE_URL = "api.fintoc.com/v1/" + PAGINATION_REGEX = '<(?.*)>;\s*rel="(?.*)"' end end diff --git a/lib/fintoc/utils.rb b/lib/fintoc/utils.rb index 9e1cf77..aac93f5 100644 --- a/lib/fintoc/utils.rb +++ b/lib/fintoc/utils.rb @@ -32,7 +32,7 @@ def pick(dict_, key) # @param suffix [String] # @return [String] def pluralize(amount, noun, suffix = 's') - quantifier = amount or 'no' + quantifier = amount || 'no' "#{quantifier} #{amount == 1 ? noun : noun + suffix}" end From 1dae4a0c8890d6fa30d86684c39ddfaa8591aa2d Mon Sep 17 00:00:00 2001 From: juanca sardin Date: Tue, 11 Jul 2023 00:11:20 -0400 Subject: [PATCH 2/6] Add a new resource base class --- lib/fintoc.rb | 15 ++++++ lib/fintoc/resource.rb | 105 ++++++++++++++++++++++++++++++++++++++++ lib/fintoc/resources.rb | 7 +++ lib/fintoc/version.rb | 2 +- 4 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 lib/fintoc/resource.rb create mode 100644 lib/fintoc/resources.rb diff --git a/lib/fintoc.rb b/lib/fintoc.rb index 022b5c8..e3ea734 100644 --- a/lib/fintoc.rb +++ b/lib/fintoc.rb @@ -2,5 +2,20 @@ require 'fintoc/errors' require 'fintoc/client' +#Name API resources +require 'fintoc/resources' + +#Support classes +require 'fintoc/resource' + + +#API Operations +require 'fintoc/api_operations/request' +require 'fintoc/api_operations/delete' + + module Fintoc + class << self + attr_accessor :api_key + end end diff --git a/lib/fintoc/resource.rb b/lib/fintoc/resource.rb new file mode 100644 index 0000000..8ae6a96 --- /dev/null +++ b/lib/fintoc/resource.rb @@ -0,0 +1,105 @@ +module Fintoc + class Resource + include Fintoc::APIOperations::Request + include Fintoc::APIOperations::Delete + + class << self + attr_reader :resource_url + + def class_name + name.split("::")[-1] + end + + def resource_url + if self == Resource + raise NotImplementedError, <<~MSG + Fintoc::Resource is an abstract class. You should perform actions + on its subsclasses (Link, Account, Movement, etc) + MSG + end + + resource_url || "#{class_name.downcase}" + end + + def find(id) + response = request(:get, "#{resource_url}/#{id}") + new(response) + end + + def all(params = {}) + response = request(:get, resource_url, params) + response.map {|data| new(data)} + end + + def belongs_to(resource_name, args={}) + class_name = args[:class_name] + unless resource_name && class_name + raise <<~MSG + You must specify the resource name and its class name + for example has_many :accounts, class_name: 'Account' + MSG + end + class_name = "::Fintoc::#{class_name}" unless class_name.include?("Fintoc") + if resource == :method + define_method(resource_name) do |params = {}| + data = request(:get, resource_url, params) + Object.const_get(class_name).new(**data) + end + end + + def has_many(resource_name, args={}) + class_name = args[:class_name] + unless resource_name && class_name + raise <<~MSG + You must specify the resource name and its class name + for example has_many :accounts, class_name: 'Account' + MSG + end + + # Add namespace to class_name + class_name = "::Fintoc::#{class_name}" unless class_name.include?("Fintoc") + define_method(resource_name) do |params = {}| + #link/accounts + fetch_next( + :get, + "#{resource_url}/#{resource_name}", params + ).lazy.map do |data| + Object.const_get(class_name).new(**data) + end + end + end + end + + def initialize(data={}) + @data = @unsaved_data = {} + initialize_from_data(data) + end + + def initilize_from_data(data) + klass = self.class + + @data = data + + data.each_key do |k| + klass.define_method(k) { @data[k]} + + klass.define_method(:"#{k}=") do |value| + @data[k] = @unsaved_data[k] = value + end + + next unless[FalseClass, TrueClass].include?( + @data[k] + ) + + klass.define_method(:"#{k}?") do + @data[k] + end + end + self + end + + def resource_url + "#{self.resource_url}/#{id}" + end + end +end diff --git a/lib/fintoc/resources.rb b/lib/fintoc/resources.rb new file mode 100644 index 0000000..1dcab09 --- /dev/null +++ b/lib/fintoc/resources.rb @@ -0,0 +1,7 @@ + +require 'fintoc/resources/account' +require 'fintoc/resources/link' +require 'fintoc/resources/institution' +require 'fintoc/resources/balance' +require 'fintoc/resources/movement' +require 'fintoc/resources/transfer_account' diff --git a/lib/fintoc/version.rb b/lib/fintoc/version.rb index fca5c30..f67bc09 100644 --- a/lib/fintoc/version.rb +++ b/lib/fintoc/version.rb @@ -1,3 +1,3 @@ module Fintoc - VERSION = "0.1.0" + VERSION = "0.2.0" end From d09f3912b80e5df99055c15ca02e252b7b37e509 Mon Sep 17 00:00:00 2001 From: juanca sardin Date: Tue, 11 Jul 2023 00:11:53 -0400 Subject: [PATCH 3/6] Abstract away the client and add operations abstractions instead --- lib/fintoc/api_operations/delete.rb | 9 ++++++ lib/fintoc/api_operations/request.rb | 45 ++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 lib/fintoc/api_operations/delete.rb create mode 100644 lib/fintoc/api_operations/request.rb diff --git a/lib/fintoc/api_operations/delete.rb b/lib/fintoc/api_operations/delete.rb new file mode 100644 index 0000000..d09c76c --- /dev/null +++ b/lib/fintoc/api_operations/delete.rb @@ -0,0 +1,9 @@ +module Fintoc + module APIOperations + module Delete + def delete + request(:delete, resource_url) + end + end + end +end diff --git a/lib/fintoc/api_operations/request.rb b/lib/fintoc/api_operations/request.rb new file mode 100644 index 0000000..d594920 --- /dev/null +++ b/lib/fintoc/api_operations/request.rb @@ -0,0 +1,45 @@ +module Fintoc + module APIOperations + module Request + def self.included(base) + base.extend(ClassMethods) + end + + module ClassMethods + def request(method, url, params={}) + check_api_key! + raise "Not supported method" unless %i[get delete].include(method) + api_client.send(method).call(url, **params) + end + + def fetch_next(method, url, params={}) + first = request(method, url, params) + return first if params.empty? + + first + Utils.flatten(api_client.fetch_next) + end + + def api_client + @api_client = Client.new(Fintoc.api_key) + end + + protected def check_api_key! + return if Fintoc.api_key + + raise AuthenticationError, <<~MSG + No API key provided. + Set your API key using 'Fitoc.api_key = ' + MSG + end + end + + protected def request(method, url, params = {}) + self.class.request(method, url, params) + end + + protected def fetch_next(method, url, params = {}) + self.class.fetch_next(method, url, params) + end + end + end +end From a0226b47afe8a587574455ddd9ef857ffa87b3bc Mon Sep 17 00:00:00 2001 From: juanca sardin Date: Tue, 11 Jul 2023 00:12:31 -0400 Subject: [PATCH 4/6] Refactor account and link resource to use the resource base class --- lib/fintoc/resources/account.rb | 59 ++------------------------------- lib/fintoc/resources/link.rb | 57 ++++++------------------------- 2 files changed, 13 insertions(+), 103 deletions(-) diff --git a/lib/fintoc/resources/account.rb b/lib/fintoc/resources/account.rb index 2946145..d5b48e0 100644 --- a/lib/fintoc/resources/account.rb +++ b/lib/fintoc/resources/account.rb @@ -4,51 +4,11 @@ require 'fintoc/resources/balance' module Fintoc - class Account + class Account < Resource include Utils - attr_reader :id, :name, :holder_name, :currency, :type, :refreshed_at, - :official_name, :number, :holder_id, :balance, :movements - def initialize( - id:, - name:, - official_name:, - number:, - holder_id:, - holder_name:, - type:, - currency:, - refreshed_at: nil, - balance: nil, - movements: nil, - client: nil, - ** - ) - @id = id - @name = name - @official_name = official_name - @number = number - @holder_id = holder_id - @holder_name = holder_name - @type = type - @currency = currency - @refreshed_at = DateTime.iso8601(refreshed_at) if refreshed_at - @balance = Fintoc::Balance.new(**balance) - @movements = movements || [] - @client = client - end - - def update_balance - @balance = Fintoc::Balance.new(**get_account[:balance]) - end - def get_movements(**params) - _get_movements(**params).lazy.map { |movement| Fintoc::Movement.new(**movement) } - end - - def update_movements(**params) - @movements += get_movements(**params).to_a - @movements = @movements.uniq.sort_by(&:post_date) - end + belongs_to :balance, class_name: Fintoc::Balance.to_s + has_many :movements, class_name: Fintoc::Movement.to_s def show_movements(rows = 5) puts("This account has #{Utils.pluralize(@movements.size, 'movement')}.") @@ -67,18 +27,5 @@ def show_movements(rows = 5) def to_s "πŸ’° #{@holder_name}’s #{@name} #{@balance}" end - - private - - def get_account - @client.get.call("accounts/#{@id}") - end - - def _get_movements(**params) - first = @client.get.call("accounts/#{@id}/movements", **params) - return first if params.empty? - - first + Utils.flatten(@client.fetch_next) - end end end diff --git a/lib/fintoc/resources/link.rb b/lib/fintoc/resources/link.rb index f1611cc..c507203 100644 --- a/lib/fintoc/resources/link.rb +++ b/lib/fintoc/resources/link.rb @@ -1,64 +1,27 @@ require 'date' require 'tabulate' require 'fintoc/utils' -require 'fintoc/resources/account' -require 'fintoc/resources/institution' require 'tabulate' module Fintoc - class Link - attr_reader :id, :username, :holder_type, :institution, :created_at, :mode, - :accounts, :link_token - include Utils - def initialize( - id:, - username:, - holder_type:, - institution:, - created_at:, - mode:, - accounts: nil, - link_token: nil, - client: nil, - ** - ) - @id = id - @username = username - @holder_type = holder_type - @institution = Fintoc::Institution.new(**institution) - @created_at = Date.iso8601(created_at) - @mode = mode - @accounts = if accounts.nil? - [] - else - accounts.map { |data| Fintoc::Account.new(**data, client: client) } - end - @token = link_token - @client = client - end + class Link < Resource - def find_all(**kwargs) - raise 'You must provide *exactly one* account field.' if kwargs.size != 1 + has_many :accounts, class_name: Account.to_s - field, value = kwargs.to_a.first - @accounts.select do |account| - account.send(field.to_sym) == value - end - end + belongs_to :institution, class_name: Institution.to_s - def find(**kwargs) - results = find_all(**kwargs) - results.any? ? results.first : nil - end + include Utils def show_accounts(rows = 5) puts "This links has #{Utils.pluralize(@accounts.size, 'account')}" return unless @accounts.any? - accounts = @accounts.to_a.slice(0, rows) - .map.with_index do |acc, index| - [index + 1, acc.name, acc.holder_name, acc.currency] - end + accounts = @accounts + .to_a + .slice(0, rows) + .map.with_index do |acc, index| + [index + 1, acc.name, acc.holder_name, acc.currency] + end headers = ['#', 'Name', 'Holder', 'Currency'] puts puts tabulate(headers, accounts, indent: 4, style: 'fancy') From 6203c3204ec09723ed1cb14a53657bbc73db1ca2 Mon Sep 17 00:00:00 2001 From: juanca sardin Date: Tue, 11 Jul 2023 00:13:20 -0400 Subject: [PATCH 5/6] Add new examples with the new api style --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/README.md b/README.md index 3ec2827..8aec5cc 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,20 @@ movements = account.get_movements # Or get all the movements since a specific date movements = account.get_movements(since: '2020-08-15') +``` +## Quickstart with the new refactored API +```ruby +require 'fintoc' + +Fintoc.api_key = 'sk_test_9c8d8CeyBTx1VcJzuDgpm4H-bywJCeSx' +link = Fintoc::Link.find('6n12zLmai3lLE9Dq_token_gvEJi8FrBge4fb3cz7Wp856W') +account = link.accounts.find(type: 'checking_account') + +# Get the las 30 movements +movements = account.movements + +# Or get all the movements since a specific date +movements = account.movements.find(since: '2020-08-15') ``` And that’s it! @@ -86,6 +100,22 @@ link.show_accounts ``` +### Get accounts with the new refactored API + +```ruby +require 'fintoc' + +client = Fintoc.api_key = 'api_key' +link = Fintoc::Link.find('link_token') +puts link.accounts + +# Or... you can pretty print all the accounts in a Link + +link = Fintoc::Link.find('link_token') +link.show_accounts + +``` + If you want to find a specific account in a link, you can use **find**. You can search by any account field: ```ruby From 569b2faaf11568eebe6698d75c48dba79c5708ba Mon Sep 17 00:00:00 2001 From: juanca sardin Date: Tue, 11 Jul 2023 10:09:53 -0400 Subject: [PATCH 6/6] some extra clean-up --- lib/fintoc/resources/account.rb | 8 ++++---- lib/fintoc/resources/link.rb | 12 ++++-------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/fintoc/resources/account.rb b/lib/fintoc/resources/account.rb index d5b48e0..cdce669 100644 --- a/lib/fintoc/resources/account.rb +++ b/lib/fintoc/resources/account.rb @@ -11,11 +11,11 @@ class Account < Resource has_many :movements, class_name: Fintoc::Movement.to_s def show_movements(rows = 5) - puts("This account has #{Utils.pluralize(@movements.size, 'movement')}.") + puts("This account has #{Utils.pluralize(movements.size, 'movement')}.") - return unless @movements.any? + return unless movements.any? - movements = @movements.to_a.slice(0, rows) + movements = movements.to_a.slice(0, rows) .map.with_index do |mov, index| [index + 1, mov.amount, mov.currency, mov.description, mov.locale_date] end @@ -25,7 +25,7 @@ def show_movements(rows = 5) end def to_s - "πŸ’° #{@holder_name}’s #{@name} #{@balance}" + "πŸ’° #{holder_name}’s #{@name} #{@balance}" end end end diff --git a/lib/fintoc/resources/link.rb b/lib/fintoc/resources/link.rb index c507203..7af1339 100644 --- a/lib/fintoc/resources/link.rb +++ b/lib/fintoc/resources/link.rb @@ -13,10 +13,10 @@ class Link < Resource include Utils def show_accounts(rows = 5) - puts "This links has #{Utils.pluralize(@accounts.size, 'account')}" + puts "This links has #{Utils.pluralize(accounts.size, 'account')}" return unless @accounts.any? - accounts = @accounts + accounts = accounts .to_a .slice(0, rows) .map.with_index do |acc, index| @@ -28,18 +28,14 @@ def show_accounts(rows = 5) end def update_accounts - @accounts.each do |account| + accounts.each do |account| account.update_balance account.update_movements end end - def delete - @client.delete_link(@id) - end - def to_s - "<#{@username}@#{@institution.name}> πŸ”— " + "<#{username}@#{institution.name}> πŸ”— " end end end