Skip to content
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

Adds Sorbet and basic type coverage #6

Merged
merged 6 commits into from
Jul 17, 2023
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
30 changes: 28 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
- uses: actions/cache@v3
with:
path: vendor/bundle
key: ${{ runner.os }}-${{ matrix.ruby }}-${{ matrix.redis-version }}-gems-${{ hashFiles('**/Gemfile.lock') }}
key: ${{ runner.os }}-${{ matrix.ruby }}-${{ matrix.redis-version }}-gems-${{ hashFiles('**/Gemfile.lock') }}-v2
restore-keys: |
${{ runner.os }}-gems-

Expand All @@ -44,5 +44,31 @@ jobs:
- name: Run RSspec
run: bundle exec rspec --format documentation

- name: Run Sorbet Typechecks
run: bundle exec spoom tc
lint:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[skip ci]')"
steps:
- uses: actions/checkout@v3

- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.2

- uses: actions/cache@v3
with:
path: vendor/bundle
key: ${{ runner.os }}-3.2-gems-${{ hashFiles('**/Gemfile.lock') }}-v2
restore-keys: |
${{ runner.os }}-gems-

- name: bundle install
run: |
gem install bundler
bundle config path vendor/bundle
bundle install -j $(getconf _NPROCESSORS_ONLN) --retry 3

- name: Run Rubocop
run: bundle exec rubocop
run: bundle exec rubocop --parallel
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
/spec/reports/
/tmp/
.ruby-version
.irb_history
1 change: 1 addition & 0 deletions .irbrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require_relative './lib/money_distributed' # rubocop:disable Packaging/RequireRelativeHardcodingLib
50 changes: 47 additions & 3 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,49 @@
Style/FileName:
inherit_from: .rubocop_todo.yml

inherit_gem:
rubocop-dbl:
- config/dbl.yml

AllCops:
TargetRubyVersion: 3.0
Exclude:
- 'sorbet/**/*'
- 'bin/*'
- 'vendor/**/*'

Layout/LineLength:
Max: 120

Style/IfUnlessModifier:
Enabled: false

Lint/UnusedMethodArgument:
Enabled: false

Style/AccessModifierDeclarations:
EnforcedStyle: inline

Naming/PredicateName:
Enabled: false

Lint/BooleanSymbol:
Enabled: false

Style/HashSyntax:
Enabled: false

Style/FrozenStringLiteralComment:
Enabled: false

Gemspec/RequiredRubyVersion:
Enabled: false
Naming/BlockForwarding:
Enabled: false

Metrics/MethodLength:
CountAsOne: ['array', 'heredoc', 'method_call']

Gemspec/DevelopmentDependencies:
EnforcedStyle: gemspec

# we do not have active support for Time.zone.now
Rails/TimeZone:
Enabled: false
31 changes: 31 additions & 0 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2023-07-17 16:35:16 UTC using RuboCop version 1.49.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.

# Offense count: 1
# Configuration parameters: CountAsOne.
RSpec/ExampleLength:
Max: 10

# Offense count: 1
RSpec/MultipleExpectations:
Max: 9

# Offense count: 7
# Configuration parameters: EnforcedStyle, IgnoreSharedExamples.
# SupportedStyles: always, named_only
RSpec/NamedSubject:
Exclude:
- 'spec/money/distributed/fetcher/file_spec.rb'
- 'spec/money/distributed/storage_spec.rb'

# Offense count: 2
# Configuration parameters: IgnoreNameless, IgnoreSymbolicNames.
RSpec/VerifiedDoubles:
Exclude:
- 'spec/money/distributed/fetcher/file_spec.rb'
- 'spec/money/distributed/redis_spec.rb'
20 changes: 20 additions & 0 deletions bin/lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env ruby

require 'English'

unless system('bundle exec rubocop --parallel --ignore-unrecognized-cops')
puts $CHILD_STATUS
exit(1)
end

unless system('bundle exec spoom tc')
puts $CHILD_STATUS
exit(1)
end

unless system('bundle exec rspec --format documentation')
puts $CHILD_STATUS
exit(1)
end

puts "\n\n✅ ALL GOOD!"
27 changes: 27 additions & 0 deletions bin/rubocop
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

#
# This file was generated by Bundler.
#
# The application 'rubocop' is installed as part of a gem, and
# this file is here to facilitate running it.
#

ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)

bundle_binstub = File.expand_path("bundle", __dir__)

if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
load(bundle_binstub)
else
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
end
end

