Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@ jobs:
test:
strategy:
matrix:
ruby: ["3.0", "3.1", "3.2"]
ruby: ["3.3", "3.4", "4.0"]
gemfile: [Gemfile]
include:
- ruby: "3.2"
- ruby: "3.3"
gemfile: "gemfiles/rails-8.0.gemfile"
- ruby: "3.3"
gemfile: "gemfiles/rails-7.2.gemfile"
- ruby: "3.3"
gemfile: "gemfiles/rails-7.1.gemfile"
- ruby: "3.3"
gemfile: "gemfiles/rails-7.0.gemfile"
Comment on lines +16 to 17

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wantedly/open_api#18 では 7ad9e95 で Ruby 4.0 × rails-7.0 のセルを追加していますが、こちらには入っていないようです。
annotator は railties / actionpack も読み込むので、対応表の中では Ruby 4.0 × Rails 7.0 がいちばん壊れやすい組み合わせだと思います (手元では spec は通りますが、Rails 7.0 から tsort の deprecation warning が出ます)。open_api#18 に揃えて追加しておくのはどうでしょう。

Suggested change
- ruby: "3.3"
gemfile: "gemfiles/rails-7.0.gemfile"
- ruby: "3.3"
gemfile: "gemfiles/rails-7.0.gemfile"
- ruby: "4.0"
gemfile: "gemfiles/rails-7.0.gemfile"

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます、open_api#18 に揃えて Ruby 4.0 × rails-7.0 のセルを追加しました (c1abd20)。
手元 (Docker) でも Ruby 4.0.9 × railties 7.0.10 で 13 examples, 0 failures を確認しています。tsort の warning は railties 7.0 の rails/initializable.rb 由来で 7.1 以降では出ないため、今回はそのままにしています。
なお open_api 0.6.3 の公開前なので、このセルも他と同様に依存解決で落ちる状態です。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ご対応ありがとうございます、c1abd20 を確認しました。LGTM です。
open_api#18 の merge と 0.6.3 のリリース後に、こちらの CI を再実行して通ったことを確認してから approve します。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

追加の 664f4e8 (belongs_to の $ref の修正と、配列でない has_many のスキップ) も確認しました。こちらも LGTM です。
approve は前回書いたとおり、open_api 0.6.3 のリリース後に CI が通ったことを確認してから行います。

- ruby: "4.0"
gemfile: "gemfiles/rails-7.0.gemfile"
- ruby: "3.2"
gemfile: "gemfiles/rails-6.1.gemfile"

env:
BUNDLE_GEMFILE: "${{ matrix.gemfile }}"
Expand All @@ -23,7 +29,3 @@ jobs:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
- run: bundle exec rake
- name: Send coverage
uses: paambaati/codeclimate-action@v5
env:
CC_TEST_REPORTER_ID: 921d046db793707b7d0c0d1305970a137fdf98fbc6ceeb6c355c44757040244f
2 changes: 1 addition & 1 deletion .ruby-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.2.2
4.0.9
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
## Not released yet

## 0.6.2

- Fix `rake api_spec` raising `NoMethodError` on Rails 7.0 or later https://github.com/wantedly/open_api_annotator/pull/20
- Fix `$ref` of `belongs_to` with `serializer:` option being an array like `#/components/schemas/["User"]` https://github.com/wantedly/open_api_annotator/pull/20
- Skip `has_many` annotated with a non-array type (e.g. `type: User`) or an empty array with a warning, instead of raising an error or emitting a broken `$ref` https://github.com/wantedly/open_api_annotator/pull/20
- Require open_api >= 0.6.3 https://github.com/wantedly/open_api_annotator/pull/20
- Bump Ruby and Rails https://github.com/wantedly/open_api_annotator/pull/20
- Supported Rubies: 3.3, 3.4, 4.0
- Supported Rails: 7.0, 7.1, 7.2, 8.0, 8.1

## 0.6.1

- Reflect ownership transfer https://github.com/wantedly/open_api_annotator/pull/18
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# OpenApiAnnotator [![Gem Version](https://badge.fury.io/rb/open_api_annotator.svg)](https://badge.fury.io/rb/open_api_annotator) [![Build Status](https://travis-ci.org/wantedly/open_api_annotator.svg?branch=master)](https://travis-ci.org/wantedly/open_api_annotator) [![Maintainability](https://api.codeclimate.com/v1/badges/8be7a273496459c62190/maintainability)](https://codeclimate.com/github/wantedly/open_api_annotator/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/8be7a273496459c62190/test_coverage)](https://codeclimate.com/github/wantedly/open_api_annotator/test_coverage)
# OpenApiAnnotator [![Gem Version](https://badge.fury.io/rb/open_api_annotator.svg)](https://badge.fury.io/rb/open_api_annotator) [![Build Status](https://travis-ci.org/wantedly/open_api_annotator.svg?branch=master)](https://travis-ci.org/wantedly/open_api_annotator)

