iter 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0e2f0aa5860775ce8a35e8071cea50f55340a251d177e6ea71c52acc6a30165b
4
+ data.tar.gz: 0fad5fafa01504d396598a07184eaeaadc3675f60317e04d51f74b8ffc1fd3f4
5
+ SHA512:
6
+ metadata.gz: e5269a14093d64f8b193cdb72d2f5838a418cc7c6ff910a0d560cdfcc371cfe7edf1baf0cdeb334427b09e1aa5c394a08d8960c63b0133d52b6f35f94b438c3c
7
+ data.tar.gz: e42058290262b82e5d20215694fb00598e4fa839e7e4e3d0a43abb5382576293ce21a85f09d886316c736af8e82608f319baa3cd4ebc0d48776a984014b271ab
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,17 @@
1
+ # Omakase Ruby styling for Rails
2
+ inherit_gem: { rubocop-rails-omakase: rubocop.yml }
3
+
4
+ AllCops:
5
+ TargetRubyVersion: 3.2.2
6
+
7
+ Style/StringLiterals:
8
+ Enabled: false
9
+
10
+ Layout/SpaceInsideArrayLiteralBrackets:
11
+ Enabled: false
12
+
13
+ Style/TrailingCommaInHashLiteral:
14
+ Enabled: false
15
+
16
+ Style/TrailingCommaInArrayLiteral:
17
+ Enabled: false
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## [0.1.0] - 2025-03-11
2
+
3
+ - Initial release
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 HouseAccount, Inc.
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,113 @@
1
+ Iter - a Ruby client for the Iterable API
2
+ ======================================================
3
+
4
+ Iter helps you write apps that need to interact with Iterable.
5
+
6
+ The **source code** is available on [GitHub](https://github.com/claudiob/iter) and the **documentation** on [RubyDoc](http://www.rubydoc.info/gems/iter/frames).
7
+
8
+ [![Code Climate](https://codeclimate.com/github/claudiob/iter.png)](https://codeclimate.com/github/claudiob/iter)
9
+ [![Code coverage](https://img.shields.io/badge/code_coverage-100%25-44d298)](https://github.com/claudiob/bookmaker/actions)
10
+ [![Rubygems](https://img.shields.io/gem/v/iter)](https://rubygems.org/gems/iter)
11
+
12
+ After [registering your app](#configuring-your-app), you can run commands like:
13
+
14
+
15
+ ```ruby
16
+ Iter::User.new(id:).update email:, fields:
17
+ Iter::User.new(id:).delete
18
+ Iter::Event.create(user_id:, name:, fields:)
19
+ ```
20
+
21
+ The **full documentation** is available at [rubydoc.info](http://www.rubydoc.info/gems/iter/frames).
22
+
23
+ How to install
24
+ ==============
25
+
26
+ To install on your system, run
27
+
28
+ gem install iter
29
+
30
+ To use inside a bundled Ruby project, add this line to the Gemfile:
31
+
32
+ gem 'iter', '~> 1.0'
33
+
34
+ Since the gem follows [Semantic Versioning](http://semver.org),
35
+ indicating the full version in your Gemfile (~> *major*.*minor*.*patch*)
36
+ guarantees that your project won’t occur in any error when you `bundle update`
37
+ and a new version of Iter is released.
38
+
39
+ Configuring your app
40
+ ====================
41
+
42
+ In order to use Iter you must have credentials to the [Iterable](https://www.iterable.com/) API.
43
+
44
+ Add them to your code with the following snippet of code (replacing with your own credentials):
45
+
46
+ ```ruby
47
+ Iter.configure do |config|
48
+ config.api_key = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ012345'
49
+ end
50
+ ```
51
+
52
+ Mocking the Iterable API
53
+ ======================
54
+
55
+ Sometimes you want to mock the API requests to Iterable and obtain results that
56
+ are equivalent to the original API calls. This can be useful to test your flow
57
+ without hitting the API.
58
+
59
+
60
+ Configuring with environment variables
61
+ --------------------------------------
62
+
63
+ As an alternative to the approach above, you can configure your app with
64
+ variables. Setting the following environment variables:
65
+
66
+ ```bash
67
+ export ITER_API_KEY="ABCDEFGHIJKLMNOPQRSTUVWXYZ012345"
68
+ ```
69
+
70
+ is equivalent to the previous approach so pick the one you prefer.
71
+ If a variable is set in both places, then `Iter.configure` takes precedence.
72
+
73
+ How to test
74
+ ===========
75
+
76
+ To run tests:
77
+
78
+ ```bash
79
+ rspec
80
+ ```
81
+
82
+ By default, tests are run with real HTTP calls to Iterable that must be
83
+ set with the environment variables specified above.
84
+
85
+ If you do not have access to Iterable, you can still run the tests mocked:
86
+
87
+ ```bash
88
+ ITER_MOCK=1 rspec
89
+ ```
90
+
91
+ How to release new versions
92
+ ===========================
93
+
94
+ If you are a manager of this project, remember to upgrade the [Iter gem](http://rubygems.org/gems/iter)
95
+ whenever a new feature is added or a bug gets fixed.
96
+ Make sure all the tests are passing and the code is 100% test-covered.
97
+ Document the changes in CHANGELOG.md and README.md, bump the version, then run:
98
+
99
+ rake release
100
+
101
+ Remember that the iter gem follows [Semantic Versioning](http://semver.org).
102
+ Any new release that is fully backward-compatible should bump the *minor* version (1.x).
103
+ Any new version that breaks compatibility should bump the *major* version (x.0)
104
+
105
+ How to contribute
106
+ =================
107
+
108
+ Iter needs your support!
109
+ The goal of Iter is to provide a Ruby interface for all the methods exposed by the Iterable API.
110
+
111
+ If you find that a method is missing, fork the project, add the missing code,
112
+ write the appropriate tests, then submit a pull request, and it will gladly
113
+ be merged!
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -0,0 +1,45 @@
1
+ require 'iter/configuration'
2
+
3
+ # An object-oriented Ruby client for Iterable.
4
+ # @see http://www.rubydoc.info/gems/iter/
5
+ module Iter
6
+ # Provides methods to read and write global configuration settings.
7
+ #
8
+ # A typical usage is to set the API key for the API calls.
9
+ #
10
+ # @example Set the API key for the API client:
11
+ # Iter.configure do |config|
12
+ # config.api_key = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ012345'
13
+ # end
14
+ #
15
+ module Config
16
+ # Yields the global configuration to the given block.
17
+ #
18
+ # @example
19
+ # Iter.configure do |config|
20
+ # config.api_key = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ012345'
21
+ # end
22
+ #
23
+ # @yield [Iter::Models::Configuration] The global configuration.
24
+ def configure
25
+ yield configuration if block_given?
26
+ end
27
+
28
+ # Returns the global {Iter::Models::Configuration} object.
29
+ #
30
+ # While this method _can_ be used to read and write configuration settings,
31
+ # it is easier to use {Iter::Config#configure} Iter.configure}.
32
+ #
33
+ # @example
34
+ # Iter.configuration.api_key = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ012345'
35
+ #
36
+ # @return [Iter::Models::Configuration] The global configuration.
37
+ def configuration
38
+ @configuration ||= Iter::Configuration.new
39
+ end
40
+ end
41
+
42
+ # @note Config is the only module auto-loaded in the Iter module,
43
+ # in order to have a syntax as easy as Iter.configure
44
+ extend Config
45
+ end
@@ -0,0 +1,39 @@
1
+ module Iter
2
+ # Provides an object to store global configuration settings.
3
+ #
4
+ # This class is typically not used directly, but by calling
5
+ # {Iter::Config#configure Iter.configure}, which creates and updates a single
6
+ # instance of {Iter::Models::Configuration}.
7
+ #
8
+ # @example Set the API key for the API client:
9
+ # Iter.configure do |config|
10
+ # config.api_key = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ012345'
11
+ # end
12
+ #
13
+ # @see Iter::Config for more examples.
14
+ #
15
+ # An alternative way to set global configuration settings is by storing
16
+ # them in the following environment variables:
17
+ #
18
+ # * +ITER_API_KEY+ to store the API key for the Iterable API
19
+ #
20
+ # In case both methods are used together,
21
+ # {Iter::Config#configure Iter.configure} takes precedence.
22
+ #
23
+ # @example Set the API credentials
24
+ # ENV['ITER_API_KEY'] = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ012345'
25
+ class Configuration
26
+ # @return [String] the API key for the API calls.
27
+ attr_accessor :api_key
28
+
29
+ # @return [Boolean] whether to mock the HTTP calls to Iterable
30
+ attr_accessor :mock
31
+
32
+ # Initialize the global configuration settings, using the values of
33
+ # the specified following environment variables by default.
34
+ def initialize
35
+ @api_key = ENV['ITER_API_KEY']
36
+ @mock = ENV['ITER_MOCK'] == '1'
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,5 @@
1
+ module Iter
2
+ # A wrapper around StandardError.
3
+ class ConnectionError < StandardError
4
+ end
5
+ end
data/lib/iter/error.rb ADDED
@@ -0,0 +1,5 @@
1
+ module Iter
2
+ # A wrapper around StandardError.
3
+ class Error < StandardError
4
+ end
5
+ end
@@ -0,0 +1,88 @@
1
+ require 'json'
2
+ require 'net/http'
3
+
4
+ require 'iter/connection_error'
5
+ require 'iter/error'
6
+
7
+ module Iter
8
+ # A wrapper around +Net::HTTP+ to send HTTP requests to the Iterable API and
9
+ # return their result or raise an error if the result is unexpected.
10
+ # The basic way to use HTTPRequest is by calling +run+ on an instance.
11
+ # @example List the species of all breeds.
12
+ # path = '/api/v1/resources/breeds/'
13
+ # body = {securityToken: api_key}
14
+ # response = Iter::HTTPRequest.new(path: path, body: body).run
15
+ # response['Breeds'].map{|breed| breed['species']}
16
+ # @api private
17
+ class Request
18
+ # Initializes an HTTPRequest object.
19
+ # @param [Hash] options the options for the request.
20
+ # @option options [String] :path The path of the request URI.
21
+ # @option options [Hash] :body The body of the request.
22
+ def initialize(options = {})
23
+ @path = options[:path]
24
+ @body = options[:body]
25
+ end
26
+
27
+ # Sends the request and returns the body parsed from the JSON response.
28
+ # @return [Hash] the body parsed from the JSON response.
29
+ # @raise [Iter::ConnectionError] if the request fails.
30
+ # @raise [Iter::Error] if parsed body includes errors.
31
+ def run
32
+ return {} if response.body.empty?
33
+ JSON(response.body).tap do |data|
34
+ raise Error, "#{data['errMsg']} #{data['Errors']}" unless data['errNo'].zero?
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ # Run the request and memoize the response or the server error received.
41
+ def response
42
+ @response ||= Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
43
+ http.request http_request
44
+ end
45
+ rescue *server_errors => e
46
+ raise ConnectionError, e.message
47
+ end
48
+
49
+ # @return [URI::HTTPS] the (memoized) URI of the request.
50
+ def uri
51
+ attributes = { host: 'https://api.iterable.com', path: @path }
52
+ @uri ||= URI::HTTPS.build attributes
53
+ end
54
+
55
+ # @return [Net::HTTPRequest] the full HTTP request object,
56
+ # inclusive of headers of request body.
57
+ def http_request
58
+ http_class = @query.any? ? Net::HTTP::Get : Net::HTTP::Post
59
+
60
+ @http_request ||= http_class.new(uri.request_uri).tap do |request|
61
+ set_request_body! request
62
+ set_request_headers! request
63
+ end
64
+ end
65
+
66
+ # Adds the request headers to the request in the appropriate format.
67
+ # The User-Agent header is also set to recognize the request.
68
+ def set_request_headers!(request)
69
+ request.initialize_http_header 'Content-Type' => 'application/json'
70
+ request.add_field 'User-Agent', 'Iter::HTTPRequest'
71
+ end
72
+
73
+ # Adds the request body to the request in the appropriate format.
74
+ def set_request_body!(request)
75
+ request.body = @body.to_json if @body
76
+ end
77
+
78
+ # Returns the list of server errors worth retrying the request once.
79
+ def server_errors
80
+ [
81
+ Errno::ECONNRESET, Errno::EHOSTUNREACH, Errno::ENETUNREACH,
82
+ Errno::ETIMEDOUT, Errno::ECONNREFUSED, Net::HTTPServerError,
83
+ OpenSSL::SSL::SSLError, OpenSSL::SSL::SSLErrorWaitReadable,
84
+ Net::OpenTimeout, SocketError,
85
+ ]
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,34 @@
1
+ module Iter
2
+ # A mock version of HTTPRequest which returns pre-built responses.
3
+ # @example Updates the email of a user.
4
+ # path = '/api/users/update'
5
+ # body = {userId: '666', email: 'iter@example.com'}
6
+ # response = Iter::Request.new(path: path, body: body).run
7
+ # response['msg'] # => User successfully created/updated.
8
+ # @api private
9
+ class Request
10
+ # Initializes an MockRequest object.
11
+ # @param [Hash] options the options for the request.
12
+ # @option options [String] :path The path of the request URI.
13
+ # @option options [Hash] :body The body of the request.
14
+ def initialize(options = {})
15
+ @path = options[:path]
16
+ @body = options[:body]
17
+ end
18
+
19
+ # Sends the request and returns the body parsed from the JSON response.
20
+ # @return [Hash] the body parsed from the JSON response.
21
+ # @raise [Iter::ConnectionError] if the request fails.
22
+ # @raise [Iter::Error] if parsed body includes errors.
23
+ def run
24
+ case @path
25
+ when '/api/users/update'
26
+ # if @body[:email].include? '@'
27
+ { "msg" => "User successfully created/updated.", "code" => "Success" }
28
+ # else
29
+ # raise Error.new '{"msg"=>"Invalid email: iterexample.com", "code": "InvalidEmailAddressError"}'
30
+ # end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,89 @@
1
+ module Iter
2
+ # Provides methods to iterate through collections of Iterable resources.
3
+ # @private
4
+ class Relation
5
+ include Enumerable
6
+
7
+ # @yield [Hash] the options to change which items to iterate through.
8
+ def initialize(options = {}, &item_block)
9
+ @options = {parts: ['objectID', 'isActive'], next_page: 1}
10
+ @options.merge! options
11
+ @item_block = item_block
12
+ end
13
+
14
+ # Specifies which items to fetch when hitting the data API.
15
+ # @param [Hash<Symbol, String>] conditions The conditions for the items.
16
+ # @return [Iter::Relation] itself.
17
+ def where(conditions = {})
18
+ if @options[:conditions] != conditions
19
+ @items = []
20
+ @options.merge! conditions: conditions
21
+ end
22
+ self
23
+ end
24
+
25
+ # Specifies which parts of the resource to fetch when hitting the data API.
26
+ # @param [Array<Symbol>] parts The parts to fetch.
27
+ # @return [Iter::Relation] itself.
28
+ def select(*parts)
29
+ if @options[:parts] != parts + ['objectID', 'isActive']
30
+ @items = nil
31
+ @options.merge! parts: (parts + ['objectID', 'isActive'])
32
+ end
33
+ self
34
+ end
35
+
36
+ def each
37
+ @last_index = 0
38
+ while next_item = find_next
39
+ yield next_item
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def request(path:, body: {}, query: {}, include_api_key: true)
46
+ if query.none? && include_api_key
47
+ body = body.merge securityToken: Iter.configuration.api_key
48
+ end
49
+
50
+ response_for path:, body:, query:
51
+ end
52
+
53
+ def response_for(path:, body: {}, query: {})
54
+ instrument do |data|
55
+ Request.new(path: path, query: query, body: body).run
56
+ end
57
+ end
58
+
59
+ def find_next
60
+ @items ||= []
61
+ if @items[@last_index].nil? && more_pages?
62
+ body = @options[:conditions].merge pageSize: 500, pageNo: @options[:next_page], sortMode: 3, sortDirection: 0
63
+
64
+ response_body = request path: '/api/v1/data/Contacts/', body: body
65
+
66
+ more_items = response_body['Contacts'].map do |contact_data|
67
+ Iter::Contact.new contact_data.slice(*@options[:parts])
68
+ end
69
+ @options.merge! next_page: (more_items.size == 500 ? @options[:next_page] + 1 : 1)
70
+ @items.concat more_items
71
+ end
72
+ @items[(@last_index +=1) -1]
73
+ end
74
+
75
+ def more_pages?
76
+ @last_index.zero? || @options[:next_page] > 1
77
+ end
78
+
79
+ # Provides instrumentation to ActiveSupport listeners
80
+ def instrument(&block)
81
+ data = { class_name: 'Iter::Contact' }
82
+ if defined?(ActiveSupport::Notifications)
83
+ ActiveSupport::Notifications.instrument 'Iter.request', data, &block
84
+ else
85
+ block.call data
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,12 @@
1
+ require ENV['ITER_MOCK'] ? 'iter/mock_request' : 'iter/http_request'
2
+
3
+ module Iter
4
+ # Provides an abstract class for every Iterable resource.
5
+ class Resource
6
+ private
7
+
8
+ def self.request(path:, body: {})
9
+ Request.new(path: path, body: body).run
10
+ end
11
+ end
12
+ end
data/lib/iter/user.rb ADDED
@@ -0,0 +1,26 @@
1
+ module Iter
2
+ # Provides methods to interact with Iterable users.
3
+ class User < Resource
4
+ PATH = '/api/v1/data/Clients/'
5
+
6
+ attr_reader :id, :email
7
+
8
+ def initialize(id:)
9
+ @id = id
10
+ end
11
+
12
+
13
+ def self.update(email:, fields:)
14
+ body = {
15
+ userId: id,
16
+ email: email,
17
+ dataFields: fields,
18
+ }
19
+
20
+ data = request path: '/api/users/update', body: body
21
+
22
+ email = value_for_field data, 238
23
+ new id: contact_id, email: email
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,3 @@
1
+ module Iter
2
+ VERSION = '0.1.0'
3
+ end
data/lib/iter.rb ADDED
@@ -0,0 +1,10 @@
1
+ # Used by multiple resources
2
+ require 'uri'
3
+ require_relative 'iter/error'
4
+ require_relative 'iter/config'
5
+
6
+ # Individual resources
7
+ require_relative 'iter/connection_error'
8
+ require_relative 'iter/event'
9
+ require_relative 'iter/user'
10
+ require_relative 'iter/version'
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: iter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - claudiob
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: simplecov
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rubocop
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: yard
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ description: A Ruby client for the Iterable API.
55
+ email:
56
+ - claudiob@users.noreply.github.com
57
+ executables: []
58
+ extensions: []
59
+ extra_rdoc_files: []
60
+ files:
61
+ - ".rspec"
62
+ - ".rubocop.yml"
63
+ - CHANGELOG.md
64
+ - LICENSE.txt
65
+ - README.md
66
+ - Rakefile
67
+ - lib/iter.rb
68
+ - lib/iter/config.rb
69
+ - lib/iter/configuration.rb
70
+ - lib/iter/connection_error.rb
71
+ - lib/iter/error.rb
72
+ - lib/iter/http_request.rb
73
+ - lib/iter/mock_request.rb
74
+ - lib/iter/relation.rb
75
+ - lib/iter/resource.rb
76
+ - lib/iter/user.rb
77
+ - lib/iter/version.rb
78
+ homepage: https://rubygems.org/gems/iter
79
+ licenses:
80
+ - MIT
81
+ metadata:
82
+ homepage_uri: https://rubygems.org/gems/iter
83
+ source_code_uri: https://github.com/claudiob/iter
84
+ changelog_uri: https://github.com/claudiob/iter/blob/main/CHANGELOG.md
85
+ rdoc_options: []
86
+ require_paths:
87
+ - lib
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: 3.0.0
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ requirements: []
99
+ rubygems_version: 3.6.9
100
+ specification_version: 4
101
+ summary: A Ruby client for the Iterable API.
102
+ test_files: []