diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..26746e2 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: 'bundler' + directory: '/' + schedule: + interval: 'daily' + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a030766..664499b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,24 +9,14 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up Ruby - uses: actions/setup-ruby@v1 - with: - ruby-version: 2.7.x - - - name: Cache ruby gems - uses: actions/cache@v1 - with: - path: vendor/bundle - key: ${{ runner.os }}-gem-cursor_paginator-${{ hashFiles('Gemfile.lock') }} - restore-keys: | - ${{ runner.os }}-gem-cursor_paginator- - - name: Install Dependencies run: | sudo apt-get install libsqlite3-dev - gem install bundler - bundle install --jobs 4 --retry 3 + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true - name: Run Specs run: | diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml new file mode 100644 index 0000000..addf11f --- /dev/null +++ b/.github/workflows/dependabot-auto-merge.yml @@ -0,0 +1,17 @@ +name: Dependabot auto-merge +on: pull_request + +permissions: + contents: write + pull-requests: write + +jobs: + dependabot: + runs-on: ubuntu-latest + if: ${{ github.actor == 'dependabot[bot]' }} + steps: + - name: Enable auto-merge for Dependabot PRs + run: gh pr merge --auto --merge "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a42214b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,36 @@ +name: release + +on: + push: + branches: + - master + +jobs: + release-please: + runs-on: ubuntu-latest + steps: + - uses: google-github-actions/release-please-action@v4 + id: release + with: + token: ${{ secrets.SOMLENG_PERSONAL_ACCESS_TOKEN }} + + - uses: actions/checkout@v4 + if: ${{ steps.release.outputs.release_created }} + + - uses: ruby/setup-ruby@v1 + if: ${{ steps.release.outputs.release_created }} + + - run: bundle install + if: ${{ steps.release.outputs.release_created }} + + - name: publish gem + if: ${{ steps.release.outputs.release_created }} + run: | + mkdir -p $HOME/.gem + touch $HOME/.gem/credentials + chmod 0600 $HOME/.gem/credentials + printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials + gem build *.gemspec + gem push *.gem + env: + GEM_HOST_API_KEY: "${{secrets.GEM_HOST_API_KEY}}" diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..466df71 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "0.1.0" +} diff --git a/.rubocop.yml b/.rubocop.yml index 1de1e8e..4ce131a 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,50 +1,25 @@ require: - - rubocop-performance - - rubocop-rspec + - "rubocop-rspec" + - "rubocop-performance" -Style/FrozenStringLiteralComment: - Enabled: false - -Style/StringLiterals: - EnforcedStyle: double_quotes - -Layout/LineLength: - Max: 100 +# Omakase Ruby styling for Rails +inherit_gem: + rubocop-rails-omakase: rubocop.yml -Style/Documentation: - Enabled: false - -Style/ClassAndModuleChildren: - Enabled: false +AllCops: + NewCops: enable -Metrics/BlockLength: +Lint: Enabled: true - Exclude: - - spec/**/*.rb -Style/BlockDelimiters: - Enabled: true - Exclude: - - spec/**/*.rb - -Style/NumericLiterals: +Style/FrozenStringLiteralComment: Enabled: false -DescribedClass: +RSpec/DescribedClass: Enabled: false -MultipleExpectations: +RSpec/ExampleLength: Enabled: false -ExampleLength: - Max: 10 - -RSpec/DescribeClass: - Enabled: true - Exclude: - - spec/system/**/*.rb - - spec/requests/**/*.rb - - spec/views/**/*.rb - -RSpec/ExampleLength: +RSpec/MultipleExpectations: Enabled: false diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..3294aed --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +ruby 3.3.0 diff --git a/Gemfile.lock b/Gemfile.lock index aea681e..ccddda0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -7,65 +7,111 @@ PATH GEM remote: https://rubygems.org/ specs: - activemodel (6.0.3) - activesupport (= 6.0.3) - activerecord (6.0.3) - activemodel (= 6.0.3) - activesupport (= 6.0.3) - activesupport (6.0.3) + activemodel (7.1.3.2) + activesupport (= 7.1.3.2) + activerecord (7.1.3.2) + activemodel (= 7.1.3.2) + activesupport (= 7.1.3.2) + timeout (>= 0.4.0) + activesupport (7.1.3.2) + base64 + bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - zeitwerk (~> 2.2, >= 2.2.2) - ast (2.4.0) - coderay (1.1.2) - concurrent-ruby (1.1.6) - diff-lcs (1.3) - i18n (1.8.2) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + minitest (>= 5.1) + mutex_m + tzinfo (~> 2.0) + ast (2.4.2) + base64 (0.2.0) + bigdecimal (3.1.7) + coderay (1.1.3) + concurrent-ruby (1.2.3) + connection_pool (2.4.1) + diff-lcs (1.5.1) + drb (2.2.1) + i18n (1.14.4) concurrent-ruby (~> 1.0) - method_source (1.0.0) - minitest (5.14.1) - parallel (1.19.1) - parser (2.7.1.2) - ast (~> 2.4.0) - pry (0.13.1) + json (2.7.2) + language_server-protocol (3.17.0.3) + method_source (1.1.0) + mini_portile2 (2.8.6) + minitest (5.22.3) + mutex_m (0.2.0) + parallel (1.24.0) + parser (3.3.0.5) + ast (~> 2.4.1) + racc + pry (0.14.2) coderay (~> 1.1) method_source (~> 1.0) - rainbow (3.0.0) + racc (1.7.3) + rack (3.0.10) + rainbow (3.1.1) rake (12.3.3) - rexml (3.2.4) - rspec (3.9.0) - rspec-core (~> 3.9.0) - rspec-expectations (~> 3.9.0) - rspec-mocks (~> 3.9.0) - rspec-core (3.9.2) - rspec-support (~> 3.9.3) - rspec-expectations (3.9.2) + regexp_parser (2.9.0) + rexml (3.2.6) + rspec (3.13.0) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-core (3.13.0) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-mocks (3.9.1) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-support (3.9.3) - rubocop (0.83.0) + rspec-support (~> 3.13.0) + rspec-support (3.13.1) + rubocop (1.63.2) + json (~> 2.3) + language_server-protocol (>= 3.17.0) parallel (~> 1.10) - parser (>= 2.7.0.1) + parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) - rexml + regexp_parser (>= 1.8, < 3.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.31.1, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 2.0) - rubocop-performance (1.5.2) - rubocop (>= 0.71.0) - rubocop-rspec (1.39.0) - rubocop (>= 0.68.1) - ruby-progressbar (1.10.1) - sqlite3 (1.4.2) - thread_safe (0.3.6) - tzinfo (1.2.7) - thread_safe (~> 0.1) - unicode-display_width (1.7.0) - zeitwerk (2.3.0) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.31.2) + parser (>= 3.3.0.4) + rubocop-capybara (2.20.0) + rubocop (~> 1.41) + rubocop-factory_bot (2.25.1) + rubocop (~> 1.41) + rubocop-minitest (0.35.0) + rubocop (>= 1.61, < 2.0) + rubocop-ast (>= 1.31.1, < 2.0) + rubocop-performance (1.21.0) + rubocop (>= 1.48.1, < 2.0) + rubocop-ast (>= 1.31.1, < 2.0) + rubocop-rails (2.24.1) + activesupport (>= 4.2.0) + rack (>= 1.1) + rubocop (>= 1.33.0, < 2.0) + rubocop-ast (>= 1.31.1, < 2.0) + rubocop-rails-omakase (1.0.0) + rubocop + rubocop-minitest + rubocop-performance + rubocop-rails + rubocop-rspec (2.29.1) + rubocop (~> 1.40) + rubocop-capybara (~> 2.17) + rubocop-factory_bot (~> 2.22) + rubocop-rspec_rails (~> 2.28) + rubocop-rspec_rails (2.28.3) + rubocop (~> 1.40) + ruby-progressbar (1.13.0) + sqlite3 (1.7.3) + mini_portile2 (~> 2.8.0) + timeout (0.4.1) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (2.5.0) PLATFORMS ruby @@ -76,10 +122,10 @@ DEPENDENCIES pry rake (~> 12.0) rspec - rubocop rubocop-performance + rubocop-rails-omakase rubocop-rspec - sqlite3 + sqlite3 (~> 1.7) BUNDLED WITH - 2.1.4 + 2.5.9 diff --git a/README.md b/README.md index 15b5e40..944b64f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # CursorPaginator -![Build](https://github.com/bongloy/cursor_paginator/workflows/Build/badge.svg) +![Build](https://github.com/somleng/cursor_paginator/workflows/Build/badge.svg) A ruby cursor pagination library inspired by [https://jsonapi.org/profiles/ethanresnick/cursor-pagination](https://jsonapi.org/profiles/ethanresnick/cursor-pagination/) @@ -111,7 +111,7 @@ After checking out the repo, run `bin/setup` to install dependencies. You can al ## Contributing -Bug reports and pull requests are welcome on GitHub at https://github.com/bongloy/cursor_paginator. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/bongloy/cursor_paginator/blob/master/CODE_OF_CONDUCT.md). +Bug reports and pull requests are welcome on GitHub at https://github.com/somleng/cursor_paginator. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/somleng/cursor_paginator/blob/master/CODE_OF_CONDUCT.md). ## License @@ -119,4 +119,4 @@ The gem is available as open source under the terms of the [MIT License](https:/ ## Code of Conduct -Everyone interacting in the CursorPaginator project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/bongloy/cursor_paginator/blob/master/CODE_OF_CONDUCT.md). +Everyone interacting in the CursorPaginator project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/somleng/cursor_paginator/blob/master/CODE_OF_CONDUCT.md). diff --git a/cursor_paginator.gemspec b/cursor_paginator.gemspec index dfd08ab..9828fae 100644 --- a/cursor_paginator.gemspec +++ b/cursor_paginator.gemspec @@ -3,12 +3,12 @@ require_relative "lib/cursor_paginator/version" Gem::Specification.new do |spec| spec.name = "cursor_paginator" spec.version = CursorPaginator::VERSION - spec.authors = ["David Wilkie", "Samnang Chhun"] - spec.email = ["dwilkie@bongloy.com", "samnang@bongloy.com"] + spec.authors = [ "David Wilkie", "Samnang Chhun" ] + spec.email = [ "dwilkie@someng.org", "samnang@somleng.org" ] spec.summary = "Cursor pagination" spec.description = "Cursor pagination for ruby" - spec.homepage = "https://github.com/bongloy/cursor_paginator" + spec.homepage = "https://github.com/somleng/cursor_paginator" spec.license = "MIT" spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0") @@ -21,15 +21,15 @@ Gem::Specification.new do |spec| end spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } - spec.require_paths = ["lib"] + spec.require_paths = [ "lib" ] spec.add_dependency "activesupport", ">= 3" spec.add_development_dependency "activerecord" spec.add_development_dependency "pry" spec.add_development_dependency "rspec" - spec.add_development_dependency "rubocop" + spec.add_development_dependency "rubocop-rails-omakase" spec.add_development_dependency "rubocop-performance" spec.add_development_dependency "rubocop-rspec" - spec.add_development_dependency "sqlite3" + spec.add_development_dependency "sqlite3", "~> 1.7" end diff --git a/lib/cursor_paginator/pagination_result.rb b/lib/cursor_paginator/pagination_result.rb index 2d20793..d5cdbce 100644 --- a/lib/cursor_paginator/pagination_result.rb +++ b/lib/cursor_paginator/pagination_result.rb @@ -54,7 +54,7 @@ def additional_record end def load_records - records = records_scope.take(paginator.page_size + 1) + records = paginator.take_records(records_scope, paginator.page_size + 1) @additional_record = records.pop if records.size > paginator.page_size paginator.cursor_direction.after? ? records : records.reverse end diff --git a/lib/cursor_paginator/paginator/active_record.rb b/lib/cursor_paginator/paginator/active_record.rb index 0135e2a..fdc2d7d 100644 --- a/lib/cursor_paginator/paginator/active_record.rb +++ b/lib/cursor_paginator/paginator/active_record.rb @@ -5,8 +5,13 @@ def paginate(scope) records = scope.reorder(paginator_options.fetch(:order_key) => order_direction) records = filter_by_cursor(records) if options_parser.filter_required? - result = PaginationResult.new(records, self) - result + PaginationResult.new(records, self) + end + + def take_records(records, limit) + return super if records.limit_value.blank? + + super(records, [ records.limit_value, limit ].min) end private diff --git a/lib/cursor_paginator/paginator/array.rb b/lib/cursor_paginator/paginator/array.rb index 8235b6e..ebaddaf 100644 --- a/lib/cursor_paginator/paginator/array.rb +++ b/lib/cursor_paginator/paginator/array.rb @@ -30,7 +30,7 @@ def split_collection(collection, cursor_index) left, right = collection.partition.with_index { |_, i| i <= cursor_index } left.pop - [left, right] + [ left, right ] end end end diff --git a/lib/cursor_paginator/paginator/base.rb b/lib/cursor_paginator/paginator/base.rb index a6abe3b..b8e016b 100644 --- a/lib/cursor_paginator/paginator/base.rb +++ b/lib/cursor_paginator/paginator/base.rb @@ -15,6 +15,10 @@ def initialize(page_options: {}, paginator_options: {}) ) end + def take_records(records, limit) + records.take(limit) + end + private def query_operator diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..1af461f --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,16 @@ +{ + "bootstrap-sha": "f85974e958a5cff373dc6ba6b595e238bf8512d7", + "include-component-in-tag": false, + "version-file": "lib/cursor_paginator/version.rb", + "packages": { + ".": { + "changelog-path": "CHANGELOG.md", + "release-type": "ruby", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false + } + }, + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json" +} diff --git a/spec/cursor_paginator_spec.rb b/spec/cursor_paginator_spec.rb index d58772b..2568efd 100644 --- a/spec/cursor_paginator_spec.rb +++ b/spec/cursor_paginator_spec.rb @@ -13,6 +13,17 @@ expect(records[1].title).to eq("newer") end + it "handles limits correctly" do + create_records("older", "newer", "newest") + + result = CursorPaginator.paginate(Post.limit(2)) + + records = result.to_a + expect(result.size).to eq(2) + expect(records[0].title).to eq("newest") + expect(records[1].title).to eq("newer") + end + it "limits the number of records returned" do create_records("older", "newer", "newest") diff --git a/spec/support/models.rb b/spec/support/models.rb index 02190b7..28aca14 100644 --- a/spec/support/models.rb +++ b/spec/support/models.rb @@ -1,3 +1,5 @@ +require "sqlite3" + ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") ActiveRecord::Schema.define do create_table :posts, force: true do |t|