OpenApiAnnotator realizes to generate OpenAPI spec by annotating to controllers and serializers.
If you use ActiveModelSerializer, this is the best way to generate OpenAPI spec.
Expand Down
2 changes: 1 addition & 1 deletion gemfiles/rails-6.1.gemfile → gemfiles/rails-7.1.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
# Specify your gem's dependencies in open_api_annotator.gemspec
gemspec path: '..'

gem 'railties', '~> 6.1.0'
gem 'railties', '~> 7.1.0'
8 changes: 8 additions & 0 deletions gemfiles/rails-7.2.gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

# Specify your gem's dependencies in open_api_annotator.gemspec
gemspec path: '..'

gem 'railties', '~> 7.2.0'
8 changes: 8 additions & 0 deletions gemfiles/rails-8.0.gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

# Specify your gem's dependencies in open_api_annotator.gemspec
gemspec path: '..'

gem 'railties', '~> 8.0.0'
8 changes: 6 additions & 2 deletions lib/open_api_annotator/components_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,12 @@ def build_has_many_association_properties(serializer)
properties = {}
serializer.open_api_has_many_associations.each do |association|
next unless association.valid?
content = association.type.first
return unless content
# Calling `first` on a non-array type (e.g. an ActiveRecord model) would run a query
content = association.type.first if association.type.is_a?(Array)
unless content
warn "Skipped #{serializer.name}##{association.name}: type of has_many should be an array with one element, but got #{association.type}."
next
end
content_name = content.try(:name) || content.to_s
properties[association.name.to_sym] = OpenApi::Schema.new(
type: "array",
Expand Down
2 changes: 1 addition & 1 deletion lib/open_api_annotator/paths_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ class RoutesFinder
def find_all
@routes ||= Rails.application.routes.routes.routes.map do |route|
parameters = []
path = PathResolver.new.resolve(route.path.ast, parameters)
path = PathResolver.new.resolve(route.path.spec, parameters)
controller = route.requirements[:controller]
action = route.requirements[:action]
Route.new(http_verb: route.verb, path: path, controller_name: controller, action_name: action, parameters: parameters)
Expand Down
2 changes: 1 addition & 1 deletion lib/open_api_annotator/serializer_annotatable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def open_api_belongs_to_associations
reflection.is_a?(ActiveModel::Serializer::BelongsToReflection)
}.map do |reflection|
serializer_class = reflection.options[:serializer]
type = serializer_class ? [serializer_class.open_api_resource_name] : reflection.options[:type]
type = serializer_class ? serializer_class.open_api_resource_name : reflection.options[:type]
Association.new(reflection.name.to_sym, type, reflection.options[:nullable])
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/open_api_annotator/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module OpenApiAnnotator
VERSION = "0.6.1"
VERSION = "0.6.2"
end
5 changes: 2 additions & 3 deletions open_api_annotator.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,13 @@ Gem::Specification.new do |spec|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]

spec.add_dependency "open_api", ">= 0.5.0"
spec.add_dependency "open_api", ">= 0.6.3"
spec.add_dependency "active_model_serializers", "~> 0.10.0"

rails_versions = ['>= 5.0', '< 7.2']
rails_versions = ['>= 5.0', '< 8.2']
spec.add_dependency "actionpack", rails_versions
spec.add_dependency "railties", rails_versions

spec.add_development_dependency "bundler", "~> 2.0"
spec.add_development_dependency "rake", "~> 13.0"
spec.add_development_dependency "rspec", "~> 3.0"
spec.add_development_dependency "pry"
Expand Down
29 changes: 29 additions & 0 deletions spec/open_api_annotator/components_builder_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,35 @@
)
end
end

context "when has_many associations are annotated with non-array types" do
let(:class_has_many_association) { OpenApiAnnotator::Association.new("logs", Log, true) }
let(:string_has_many_association) { OpenApiAnnotator::Association.new("notes", "Note", true) }

