Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,22 @@ on: [push]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
ruby-version: ['3.3', '3.4', '4.0']
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: 3.2.1
ruby-version: ${{ matrix.ruby-version }}
bundler-cache: true
- run: bundle install
- run: bundle exec rspec
rubocop:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: 3.2.1
ruby-version: '4.0'
bundler-cache: true
- run: bundle install
- run: bundle exec rubocop
2 changes: 1 addition & 1 deletion .ruby-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.2.2
4.0.1
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.0] - 2026-02-11

### Breaking Changes
- Now requires Faraday 2.x (previously supported Faraday 1.x)
- Dropped support for Ruby versions older than 3.3. Officially supported versions are Ruby 3.3, 3.4, and 4.0.

### Added
- `ServerError` exception class for 500-599 status codes
- `ClientError` exception class for 400-499 status codes (except 401/404)
- Comprehensive test coverage for all error scenarios (401, 404, 400, 422, 500, 503)

### Removed
- Removed juwelier dependency and tasks

## [0.5.1] - Previous release

(Previous changelog entries would go here)
13 changes: 2 additions & 11 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,13 +1,4 @@
source "https://rubygems.org"

gem 'faraday', '> 0.15'

group :development do
gem 'rspec'
gem 'juwelier', git: 'https://github.com/flajann2/juwelier.git'
gem 'rake'
gem 'webmock'
gem 'rubocop'
gem 'dotenv'
gem 'byebug'
end
# Specify your gem's dependencies in mobilize-america-client.gemspec
gemspec
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@