require "rubygems"
require "bundler/setup"

load Gem.bin_path("rubocop", "rubocop")
27 changes: 27 additions & 0 deletions bin/spoom
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

#
# This file was generated by Bundler.
#
# The application 'spoom' is installed as part of a gem, and
# this file is here to facilitate running it.
#

ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)

bundle_binstub = File.expand_path("bundle", __dir__)

if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
load(bundle_binstub)
else
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
end
end

require "rubygems"
require "bundler/setup"

load Gem.bin_path("spoom", "spoom")
27 changes: 27 additions & 0 deletions bin/tapioca
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

#
# This file was generated by Bundler.
#
# The application 'tapioca' is installed as part of a gem, and
# this file is here to facilitate running it.
#

ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)

bundle_binstub = File.expand_path("bundle", __dir__)

if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
load(bundle_binstub)
else
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
end
end

require "rubygems"
require "bundler/setup"

load Gem.bin_path("tapioca", "tapioca")
5 changes: 0 additions & 5 deletions lib/money-distributed.rb

This file was deleted.

15 changes: 6 additions & 9 deletions lib/money/distributed/fetcher/base.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# typed: true
# frozen_string_literal: true

require 'money'

class Money
module Distributed
module Fetcher
Expand All @@ -17,22 +16,20 @@ def fetch

currencies.each { |cur| add_rate(cur, cur, 1) }

currencies.combination(2).each do |curr1, curr2|
rate = rates[curr2] / rates[curr1]
add_rate(curr1, curr2, rate)
currencies.combination(2).each do |curr_1, curr_2|
rate = rates[curr_2] / rates[curr_1]
add_rate(curr_1, curr_2, rate)
end
end

private

def add_rate(from_iso, to_iso, rate)
private def add_rate(from_iso, to_iso, rate)
@bank.add_rate(from_iso, to_iso, rate.round(4))
return if from_iso == to_iso

@bank.add_rate(to_iso, from_iso, (1 / rate).round(4))
end

def exchange_rates
private def exchange_rates
raise NotImplementedError
end
end
Expand Down
21 changes: 15 additions & 6 deletions lib/money/distributed/fetcher/file.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# typed: strict
# frozen_string_literal: true

require 'json'
Expand All @@ -9,19 +10,27 @@ module Distributed
module Fetcher
# Fetcher that loads rates from a file
class File
extend T::Sig
include Base

sig do
params(
file_path: T.any(String, Pathname),
bank: T.untyped,
).void
end
def initialize(file_path, bank = nil)
super(bank)
@file_path = file_path
end

private

def exchange_rates
::File.open(@file_path).read.split("\n").each_with_object({}) do |line, h|
code_rate = line.split(' ')
h[code_rate[0]] = BigDecimal(code_rate[1])
sig { returns(T::Hash[String, BigDecimal]) }
private def exchange_rates
::File.read(@file_path).split("\n").each_with_object({}) do |line, h|
code_rate = line.split
currency = T.cast(code_rate[0], String)
rate = T.cast(code_rate[1], T.any(Integer, Float, Rational, BigDecimal, String))
h[currency] = BigDecimal(rate)
end
end
end
Expand Down
29 changes: 20 additions & 9 deletions lib/money/distributed/redis.rb
Original file line number Diff line number Diff line change
@@ -1,24 +1,36 @@
# typed: true
# frozen_string_literal: true

require 'redis'
require 'connection_pool'

class Money
module Distributed
# Wrapper over different parameters that can be provided for redis
class Redis
extend T::Sig

sig do
params(
redis: T.any(::Redis, ConnectionPool, Hash, Proc),
).void
end
def initialize(redis)
@redis_proc = build_redis_proc(redis)
end

sig do
params(
block: T.proc.returns(T.untyped),
).returns(T.untyped)
end
def exec(&block)
@redis_proc.call(&block)
end

private

# rubocop: disable Metrics/MethodLength
def build_redis_proc(redis)
sig do
params(
redis: T.any(::Redis, ConnectionPool, Hash, Proc),
).returns(Proc)
end
private def build_redis_proc(redis)
case redis
when ::Redis
proc { |&b| b.call(redis) }
Expand All @@ -29,10 +41,9 @@ def build_redis_proc(redis)
when Proc
redis
else
raise ArgumentError, 'Redis, ConnectionPool, Hash or Proc is required'
T.absurd(redis)
end
end
# rubocop: enable Metrics/MethodLength
end
end
end
Loading