-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Normalize API versions #5371
base: master
Are you sure you want to change the base?
Normalize API versions #5371
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
module ActiveMerchant | ||
module Versionable | ||
def self.included(base) | ||
if base.respond_to?(:class_attribute) | ||
base.class_attribute :versions, default: {} | ||
base.extend(ClassMethods) | ||
end | ||
end | ||
|
||
module ClassMethods | ||
def inherited(subclass) | ||
super | ||
subclass.versions = {} | ||
end | ||
|
||
def version(version, feature = :default_api) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How is this method used? Is it just for the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method is inside the ClassMethods module. This module is extended to the base class in line 6, so any methods inside this module will be class methods. This allows for the versions to be set and fetch without creating an instance. The intended usage for this method is to be used at the top of any Gateway subclass. class MyTestGateway < Gateway
version 'v3' # This sets the :default_api key in the versions hash.
version 'v5', :recurring_api # This sets the :recurring_api key in the versions hash
end You can see this method being used in this PR in |
||
versions[feature] = version | ||
end | ||
|
||
def fetch_version(feature = :default_api) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also this one, how is it used? What is the difference between this one and the one in line 25? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The For example, some gateways create module ActiveMerchant # :nodoc:
module Billing # :nodoc:
class ForteGateway < Gateway
version 'v2'
self.test_url = "https://sandbox.forte.net/api/#{fetch_version}"
self.live_url = "https://api.forte.net/#{fetch_version}"
# other methods ...
end
end
end The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe Something with less "moving parts" using class instance variables like this: module ActiveMerchant
module Versionable
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def versions
@versions ||= {}
end
def version(version, feature = :default_api)
versions[feature] = version
end
def fetch_version(feature = :default_api)
versions[feature]
end
end
def fetch_version(feature = :default_api)
self.class.versions[feature]
end
end
end I Already ran unit tests and seems to work properly 😉 |
||
versions[feature] | ||
end | ||
end | ||
|
||
def fetch_version(feature = :default_api) | ||
versions[feature] | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
require 'test_helper' | ||
|
||
class VersionableTest < Test::Unit::TestCase | ||
class ParentClass | ||
include ActiveMerchant::Versionable | ||
end | ||
|
||
class DummyClass < ParentClass | ||
end | ||
|
||
class FakeClass < ParentClass | ||
end | ||
|
||
class DummyChildClass < DummyClass | ||
end | ||
|
||
def setup | ||
@dummy_instance = DummyClass.new | ||
@fake_instance = FakeClass.new | ||
@dummy_child_instance = DummyChildClass.new | ||
end | ||
|
||
def test_class_can_set_and_fetch_default_version | ||
DummyClass.version('1.0') | ||
assert_equal '1.0', DummyClass.fetch_version, 'Class should return the correct version' | ||
end | ||
|
||
def test_class_can_set_and_fetch_custom_feature_version | ||
DummyClass.version('2.0', :custom_api) | ||
DummyClass.version('v2', :some_feature) | ||
assert_equal '2.0', DummyClass.fetch_version(:custom_api), 'Class should return the correct version' | ||
assert_equal 'v2', DummyClass.fetch_version(:some_feature), 'Class should return the correct version' | ||
end | ||
|
||
def test_instance_can_fetch_default_version | ||
DummyClass.version('v3') | ||
assert_equal 'v3', @dummy_instance.fetch_version, 'Instance should return the correct version' | ||
end | ||
|
||
def test_instance_can_fetch_custom_feature_version | ||
DummyClass.version('v4', :custom_api) | ||
DummyClass.version('4.0', :some_feature) | ||
assert_equal 'v4', @dummy_instance.fetch_version(:custom_api), 'Instance should return the correct version' | ||
assert_equal '4.0', @dummy_instance.fetch_version(:some_feature), 'Instance should return the correct version' | ||
end | ||
|
||
def test_fetch_version_returns_nil_for_unset_feature | ||
assert_nil DummyClass.fetch_version(:nonexistent_feature), 'Class should return nil for an unset feature' | ||
assert_nil @dummy_instance.fetch_version(:nonexistent_feature), 'Instance should return nil for an unset feature' | ||
end | ||
|
||
def test_classes_dont_share_versions | ||
# Default key | ||
DummyClass.version('1.0') | ||
FakeClass.version('2.0') | ||
DummyChildClass.version('3.0') | ||
|
||
# Custom key :some_feature | ||
DummyChildClass.version('v5', :some_feature) | ||
FakeClass.version('v3', :some_feature) | ||
DummyClass.version('v4', :some_feature) | ||
|
||
assert_equal '1.0', DummyClass.fetch_version, 'Class should return the correct version' | ||
assert_equal 'v4', DummyClass.fetch_version(:some_feature), 'Class should return the correct version' | ||
assert_equal '2.0', FakeClass.fetch_version, 'Class should return the correct version' | ||
assert_equal 'v3', FakeClass.fetch_version(:some_feature), 'Class should return the correct version' | ||
assert_equal '3.0', DummyChildClass.fetch_version, 'Class should return the correct version' | ||
assert_equal 'v5', DummyChildClass.fetch_version(:some_feature), 'Class should return the correct version' | ||
end | ||
|
||
def test_instances_dont_share_versions | ||
# Default key | ||
DummyClass.version('1.0') | ||
FakeClass.version('2.0') | ||
DummyChildClass.version('3.0') | ||
|
||
# Custom key :some_feature | ||
DummyChildClass.version('v5', :some_feature) | ||
FakeClass.version('v3', :some_feature) | ||
DummyClass.version('v4', :some_feature) | ||
|
||
assert_equal '1.0', @dummy_instance.fetch_version, 'Instance should return the correct version' | ||
assert_equal 'v4', @dummy_instance.fetch_version(:some_feature), 'Instance should return the correct version' | ||
assert_equal '2.0', @fake_instance.fetch_version, 'Instance should return the correct version' | ||
assert_equal 'v3', @fake_instance.fetch_version(:some_feature), 'Instance should return the correct version' | ||
assert_equal '3.0', @dummy_child_instance.fetch_version, 'Instance should return the correct version' | ||
assert_equal 'v5', @dummy_child_instance.fetch_version(:some_feature), 'Instance should return the correct version' | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After the recent issue with this change where gateways would share versions, I added this hook, so a new hash would be created each time the gateway class is inherited. Let me know what you think of this.