From 9f6ecb935d8d1aa5a9da5c169eed455645520add Mon Sep 17 00:00:00 2001 From: Muhammad Nawzad Date: Sat, 29 Jul 2023 16:55:20 +0300 Subject: [PATCH] Fixes rubocop offenses --- .rubocop.yml | 18 +- Gemfile | 13 +- Gemfile.lock | 28 ++-- Rakefile | 6 +- lib/generators/schemable/install_generator.rb | 4 +- lib/generators/schemable/model_generator.rb | 4 +- lib/schemable.rb | 156 +++++++----------- lib/schemable/version.rb | 2 +- schemable.gemspec | 33 ++-- spec/schemable_spec.rb | 4 +- spec/spec_helper.rb | 4 +- 11 files changed, 131 insertions(+), 141 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index fbbb4a2..4863774 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -108,10 +108,24 @@ Metrics/AbcSize: Metrics/MethodLength: Enabled: false Metrics/CyclomaticComplexity: - Max: 15 + Enabled: false Metrics/PerceivedComplexity: - Max: 15 + Enabled: false Lint/DuplicateMethods: # Disables duplicate methods warning Enabled: false Gemspec/RequiredRubyVersion: # Disables required ruby version warning Enabled: false +Metrics/ParameterLists: # Disables parameter lists warning + Enabled: false +Lint/NextWithoutAccumulator: # Disables next without accumulator warning + Enabled: false +Lint/ShadowingOuterLocalVariable: # Disables shadowing outer local variable warning + Enabled: false +Metrics/ModuleLength: # Disables module length warning + Enabled: false +Layout/EmptyLinesAroundClassBody: # Disables empty lines around class body warning + Enabled: false +Layout/HeredocIndentation: # Disables heredoc indentation warning + Enabled: false +Layout/ClosingHeredocIndentation: # Disables closing heredoc indentation warning + Enabled: false \ No newline at end of file diff --git a/Gemfile b/Gemfile index 0014dd4..2df8685 100644 --- a/Gemfile +++ b/Gemfile @@ -1,14 +1,15 @@ # frozen_string_literal: true -source "https://rubygems.org" +source 'https://rubygems.org' gemspec -gem "rake", "~> 13.0" -gem "rspec", "~> 3.0" -gem "rubocop", "~> 1.21" +gem 'rake', '~> 13.0.6' +gem 'rspec', '~> 3.12' +gem 'rubocop', '~> 1.55' +gem 'rubocop-rails', '~> 2.20.2' group :development, :test do - gem 'jsonapi-rails', '~> 0.4.1' gem 'factory_bot_rails', '~> 6.2' -end \ No newline at end of file + gem 'jsonapi-rails', '~> 0.4.1' +end diff --git a/Gemfile.lock b/Gemfile.lock index 814ee68..d234ff8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -51,6 +51,7 @@ GEM jsonapi-renderer (0.2.2) jsonapi-serializable (0.3.1) jsonapi-renderer (~> 0.2.0) + language_server-protocol (3.17.0.3) loofah (2.21.2) crass (~> 1.0.2) nokogiri (>= 1.12.0) @@ -59,8 +60,9 @@ GEM nokogiri (1.14.4-x86_64-linux) racc (~> 1.4) parallel (1.23.0) - parser (3.2.2.1) + parser (3.2.2.3) ast (~> 2.4.1) + racc racc (1.6.2) rack (2.2.7) rack-test (2.1.0) @@ -79,8 +81,8 @@ GEM zeitwerk (~> 2.5) rainbow (3.1.1) rake (13.0.6) - regexp_parser (2.8.0) - rexml (3.2.5) + regexp_parser (2.8.1) + rexml (3.2.6) rspec (3.12.0) rspec-core (~> 3.12.0) rspec-expectations (~> 3.12.0) @@ -94,18 +96,23 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.12.0) rspec-support (3.12.0) - rubocop (1.51.0) + rubocop (1.55.0) json (~> 2.3) + language_server-protocol (>= 3.17.0) parallel (~> 1.10) - parser (>= 3.2.0.0) + parser (>= 3.2.2.3) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.28.0, < 2.0) + rubocop-ast (>= 1.28.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.28.1) + rubocop-ast (1.29.0) parser (>= 3.2.1.0) + rubocop-rails (2.20.2) + activesupport (>= 4.2.0) + rack (>= 1.1) + rubocop (>= 1.33.0, < 2.0) ruby-progressbar (1.13.0) thor (1.2.2) tzinfo (2.0.6) @@ -119,9 +126,10 @@ PLATFORMS DEPENDENCIES factory_bot_rails (~> 6.2) jsonapi-rails (~> 0.4.1) - rake (~> 13.0) - rspec (~> 3.0) - rubocop (~> 1.21) + rake (~> 13.0.6) + rspec (~> 3.12) + rubocop (~> 1.55) + rubocop-rails (~> 2.20.2) schemable! BUNDLED WITH diff --git a/Rakefile b/Rakefile index cca7175..4964751 100644 --- a/Rakefile +++ b/Rakefile @@ -1,11 +1,11 @@ # frozen_string_literal: true -require "bundler/gem_tasks" -require "rspec/core/rake_task" +require 'bundler/gem_tasks' +require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) -require "rubocop/rake_task" +require 'rubocop/rake_task' RuboCop::RakeTask.new diff --git a/lib/generators/schemable/install_generator.rb b/lib/generators/schemable/install_generator.rb index 101de4a..dcfcb14 100644 --- a/lib/generators/schemable/install_generator.rb +++ b/lib/generators/schemable/install_generator.rb @@ -4,8 +4,8 @@ module Schemable source_root File.expand_path('../../templates', __dir__) class_option :model_name, type: :string, default: 'Model', desc: 'Name of the model' - def initialize(*) - super(*) + def initialize(*args) + super(*args) end def copy_initializer diff --git a/lib/generators/schemable/model_generator.rb b/lib/generators/schemable/model_generator.rb index d0eba91..7fbdf03 100644 --- a/lib/generators/schemable/model_generator.rb +++ b/lib/generators/schemable/model_generator.rb @@ -4,8 +4,8 @@ module Schemable source_root File.expand_path('../../templates', __dir__) class_option :model_name, type: :string, default: 'Model', desc: 'Name of the model' - def initialize(*) - super(*) + def initialize(*args) + super(*args) @model_name = options[:model_name] @model_name != 'Model' || raise('Model name is required') diff --git a/lib/schemable.rb b/lib/schemable.rb index 9ecdcea..6a17329 100644 --- a/lib/schemable.rb +++ b/lib/schemable.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "schemable/version" +require_relative 'schemable/version' require 'active_support/concern' module Schemable @@ -9,7 +9,6 @@ module Schemable extend ActiveSupport::Concern included do - # Maps a given type name to a corresponding JSON schema object that represents that type. # # @param type_name [String, Symbol] A String or Symbol representing the type of the property to be mapped. @@ -22,7 +21,7 @@ module Schemable integer: { type: :integer }, float: { type: :number, format: :float }, decimal: { type: :number, format: :double }, - datetime: { type: :string, format: :"date-time" }, + datetime: { type: :string, format: :'date-time' }, date: { type: :string, format: :date }, time: { type: :string, format: :time }, boolean: { type: :boolean }, @@ -70,21 +69,15 @@ module Schemable def modify_schema(original_schema, new_props, given_path = nil, delete: false) return new_props if original_schema.nil? - if given_path.nil? && delete - raise ArgumentError, "Cannot delete without a given path" - end + raise ArgumentError, 'Cannot delete without a given path' if given_path.nil? && delete if given_path.present? path_segments = given_path.split('.').map(&:to_sym) if path_segments.size == 1 - unless original_schema.key?(path_segments.first) - raise ArgumentError, "Given path does not exist in the original schema" - end + raise ArgumentError, 'Given path does not exist in the original schema' unless original_schema.key?(path_segments.first) else - unless original_schema.dig(*path_segments[0..-2]).is_a?(Hash) && original_schema.dig(*path_segments) - raise ArgumentError, "Given path does not exist in the original schema" - end + raise ArgumentError, 'Given path does not exist in the original schema' unless original_schema.dig(*path_segments[0..-2]).is_a?(Hash) && original_schema.dig(*path_segments) end path_hash = path_segments.reverse.reduce(new_props) { |a, n| { n => a } } @@ -117,31 +110,21 @@ module Schemable column_hash = model.columns_hash[attribute.to_s] # Check if this attribute has a custom JSON Schema definition - if array_types.keys.include?(attribute) - return array_types[attribute] - end + return array_types[attribute] if array_types.keys.include?(attribute) - if additional_response_attributes.keys.include?(attribute) - return additional_response_attributes[attribute] - end + return additional_response_attributes[attribute] if additional_response_attributes.keys.include?(attribute) # Check if this is an array attribute - if column_hash.as_json.try(:[], 'sql_type_metadata').try(:[], 'sql_type').include?('[]') - return type_mapper(:array) - end + return type_mapper(:array) if column_hash.as_json.try(:[], 'sql_type_metadata').try(:[], 'sql_type').include?('[]') # Map the column type to a JSON Schema type if none of the above conditions are met response = type_mapper(column_hash.try(:type)) # If the attribute is nullable, modify the schema accordingly - if response && nullable_attributes.include?(attribute) - return modify_schema(response, { nullable: true }) - end + return modify_schema(response, { nullable: true }) if response && nullable_attributes.include?(attribute) # If attribute is an enum, modify the schema accordingly - if response && model.defined_enums.key?(attribute.to_s) - return modify_schema(response, { type: :string, enum: model.defined_enums[attribute.to_s].keys }) - end + return modify_schema(response, { type: :string, enum: model.defined_enums[attribute.to_s].keys }) if response && model.defined_enums.key?(attribute.to_s) return response unless response.nil? @@ -153,7 +136,6 @@ module Schemable # If we still haven't found a schema type, default to object type_mapper(:object) - rescue NoMethodError # Log a warning if the attribute does not exist on the model Rails.logger.warn("\e[33mWARNING: #{model} does not have an attribute named \e[31m#{attribute}\e[0m") @@ -178,14 +160,13 @@ module Schemable def attributes_schema schema = { type: :object, - properties: attributes.reduce({}) do |props, attr| - props[attr] = attribute_schema(attr) - props + properties: attributes.index_with do |attr| + attribute_schema(attr) end } # modify the schema to include additional response relations - schema = modify_schema(schema, additional_response_attributes, given_path = "properties") + schema = modify_schema(schema, additional_response_attributes, 'properties') # modify the schema to exclude response relations excluded_response_attributes.each do |key| @@ -248,7 +229,6 @@ module Schemable if relation_type == :has_many props.merge!( relation_definitions.keys.index_with do |relationship| - result = { type: :object, properties: { @@ -273,7 +253,6 @@ module Schemable else props.merge!( relation_definitions.keys.index_with do |relationship| - result = { type: :object, properties: { @@ -298,7 +277,7 @@ module Schemable } # modify the schema to include additional response relations - schema = modify_schema(schema, additional_response_relations, "properties") + schema = modify_schema(schema, additional_response_relations, 'properties') # modify the schema to exclude response relations excluded_response_relations.each do |key| @@ -356,7 +335,7 @@ module Schemable type: :array, items: { anyOf: - relations.reduce([]) do |props, (relation_type, relation_definitions)| + relations.reduce([]) do |props, (_relation_type, relation_definitions)| props + relation_definitions.keys.reduce([]) do |props, relationship| props + [ unless exclude_from_expansion.include?(relationship) @@ -366,16 +345,16 @@ module Schemable type: { type: :string, default: relation_definitions[relationship].model_name }, id: { type: :string }, attributes: begin - relation_definitions[relationship].new.attributes_schema || {} - rescue NoMethodError - {} - end + relation_definitions[relationship].new.attributes_schema || {} + rescue NoMethodError + {} + end }.merge( if relation_definitions[relationship].new.relationships != { belongs_to: {}, has_many: {} } || relation_definitions[relationship].new.relationships.blank? if !expand || metadata.blank? { relationships: relation_definitions[relationship].new.relationships_schema(expand: false) } else - { relationships: relation_definitions[relationship].new.relationships_schema(relations = metadata[:nested_relationships][relationship], expand: true, exclude_from_expansion: exclude_from_expansion) } + { relationships: relation_definitions[relationship].new.relationships_schema(relations = metadata[:nested_relationships][relationship], expand: true, exclude_from_expansion:) } end else {} @@ -385,36 +364,34 @@ module Schemable end ].concat( [ - if expand && metadata.present? && !exclude_from_expansion.include?(relationship) + if expand && metadata.present? && exclude_from_expansion.exclude?(relationship) extra_relations = [] metadata[:nested_relationships].keys.reduce({}) do |props, nested_relationship| - if metadata[:nested_relationships][relationship].present? - props.merge!(metadata[:nested_relationships][nested_relationship].keys.each_with_object({}) do |relationship_type, inner_props| - props.merge!(metadata[:nested_relationships][nested_relationship][relationship_type].keys.each_with_object({}) do |relationship, inner_inner_props| + next if metadata[:nested_relationships][relationship].blank? - extra_relation_schema = { - type: :object, - properties: { - type: { type: :string, default: metadata[:nested_relationships][nested_relationship][relationship_type][relationship].model_name }, - id: { type: :string }, - attributes: metadata[:nested_relationships][nested_relationship][relationship_type][relationship].new.attributes_schema - }.merge( - if metadata[:nested_relationships][nested_relationship][relationship_type][relationship].new.relationships == { belongs_to: {}, has_many: {} } || metadata[:nested_relationships][nested_relationship][relationship_type][relationship].new.relationships.blank? - {} - else - result = { relationships: metadata[:nested_relationships][nested_relationship][relationship_type][relationship].new.relationships_schema(expand: false) } - return {} if result == { relationships: {} } - result - end - ) - } + props.merge!(metadata[:nested_relationships][nested_relationship].keys.each_with_object({}) do |relationship_type, _inner_props| + props.merge!(metadata[:nested_relationships][nested_relationship][relationship_type].keys.each_with_object({}) do |relationship, _inner_inner_props| + extra_relation_schema = { + type: :object, + properties: { + type: { type: :string, default: metadata[:nested_relationships][nested_relationship][relationship_type][relationship].model_name }, + id: { type: :string }, + attributes: metadata[:nested_relationships][nested_relationship][relationship_type][relationship].new.attributes_schema + }.merge( + if metadata[:nested_relationships][nested_relationship][relationship_type][relationship].new.relationships == { belongs_to: {}, has_many: {} } || metadata[:nested_relationships][nested_relationship][relationship_type][relationship].new.relationships.blank? + {} + else + result = { relationships: metadata[:nested_relationships][nested_relationship][relationship_type][relationship].new.relationships_schema(expand: false) } + return {} if result == { relationships: {} } - extra_relations << extra_relation_schema - end - ) - end - ) - end + result + end + ) + } + + extra_relations << extra_relation_schema + end) + end) end extra_relations @@ -427,9 +404,7 @@ module Schemable } } - schema = modify_schema(schema, additional_response_included, "included.items") - - schema + modify_schema(schema, additional_response_included, 'included.items') end # Generates the schema for the response of a resource or collection of resources in JSON API format. @@ -451,18 +426,17 @@ module Schemable # @example # "The returned schema will have a JSON API format, including the data (included attributes and relationships), included and meta keys." def response_schema(relations = try(:relationships), expand: false, exclude_from_expansion: [], multi: false, nested: false, metadata: { nested_relationships: try(:nested_relationships) }) - data = { type: :object, properties: { type: { type: :string, default: itself.class.model_name }, id: { type: :string }, - attributes: attributes_schema, + attributes: attributes_schema }.merge( if relations.blank? || relations == { belongs_to: {}, has_many: {} } {} else - { relationships: relationships_schema(relations, expand: expand, exclude_from_expansion: exclude_from_expansion) } + { relationships: relationships_schema(relations, expand:, exclude_from_expansion:) } end ) } @@ -476,26 +450,26 @@ module Schemable } else { - data: data + data: } end schema.merge!( if nested && expand - included_schema(relations, expand: nested, exclude_from_expansion: exclude_from_expansion, metadata: metadata) + included_schema(relations, expand: nested, exclude_from_expansion:, metadata:) elsif !nested && expand - included_schema(relations, expand: nested, exclude_from_expansion: exclude_from_expansion) + included_schema(relations, expand: nested, exclude_from_expansion:) else {} end ).merge!( - if !expand - { meta: meta } - else + if expand {} + else + { meta: } end ).merge!( - jsonapi: jsonapi + jsonapi: ) { @@ -504,7 +478,6 @@ module Schemable } end - # Generates the schema for the creation request payload of a resource. # # @note The `additional_create_request_attributes` and `excluded_create_request_attributes` applied to the returned schema by this method. @@ -535,7 +508,7 @@ module Schemable } } - schema = modify_schema(schema, additional_create_request_attributes, "properties.data.properties") + schema = modify_schema(schema, additional_create_request_attributes, 'properties.data.properties') excluded_create_request_attributes.each do |key| schema = modify_schema(schema, {}, "properties.data.properties.#{key}", delete: true) @@ -545,12 +518,9 @@ module Schemable required: (schema.as_json['properties']['data']['properties'].keys - optional_create_request_attributes.map(&:to_s) - nullable_attributes.map(&:to_s)).map { |key| key.to_s.camelize(:lower).to_sym } } - schema = modify_schema(schema, required_attributes, "properties.data") - - schema + modify_schema(schema, required_attributes, 'properties.data') end - # Generates the schema for the update request payload of a resource. # # @note The `additional_update_request_attributes` and `excluded_update_request_attributes` applied to the returned schema by this method. @@ -581,7 +551,7 @@ module Schemable } } - schema = modify_schema(schema, additional_update_request_attributes, "properties.data.properties") + schema = modify_schema(schema, additional_update_request_attributes, 'properties.data.properties') excluded_update_request_attributes.each do |key| schema = modify_schema(schema, {}, "properties.data.properties.#{key}", delete: true) @@ -591,9 +561,7 @@ module Schemable required: (schema.as_json['properties']['data']['properties'].keys - optional_update_request_attributes.map(&:to_s) - nullable_attributes.map(&:to_s)).map { |key| key.to_s.camelize(:lower).to_sym } } - schema = modify_schema(schema, required_attributes, "properties.data") - - schema + modify_schema(schema, required_attributes, 'properties.data') end # Returns the schema for the meta data of the response body. @@ -640,7 +608,7 @@ module Schemable properties: { version: { type: :string, - default: "1.0" + default: '1.0' } } } @@ -911,7 +879,7 @@ module Schemable # @example # User def model - self.class.name.gsub("Swagger::Definitions::", '').constantize + self.class.name.gsub('Swagger::Definitions::', '').constantize end # Returns the model name. Used for schema type naming. @@ -922,7 +890,7 @@ module Schemable # 'users' for the User model # 'citizen_applications' for the CitizenApplication model def self.model_name - name.gsub("Swagger::Definitions::", '').pluralize.underscore.downcase + name.gsub('Swagger::Definitions::', '').pluralize.underscore.downcase end # Returns the generated schemas in JSONAPI format that are used in the swagger documentation. @@ -944,7 +912,7 @@ module Schemable # @note The method can be overridden in the definition class if there are any additional customizations needed. # def self.definitions - schema_instance = self.new + schema_instance = new [ "#{schema_instance.model}CreateRequest": schema_instance.camelize_keys(schema_instance.create_request_schema), "#{schema_instance.model}UpdateRequest": schema_instance.camelize_keys(schema_instance.update_request_schema), diff --git a/lib/schemable/version.rb b/lib/schemable/version.rb index 25da6dc..3ffe440 100644 --- a/lib/schemable/version.rb +++ b/lib/schemable/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Schemable - VERSION = "0.1.3" + VERSION = '0.1.3' end diff --git a/schemable.gemspec b/schemable.gemspec index d8fd544..2048249 100644 --- a/schemable.gemspec +++ b/schemable.gemspec @@ -1,37 +1,36 @@ # frozen_string_literal: true -require_relative "lib/schemable/version" +require_relative 'lib/schemable/version' Gem::Specification.new do |spec| - spec.name = "schemable" + spec.name = 'schemable' spec.version = Schemable::VERSION - spec.authors = ["Muhammad Nawzad"] - spec.email = ["hama127n@gmail.com"] + spec.authors = ['Muhammad Nawzad'] + spec.email = ['hama127n@gmail.com'] - spec.summary = "An opiniated Gem for Rails applications to auto generate schema in JSONAPI format." + spec.summary = 'An opiniated Gem for Rails applications to auto generate schema in JSONAPI format.' spec.description = "The schemable gem is an opiniated Gem for Rails applications to auto generate schema for models in JSONAPI format. It is designed to work with rswag's swagger documentation since it can generate the schemas for it." - spec.homepage = "https://github.com/muhammadnawzad/schemable" - spec.license = "MIT" - spec.required_ruby_version = ">= 3.1.2" + spec.homepage = 'https://github.com/muhammadnawzad/schemable' + spec.license = 'MIT' + spec.required_ruby_version = '>= 3.1.2' - spec.metadata["allowed_push_host"] = 'https://rubygems.org' - - spec.metadata["homepage_uri"] = spec.homepage - spec.metadata["source_code_uri"] = 'https://github.com/muhammadnawzad/schemable' - spec.metadata["changelog_uri"] = 'https://github.com/muhammadnawzad/schemable/blob/main/CHANGELOG.md' + spec.metadata['allowed_push_host'] = 'https://rubygems.org' + spec.metadata['homepage_uri'] = spec.homepage + spec.metadata['source_code_uri'] = 'https://github.com/muhammadnawzad/schemable' + spec.metadata['changelog_uri'] = 'https://github.com/muhammadnawzad/schemable/blob/main/CHANGELOG.md' spec.files = Dir.chdir(__dir__) do `git ls-files -z`.split("\x0").reject do |f| (File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor]) end end - spec.bindir = "exe" + spec.bindir = 'exe' spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } - spec.require_paths = ["lib"] + spec.require_paths = ['lib'] - spec.add_dependency "jsonapi-rails", "~> 0.4.1" - spec.add_dependency "factory_bot_rails", "~> 6.2.0" + spec.add_dependency 'factory_bot_rails', '~> 6.2.0' + spec.add_dependency 'jsonapi-rails', '~> 0.4.1' spec.metadata['rubygems_mfa_required'] = 'true' end diff --git a/spec/schemable_spec.rb b/spec/schemable_spec.rb index 9f994c3..1b7808f 100644 --- a/spec/schemable_spec.rb +++ b/spec/schemable_spec.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true RSpec.describe Schemable do - it "has a version number" do + it 'has a version number' do expect(Schemable::VERSION).not_to be nil end - it "does something useful" do + it 'does something useful' do expect(true).to eq(true) end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 433f41d..abd54ea 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true -require "schemable" +require 'schemable' RSpec.configure do |config| # Enable flags like --only-failures and --next-failure - config.example_status_persistence_file_path = ".rspec_status" + config.example_status_persistence_file_path = '.rspec_status' # Disable RSpec exposing methods globally on `Module` and `main` config.disable_monkey_patching!