before do
# Like an ActiveRecord model, `first` would run a query
stub_const("Log", Class.new { def self.first; raise "Log.first must not be called"; end })
allow(BookSerializer).to receive(:open_api_has_many_associations).and_return([class_has_many_association, string_has_many_association, has_many_association])
end

it "skips only the associations with warnings" do
expect { subject }.to output(/Skipped BookSerializer#logs: .* but got Log\..*Skipped BookSerializer#notes: .* but got Note\./m).to_stderr
expect(subject.schemas["Book"].properties.keys).to contain_exactly(:id, :published_at, :tags, :authors, :cover_image, :publisher)
end
end

context "when a has_many association is annotated with an empty array" do
let(:empty_array_has_many_association) { OpenApiAnnotator::Association.new("logs", [], true) }

before do
allow(BookSerializer).to receive(:open_api_has_many_associations).and_return([empty_array_has_many_association, has_many_association])
end

it "skips only the association with a warning" do
expect { subject }.to output(/Skipped BookSerializer#logs/).to_stderr
expect(subject.schemas["Book"].properties.keys).to contain_exactly(:id, :published_at, :tags, :authors, :cover_image, :publisher)
end
end
end

end
36 changes: 35 additions & 1 deletion spec/open_api_annotator/routes_finder_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
let(:journey_route) { double(:journey_route) }

before do
allow(path).to receive(:ast).and_return(double(:ast))
allow(path).to receive(:spec).and_return(double(:spec))
allow(journey_route).to receive(:path).and_return(path)
allow(journey_route).to receive(:verb).and_return("GET")
allow(journey_route).to receive(:requirements).and_return({ controller: "/api/v1/books", action: "show" })
Expand All @@ -36,6 +36,40 @@
)
end
end

describe "#find_all with routes drawn by Rails" do
subject { described_class.new.find_all }

let(:rails_application) { double(:rails_application) }
let(:route_set) do
ActionDispatch::Routing::RouteSet.new.tap do |route_set|
route_set.draw do
get "/api/v1/books/:book_id/authors/:id", to: "api/v1/authors#show"
end
# Rails 7.0+ wraps Pattern#ast in Journey::Ast and clears it on eager_load!, while Pattern#spec survives both
route_set.eager_load!
end
end

before do
allow(Rails).to receive(:application).and_return(rails_application)
allow(rails_application).to receive(:routes).and_return(route_set)
end

it "resolves path and parameters from the route" do
is_expected.to match(
[
OpenApiAnnotator::Route.new(
http_verb: "GET",
path: "/api/v1/books/{book_id}/authors/{id}",
controller_name: "api/v1/authors",
action_name: "show",
parameters: [{ name: "book_id" }, { name: "id" }]
)
]
)
end
end
end

RSpec.describe OpenApiAnnotator::PathResolver do
Expand Down
41 changes: 41 additions & 0 deletions spec/open_api_annotator/serializer_annotatable_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
require 'spec_helper'
require 'active_model_serializers'

RSpec.describe OpenApiAnnotator::SerializerAnnotatable do
before do
# Annotations without `type:` are warned via Rails.logger
allow(Rails).to receive(:logger).and_return(Logger.new(nil))
stub_const("AuthorSerializer", Class.new(ActiveModel::Serializer) { include OpenApiAnnotator::SerializerAnnotatable })
stub_const("BookSerializer", Class.new(ActiveModel::Serializer) { include OpenApiAnnotator::SerializerAnnotatable })
end

describe ".open_api_has_many_associations" do
subject { BookSerializer.open_api_has_many_associations }

before { BookSerializer.has_many :authors, nullable: false, serializer: AuthorSerializer }

it "returns the resource name of the serializer as an array type" do
is_expected.to eq [OpenApiAnnotator::Association.new(:authors, ["Author"], false)]
end
end

describe ".open_api_has_one_associations" do
subject { BookSerializer.open_api_has_one_associations }

before { BookSerializer.has_one :author, nullable: false, serializer: AuthorSerializer }

it "returns the resource name of the serializer as a single type" do
is_expected.to eq [OpenApiAnnotator::Association.new(:author, "Author", false)]
end
end

describe ".open_api_belongs_to_associations" do
subject { BookSerializer.open_api_belongs_to_associations }

before { BookSerializer.belongs_to :author, nullable: false, serializer: AuthorSerializer }

it "returns the resource name of the serializer as a single type" do
is_expected.to eq [OpenApiAnnotator::Association.new(:author, "Author", false)]
end
end
end
Loading