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

Kaminari pagination is not supported by Tapioca #1859

Open
lavoiesl opened this issue Apr 9, 2024 · 1 comment · May be fixed by #2100
Open

Kaminari pagination is not supported by Tapioca #1859

lavoiesl opened this issue Apr 9, 2024 · 1 comment · May be fixed by #2100

Comments

@lavoiesl
Copy link
Contributor

lavoiesl commented Apr 9, 2024

Trying to update tapioca from 0.12.0 to 0.13.1 results in this error:

path/to/file.rb:32: Method page does not exist on Model::PrivateAssociationRelation https://srb.help/7003
    32 |    @records = Model.all.page(params[:page]).per(25)

Investigation

The page method is defined on the class by kaminari:
https://github.com/kaminari/kaminari/blob/9182d065c144afa45c6b7cf444f810bea1fd7201/kaminari-activerecord/lib/kaminari/activerecord/active_record_model_extension.rb#L14-L23

But attempting to introspect it at runtime yields nothing:

pry(main)> Model.all.method(:page)
=> #<Method: Model::ActiveRecord_Relation#page(*)>
pry(main)> Model.all.method(:page).source_location
=> nil
pry(main)> Model.all.method(:page).owner
=> Model::ActiveRecord_Relation
pry(main)> Model.all.method(:page).owner.instance_method(:page)
NameError: undefined method `page' for class `ActiveRecord::Relation'
from (pry):1:in `instance_method'from (pry):35:in `method'
pry(main)> Model.all.methods.include?(:page)
=> false

This is because it is delegated by https://github.com/rails/rails/blob/d37c533139f70efdcd95f8dadd48c10eba429f94/activerecord/lib/active_record/relation/delegation.rb#L71-L88

Which is generated on demand when the method is missing:
https://github.com/rails/rails/blob/d37c533139f70efdcd95f8dadd48c10eba429f94/activerecord/lib/active_record/relation/delegation.rb#L115-L124

Attempting the same as above after calling the method once does yield something a bit more interesting:

pry(main)> Model.all.method(:page)
=> #<Method: Model::ActiveRecord_Relation(Model::GeneratedRelationMethods)#page(...) /Users/seb/.gem/ruby/3.2.2/gems/activerecord-7.1.3.2/lib/active_record/relation/delegation.rb:78>
pry(main)> Model.all.method(:page).source_location
=> ["/Users/seb/.gem/ruby/3.2.2/gems/activerecord-7.1.3.2/lib/active_record/relation/delegation.rb", 78]
pry(main)> Model.all.method(:page).owner
=> Model::GeneratedRelationMethods
pry(main)> Model.all.method(:page).owner.instance_method(:page)
=> #<UnboundMethod: Model::GeneratedRelationMethods#page(...) /Users/seb/.gem/ruby/3.2.2/gems/activerecord-7.1.3.2/lib/active_record/relation/delegation.rb:78>
pry(main)> Model.all.methods.include?(:page)
=> true

The same can be achieved by calling Model.generate_relation_method(:page)

Workaround

Adding this in config/initializers/kaminari.rb makes tapioca generate the methods:

if defined?(Tapioca)
  Rails.application.config.after_initialize do
    Rails.application.eager_load!

    ActiveRecord::Base.descendants.each do |model|
      model.generate_relation_method(:page)
      model.generate_relation_method(:per)
    end
  end
end
@rzane
Copy link
Contributor

rzane commented Nov 28, 2024

The solution above works pretty well, but it's not aware of methods like #total_count, etc. Here's a compiler that makes the types a bit more accurate. It's based on the ActiveRecordScope compiler, but I took some shortcuts.

I might get around to upstreaming this, but it'd be really cool if someone took it from here.

# typed: ignore

require 'tapioca/dsl/helpers/active_record_constants_helper'
require 'kaminari/activerecord/active_record_model_extension'

module Tapioca
  module Dsl
    module Compilers
      class Kaminari < Tapioca::Dsl::Compiler
        include Tapioca::Dsl::Helpers::ActiveRecordConstantsHelper

        def self.gather_constants
          all_classes
            .select { |c| c < ::Kaminari::ActiveRecordModelExtension }
            .reject(&:abstract_class?)
        end

        def decorate
          root.create_path(constant) do |model|
            generate_page_method(
              model.create_module(RelationMethodsModuleName),
              RelationClassName,
            )

            generate_page_method(
              model.create_module(AssociationRelationMethodsModuleName),
              AssociationRelationClassName,
            )

            model.create_extend(RelationMethodsModuleName)
          end
        end

        private

        def generate_page_method(mod, return_type)
          mod.create_method(
            'page',
            parameters: [create_opt_param('num', type: 'T.any(Integer, String)', default: 'nil')],
            return_type: "T.all(#{return_type}, Kaminari::PageScopeMethods, Kaminari::ActiveRecordRelationMethods)",
          )
        end
      end
    end
  end
end

@rzane rzane linked a pull request Nov 28, 2024 that will close this issue
@amomchilov amomchilov changed the title kaminari pagination is not supported by tapioca Kaminari pagination is not supported by Tapioca Dec 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants