kobana 0.2.3 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c826dcdcf3f4d824b323ba7ae2c0d6fd53f148c8c785afcd2c6fd34dbdf1593f
4
- data.tar.gz: c014e264cf67d522805bbd975e74c4805d9359cb2406589e15bdfd56ba549f6a
3
+ metadata.gz: 3bc6a9da2db1bd8cb2aa863f729e4ee3ef9cd4c143e9c38e5ce5e64c633e1f98
4
+ data.tar.gz: 2841dae640966617c6c9a897681cc26ae0ace4c933613ae0147d91ab0ffda53e
5
5
  SHA512:
6
- metadata.gz: 7487722905d0571414401b83d4255e18779decb322a7b724ab78dea4d6393b4f510be6999395547abba805c6e4362352ad387a8046e204f24134e5fa6edf0ff2
7
- data.tar.gz: 40fd5452ee3e80e339930645233b88c59d0530594093e7ccb12b67d36cd584f0d509b910c51c8c241648a384cfb90e54c0149f34322a0113fe4c6446ec0c495c
6
+ metadata.gz: 1740ebb8d3b894fd4b0b215450ea4563da1ece74da1002d046f874b8661007716989e9ed8c52279ed95983714e42cca1b3bc3a4c05e369910ca2aec6b68b67f8
7
+ data.tar.gz: 75ec69765d55582b1363d014ba78ebc4e93d5407e6aef4b18020bd1c3c78d1d4c59b8207d0d40bf9522e81a08364e03779d7743ebcd9a8b5894f15d58d91f4bf
data/README.md CHANGED
@@ -36,6 +36,8 @@ $ gem install kobana
36
36
 
37
37
  ### Configuration
38
38
 
39
+ #### Global Configuration (Single API Token)
40
+
39
41
  Configure your API key by creating an initializer in your Rails project:
40
42
 
41
43
  `config/initializers/kobana.rb`
@@ -49,12 +51,52 @@ end
49
51
 
50
52
  Replace `'YOUR_API_TOKEN'` with your actual API key from the corresponding environment.
51
53
 
54
+ #### Multi-Client Configuration (Multiple API Tokens)
55
+
56
+ For applications that need to work with multiple Kobana accounts simultaneously (e.g., multi-tenant applications), you can create multiple client instances:
57
+
58
+ ```ruby
59
+ # Create separate clients for different accounts
60
+ client1 = Kobana::Client.new(
61
+ api_token: 'CLIENT1_API_TOKEN',
62
+ environment: :production
63
+ )
64
+
65
+ client2 = Kobana::Client.new(
66
+ api_token: 'CLIENT2_API_TOKEN',
67
+ environment: :sandbox
68
+ )
69
+
70
+ # Use client-specific resources
71
+ pix1 = client1.charge.pix.create(attributes)
72
+ pix2 = client2.charge.pix.create(attributes)
73
+
74
+ # Each client maintains its own configuration
75
+ account1 = client1.financial.account.find(account_id)
76
+ account2 = client2.financial.account.find(account_id)
77
+ ```
78
+
79
+ You can also configure clients after initialization:
80
+
81
+ ```ruby
82
+ client = Kobana::Client.new
83
+ client.configure do |config|
84
+ config.api_token = 'YOUR_API_TOKEN'
85
+ config.environment = :production
86
+ config.custom_headers = { 'X-Custom-Header' => 'Value' }
87
+ config.debug = true
88
+ end
89
+ ```
90
+
52
91
  ### Usage
53
92
 
93
+ The gem supports both global configuration (backward compatible) and multi-client usage patterns.
94
+
54
95
  #### **Charges**
55
96
 
56
97
  ##### Creating a Charge
57
98
 
