diff --git a/.devcontainer-lock.json b/.devcontainer-lock.json new file mode 100644 index 0000000..12ff591 --- /dev/null +++ b/.devcontainer-lock.json @@ -0,0 +1,24 @@ +{ + "features": { + "ghcr.io/devcontainers/features/copilot-cli:1": { + "version": "1.1.3", + "resolved": "ghcr.io/devcontainers/features/copilot-cli@sha256:e10d091ae7ef9b8d2ed5f601d75f5a090bc04acbac1a26d4cd3c0d5edde4ea10", + "integrity": "sha256:e10d091ae7ef9b8d2ed5f601d75f5a090bc04acbac1a26d4cd3c0d5edde4ea10" + }, + "ghcr.io/devcontainers/features/docker-in-docker:4.1.1": { + "version": "4.1.1", + "resolved": "ghcr.io/devcontainers/features/docker-in-docker@sha256:2b44bcb32e75d5a3c028479f7f957cf7d5e42d81761373025a8d501dd3748f88", + "integrity": "sha256:2b44bcb32e75d5a3c028479f7f957cf7d5e42d81761373025a8d501dd3748f88" + }, + "ghcr.io/devcontainers/features/node:2.1.0": { + "version": "2.1.0", + "resolved": "ghcr.io/devcontainers/features/node@sha256:586c9a6f7dd40bd3ba2cd41e7f2f88dcc31fbe5d1442afcbf07ffbc66b686857", + "integrity": "sha256:586c9a6f7dd40bd3ba2cd41e7f2f88dcc31fbe5d1442afcbf07ffbc66b686857" + }, + "ghcr.io/devcontainers/features/ruby:2": { + "version": "2.0.0", + "resolved": "ghcr.io/devcontainers/features/ruby@sha256:31b07475acf72a649c2f323d2ab5c15e1a0c286e7495be822b0850e240a9b5b7", + "integrity": "sha256:31b07475acf72a649c2f323d2ab5c15e1a0c286e7495be822b0850e240a9b5b7" + } + } +} diff --git a/.devcontainer.json b/.devcontainer.json new file mode 100644 index 0000000..d1a61cf --- /dev/null +++ b/.devcontainer.json @@ -0,0 +1,19 @@ +{ + "image": "mcr.microsoft.com/devcontainers/ruby:trixie", + "features": { + "ghcr.io/devcontainers/features/ruby:2": { + "versionManager": "rbenv" // Options: "none", "rbenv", or "rvm" + }, + "ghcr.io/devcontainers/features/docker-in-docker:4.1.1": { + "version": "latest", + "moby": false + }, + "ghcr.io/devcontainers/features/node:2.1.0": { + "version": "24" + }, + "ghcr.io/devcontainers/features/copilot-cli:1": {} + }, + "mounts": [ + "source=${localEnv:HOME}/.ssh,target=/home/vscode/.ssh,type=bind,consistency=cached" + ] +} \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..4b3cf8d --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,24 @@ +name: Test + +on: + push: + pull_request: + +jobs: + test: + strategy: + fail-fast: false + matrix: + ruby: ["3.2"] + rails: ["~> 7.1.0", "~> 7.2.0", "~> 8.0.0", "~> 8.1.0"] + runs-on: ubuntu-latest + env: + RAILS_VERSION: ${{ matrix.rails }} + steps: + - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: false + - run: bundle install --jobs 4 --retry 3 + - run: bundle exec ruby -Itest test/rails8_smoke_test.rb diff --git a/Gemfile b/Gemfile index 5e1c662..c44d667 100644 --- a/Gemfile +++ b/Gemfile @@ -4,12 +4,15 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" } # Specify your gem's dependencies in grape_api.gemspec. gemspec -group :development do - gem 'sqlite3' +rails_version = ENV.fetch("RAILS_VERSION", ">= 7.1") +gem "rails", rails_version + +group :development, :test do + gem 'sqlite3', '>= 2.1' end # To use a debugger gem 'byebug', group: [:development, :test] -gem "grape", "~> 1.3" +gem "grape", ">= 2.0", "< 3" gem "grape_on_rails_routes", "~> 0.3.2" -gem "grape-entity", "~> 0.8.0" \ No newline at end of file +gem "grape-entity", ">= 1.0", "< 2" \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index 90e0eb0..c874d53 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,81 +1,98 @@ PATH remote: . specs: - grape_api (0.1.0) - cancancan - grape - grape-cancan - grape-entity (~> 0.8.0) + grape_api (3.0.0) + grape (>= 2.0, < 3) + grape-entity (>= 1.0, < 2) grape_on_rails_routes (~> 0.3.2) - rails (~> 6.1.3) + rails (>= 7.1, < 9) GEM remote: https://rubygems.org/ specs: - actioncable (6.1.3) - actionpack (= 6.1.3) - activesupport (= 6.1.3) + action_text-trix (2.1.19) + railties + actioncable (8.1.3.1) + actionpack (= 8.1.3.1) + activesupport (= 8.1.3.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.1.3) - actionpack (= 6.1.3) - activejob (= 6.1.3) - activerecord (= 6.1.3) - activestorage (= 6.1.3) - activesupport (= 6.1.3) - mail (>= 2.7.1) - actionmailer (6.1.3) - actionpack (= 6.1.3) - actionview (= 6.1.3) - activejob (= 6.1.3) - activesupport (= 6.1.3) - mail (~> 2.5, >= 2.5.4) - rails-dom-testing (~> 2.0) - actionpack (6.1.3) - actionview (= 6.1.3) - activesupport (= 6.1.3) - rack (~> 2.0, >= 2.0.9) + zeitwerk (~> 2.6) + actionmailbox (8.1.3.1) + actionpack (= 8.1.3.1) + activejob (= 8.1.3.1) + activerecord (= 8.1.3.1) + activestorage (= 8.1.3.1) + activesupport (= 8.1.3.1) + mail (>= 2.8.0) + actionmailer (8.1.3.1) + actionpack (= 8.1.3.1) + actionview (= 8.1.3.1) + activejob (= 8.1.3.1) + activesupport (= 8.1.3.1) + mail (>= 2.8.0) + rails-dom-testing (~> 2.2) + actionpack (8.1.3.1) + actionview (= 8.1.3.1) + activesupport (= 8.1.3.1) + nokogiri (>= 1.8.5) + rack (>= 2.2.4) + rack-session (>= 1.0.1) rack-test (>= 0.6.3) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.1.3) - actionpack (= 6.1.3) - activerecord (= 6.1.3) - activestorage (= 6.1.3) - activesupport (= 6.1.3) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) + actiontext (8.1.3.1) + action_text-trix (~> 2.1.15) + actionpack (= 8.1.3.1) + activerecord (= 8.1.3.1) + activestorage (= 8.1.3.1) + activesupport (= 8.1.3.1) + globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (6.1.3) - activesupport (= 6.1.3) + actionview (8.1.3.1) + activesupport (= 8.1.3.1) builder (~> 3.1) - erubi (~> 1.4) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (6.1.3) - activesupport (= 6.1.3) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (8.1.3.1) + activesupport (= 8.1.3.1) globalid (>= 0.3.6) - activemodel (6.1.3) - activesupport (= 6.1.3) - activerecord (6.1.3) - activemodel (= 6.1.3) - activesupport (= 6.1.3) - activestorage (6.1.3) - actionpack (= 6.1.3) - activejob (= 6.1.3) - activerecord (= 6.1.3) - activesupport (= 6.1.3) - marcel (~> 0.3.1) - mimemagic (~> 0.3.2) - activesupport (6.1.3) - concurrent-ruby (~> 1.0, >= 1.0.2) + activemodel (8.1.3.1) + activesupport (= 8.1.3.1) + activerecord (8.1.3.1) + activemodel (= 8.1.3.1) + activesupport (= 8.1.3.1) + timeout (>= 0.4.0) + activestorage (8.1.3.1) + actionpack (= 8.1.3.1) + activejob (= 8.1.3.1) + activerecord (= 8.1.3.1) + activesupport (= 8.1.3.1) + marcel (~> 1.0) + activesupport (8.1.3.1) + base64 + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb i18n (>= 1.6, < 2) + json + logger (>= 1.4.2) minitest (>= 5.1) - tzinfo (~> 2.0) - zeitwerk (~> 2.3) - builder (3.2.4) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) + base64 (0.3.0) + bigdecimal (4.1.3) + builder (3.3.0) byebug (11.1.3) - cancancan (3.2.1) - concurrent-ruby (1.1.8) - crass (1.0.6) + concurrent-ruby (1.3.8) + connection_pool (3.0.2) + crass (1.0.7) + date (3.5.1) + drb (2.2.3) dry-configurable (0.12.1) concurrent-ruby (~> 1.0) dry-core (~> 0.5, >= 0.5.0) @@ -94,107 +111,144 @@ GEM dry-core (~> 0.5, >= 0.5) dry-inflector (~> 0.1, >= 0.1.2) dry-logic (~> 1.0, >= 1.0.2) - erubi (1.10.0) - globalid (0.4.2) - activesupport (>= 4.2.0) - grape (1.5.3) - activesupport - builder + erb (6.0.7) + erubi (1.13.1) + globalid (1.4.0) + activesupport (>= 6.1) + grape (2.4.0) + activesupport (>= 6.1) dry-types (>= 1.1) - mustermann-grape (~> 1.0.0) - rack (>= 1.3.0) - rack-accept - grape-cancan (0.0.3) - cancancan - grape (~> 1.2) - grape-entity (0.8.2) + mustermann-grape (~> 1.1.0) + rack (>= 2) + zeitwerk + grape-entity (1.1.0) activesupport (>= 3.0.0) - multi_json (>= 1.3.2) grape_on_rails_routes (0.3.2) rails (>= 3.1.1) - i18n (1.8.9) + i18n (1.15.2) concurrent-ruby (~> 1.0) - loofah (2.9.0) + io-console (0.9.4) + irb (1.18.0) + pp (>= 0.6.0) + prism (>= 1.3.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + json (3.0.2) + logger (1.7.0) + loofah (2.25.2) crass (~> 1.0.2) - nokogiri (>= 1.5.9) - mail (2.7.1) + nokogiri (>= 1.12.0) + mail (2.9.1) + logger mini_mime (>= 0.1.1) - marcel (0.3.3) - mimemagic (~> 0.3.2) - method_source (1.0.0) - mimemagic (0.3.9) - nokogiri (~> 1) - rake - mini_mime (1.0.3) - minitest (5.14.4) - multi_json (1.15.0) - mustermann (1.1.1) - ruby2_keywords (~> 0.0.1) - mustermann-grape (1.0.1) + net-imap + net-pop + net-smtp + marcel (1.2.1) + mini_mime (1.1.5) + minitest (6.0.6) + drb (~> 2.0) + prism (~> 1.5) + mustermann (4.0.0) + mustermann-grape (1.1.0) mustermann (>= 1.0.0) - nio4r (2.5.7) - nokogiri (1.11.2-x86_64-linux) + net-imap (0.6.7) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.4.0) + timeout + net-smtp (0.5.1) + net-protocol + nio4r (2.7.5) + nokogiri (1.19.4-aarch64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.4-x86_64-linux-gnu) racc (~> 1.4) - racc (1.5.2) - rack (2.2.3) - rack-accept (0.4.5) - rack (>= 0.4) - rack-test (1.1.0) - rack (>= 1.0, < 3) - rails (6.1.3) - actioncable (= 6.1.3) - actionmailbox (= 6.1.3) - actionmailer (= 6.1.3) - actionpack (= 6.1.3) - actiontext (= 6.1.3) - actionview (= 6.1.3) - activejob (= 6.1.3) - activemodel (= 6.1.3) - activerecord (= 6.1.3) - activestorage (= 6.1.3) - activesupport (= 6.1.3) + pp (0.6.4) + prettyprint + prettyprint (0.2.0) + prism (1.9.0) + racc (1.8.1) + rack (3.2.7) + rack-session (2.1.2) + base64 (>= 0.1.0) + rack (>= 3.0.0) + rack-test (2.2.0) + rack (>= 1.3) + rackup (2.3.1) + rack (>= 3) + rails (8.1.3.1) + actioncable (= 8.1.3.1) + actionmailbox (= 8.1.3.1) + actionmailer (= 8.1.3.1) + actionpack (= 8.1.3.1) + actiontext (= 8.1.3.1) + actionview (= 8.1.3.1) + activejob (= 8.1.3.1) + activemodel (= 8.1.3.1) + activerecord (= 8.1.3.1) + activestorage (= 8.1.3.1) + activesupport (= 8.1.3.1) bundler (>= 1.15.0) - railties (= 6.1.3) - sprockets-rails (>= 2.0.0) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) + railties (= 8.1.3.1) + rails-dom-testing (2.3.0) + activesupport (>= 5.0.0) + minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.3.0) - loofah (~> 2.3) - railties (6.1.3) - actionpack (= 6.1.3) - activesupport (= 6.1.3) - method_source - rake (>= 0.8.7) - thor (~> 1.0) - rake (13.0.3) - ruby2_keywords (0.0.4) - sprockets (4.0.2) - concurrent-ruby (~> 1.0) - rack (> 1, < 3) - sprockets-rails (3.2.2) - actionpack (>= 4.0) - activesupport (>= 4.0) - sprockets (>= 3.0.0) - sqlite3 (1.4.2) - thor (1.1.0) - tzinfo (2.0.4) + rails-html-sanitizer (1.7.1) + loofah (~> 2.25, >= 2.25.2) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (8.1.3.1) + actionpack (= 8.1.3.1) + activesupport (= 8.1.3.1) + irb (~> 1.13) + rackup (>= 1.0.0) + rake (>= 12.2) + thor (~> 1.0, >= 1.2.2) + tsort (>= 0.2) + zeitwerk (~> 2.6) + rake (13.4.2) + rbs (4.2.0) + logger + prism (>= 1.6.0) + tsort + rdoc (8.0.0) + erb + prism (>= 1.6.0) + rbs (>= 4.0.0) + tsort + reline (0.7.0) + io-console (~> 0.5) + securerandom (0.4.1) + sqlite3 (2.9.6-aarch64-linux-gnu) + sqlite3 (2.9.6-x86_64-linux-gnu) + thor (1.5.0) + timeout (0.6.1) + tsort (0.2.0) + tzinfo (2.0.6) concurrent-ruby (~> 1.0) - websocket-driver (0.7.3) + uri (1.1.1) + useragent (0.16.11) + websocket-driver (0.8.2) + base64 websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) - zeitwerk (2.4.2) + zeitwerk (2.8.3) PLATFORMS + aarch64-linux x86_64-linux DEPENDENCIES byebug - grape (~> 1.3) - grape-entity (~> 0.8.0) + grape (>= 2.0, < 3) + grape-entity (>= 1.0, < 2) grape_api! grape_on_rails_routes (~> 0.3.2) - sqlite3 + rails (>= 7.1) + sqlite3 (>= 2.1) BUNDLED WITH - 2.2.15 + 4.0.20 diff --git a/bin/rails b/bin/rails index 52cd21e..5f5acff 100755 --- a/bin/rails +++ b/bin/rails @@ -20,6 +20,5 @@ require "action_controller/railtie" require "action_mailer/railtie" require "action_view/railtie" require "action_cable/engine" -require "sprockets/railtie" # require "rails/test_unit/railtie" require "rails/engine/commands" diff --git a/grape_api.gemspec b/grape_api.gemspec index 1abeb2f..f7efb77 100644 --- a/grape_api.gemspec +++ b/grape_api.gemspec @@ -21,9 +21,10 @@ Gem::Specification.new do |spec| spec.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"] - spec.add_dependency "rails", "~> 6.1.3" - spec.add_dependency "grape" + spec.required_ruby_version = ">= 3.2" + spec.add_dependency "rails", ">= 7.1", "< 9" + spec.add_dependency "grape", ">= 2.0", "< 3" spec.add_dependency "grape_on_rails_routes", "~> 0.3.2" - spec.add_dependency "grape-entity", "~> 0.8.0" + spec.add_dependency "grape-entity", ">= 1.0", "< 2" end diff --git a/lib/grape_api.rb b/lib/grape_api.rb index 185028e..7ce3570 100644 --- a/lib/grape_api.rb +++ b/lib/grape_api.rb @@ -15,6 +15,9 @@ module GrapeAPI mattr_accessor :base_api_class @@base_api_class = "API::Base" + mattr_accessor :api_namespace + @@api_namespace = "Api" + def self.config yield(self) end diff --git a/lib/grape_api/pagination.rb b/lib/grape_api/pagination.rb index 6890b34..6b6876f 100644 --- a/lib/grape_api/pagination.rb +++ b/lib/grape_api/pagination.rb @@ -3,7 +3,7 @@ module Pagination def self.included(base) Grape::Endpoint.class_eval do def paginate(collection) - per_page = GrapeAPI.pagination.config.per_page_param(params) || route_setting(:per_page) + per_page = GrapeAPI.pagination.config.per_page_param(params) || route_setting(:per_page) || GrapeAPI.pagination.config.per_page_count options = { :page => GrapeAPI.pagination.config.page_param(params), @@ -24,7 +24,6 @@ def paginate(collection) per_page = GrapeAPI.pagination.config.per_page page = GrapeAPI.pagination.config.page include_total = GrapeAPI.pagination.config.include_total - header 'Link', links.join(', ') unless links.empty? header total, GrapeAPI::Pagination.total_from(pagy || collection).to_s if include_total header per_page, options[:per_page].to_s @@ -98,10 +97,11 @@ def total_from(collection) private def paginate_with_pagy(collection, options) - if Pagy::VARS[:max_per_page] && options[:per_page] > Pagy::VARS[:max_per_page] - options[:per_page] = Pagy::VARS[:max_per_page] + default = Pagy::VERSION >= "4" ? Pagy::DEFAULT : Pagy::VARS + if default[:max_per_page] && options[:per_page] > default[:max_per_page] + options[:per_page] = default[:max_per_page] elsif options[:per_page] <= 0 - options[:per_page] = Pagy::VARS[:items] + options[:per_page] = default[:items] end pagy = pagy_from(collection, options) @@ -120,7 +120,9 @@ def pagy_from(collection, options) else count = collection.is_a?(Array) ? collection.count : collection.count(:all) end - + if count.is_a?(Hash) + count = count.values.sum + end Pagy.new(count: count, items: options[:per_page], page: options[:page]) end diff --git a/lib/grape_api/resourceful/resource.rb b/lib/grape_api/resourceful/resource.rb index b6947e6..405d0d5 100644 --- a/lib/grape_api/resourceful/resource.rb +++ b/lib/grape_api/resourceful/resource.rb @@ -2,12 +2,12 @@ module GrapeAPI module Resourceful module Resource - def self.included base - base.class_eval do + def self.included mod + mod.class_eval do class_attribute :resourceful_params_ self.resourceful_params_ = {} end - base.extend ClassMethods + mod.extend ClassMethods ::Grape::Endpoint.include HelperMethods if defined? ::Grape::Endpoint end @@ -16,6 +16,7 @@ module ClassMethods def resourceful_params key=nil if self.resourceful_params_[self.to_s].blank? self.resourceful_params_[self.to_s] = { + executed: [], model_klass: nil, resource_identifier: nil, resource_finder_key: nil, @@ -37,6 +38,10 @@ def resourceful_params key=nil end end + def is_executed? name + self.resourceful_params(:executed).include?(name) + end + def resourceful_params_merge! opts = {} current_opts = resourceful_params current_opts = current_opts.merge!(opts) @@ -48,13 +53,20 @@ def set_resource_param key, value end def fetch_resource_and_collection!(resourceful_params = {}, &block) - fetch_resource! resourceful_params, &block - fetch_collection! resourceful_params, &block + fetch_resource! resourceful_params + fetch_collection! resourceful_params + unless is_executed? :fetch_resource_and_collection! + yield if block_given? + set_resource_param :executed, resourceful_params(:executed).append(:fetch_resource_and_collection!).uniq + end end def fetch_resource!(resourceful_params = {}, &block) resourceful_params_merge!(resourceful_params) - yield if block_given? + unless is_executed? :fetch_resource! + yield if block_given? + set_resource_param :executed, resourceful_params(:executed).append(:fetch_resource!).uniq + end context = self after_validation do unless route.settings[:skip_resource] @@ -65,7 +77,10 @@ def fetch_resource!(resourceful_params = {}, &block) def fetch_collection!(resourceful_params = {}, &block) resourceful_params_merge!(resourceful_params) - yield if block_given? + unless is_executed? :fetch_collection! + yield if block_given? + set_resource_param :executed, resourceful_params(:executed).append(:fetch_collection!).uniq + end context = self after_validation do unless route.settings[:skip_collection] @@ -85,28 +100,35 @@ def actions(kind: :resource, only: [], except: [], also: []) end def resource_actions - resourceful_params[:resource_actions] + self.resourceful_params[:resource_actions] end def collection_actions - resourceful_params[:collection_actions] + self.resourceful_params[:collection_actions] end def model_klass(klass = nil) - klass = resourceful_params[:model_klass] if klass.nil? if klass.nil? - klass = self.to_s.demodulize.singularize.camelcase + klass = self.resourceful_params[:model_klass] + if klass.blank? + klass= self.to_s.demodulize.singularize.camelcase + set_resource_param :model_klass, klass + end + klass + else + set_resource_param :model_klass, klass + klass end - set_resource_param :model_klass, klass - klass end def model_klass_constant if class_exists?(model_klass) model_klass.constantize else - raise { ActiveRecord::RecordNotFound } + model_klass.constantize end + rescue + raise { ActiveRecord::RecordNotFound } end def attributes &block @@ -120,43 +142,62 @@ def class_exists?(klass) false end - def query_includes(includes = nil) - current_params = resourceful_params - includes = current_params[:query_includes] if includes.nil? - set_resource_param(:query_includes, includes) unless includes.nil? - includes + def query_includes(includes = nil, &block) + if includes.nil? && !block_given? + resourceful_params(:includes) + else + if block_given? + set_resource_param(:query_includes, block) + else + set_resource_param(:query_includes, includes) + end + end end - def query_scope(query = nil) - query = resourceful_params(:query_scope) if query.nil? - query = model_klass_constant if query.nil? - set_resource_param(:query_scope, query) - #query.respond_to?(:call) ? query.call(model_klass_constant) : query - query + def query_scope(query = nil, &block) + if query.blank? && !block_given? + resourceful_params(:query_scope) + else + if block_given? + set_resource_param :query_scope, block + else + set_resource_param :query_scope, query + end + end end - def resource_identifier(identifier = nil) - if identifier.nil? + def resource_identifier(identifier = nil, &block) + if identifier.blank? && !block_given? identifier = resourceful_params(:resource_identifier) - if(identifier.nil?) + if identifier.blank? identifier = model_klass_constant.primary_key - set_resource_param(:resource_identifier, identifier) + set_resource_param :resource_identifier, identifier end + identifier else - set_resource_param(:resource_identifier, identifier) + if block_given? + set_resource_param(:resource_identifier, block) + else + set_resource_param(:resource_identifier, identifier) + end end - identifier end - def resource_finder_key(key = nil) - key = resourceful_params(:resource_finder_key) if key.nil? - if key.nil? - key = model_klass_constant.primary_key - set_resource_param(:resource_finder_key, key) + def resource_finder_key(key = nil, &block) + if key.blank? && !block_given? + key = resourceful_params(:resource_finder_key) + if key.nil? + key = model_klass_constant.primary_key + set_resource_param :resource_finder_key, key + end + key else - set_resource_param(:resource_finder_key, key) + if block_given? + set_resource_param(:resource_finder_key, block) + else + set_resource_param(:resource_finder_key, identifier) + end end - key end def resource_identifier_and_finder_key identifier @@ -177,40 +218,62 @@ def resource_params_key(key = nil) key end - def resource_params_attributes(*attributes) - if attributes.empty? - attributes = resourceful_params(:resource_params_attributes) - end - unless attributes.empty? - set_resource_param(:resource_params_attributes, attributes) + def resource_params_attributes(*attributes, &block) + if attributes.blank? && !block_given? + resourceful_params(:resource_params_attributes) + else + if block_given? + set_resource_param(:resource_params_attributes, block) + else + set_resource_param(:resource_params_attributes, attributes) + end end - attributes end - def resource_friendly?(friendly = nil) - friendly = resourceful_params[:resource_friendly] if friendly.nil? - if friendly.nil? - set_resource_param(:resource_friendly, false) + def resource_friendly?(friendly = nil, &block) + if friendly.blank? && !block_given? + friendly = self.resourceful_params[:resource_friendly] + if friendly.blank? + friendly = false + set_resource_param :resource_friendly, friendly + end + friendly + else + if block_given? + set_resource_param :resource_friendly, block + else + set_resource_param :resource_friendly, friendly + end end - set_resource_param(:resource_friendly, friendly) - friendly end - def got_resource_callback(proc = nil) - proc = resourceful_params[:got_resource_callback] if proc.nil? - unless proc.nil? - set_resource_param(:got_resource_callback, proc) + def got_resource_callback proc = nil, &block + if proc.blank? && !block_given? + self.resourceful_params[:got_resource_callback] + else + if block_given? + set_resource_param :got_resource_callback, block + else + set_resource_param :got_resource_callback, proc + end end - proc end - def should_paginate? pg = nil - pg = resourceful_params[:should_paginate] if pg.nil? - if pg.nil? - set_resource_param(:should_paginate, false) + def should_paginate? pg = nil, &block + if pg.blank? && !block_given? + pg = self.resourceful_params[:should_paginate] + if pg.blank? + pg = false + set_resource_param :should_paginate, pg + end + pg + else + if block_given? + set_resource_param :should_paginate, block + else + set_resource_param :should_paginate, pg + end end - set_resource_param(:should_paginate, pg) - pg end end @@ -266,6 +329,10 @@ def posts @strong_parameter_object ||= ActionController::Parameters.new(params) end + def permitted_attributes + _resource_params + end + def _resource_params attributes = {} if(class_context) @@ -274,12 +341,12 @@ def _resource_params if attributes.empty? if posts.present? # attributes = params.require(class_context.resource_params_key) - if class_context.resource_params_attributes.empty? + if class_context.resource_params_attributes.blank? attributes = {} else _resource_params_attributes_ = class_context.resource_params_attributes - if _resource_params_attributes_[0].is_a?(Proc) - _resource_params_attributes_ = instance_exec(&_resource_params_attributes_[0]) + if _resource_params_attributes_.is_a?(Proc) + _resource_params_attributes_ = instance_exec(&_resource_params_attributes_) end attributes = posts.permit(_resource_params_attributes_) end @@ -289,22 +356,43 @@ def _resource_params attributes end + def _apply_query_includes query + unless class_context.query_includes.blank? + includes = class_context.query_includes + if includes.is_a?(Array) + query = query.includes(*class_context.query_includes) + else + query = query.includes(class_context.query_includes) + end + end + query + end + + def build_recursive_params(recursive_key:, parameters: posts, permitted_attributes:) + template = { recursive_key => permitted_attributes } + + nested_permit_list = template.deep_dup + current_node = nested_permit_list[recursive_key] + + nested_count = parameters.to_s.scan(/#{recursive_key}/).count + (1..nested_count).each do |i| + new_element = template.deep_dup + current_node << new_element + current_node = new_element[recursive_key] + end + nested_permit_list + end + def _query if(class_context) query = class_context.query_scope if query.respond_to?(:call) - query = instance_exec(model_class, &query) + model = _apply_query_includes(model_class) + query = instance_exec(model, &query) else + query = _apply_query_includes(query) query = query.where.not id: nil end - unless class_context.query_includes.blank? - includes = class_context.query_includes - if includes.is_a?(Array) - query = query.includes(*class_context.query_includes) - else - query = query.includes(class_context.query_includes) - end - end if(params[:order_by]) query = query.order params[:order_by] end @@ -365,7 +453,11 @@ def _set_collection(context) var_name = class_context do |context| context.model_klass.demodulize.underscore.downcase.pluralize end - @_resources = class_context.should_paginate?? paginate(_get_resources) : _get_resources + should_paginate = class_context.should_paginate? + if should_paginate.is_a?(Proc) + should_paginate = instance_exec(&should_paginate) + end + @_resources = should_paginate ? paginate(_get_resources) : _get_resources instance_variable_set("@#{var_name}", @_resources) end diff --git a/lib/grape_api/resourceful/responder.rb b/lib/grape_api/resourceful/responder.rb index 7d4c044..564ed46 100644 --- a/lib/grape_api/resourceful/responder.rb +++ b/lib/grape_api/resourceful/responder.rb @@ -88,10 +88,10 @@ def presenter collection, options={} end ver = version.upcase begin - presenter_class = "::API::#{ver}::Presenters::#{presenter_name}".constantize + presenter_class = "::#{GrapeAPI.api_namespace}::#{ver}::Presenters::#{presenter_name}".constantize rescue NameError begin - presenter_class = "::API::#{ver}::Presenters::#{presenter_name.demodulize}".constantize + presenter_class = "::#{GrapeAPI.api_namespace}::#{ver}::Presenters::#{presenter_name.demodulize}".constantize rescue NameError presenter_class = presenter_name.constantize end diff --git a/lib/grape_api/version.rb b/lib/grape_api/version.rb index f74e800..845d3a3 100644 --- a/lib/grape_api/version.rb +++ b/lib/grape_api/version.rb @@ -1,3 +1,3 @@ module GrapeAPI - VERSION = '1.0.0' + VERSION = '3.0.0' end diff --git a/spec/dummy/config/application.rb b/spec/dummy/config/application.rb index b188f0f..e4ea4b8 100644 --- a/spec/dummy/config/application.rb +++ b/spec/dummy/config/application.rb @@ -12,7 +12,6 @@ require "action_text/engine" require "action_view/railtie" require "action_cable/engine" -require "sprockets/railtie" # require "rails/test_unit/railtie" # Require the gems listed in Gemfile, including any gems diff --git a/spec/dummy/config/environments/development.rb b/spec/dummy/config/environments/development.rb index 438743c..c82ba91 100644 --- a/spec/dummy/config/environments/development.rb +++ b/spec/dummy/config/environments/development.rb @@ -56,10 +56,8 @@ # Debug mode disables concatenation and preprocessing of assets. # This option may cause significant delays in view rendering with a large # number of complex assets. - config.assets.debug = true # Suppress logger output for asset requests. - config.assets.quiet = true # Raises error for missing translations. # config.i18n.raise_on_missing_translations = true diff --git a/spec/dummy/config/environments/production.rb b/spec/dummy/config/environments/production.rb index abb1289..9e4c1e9 100644 --- a/spec/dummy/config/environments/production.rb +++ b/spec/dummy/config/environments/production.rb @@ -25,10 +25,8 @@ config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? # Compress CSS using a preprocessor. - # config.assets.css_compressor = :sass # Do not fallback to assets pipeline if a precompiled asset is missed. - config.assets.compile = false # Enable serving of images, stylesheets, and JavaScripts from an asset server. # config.asset_host = 'http://assets.example.com' diff --git a/spec/dummy/config/initializers/assets.rb b/spec/dummy/config/initializers/assets.rb index fe48fc3..0f1b712 100644 --- a/spec/dummy/config/initializers/assets.rb +++ b/spec/dummy/config/initializers/assets.rb @@ -1,7 +1,7 @@ +# Asset pipeline is not required by the engine compatibility fixture. # Be sure to restart your server when you modify this file. # Version of your assets, change this if you want to expire all your assets. -Rails.application.config.assets.version = '1.0' # Add additional assets to the asset load path. # Rails.application.config.assets.paths << Emoji.images_path diff --git a/test/rails8_smoke_test.rb b/test/rails8_smoke_test.rb new file mode 100644 index 0000000..c804cab --- /dev/null +++ b/test/rails8_smoke_test.rb @@ -0,0 +1,21 @@ +require_relative "test_helper" +require "rack/mock" + +class Rails8SmokeAPI < GrapeAPI::Endpoint::Base + get :ping do + { status: "ok" } + end +end + +class Rails8SmokeTest < Minitest::Test + def test_rails_and_gem_boot + assert_operator Gem::Version.new(Rails.version), :>=, Gem::Version.new("7.1") + assert_equal "3.0.0", GrapeAPI::VERSION + end + + def test_grape_endpoint_responds + response = Rack::MockRequest.new(Rails8SmokeAPI).get("/api/v1/ping") + assert_equal 200, response.status + assert_includes response.body, "ok" + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000..d557cc5 --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,4 @@ +ENV["RAILS_ENV"] ||= "test" +require "bundler/setup" +require "minitest/autorun" +require_relative "../spec/dummy/config/environment"