From 1d67ec59d1a3b0622f8308a777a4a8ae01c0926c Mon Sep 17 00:00:00 2001 From: Stephen Ierodiaconou Date: Tue, 17 Sep 2024 16:56:47 +0200 Subject: [PATCH] Migrate quo to be a Rails Engine, change config to use more standard engine like configuration and setup autoloading. Also make derived Quo Query classes use a configurable base class --- .gitignore | 1 + gemfiles/rails_7.0.gemfile | 15 +++++++ gemfiles/rails_7.1.gemfile | 15 +++++++ gemfiles/rails_7.2.gemfile | 15 +++++++ lib/quo.rb | 50 +++++++++------------ lib/quo/composed_query.rb | 2 +- lib/quo/eager_query.rb | 2 +- lib/quo/{railtie.rb => engine.rb} | 4 +- lib/quo/query.rb | 10 ++--- lib/quo/utilities/callstack.rb | 4 +- lib/quo/wrapped_query.rb | 2 +- sig/quo.rbs | 13 ------ test/dummy/app/queries/application_query.rb | 7 +++ test/dummy/config/initializers/quo.rb | 10 ++--- test/quo/custom_base_class_test.rb | 29 ++++++++++++ 15 files changed, 121 insertions(+), 58 deletions(-) create mode 100644 gemfiles/rails_7.0.gemfile create mode 100644 gemfiles/rails_7.1.gemfile create mode 100644 gemfiles/rails_7.2.gemfile rename lib/quo/{railtie.rb => engine.rb} (55%) create mode 100644 test/dummy/app/queries/application_query.rb create mode 100644 test/quo/custom_base_class_test.rb diff --git a/.gitignore b/.gitignore index 9480f8d..a942040 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ Gemfile.lock /.idea/ /.gem_rbs_collection/ rbs_collection.lock.yaml +*.gemfile.lock diff --git a/gemfiles/rails_7.0.gemfile b/gemfiles/rails_7.0.gemfile new file mode 100644 index 0000000..f6bf508 --- /dev/null +++ b/gemfiles/rails_7.0.gemfile @@ -0,0 +1,15 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "rails", "~> 7.0" + +group :development, :test do + gem "sqlite3" + gem "rake", "~> 13.0" + gem "minitest", "~> 5.0" + gem "standard" + gem "steep" +end + +gemspec path: "../" diff --git a/gemfiles/rails_7.1.gemfile b/gemfiles/rails_7.1.gemfile new file mode 100644 index 0000000..80484a0 --- /dev/null +++ b/gemfiles/rails_7.1.gemfile @@ -0,0 +1,15 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "rails", "~> 7.1" + +group :development, :test do + gem "sqlite3" + gem "rake", "~> 13.0" + gem "minitest", "~> 5.0" + gem "standard" + gem "steep" +end + +gemspec path: "../" diff --git a/gemfiles/rails_7.2.gemfile b/gemfiles/rails_7.2.gemfile new file mode 100644 index 0000000..9bfbdba --- /dev/null +++ b/gemfiles/rails_7.2.gemfile @@ -0,0 +1,15 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "rails", "~> 7.2" + +group :development, :test do + gem "sqlite3" + gem "rake", "~> 13.0" + gem "minitest", "~> 5.0" + gem "standard" + gem "steep" +end + +gemspec path: "../" diff --git a/lib/quo.rb b/lib/quo.rb index 0bb70d8..9272d36 100644 --- a/lib/quo.rb +++ b/lib/quo.rb @@ -1,39 +1,31 @@ # frozen_string_literal: true require_relative "quo/version" -require_relative "quo/railtie" if defined?(Rails) -require_relative "quo/query" -require_relative "quo/eager_query" -require_relative "quo/loaded_query" -require_relative "quo/wrapped_query" -require_relative "quo/composed_query" -require_relative "quo/results" +require "quo/engine" module Quo - class << self - def configuration - @configuration ||= Configuration.new - end + extend ActiveSupport::Autoload - def configure - yield(configuration) if block_given? - configuration - end - end + autoload :Callstack, "quo/utilities/callstack" + autoload :Compose, "quo/utilities/compose" + autoload :Sanitize, "quo/utilities/sanitize" + autoload :Wrap, "quo/utilities/wrap" + + autoload :Query + autoload :Results + autoload :ComposedQuery + autoload :EagerQuery + autoload :LoadedQuery + autoload :WrappedQuery - class Configuration - attr_accessor :formatted_query_log, - :query_show_callstack_size, - :logger, - :max_page_size, - :default_page_size + mattr_accessor :base_query_class, default: "Quo::Query" + mattr_accessor :formatted_query_log, default: true + mattr_accessor :query_show_callstack_size, default: 10 + mattr_accessor :logger, default: nil + mattr_accessor :max_page_size, default: 200 + mattr_accessor :default_page_size, default: 20 - def initialize - @formatted_query_log = true - @query_show_callstack_size = 10 - @logger = nil - @max_page_size = 200 - @default_page_size = 20 - end + def self.base_query_class + @@base_query_class.constantize end end diff --git a/lib/quo/composed_query.rb b/lib/quo/composed_query.rb index 6c6322b..3d70901 100644 --- a/lib/quo/composed_query.rb +++ b/lib/quo/composed_query.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Quo - class ComposedQuery < Quo::Query + class ComposedQuery < Quo.base_query_class class << self # Combine two Query classes into a new composed query class def compose(left_query_class, right_query_class, joins: nil) diff --git a/lib/quo/eager_query.rb b/lib/quo/eager_query.rb index 9b1f7f4..a44a095 100644 --- a/lib/quo/eager_query.rb +++ b/lib/quo/eager_query.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Quo - class EagerQuery < Quo::Query + class EagerQuery < Quo.base_query_class # Optionally return the `total_count` option if it has been set. # This is useful when the total count is known and not equal to size # of wrapped collection. diff --git a/lib/quo/railtie.rb b/lib/quo/engine.rb similarity index 55% rename from lib/quo/railtie.rb rename to lib/quo/engine.rb index fe9d29f..08773bc 100644 --- a/lib/quo/railtie.rb +++ b/lib/quo/engine.rb @@ -1,5 +1,7 @@ module Quo - class Railtie < ::Rails::Railtie + class Engine < ::Rails::Engine + isolate_namespace Quo + rake_tasks do load "tasks/quo.rake" end diff --git a/lib/quo/query.rb b/lib/quo/query.rb index 0559e3a..8ffdedf 100644 --- a/lib/quo/query.rb +++ b/lib/quo/query.rb @@ -42,7 +42,7 @@ def call!(**options) prop :page, _Nilable(Integer), &COERCE_TO_INT prop :current_page, _Nilable(Integer), &COERCE_TO_INT - prop(:page_size, _Nilable(Integer), default: -> { Quo.configuration.default_page_size || 20 }, &COERCE_TO_INT) + prop(:page_size, _Nilable(Integer), default: -> { Quo.default_page_size || 20 }, &COERCE_TO_INT) # TODO: maybe deprecate these, they are set using the chainable method and when merging we can handle them separately? prop :group, _Nilable(_Any), reader: false, writer: false @@ -54,7 +54,7 @@ def call!(**options) # def after_initialization # @current_page = options[:page]&.to_i || options[:current_page]&.to_i - # @page_size = options[:page_size]&.to_i || Quo.configuration.default_page_size || 20 + # @page_size = options[:page_size]&.to_i || Quo.default_page_size || 20 # end def page_index @@ -246,7 +246,7 @@ def unwrap_unpaginated private def formatted_queries? - !!Quo.configuration.formatted_query_log + !!Quo.formatted_query_log end # 'trim' a query, ie remove comments and remove newlines @@ -283,14 +283,14 @@ def configured_query def sanitised_page_size if page_size&.positive? given_size = page_size.to_i - max_page_size = Quo.configuration.max_page_size || 200 + max_page_size = Quo.max_page_size || 200 if given_size > max_page_size max_page_size else given_size end else - Quo.configuration.default_page_size || 20 + Quo.default_page_size || 20 end end diff --git a/lib/quo/utilities/callstack.rb b/lib/quo/utilities/callstack.rb index ffb708d..1a2e5da 100644 --- a/lib/quo/utilities/callstack.rb +++ b/lib/quo/utilities/callstack.rb @@ -5,7 +5,7 @@ module Utilities module Callstack def debug_callstack return unless Rails.env.development? - callstack_size = Quo.configuration.query_show_callstack_size + callstack_size = Quo.query_show_callstack_size return unless callstack_size&.positive? working_dir = Dir.pwd exclude = %r{/(gems/|rubies/|query\.rb)} @@ -13,7 +13,7 @@ def debug_callstack stack_to_display = stack[0..callstack_size] message = "\n[Query stack]: -> #{stack_to_display&.join("\n &> ")}\n" message += " (truncated to #{callstack_size} most recent)" if callstack_size && stack.size > callstack_size - logger = Quo.configuration.logger&.call + logger = Quo.logger&.call logger&.info(message) end end diff --git a/lib/quo/wrapped_query.rb b/lib/quo/wrapped_query.rb index 1e9ffb4..927f72c 100644 --- a/lib/quo/wrapped_query.rb +++ b/lib/quo/wrapped_query.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Quo - class WrappedQuery < Quo::Query + class WrappedQuery < Quo.base_query_class def self.wrap(query = nil, props: {}, &block) klass = Class.new(self) do props.each do |name, type| diff --git a/sig/quo.rbs b/sig/quo.rbs index 34829bd..938df5d 100644 --- a/sig/quo.rbs +++ b/sig/quo.rbs @@ -25,17 +25,4 @@ module Quo def error: (String) -> void def debug: (String) -> void end - - class Configuration - attr_accessor formatted_query_log: bool? - attr_accessor query_show_callstack_size: Integer? - attr_accessor logger: _Logger? - attr_accessor max_page_size: Integer? - attr_accessor default_page_size: Integer? - - def initialize: () -> void - end - attr_reader self.configuration: Configuration - - def self.configure: () { (Configuration config) -> void } -> void end diff --git a/test/dummy/app/queries/application_query.rb b/test/dummy/app/queries/application_query.rb new file mode 100644 index 0000000..add9fab --- /dev/null +++ b/test/dummy/app/queries/application_query.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class ApplicationQuery < Quo::Query + def hello + "world" + end +end diff --git a/test/dummy/config/initializers/quo.rb b/test/dummy/config/initializers/quo.rb index bfdba99..4b7fa9e 100644 --- a/test/dummy/config/initializers/quo.rb +++ b/test/dummy/config/initializers/quo.rb @@ -1,5 +1,5 @@ -Quo.configure do |config| - config.formatted_query_log = true - config.query_show_callstack_size = 5 - config.logger = -> { Rails.logger } -end +Quo.formatted_query_log = true +Quo.query_show_callstack_size = 5 +Quo.logger = -> { Rails.logger } +Quo.base_query_class = "ApplicationQuery" + diff --git a/test/quo/custom_base_class_test.rb b/test/quo/custom_base_class_test.rb new file mode 100644 index 0000000..a2c383d --- /dev/null +++ b/test/quo/custom_base_class_test.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require_relative "../test_helper" + +class Quo::CustomBaseClassTest < ActiveSupport::TestCase + def setup + a1 = Author.create!(name: "John") + a2 = Author.create!(name: "Jane") + p1 = Post.create!(title: "Post 1", author: a1) + p2 = Post.create!(title: "Post 2", author: a2) + Comment.create!(post: p1, body: "abc", read: false) + Comment.create!(post: p2, body: "def", read: false, spam_score: 0.8) + + @q1 = Quo::WrappedQuery.wrap(props: {since_date: Time}) do + Comment.recent(since_date) + end + @q2 = Quo::WrappedQuery.wrap(props: {spam_score: Float}) do + Comment.not_spam(spam_score) + end + end + + test "wrapped query inherits from custom base class" do + assert_kind_of ApplicationQuery, @q1.new(since_date: 1.day.ago) + assert_equal "world", @q1.new(since_date: 1.day.ago).hello + + klass = Quo::ComposedQuery.compose(Comment.recent, Comment.not_spam) + assert_kind_of ApplicationQuery, klass.new + end +end