99
+ Using global configuration:
58
100
  ```ruby
59
101
  attributes = {
60
102
  'amount' => 100.50,
@@ -71,7 +113,15 @@ attributes = {
71
113
  'custom_data' => '{"order_id": "12345"}'
72
114
  }
73
115
 
116
+ # Global configuration approach (backward compatible)
74
117
  pix = Kobana::Resources::Charge::Pix.create(attributes)
118
+ ```
119
+
120
+ Using client-specific configuration:
121
+ ```ruby
122
+ # Client-specific approach
123
+ client = Kobana::Client.new(api_token: 'YOUR_TOKEN')
124
+ pix = client.charge.pix.create(attributes)
75
125
  pix.id # 1
76
126
  pix.new_record? false
77
127
  pix.created? # true
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kobana
4
+ class Client
5
+ attr_reader :configuration
6
+
7
+ def initialize(api_token: nil, environment: :sandbox, custom_headers: {}, debug: false, **extra_config)
8
+ @configuration = Configuration.new
9
+ @configuration.api_token = api_token
10
+ @configuration.environment = environment
11
+ @configuration.custom_headers = custom_headers
12
+ @configuration.debug = debug
13
+
14
+ # Allow any additional configuration options
15
+ extra_config.each do |key, value|
16
+ @configuration.public_send("#{key}=", value) if @configuration.respond_to?("#{key}=")
17
+ end
18
+ end
19
+
20
+ def configure
21
+ yield(@configuration) if block_given?
22
+ end
23
+
24
+ # Dynamically create resource accessors with metaprogramming
25
+ %i[charge financial admin].each do |resource_type|
26
+ define_method(resource_type) do
27
+ instance_variable_get("@#{resource_type}") ||
28
+ instance_variable_set("@#{resource_type}",
29
+ ResourceProxy.new(self, Resources.const_get(resource_type.to_s.capitalize)))
30
+ end
31
+ end
32
+
33
+ # Generic proxy for all resource modules
34
+ class ResourceProxy
35
+ def initialize(client, module_ref)
36
+ @client = client
37
+ @module = module_ref
38
+ end
39
+
40
+ def method_missing(method_name, *args, &)
41
+ # Convert method name to class name (e.g., :pix => Pix, :account_balance => AccountBalance)
42
+ class_name = method_name.to_s.split("_").map(&:capitalize).join
43
+
44
+ # Try to find the resource class
45
+ if @module.const_defined?(class_name)
46
+ resource_class = @module.const_get(class_name)
47
+ # Return a client-bound version of the class
48
+ resource_class.with_client(@client)
49
+ else
50
+ super
51
+ end
52
+ end
53
+
54
+ def respond_to_missing?(method_name, include_private = false)
55
+ class_name = method_name.to_s.split("_").map(&:capitalize).join
56
+ @module.const_defined?(class_name) || super
57
+ end
58
+ end
59
+ end
60
+ end
@@ -2,11 +2,12 @@
2
2
 
3
3
  module Kobana
4
4
  class Configuration
5
- attr_accessor :api_token, :environment, :custom_headers, :debug
5
+ attr_accessor :api_token, :environment, :custom_headers, :debug, :api_version
6
6
 
7
7
  def initialize
8
8
  @custom_headers = {}
9
9
  @environment = :sandbox
10
+ @api_version = :v2
10
11
  end
11
12
 
12
13
  def inspect
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kobana
4
+ class Error < StandardError; end
5
+
6
+ class ConfigurationError < Error; end
7
+ class ConnectionError < Error; end
8
+ class ResourceNotFoundError < Error; end
9
+ class UnauthorizedError < Error; end
10
+ class ValidationError < Error; end
11
+
12
+ class APIError < Error
13
+ attr_reader :status, :response_body, :errors
14
+
15
+ def initialize(message = nil, status: nil, response_body: nil, errors: nil)
16
+ @status = status
17
+ @response_body = response_body
18
+ @errors = errors
19
+ super(message || default_message)
20
+ end
21
+
22
+ private
23
+
24
+ def default_message
25
+ "API request failed with status #{status}"
26
+ end
27
+ end
28
+ end
@@ -10,13 +10,24 @@ module Kobana
10
10
  include Operations
11
11
 
12
12
  class << self
13
- attr_accessor :primary_key, :api_version, :resource_endpoint, :errors, :default_attributes
13
+ attr_accessor :primary_key, :api_version, :resource_endpoint, :errors, :default_attributes, :client
14
+
15
+ def with_client(client)
16
+ @client_classes ||= {}.compare_by_identity
17
+ @client_classes[client] ||= begin
18
+ klass = Class.new(self)
19
+ klass.client = client
20
+ klass
21
+ end
22
+ end
14
23
 
15
24
  def inherited(subclass)
16
25
  super
26
+ # Set defaults only if not already set by parent classes
17
27
  subclass.resource_endpoint ||= infer_resource_endpoint(subclass)
18
28
  subclass.primary_key ||= :uid
19
- subclass.api_version ||= :v2
29
+ # Don't override api_version if it's already been set
30
+ subclass.api_version = :v2 unless subclass.instance_variable_defined?(:@api_version)
20
31
  subclass.errors ||= []
21
32
  subclass.default_attributes ||= {}
22
33
  end
@@ -34,7 +45,7 @@ module Kobana
34
45
  end
35
46
 
36
47
  def interpolate(template, attributes)
37
- template.gsub(/\{([^\}]+)\}/) do
48
+ template.gsub(/\{([^}]+)\}/) do
38
49
  key = Regexp.last_match(1)
39
50
  begin
40
51
  if key.include?(".")
@@ -62,6 +73,11 @@ module Kobana
62
73
  @errors = []
63
74
  end
64
75
 
76
+ # Access to client configuration through class
77
+ def client
78
+ self.class.client
79
+ end
80
+
65
81
  def [](key)
66
82
  attributes[key.to_sym]
67
83
  end
@@ -22,23 +22,44 @@ module Kobana
22
22
 
23
23
  module ClassMethods
24
24
  def headers
25
+ config = client&.configuration || Kobana.configuration
25
26
  {
26
- "Authorization" => "Bearer #{Kobana.configuration.api_token}",
27
+ "Authorization" => "Bearer #{config.api_token}",
27
28
  "Content-Type" => "application/json"
28
- }.merge(Kobana.configuration.custom_headers)
29
+ }.merge(config.custom_headers)
29
30
  end
30
31
 
31
32
  def connection
33
+ config = client&.configuration || Kobana.configuration
34
+ # Don't cache connection when debugging to ensure logger works
35
+ return build_connection(config) if config.debug
36
+
37
+ @connection ||= build_connection(config)
38
+ end
39
+
40
+ private
41
+
42
+ def build_connection(config)
32
43
  Faraday.new(url: base_url) do |faraday|
33
44
  faraday.request :url_encoded
34
45
  faraday.request :json
35
46
  faraday.adapter Faraday.default_adapter
36
47
  faraday.headers = headers
37
- faraday.response :logger, logger if Kobana.configuration.debug
48
+ faraday.response :logger, logger if config.debug
38
49
  end
39
50
  end
40
51
 
52
+ public
53
+
41
54
  def multipart_connection
55
+ config = client&.configuration || Kobana.configuration
56
+ # Don't cache connection when debugging
57
+ return build_multipart_connection(config) if config.debug
58
+
59
+ @multipart_connection ||= build_multipart_connection(config)
60
+ end
61
+
62
+ def build_multipart_connection(config)
42
63
  Faraday.new(url: base_url) do |faraday|
43
64
  faraday.request :multipart
44
65
  faraday.request :url_encoded
@@ -46,14 +67,14 @@ module Kobana
46
67
  faraday.headers = headers.merge(
47
68
  "Content-Type" => "multipart/form-data"
48
69
  )
49
- faraday.response :logger, logger if Kobana.configuration.debug
70
+ faraday.response :logger, logger if config.debug
50
71
  end
51
72
  end
52
73
 
53
74
  def logger
54
75
  logger = Logger.new($stdout)
55
76
  logger.formatter = proc do |severity, datetime, _progname, msg|
56
- redacted_msg = msg.gsub(/(Bearer|Token)\s+[A-Za-z0-9\-_\.]+/, '\1 [REDACTED]')
77
+ redacted_msg = msg.gsub(/(Bearer|Token)\s+[A-Za-z0-9\-_.]+/, '\1 [REDACTED]')
57
78
  "#{severity} #{datetime}: #{redacted_msg}\n"
58
79
  end
59
80
  logger
@@ -84,7 +105,10 @@ module Kobana
84
105
  end
85
106
 
86
107
  def base_url
87
- @base_url ||= BASE_URI[api_version&.to_sym][Kobana.configuration.environment&.to_sym]
108
+ config = client&.configuration || Kobana.configuration
109
+ # Prioritize client configuration api_version over class api_version
110
+ version = config.api_version&.to_sym || api_version&.to_sym
111
+ BASE_URI[version][config.environment&.to_sym]
88
112
  end
89
113
  end
90
114
  end
@@ -60,7 +60,7 @@ module Kobana
60
60
  if options[:find_by_id]
61
61
  find(params, options[:find_params]) || create(attributes)
62
62
  else
63
- find_by(params, options) || create(attributes.merge(params.deep_symbolize_keys))
63
+ find_by(params, options) || create(attributes.merge(params.deep_symbolize_keys), options)
64
64
  end
65
65
  end
66
66
 
@@ -72,6 +72,22 @@ module Kobana
72
72
  end
73
73
  end
74
74
 
75
+ def save
76
+ if new_record?
77
+ response = self.class.create(attributes)
78
+ if response.created?
79
+ @attributes = response.attributes
80
+ @errors = []
81
+ true
82
+ else
83
+ @errors = response.errors
84
+ false
85
+ end
86
+ else
87
+ update({})
88
+ end
89
+ end
90
+
75
91
  def update(new_attributes = {})
76
92
  return if new_attributes.empty?
77
93
 
@@ -79,7 +95,7 @@ module Kobana
79
95
  response = request(:put, uri, data.to_json)
80
96
  case response[:status]
81
97
  when 200..204
82
- new(response[:data].merge(updated: true))
98
+ self.class.new(response[:data].merge(updated: true))
83
99
  else
84
100
  handle_error_response(response)
85
101
  resource = self.class.new(attributes.merge(updated: false))
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kobana
4
- VERSION = "0.2.3"
4
+ VERSION = "0.3.1"
5
5
  end
data/lib/kobana.rb CHANGED
@@ -7,6 +7,7 @@ require "json"
7
7
  require "support/string"
8
8
  require "support/hash"
9
9
  require "kobana/configuration"
10
+ require "kobana/errors"
10
11
 
11
12
  module Kobana
12
13
  class << self
@@ -22,6 +23,7 @@ module Kobana
22
23
  end
23
24
 
24
25
  autoload :Version, "kobana/version"
26
+ autoload :Client, "kobana/client"
25
27
 
26
28
  module Resources
27
29
  autoload :Base, "kobana/resources/base"
metadata CHANGED
@@ -1,15 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kobana
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kivanio Barbosa
8
8
  - Rafael Lima
9
- autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2025-08-09 00:00:00.000000000 Z
11
+ date: 1980-01-02 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: faraday
@@ -72,7 +71,9 @@ files:
72
71
  - README.md
73
72
  - Rakefile
74
73
  - lib/kobana.rb
74
+ - lib/kobana/client.rb
75
75
  - lib/kobana/configuration.rb
76
+ - lib/kobana/errors.rb
76
77
  - lib/kobana/resources/admin/subaccount.rb
77
78
  - lib/kobana/resources/base.rb
78
79
  - lib/kobana/resources/charge/bank_billet.rb
@@ -96,7 +97,6 @@ metadata:
96
97
  bug_tracker_uri: https://github.com/universokobana/kobana-ruby-client/issues
97
98
  documentation_uri: https://github.com/universokobana/kobana-ruby-client/wiki
98
99
  rubygems_mfa_required: 'true'
99
- post_install_message:
100
100
  rdoc_options: []
101
101
  require_paths:
102
102
  - lib
@@ -111,8 +111,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
111
111
  - !ruby/object:Gem::Version
112
112
  version: '0'
113
113
  requirements: []
114
- rubygems_version: 3.5.23
115
- signing_key:
114
+ rubygems_version: 3.7.1
116
115
  specification_version: 4
117
116
  summary: Kobana API Client
118
117
  test_files: []