From 7d1156f8e70d4a7c46aae12d63539310308b83ab Mon Sep 17 00:00:00 2001 From: Michael Baldry Date: Tue, 6 Sep 2016 13:43:34 +0100 Subject: [PATCH 1/5] Updating ruby version. Making it accept a lambda that can checkout Redis from a connection pool. Using MULTI instead of pipelining the requests, so they are atomic --- .ruby-version | 2 +- lib/ratelimit.rb | 37 ++++++++++++++++++++++++++++--------- spec/ratelimit_spec.rb | 19 +++++++++++++++++-- 3 files changed, 46 insertions(+), 12 deletions(-) diff --git a/.ruby-version b/.ruby-version index ec6b00f..c1026d2 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -ruby-2.1.2 +ruby-2.3.1 diff --git a/lib/ratelimit.rb b/lib/ratelimit.rb index 607df69..8268a15 100644 --- a/lib/ratelimit.rb +++ b/lib/ratelimit.rb @@ -11,6 +11,7 @@ class Ratelimit # @option options [Integer] :bucket_interval (5) How many seconds each bucket represents # @option options [Integer] :bucket_expiry (@bucket_span) How long we keep data in each bucket before it is auto expired. Cannot be larger than the bucket_span. # @option options [Redis] :redis (nil) Redis client if you need to customize connection options + # @option options [Lambda] :checkout_redis_with (nil) Lambda that yields to a passed block with a Redis instance, for use with a Redis connection pool # # @return [RateLimit] RateLimit instance # @@ -30,6 +31,7 @@ def initialize(key, options = {}) raise ArgumentError.new("Cannot have less than 3 buckets") end @redis = options[:redis] + @checkout_redis_with = options[:checkout_redis_with] end # Add to the counter for a given subject. @@ -41,12 +43,14 @@ def initialize(key, options = {}) def add(subject, count = 1) bucket = get_bucket subject = "#{@key}:#{subject}" - redis.pipelined do - redis.hincrby(subject, bucket, count) - redis.hdel(subject, (bucket + 1) % @bucket_count) - redis.hdel(subject, (bucket + 2) % @bucket_count) - redis.expire(subject, @bucket_expiry) - end.first + use_redis do |r| + r.multi do + r.hincrby(subject, bucket, count) + r.hdel(subject, (bucket + 1) % @bucket_count) + r.hdel(subject, (bucket + 2) % @bucket_count) + r.expire(subject, @bucket_expiry) + end.first + end end # Returns the count for a given subject and interval @@ -62,7 +66,10 @@ def count(subject, interval) keys = (0..count - 1).map do |i| (bucket - i) % @bucket_count end - return redis.hmget(subject, *keys).inject(0) {|a, i| a + i.to_i} + + return use_redis do |r| + r.hmget(subject, *keys).inject(0) { |a, i| a + i.to_i } + end end # Check if the rate limit has been exceeded. @@ -113,7 +120,19 @@ def get_bucket(time = Time.now.to_i) ((time % @bucket_span) / @bucket_interval).floor end - def redis - @redis ||= Redis::Namespace.new(:ratelimit, :redis => @redis || Redis.new) + def use_redis + if @checkout_redis_with + @checkout_redis_with.call { |redis| yield(namespaced_redis_instance(redis)) } + else + yield single_redis_instance + end + end + + def single_redis_instance + @redis ||= namespaced_redis_instance(@redis || Redis.new) + end + + def namespaced_redis_instance(redis) + Redis::Namespace.new(:ratelimit, redis: redis) end end diff --git a/spec/ratelimit_spec.rb b/spec/ratelimit_spec.rb index d390cd3..e490455 100644 --- a/spec/ratelimit_spec.rb +++ b/spec/ratelimit_spec.rb @@ -1,10 +1,9 @@ require 'spec_helper' describe Ratelimit do - before do @r = Ratelimit.new("key") - @r.send(:redis).flushdb + @r.send(:use_redis) { |r| r.flushdb } end it "should set_bucket_expiry to the bucket_span if not defined" do @@ -106,4 +105,20 @@ expect(@r.count('value1', 10)).to eql(1) end + context "using the checkout_redis_with option" do + let(:redis) { Redis.new } + let(:redis_checkout_lambda) do + lambda do |&block| + block.call(redis) + end + end + + let(:key) { SecureRandom.hex(6) } + subject { Ratelimit.new(key, checkout_redis_with: redis_checkout_lambda) } + + it "adds correctly" do + subject.add("value1", 3) + expect(subject.count("value1", 1)).to eq(3) + end + end end From 049bf462e46862887091007f9a3f52e6cb9e7436 Mon Sep 17 00:00:00 2001 From: mikebaldry Date: Fri, 4 Sep 2020 14:41:58 +0100 Subject: [PATCH 2/5] Removing dependency on super old bundler, removing redis-namespace as it isn't required --- .ruby-gemset | 1 - .ruby-version | 1 - .tool-versions | 1 + Rakefile | 1 - lib/ratelimit.rb | 14 +++++--------- ratelimit.gemspec | 3 +-- 6 files changed, 7 insertions(+), 14 deletions(-) delete mode 100644 .ruby-gemset delete mode 100644 .ruby-version create mode 100644 .tool-versions diff --git a/.ruby-gemset b/.ruby-gemset deleted file mode 100644 index 6dcedfe..0000000 --- a/.ruby-gemset +++ /dev/null @@ -1 +0,0 @@ -ratelimit diff --git a/.ruby-version b/.ruby-version deleted file mode 100644 index c1026d2..0000000 --- a/.ruby-version +++ /dev/null @@ -1 +0,0 @@ -ruby-2.3.1 diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..7811505 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +ruby 2.6.5 diff --git a/Rakefile b/Rakefile index 3c4590c..204a896 100644 --- a/Rakefile +++ b/Rakefile @@ -34,5 +34,4 @@ namespace :doc do task :clean do rm_r doc_dir if File.exists?(doc_destination) end - end diff --git a/lib/ratelimit.rb b/lib/ratelimit.rb index 8268a15..374fa7d 100644 --- a/lib/ratelimit.rb +++ b/lib/ratelimit.rb @@ -1,5 +1,4 @@ require 'redis' -require 'redis-namespace' class Ratelimit @@ -16,6 +15,7 @@ class Ratelimit # @return [RateLimit] RateLimit instance # def initialize(key, options = {}) + @namespace = "ratelimit" @key = key unless options.is_a?(Hash) raise ArgumentError.new("Redis object is now passed in via the options hash - options[:redis]") @@ -42,7 +42,7 @@ def initialize(key, options = {}) # @return [Integer] The counter value def add(subject, count = 1) bucket = get_bucket - subject = "#{@key}:#{subject}" + subject = [@namespace, @key, subject].join(":") use_redis do |r| r.multi do r.hincrby(subject, bucket, count) @@ -61,7 +61,7 @@ def count(subject, interval) bucket = get_bucket interval = [interval, @bucket_interval].max count = (interval / @bucket_interval).floor - subject = "#{@key}:#{subject}" + subject = [@namespace, @key, subject].join(":") keys = (0..count - 1).map do |i| (bucket - i) % @bucket_count @@ -122,17 +122,13 @@ def get_bucket(time = Time.now.to_i) def use_redis if @checkout_redis_with - @checkout_redis_with.call { |redis| yield(namespaced_redis_instance(redis)) } + @checkout_redis_with.call { |redis| yield(redis) } else yield single_redis_instance end end def single_redis_instance - @redis ||= namespaced_redis_instance(@redis || Redis.new) - end - - def namespaced_redis_instance(redis) - Redis::Namespace.new(:ratelimit, redis: redis) + @redis ||= Redis.new end end diff --git a/ratelimit.gemspec b/ratelimit.gemspec index 13c8a16..aa03a9e 100644 --- a/ratelimit.gemspec +++ b/ratelimit.gemspec @@ -19,8 +19,7 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib"] spec.add_dependency "redis", ">= 2.0.0" - spec.add_dependency "redis-namespace", ">= 1.0.0" - spec.add_development_dependency "bundler", "~> 1.6" + spec.add_development_dependency "bundler" spec.add_development_dependency "rake" spec.add_development_dependency "fakeredis" spec.add_development_dependency "timecop" From b05b01b960af8f821f235e09008912e1f7231972 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Brize?= Date: Mon, 14 Feb 2022 09:20:33 +0100 Subject: [PATCH 3/5] Prepare for Redis 5.0 - remove deprecations --- .tool-versions | 2 +- lib/ratelimit.rb | 10 +++++----- spec/ratelimit_spec.rb | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.tool-versions b/.tool-versions index 7811505..a4023dc 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -ruby 2.6.5 +ruby 2.7.5 diff --git a/lib/ratelimit.rb b/lib/ratelimit.rb index 374fa7d..d74ce75 100644 --- a/lib/ratelimit.rb +++ b/lib/ratelimit.rb @@ -44,11 +44,11 @@ def add(subject, count = 1) bucket = get_bucket subject = [@namespace, @key, subject].join(":") use_redis do |r| - r.multi do - r.hincrby(subject, bucket, count) - r.hdel(subject, (bucket + 1) % @bucket_count) - r.hdel(subject, (bucket + 2) % @bucket_count) - r.expire(subject, @bucket_expiry) + r.multi do |pipeline| + pipeline.hincrby(subject, bucket, count) + pipeline.hdel(subject, (bucket + 1) % @bucket_count) + pipeline.hdel(subject, (bucket + 2) % @bucket_count) + pipeline.expire(subject, @bucket_expiry) end.first end end diff --git a/spec/ratelimit_spec.rb b/spec/ratelimit_spec.rb index e490455..3a985ce 100644 --- a/spec/ratelimit_spec.rb +++ b/spec/ratelimit_spec.rb @@ -83,7 +83,7 @@ @value = nil expect do - timeout(1) do + Timeout.timeout(1) do @r.exec_within_threshold("key", {:threshold => 30, :interval => 30}) do @value = 2 end From 393b4019e0e933f1c26695f27850ac2850a3ca17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Brize?= Date: Wed, 25 Jan 2023 12:53:21 +0100 Subject: [PATCH 4/5] run on ruby 3.2 (#1) --- .circleci/config.yml | 80 ++++++++++++++++++++++++++++++++++++++++ .gitignore | 1 - .tool-versions | 2 +- .travis.yml | 9 ----- Gemfile | 3 -- Gemfile.lock | 57 ++++++++++++++++++++++++++++ lib/ratelimit/version.rb | 2 +- ratelimit.gemspec | 6 ++- spec/spec_helper.rb | 3 +- 9 files changed, 145 insertions(+), 18 deletions(-) create mode 100644 .circleci/config.yml delete mode 100644 .travis.yml create mode 100644 Gemfile.lock diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..96ec0b6 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,80 @@ +version: 2.1 +jobs: + build: + docker: + - image: cimg/ruby:3.2.0 + + working_directory: ~/ratelimit + + steps: + - checkout + + - run: + name: "Ruby Bundler: Configure bundler" + command: | + bundle config set path '.bundle' + + - restore_cache: + keys: + - v1-ruby-3.2.0-deps-{{ checksum "Gemfile.lock" }} + # fallback to using the latest cache if no exact match is found + - v1-ruby-3.2.0-deps- + + - run: + name: install dependencies + command: | + bundle install --jobs=4 --retry=3 + bundle clean --force + + - save_cache: + paths: + - .bundle + key: v1-ruby-3.2.0-deps-{{ checksum "Gemfile.lock" }} + + - run: + name: run tests + command: | + mkdir /tmp/test-results + + bundle exec rspec --format progress \ + --format RspecJunitFormatter \ + --out /tmp/test-results/rspec.xml \ + --format progress + + - store_test_results: + path: /tmp/test-results + - store_artifacts: + path: /tmp/test-results + destination: test-results + deploy: + docker: + - image: cimg/ruby:3.2.0 + working_directory: ~/ratelimit + steps: + - checkout + - run: + name: Set credentials + command: | + mkdir -p ~/.gem + touch ~/.gem/credentials + chmod 0600 ~/.gem/credentials + printf -- "---\n:github: Bearer ${GITHUB_TOKEN}\n" > ~/.gem/credentials + - run: + name: Release Gem + command: | + gem build ratelimit.gemspec + gem push --KEY github --host https://rubygems.pkg.github.com/buyapowa *.gem + +workflows: + version: 2 + build-and-deploy: + jobs: + - build + - deploy: + context: + - gem-deploy + requires: + - build + filters: + branches: + only: master diff --git a/.gitignore b/.gitignore index 31cafb5..e74cb7b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ .bundle .config .yardoc -Gemfile.lock InstalledFiles _yardoc coverage diff --git a/.tool-versions b/.tool-versions index a4023dc..c23af94 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -ruby 2.7.5 +ruby 3.2.0 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index c3a1ed7..0000000 --- a/.travis.yml +++ /dev/null @@ -1,9 +0,0 @@ -script: "bundle exec rspec" -rvm: - - 1.9.2 - - 1.9.3 - - 2.0.0 - - 2.1.1 - - 2.1.2 - - rbx-2 - - jruby-19mode diff --git a/Gemfile b/Gemfile index 4cff8c5..fa75df1 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,3 @@ source 'https://rubygems.org' -# Specify your gem's dependencies in ratelimit.gemspec gemspec - -gem 'coveralls', require: false diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..5441c9e --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,57 @@ +PATH + remote: . + specs: + ratelimit (1.1.0) + redis (< 5.0.0) + +GEM + remote: https://rubygems.org/ + specs: + diff-lcs (1.5.0) + fakeredis (0.8.0) + redis (~> 4.1) + maruku (0.7.3) + psych (5.0.2) + stringio + rake (13.0.6) + rdoc (6.5.0) + psych (>= 4.0.0) + redis (4.8.0) + rspec (3.12.0) + rspec-core (~> 3.12.0) + rspec-expectations (~> 3.12.0) + rspec-mocks (~> 3.12.0) + rspec-core (3.12.0) + rspec-support (~> 3.12.0) + rspec-expectations (3.12.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.12.0) + rspec-mocks (3.12.3) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.12.0) + rspec-support (3.12.0) + rspec_junit_formatter (0.6.0) + rspec-core (>= 2, < 4, != 2.12.0) + stringio (3.0.4) + timecop (0.9.6) + webrick (1.7.0) + yard (0.9.28) + webrick (~> 1.7.0) + +PLATFORMS + ruby + +DEPENDENCIES + bundler + fakeredis + maruku + rake + ratelimit! + rdoc + rspec + rspec_junit_formatter (~> 0.6) + timecop + yard + +BUNDLED WITH + 2.4.5 diff --git a/lib/ratelimit/version.rb b/lib/ratelimit/version.rb index d3c5917..e88203f 100644 --- a/lib/ratelimit/version.rb +++ b/lib/ratelimit/version.rb @@ -1,3 +1,3 @@ class Ratelimit - VERSION = "1.0.2" + VERSION = "1.1.0" end diff --git a/ratelimit.gemspec b/ratelimit.gemspec index aa03a9e..29ead44 100644 --- a/ratelimit.gemspec +++ b/ratelimit.gemspec @@ -18,7 +18,10 @@ Gem::Specification.new do |spec| spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ["lib"] - spec.add_dependency "redis", ">= 2.0.0" + spec.metadata["allowed_push_host"] = "https://rubygems.pkg.github.com" + spec.metadata["github_repo"] = "ssh://github.com/Buyapowa/ratelimit" + + spec.add_dependency "redis", "< 5.0.0" spec.add_development_dependency "bundler" spec.add_development_dependency "rake" spec.add_development_dependency "fakeredis" @@ -27,4 +30,5 @@ Gem::Specification.new do |spec| spec.add_development_dependency "yard" spec.add_development_dependency "maruku" spec.add_development_dependency "rdoc" + spec.add_development_dependency "rspec_junit_formatter", "~> 0.6" end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 0a8bc39..921aa1d 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -14,11 +14,10 @@ # users commonly want. # # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration -require 'coveralls' -Coveralls.wear! require 'fakeredis' require 'timecop' require 'ratelimit' +require 'securerandom' RSpec.configure do |config| # The settings below are suggested to provide a good initial experience From 1b4c1a4bbc4eff0260a2f10c52f3fdd5f6e699cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Brize?= Date: Thu, 26 Jan 2023 12:21:48 +0100 Subject: [PATCH 5/5] use GEM_HOST_API_KEY do release (#2) --- .circleci/config.yml | 9 +-------- Gemfile.lock | 2 +- lib/ratelimit/version.rb | 2 +- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 96ec0b6..0929c17 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -52,18 +52,11 @@ jobs: working_directory: ~/ratelimit steps: - checkout - - run: - name: Set credentials - command: | - mkdir -p ~/.gem - touch ~/.gem/credentials - chmod 0600 ~/.gem/credentials - printf -- "---\n:github: Bearer ${GITHUB_TOKEN}\n" > ~/.gem/credentials - run: name: Release Gem command: | gem build ratelimit.gemspec - gem push --KEY github --host https://rubygems.pkg.github.com/buyapowa *.gem + gem push --host https://rubygems.pkg.github.com/buyapowa *.gem workflows: version: 2 diff --git a/Gemfile.lock b/Gemfile.lock index 5441c9e..b162b75 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - ratelimit (1.1.0) + ratelimit (1.1.1) redis (< 5.0.0) GEM diff --git a/lib/ratelimit/version.rb b/lib/ratelimit/version.rb index e88203f..c34d32b 100644 --- a/lib/ratelimit/version.rb +++ b/lib/ratelimit/version.rb @@ -1,3 +1,3 @@ class Ratelimit - VERSION = "1.1.0" + VERSION = "1.1.1" end