A minimalist API client for the [MobilizeAmerica API](https://github.com/mobilizeamerica/api):

## Requirements

- Ruby 3.3, 3.4, or 4.0
- Faraday ~> 2.0

## Installation

Add this line to your application's Gemfile:
Expand Down
51 changes: 4 additions & 47 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -1,51 +1,8 @@
# encoding: utf-8

require 'rubygems'
require 'bundler'
begin
Bundler.setup(:default, :development)
rescue Bundler::BundlerError => e
$stderr.puts e.message
$stderr.puts "Run `bundle install` to install missing gems"
exit e.status_code
end
require 'rake'
require 'juwelier'
Juwelier::Tasks.new do |gem|
# gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options
gem.name = "mobilize-america-client"
gem.homepage = "http://github.com/controlshift/mobilize_america_client"
gem.license = "MIT"
gem.summary = %Q{Client gem for the MobilizeAmerica API}
gem.email = "grey@controlshiftlabs.com"
gem.authors = ["Grey Moore"]

# dependencies defined in Gemfile
end
Juwelier::RubygemsDotOrgTasks.new
# frozen_string_literal: true

require 'bundler/gem_tasks'
require 'rspec/core/rake_task'
desc "Run specs"
RSpec::Core::RakeTask.new do |t|
t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
t.rspec_opts = '--color'
end


desc "Code coverage detail"
task :simplecov do
ENV['COVERAGE'] = "true"
Rake::Task['test'].execute
end

task :default => :spec

require 'rdoc/task'
Rake::RDocTask.new do |rdoc|
version = File.exist?('VERSION') ? File.read('VERSION') : ""
RSpec::Core::RakeTask.new(:spec)

rdoc.rdoc_dir = 'rdoc'
rdoc.title = "mobilize-america-client #{version}"
rdoc.rdoc_files.include('README*')
rdoc.rdoc_files.include('lib/**/*.rb')
end
task default: :spec
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.5.1
1.0.0
5 changes: 4 additions & 1 deletion lib/mobilize_america_client/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ def initialize(options = {})

api_domain = options[:api_domain] || API_DOMAIN

@connection = Faraday.new(url: "https://#{api_domain}", request: { params_encoder: Faraday::FlatParamsEncoder })
@connection = Faraday.new(url: "https://#{api_domain}", request: { params_encoder: Faraday::FlatParamsEncoder }) do |f|
f.request :json
f.response :json
end
end

include MobilizeAmericaClient::Request
Expand Down
6 changes: 6 additions & 0 deletions lib/mobilize_america_client/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,10 @@ class NotFoundError < StandardError

class UnauthorizedError < StandardError
end

class ServerError < StandardError
end

class ClientError < StandardError
end
end
23 changes: 11 additions & 12 deletions lib/mobilize_america_client/request.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
require 'json'

module MobilizeAmericaClient
module Request
API_DOMAIN = 'api.mobilize.us'.freeze
Expand All @@ -23,24 +21,25 @@ def request(method:, path:, params: {}, body: {})
response = connection.send(method) do |req|
req.path = "#{API_BASE_PATH}#{path}"
req.params = params
req.headers['Content-Type'] = 'application/json'
req.body = body

unless api_key.nil?
req.headers['Authorization'] = "Bearer #{api_key}"
end

req.body = ::JSON.generate(body) unless body.empty?
end

if response.status == 401
raise MobilizeAmericaClient::UnauthorizedError
end

if response.status == 404
raise MobilizeAmericaClient::NotFoundError
case response.status
when 401
raise MobilizeAmericaClient::UnauthorizedError, "Unauthorized: #{response.body}"
when 404
raise MobilizeAmericaClient::NotFoundError, "Not Found: #{response.body}"
when 400..499
raise MobilizeAmericaClient::ClientError, "Client Error (#{response.status}): #{response.body}"
when 500..599
raise MobilizeAmericaClient::ServerError, "Server Error (#{response.status}): #{response.body}"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

end

JSON.parse(response.body)
response.body
end
end
end
100 changes: 34 additions & 66 deletions mobilize-america-client.gemspec
Original file line number Diff line number Diff line change
@@ -1,71 +1,39 @@
# Generated by juwelier
# DO NOT EDIT THIS FILE DIRECTLY
# Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
# -*- encoding: utf-8 -*-
# stub: mobilize-america-client 0.5.1 ruby lib
# frozen_string_literal: true

Gem::Specification.new do |s|
s.name = "mobilize-america-client".freeze
s.version = "0.5.1"
lib = File.expand_path('lib', __dir__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)

s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
s.require_paths = ["lib".freeze]
s.authors = ["Grey Moore".freeze]
s.date = "2023-03-02"
s.email = "grey@controlshiftlabs.com".freeze
s.executables = ["console".freeze, "setup".freeze]
s.extra_rdoc_files = [
"LICENSE",
"LICENSE.txt",
"README.md"
]
s.files = [
".github/workflows/ci.yml",
".rspec",
".rubocop.yml",
".ruby-gemset",
".ruby-version",
"CODE_OF_CONDUCT.md",
"Gemfile",
"LICENSE",
"LICENSE.txt",
"README.md",
"Rakefile",
"VERSION",
"bin/console",
"bin/setup",
"example.rb",
"lib/mobilize_america_client.rb",
"lib/mobilize_america_client/client.rb",
"lib/mobilize_america_client/client/attendances.rb",
"lib/mobilize_america_client/client/enums.rb",
"lib/mobilize_america_client/client/events.rb",
"lib/mobilize_america_client/client/organizations.rb",
"lib/mobilize_america_client/errors.rb",
"lib/mobilize_america_client/request.rb",
"mobilize-america-client.gemspec",
"spec/client/attendances_spec.rb",
"spec/client/client_spec.rb",
"spec/client/enums_spec.rb",
"spec/client/events_spec.rb",
"spec/client/organizations_spec.rb",
"spec/fixtures/organizations.json",
"spec/spec_helper.rb"
]
s.homepage = "http://github.com/controlshift/mobilize_america_client".freeze
s.licenses = ["MIT".freeze]
s.rubygems_version = "3.4.3".freeze
s.summary = "Client gem for the MobilizeAmerica API".freeze
Gem::Specification.new do |spec|
spec.name = 'mobilize-america-client'
spec.version = File.read(File.expand_path('VERSION', __dir__)).strip
spec.authors = ['Grey Moore', 'Owens Ehimen', 'Diego Marcet']
spec.email = ['talk@controlshiftlabs.com']

s.specification_version = 4
spec.summary = 'Client gem for the MobilizeAmerica API'
spec.description = 'A Ruby client library for the MobilizeAmerica API'
spec.homepage = 'https://github.com/controlshift/mobilize_america_client'
spec.license = 'MIT'

s.add_runtime_dependency(%q<faraday>.freeze, ["> 0.15"])
s.add_development_dependency(%q<rspec>.freeze, [">= 0"])
s.add_development_dependency(%q<juwelier>.freeze, [">= 0"])
s.add_development_dependency(%q<rake>.freeze, [">= 0"])
s.add_development_dependency(%q<webmock>.freeze, [">= 0"])
s.add_development_dependency(%q<rubocop>.freeze, [">= 0"])
s.add_development_dependency(%q<dotenv>.freeze, [">= 0"])
s.add_development_dependency(%q<byebug>.freeze, [">= 0"])
end
spec.files = Dir.chdir(File.expand_path(__dir__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
end
spec.bindir = 'bin'
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this was in here before, but is the stuff in bin actually useful to clients of the gem? Or just developers? (Or, uh, no one at all, if they're just automatically generated stubs)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Autogenerated stubs - best to leave it in IMO

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤷 I'm happy to keep the stubs, but making them part of the released gem feels like an accident. It would be unexpected for a client who installed this gem in their project to type "console" or "setup" and get something specific to the gem.

(My understanding is that the executables setting is intended for gems like rake, where the point of installing it is that now you can run rake in your project.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I havent released the gem yet so I can take it out

spec.require_paths = ['lib']

spec.required_ruby_version = ['>= 3.3', '< 5.0']

# Runtime dependencies
spec.add_runtime_dependency 'faraday', '~> 2.0'

# Development dependencies
spec.add_development_dependency 'bundler', '>= 2.0', '< 5.0'
spec.add_development_dependency 'byebug', '~> 13.0'
spec.add_development_dependency 'dotenv', '~> 3.0'
spec.add_development_dependency 'rake', '~> 13.0'
spec.add_development_dependency 'rspec', '~> 3.0'
spec.add_development_dependency 'rubocop', '~> 1.0'
spec.add_development_dependency 'webmock', '~> 3.0'

spec.metadata['rubygems_mfa_required'] = 'true'
end
17 changes: 9 additions & 8 deletions spec/client/attendances_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

RSpec.describe MobilizeAmericaClient::Client::Attendances do
let(:api_key) { 'abcde-123456' }
let(:expected_headers) { {'Content-Type' => 'application/json', 'Authorization' => "Bearer #{api_key}"} }
let(:request_headers) { { 'Content-Type' => 'application/json', 'Authorization' => "Bearer #{api_key}"} }
let(:response_headers) {{ 'Content-Type' => 'application/json' }}
let(:base_url) { "https://#{MobilizeAmericaClient::Client::API_DOMAIN}#{MobilizeAmericaClient::Client::API_BASE_PATH}" }

subject { MobilizeAmericaClient::Client.new(api_key: api_key) }
Expand All @@ -23,34 +24,34 @@
end

it 'should raise if response status is 404' do
stub_request(:get, attendances_url).with(headers: expected_headers).to_return(status: 404, body: {error: 'not found'}.to_json)
stub_request(:get, attendances_url).with(headers: request_headers).to_return(status: 404, body: { error: 'not found'}.to_json)

expect { subject.organization_attendances(organization_id: org_id) }.to raise_error MobilizeAmericaClient::NotFoundError
end

it 'should call the endpoint and return JSON' do
stub_request(:get, attendances_url).with(headers: expected_headers).to_return(body: response.to_json)
stub_request(:get, attendances_url).with(headers: request_headers).to_return(body: response.to_json, headers: response_headers)
expect(subject.organization_attendances(organization_id: org_id)).to eq(response)
end

it 'should escape the organization ID' do
expected_url = "#{base_url}/organizations/foo%2Fbar/attendances"
stub_request(:get, expected_url).with(headers: expected_headers).to_return(body: response.to_json)
stub_request(:get, expected_url).with(headers: request_headers).to_return(body: response.to_json, headers: response_headers)
expect(subject.organization_attendances(organization_id: 'foo/bar')).to eq(response)
end

it 'should support an updated_since parameter' do
updated_since = Time.new
stub_request(:get, attendances_url)
.with(headers: expected_headers, query: {updated_since: updated_since.to_i})
.to_return(body: response.to_json)
.with(headers: request_headers, query: { updated_since: updated_since.to_i})
.to_return(body: response.to_json, headers: response_headers)
expect(subject.organization_attendances(organization_id: org_id, updated_since: updated_since)).to eq(response)
end

it 'should support pagination parameters' do
stub_request(:get, attendances_url)
.with(headers: expected_headers, query: {page: 2, per_page: 100})
.to_return(body: response.to_json)
.with(headers: request_headers, query: { page: 2, per_page: 100})
.to_return(body: response.to_json, headers: response_headers)
expect(subject.organization_attendances(organization_id: org_id, page: 2, per_page: 100)).to eq(response)
end
end
Expand Down
2 changes: 1 addition & 1 deletion spec/client/enums_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
let(:response) { {'hello' => 'world'} }

it 'should call the endpoint and return JSON' do
stub_request(:get, enums_url).with(headers: standard_headers).to_return(body: response.to_json)
stub_request(:get, enums_url).with(headers: standard_headers).to_return(body: response.to_json, headers: { 'Content-Type' => 'application/json' })
expect(subject.enums).to eq response
end
end
Expand Down
Loading