diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 0000000..77c35c7 --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,50 @@ +# This is a sample .codeclimate.yml configured for Engine analysis on Code +# Climate Platform. For an overview of the Code Climate Platform, see here: +# http://docs.codeclimate.com/article/300-the-codeclimate-platform + +# Under the engines key, you can configure which engines will analyze your repo. +# Each key is an engine name. For each value, you need to specify enabled: true +# to enable the engine as well as any other engines-specific configuration. + +# For more details, see here: +# http://docs.codeclimate.com/article/289-configuring-your-repository-via-codeclimate-yml#platform + +# For a list of all available engines, see here: +# http://docs.codeclimate.com/article/296-engines-available-engines + +engines: +# to turn on an engine, add it here and set enabled to `true` +# to turn off an engine, set enabled to `false` or remove it + rubocop: + enabled: true + # golint: + # enabled: true + # gofmt: + # enabled: true + # eslint: + # enabled: true + # csslint: + # enabled: true + +# Engines can analyze files and report issues on them, but you can separately +# decide which files will receive ratings based on those issues. This is +# specified by path patterns under the ratings key. + +# For more details see here: +# http://docs.codeclimate.com/article/289-configuring-your-repository-via-codeclimate-yml#platform + +# Note: If the ratings key is not specified, this will result in a 0.0 GPA on your dashboard. + +ratings: + paths: + - lib/** +# - app/** +# - "**.rb" +# - "**.go" + +# You can globally exclude files from being analyzed by any engine using the +# exclude_paths key. + +exclude_paths: +- spec/**/* +- vendor/**/* diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..eab6666 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,75 @@ +AllCops: + Exclude: + - config/initializers/forbidden_yaml.rb + - !ruby/regexp /(vendor|bundle|bin|db|tmp)\/.*/ + DisplayCopNames: true + DisplayStyleGuide: true + +Lint/AssignmentInCondition: + Enabled: false + +Lint/NestedMethodDefinition: + Enabled: false + Exclude: + - test/action_controller/serialization_test.rb + +Style/Alias: + EnforcedStyle: prefer_alias_method + +Style/FrozenStringLiteralComment: + EnforcedStyle: always + +Style/StringLiterals: + EnforcedStyle: single_quotes + +Metrics/AbcSize: + Max: 35 # TODO: Lower to 15 + +Metrics/ClassLength: + Max: 261 # TODO: Lower to 100 + Exclude: + - test/**/*.rb + +Metrics/CyclomaticComplexity: + Max: 7 # TODO: Lower to 6 + +Metrics/LineLength: + Max: 110 # TODO: Lower to 80 + +Metrics/MethodLength: + Max: 25 + +Metrics/PerceivedComplexity: + Max: 9 # TODO: Lower to 7 + +Style/AlignParameters: + EnforcedStyle: with_fixed_indentation + +Style/ClassAndModuleChildren: + EnforcedStyle: nested + +Style/Documentation: + Enabled: false + +Style/DoubleNegation: + Enabled: false + +Style/MissingElse: + Enabled: false # TODO: maybe enable this? + EnforcedStyle: case + +Style/EmptyElse: + EnforcedStyle: empty + +Style/MultilineOperationIndentation: + EnforcedStyle: indented + +Style/BlockDelimiters: + Enabled: true + EnforcedStyle: line_count_based + +Style/PredicateName: + Enabled: false # TODO: enable with correct prefixes + +Style/ClassVars: + Enabled: false diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..a3c9ecb --- /dev/null +++ b/.travis.yml @@ -0,0 +1,19 @@ +language: ruby +bundler_args: --without guard +rvm: + - "2.3.0" + - "2.3.1" + - ruby-head +script: "bundle exec rake" +addons: + code_climate: + repo_token: '9b1e2d38c6d07358eab4a8a1ad4846df8d7f34bfdb9dc3f885dfc4ca44c16e4c' +branches: + only: master +notifications: + email: false + +matrix: + fast_finish: true + allow_failures: + - rvm: ruby-head diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..5c88288 --- /dev/null +++ b/Gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true +source 'https://rubygems.org' + +# Specify your gem's dependencies in authorizable.gemspec +gemspec diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..3e7b598 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,68 @@ +PATH + remote: . + specs: + json_key_transform (0.1) + activesupport + +GEM + remote: https://rubygems.org/ + specs: + activesupport (5.0.0.1) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (~> 0.7) + minitest (~> 5.1) + tzinfo (~> 1.1) + ast (2.3.0) + awesome_print (1.7.0) + byebug (9.0.5) + codeclimate-test-reporter (0.6.0) + simplecov (>= 0.7.1, < 1.0.0) + coderay (1.1.1) + concurrent-ruby (1.0.2) + docile (1.1.5) + i18n (0.7.0) + json (2.0.2) + method_source (0.8.2) + minitest (5.9.0) + parser (2.3.1.2) + ast (~> 2.2) + powerpack (0.1.1) + pry (0.10.4) + coderay (~> 1.1.0) + method_source (~> 0.8.1) + slop (~> 3.4) + pry-byebug (3.4.0) + byebug (~> 9.0) + pry (~> 0.10) + rainbow (2.1.0) + rubocop (0.42.0) + parser (>= 2.3.1.1, < 3.0) + powerpack (~> 0.1) + rainbow (>= 1.99.1, < 3.0) + ruby-progressbar (~> 1.7) + unicode-display_width (~> 1.0, >= 1.0.1) + ruby-progressbar (1.8.1) + simplecov (0.12.0) + docile (~> 1.1.0) + json (>= 1.8, < 3) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.0) + slop (3.6.0) + thread_safe (0.3.5) + tzinfo (1.2.2) + thread_safe (~> 0.1) + unicode-display_width (1.1.1) + +PLATFORMS + ruby + +DEPENDENCIES + awesome_print + codeclimate-test-reporter + json_key_transform! + minitest + pry-byebug + rubocop + +BUNDLED WITH + 1.13.1 diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..17d393c --- /dev/null +++ b/Rakefile @@ -0,0 +1,49 @@ +# frozen_string_literal: true +require 'bundler/gem_tasks' + +# rubocop config copied from AMS +begin + require 'rubocop' + require 'rubocop/rake_task' +rescue LoadError # rubocop:disable Lint/HandleExceptions +else + Rake::Task[:rubocop].clear if Rake::Task.task_defined?(:rubocop) + require 'rbconfig' + # https://github.com/bundler/bundler/blob/1b3eb2465a/lib/bundler/constants.rb#L2 + windows_platforms = /(msdos|mswin|djgpp|mingw)/ + if RbConfig::CONFIG['host_os'] =~ windows_platforms + desc 'No-op rubocop on Windows-- unsupported platform' + task :rubocop do + puts 'Skipping rubocop on Windows' + end + elsif defined?(::Rubinius) + desc 'No-op rubocop to avoid rbx segfault' + task :rubocop do + puts 'Skipping rubocop on rbx due to segfault' + puts 'https://github.com/rubinius/rubinius/issues/3499' + end + else + Rake::Task[:rubocop].clear if Rake::Task.task_defined?(:rubocop) + desc 'Execute rubocop' + RuboCop::RakeTask.new(:rubocop) do |task| + task.options = ['--rails', '--display-cop-names', '--display-style-guide'] + task.fail_on_error = true + end + end +end + +require 'rake/testtask' + +Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.libs << 'test' + t.pattern = 'test/**/*_test.rb' + t.ruby_opts = ['-r./test/test_helper.rb'] + t.ruby_opts << ' -w' unless ENV['NO_WARN'] == 'true' + t.verbose = true +end + +task default: [:test, :rubocop] + +desc 'CI test task' +task ci: [:default] diff --git a/json_key_transform.gemspec b/json_key_transform.gemspec new file mode 100644 index 0000000..2c3bd60 --- /dev/null +++ b/json_key_transform.gemspec @@ -0,0 +1,38 @@ +# -*- encoding: utf-8 -*- +# frozen_string_literal: true + +# allows bundler to use the gemspec for dependencies +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) + +require 'json_key_transform/version' + +Gem::Specification.new do |s| + s.name = 'json_key_transform' + s.version = JsonKeyTransform::VERSION + s.platform = Gem::Platform::RUBY + s.license = 'MIT' + s.authors = ['L. Preston Sego III'] + s.email = 'LPSego3+dev@gmail.com' + s.homepage = 'https://github.com/NullVoxPopuli/json_key_transform' + s.summary = "JsonKeyTransform-#{JsonKeyTransform::VERSION}" + s.description = 'Extraction of the key_transform abilities of ActiveModelSerializers' + + s.files = Dir['CHANGELOG.md', 'LICENSE', 'README.md', 'lib/**/*'] + s.require_path = 'lib' + + s.test_files = s.files.grep(%r{^(test|spec|features)/}) + + s.required_ruby_version = '>= 2.0' + + s.add_runtime_dependency 'activesupport' + + # Quality Control + s.add_development_dependency 'rubocop' + s.add_development_dependency 'codeclimate-test-reporter' + s.add_development_dependency 'minitest' + + # Debugging + s.add_development_dependency 'awesome_print' + s.add_development_dependency 'pry-byebug' +end diff --git a/lib/json_key_transform.rb b/lib/json_key_transform.rb new file mode 100644 index 0000000..2fd4efa --- /dev/null +++ b/lib/json_key_transform.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true +require 'active_support/core_ext/hash/keys' +require 'active_support/core_ext/string' + +require 'json_key_transform/version' + +module JsonKeyTransform + module_function + + # Transforms values to UpperCamelCase or PascalCase. + # + # @example: + # "some_key" => "SomeKey", + def camel(value) + case value + when Array then value.map { |item| camel(item) } + when Hash then value.deep_transform_keys! { |key| camel(key) } + when Symbol then camel(value.to_s).to_sym + when String then value.underscore.camelize + else value + end + end + + # Transforms values to camelCase. + # + # @example: + # "some_key" => "someKey", + def camel_lower(value) + case value + when Array then value.map { |item| camel_lower(item) } + when Hash then value.deep_transform_keys! { |key| camel_lower(key) } + when Symbol then camel_lower(value.to_s).to_sym + when String then value.underscore.camelize(:lower) + else value + end + end + + # Transforms values to dashed-case. + # This is the default case for the JsonApi adapter. + # + # @example: + # "some_key" => "some-key", + def dash(value) + case value + when Array then value.map { |item| dash(item) } + when Hash then value.deep_transform_keys! { |key| dash(key) } + when Symbol then dash(value.to_s).to_sym + when String then value.underscore.dasherize + else value + end + end + + # Transforms values to underscore_case. + # This is the default case for deserialization in the JsonApi adapter. + # + # @example: + # "some-key" => "some_key", + def underscore(value) + case value + when Array then value.map { |item| underscore(item) } + when Hash then value.deep_transform_keys! { |key| underscore(key) } + when Symbol then underscore(value.to_s).to_sym + when String then value.underscore + else value + end + end + + # Returns the value unaltered + def unaltered(value) + value + end +end diff --git a/lib/json_key_transform/version.rb b/lib/json_key_transform/version.rb new file mode 100644 index 0000000..8e7c155 --- /dev/null +++ b/lib/json_key_transform/version.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true +module JsonKeyTransform + VERSION = '0.1'.freeze +end diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000..4e3ec5b --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true +require 'bundler/setup' + +begin + require 'simplecov' + Coverage.start +rescue LoadError + STDERR.puts 'Running without SimpleCov' +end + +require 'pry-byebug' +require 'json_key_transform' + +require 'minitest' +require 'minitest/autorun' +Minitest.backtrace_filter = Minitest::BacktraceFilter.new diff --git a/test/transforms/camel_lower_test.rb b/test/transforms/camel_lower_test.rb new file mode 100644 index 0000000..591af0f --- /dev/null +++ b/test/transforms/camel_lower_test.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true +require 'test_helper' + +describe JsonKeyTransform do + describe 'Transforms' do + describe 'camel_lower' do + it 'transforms to lowerCamelCase' do + obj = Object.new + scenarios = [ + { + value: { :"some-key" => 'value' }, + expected: { someKey: 'value' } + }, + { + value: { SomeKey: 'value' }, + expected: { someKey: 'value' } + }, + { + value: { some_key: 'value' }, + expected: { someKey: 'value' } + }, + { + value: { 'some-key' => 'value' }, + expected: { 'someKey' => 'value' } + }, + { + value: { 'SomeKey' => 'value' }, + expected: { 'someKey' => 'value' } + }, + { + value: { 'some_key' => 'value' }, + expected: { 'someKey' => 'value' } + }, + { + value: :"some-value", + expected: :someValue + }, + { + value: :SomeValue, + expected: :someValue + }, + { + value: :some_value, + expected: :someValue + }, + { + value: 'some-value', + expected: 'someValue' + }, + { + value: 'SomeValue', + expected: 'someValue' + }, + { + value: 'some_value', + expected: 'someValue' + }, + { + value: obj, + expected: obj + }, + { + value: nil, + expected: nil + }, + { + value: [ + { some_value: 'value' } + ], + expected: [ + { someValue: 'value' } + ] + } + ] + scenarios.each do |s| + result = JsonKeyTransform.camel_lower(s[:value]) + assert_equal s[:expected], result + end + end + end + end +end diff --git a/test/transforms/camel_test.rb b/test/transforms/camel_test.rb new file mode 100644 index 0000000..f7c10b2 --- /dev/null +++ b/test/transforms/camel_test.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true +require 'test_helper' + +describe JsonKeyTransform do + describe 'Transforms' do + describe 'camel' do + it 'transforms to camel case (PascalCase)' do + obj = Object.new + scenarios = [ + { + value: { :"some-key" => 'value' }, + expected: { SomeKey: 'value' } + }, + { + value: { someKey: 'value' }, + expected: { SomeKey: 'value' } + }, + { + value: { some_key: 'value' }, + expected: { SomeKey: 'value' } + }, + { + value: { 'some-key' => 'value' }, + expected: { 'SomeKey' => 'value' } + }, + { + value: { 'someKey' => 'value' }, + expected: { 'SomeKey' => 'value' } + }, + { + value: { 'some_key' => 'value' }, + expected: { 'SomeKey' => 'value' } + }, + { + value: :"some-value", + expected: :SomeValue + }, + { + value: :some_value, + expected: :SomeValue + }, + { + value: :someValue, + expected: :SomeValue + }, + { + value: 'some-value', + expected: 'SomeValue' + }, + { + value: 'someValue', + expected: 'SomeValue' + }, + { + value: 'some_value', + expected: 'SomeValue' + }, + { + value: obj, + expected: obj + }, + { + value: nil, + expected: nil + }, + { + value: [ + { some_value: 'value' } + ], + expected: [ + { SomeValue: 'value' } + ] + } + ] + scenarios.each do |s| + result = JsonKeyTransform.camel(s[:value]) + assert_equal s[:expected], result + end + end + end + end +end diff --git a/test/transforms/dash_test.rb b/test/transforms/dash_test.rb new file mode 100644 index 0000000..5ccd5c3 --- /dev/null +++ b/test/transforms/dash_test.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true +require 'test_helper' + +describe JsonKeyTransform do + describe 'Transforms' do + describe 'dash' do + it 'transforms to dash (hyphenated words)' do + obj = Object.new + scenarios = [ + { + value: { some_key: 'value' }, + expected: { :"some-key" => 'value' } + }, + { + value: { 'some_key' => 'value' }, + expected: { 'some-key' => 'value' } + }, + { + value: { SomeKey: 'value' }, + expected: { :"some-key" => 'value' } + }, + { + value: { 'SomeKey' => 'value' }, + expected: { 'some-key' => 'value' } + }, + { + value: { someKey: 'value' }, + expected: { :"some-key" => 'value' } + }, + { + value: { 'someKey' => 'value' }, + expected: { 'some-key' => 'value' } + }, + { + value: :some_value, + expected: :"some-value" + }, + { + value: :SomeValue, + expected: :"some-value" + }, + { + value: 'SomeValue', + expected: 'some-value' + }, + { + value: :someValue, + expected: :"some-value" + }, + { + value: 'someValue', + expected: 'some-value' + }, + { + value: obj, + expected: obj + }, + { + value: nil, + expected: nil + }, + { + value: [ + { 'some_value' => 'value' } + ], + expected: [ + { 'some-value' => 'value' } + ] + } + ] + scenarios.each do |s| + result = JsonKeyTransform.dash(s[:value]) + assert_equal s[:expected], result + end + end + end + end +end diff --git a/test/transforms/underscore_test.rb b/test/transforms/underscore_test.rb new file mode 100644 index 0000000..e41c848 --- /dev/null +++ b/test/transforms/underscore_test.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true +require 'test_helper' + +describe JsonKeyTransform do + describe 'Transforms' do + describe 'underscore' do + it 'transforms to underscore (snake case)' do + obj = Object.new + scenarios = [ + { + value: { :"some-key" => 'value' }, + expected: { some_key: 'value' } + }, + { + value: { 'some-key' => 'value' }, + expected: { 'some_key' => 'value' } + }, + { + value: { SomeKey: 'value' }, + expected: { some_key: 'value' } + }, + { + value: { 'SomeKey' => 'value' }, + expected: { 'some_key' => 'value' } + }, + { + value: { someKey: 'value' }, + expected: { some_key: 'value' } + }, + { + value: { 'someKey' => 'value' }, + expected: { 'some_key' => 'value' } + }, + { + value: :"some-value", + expected: :some_value + }, + { + value: :SomeValue, + expected: :some_value + }, + { + value: :someValue, + expected: :some_value + }, + { + value: 'some-value', + expected: 'some_value' + }, + { + value: 'SomeValue', + expected: 'some_value' + }, + { + value: 'someValue', + expected: 'some_value' + }, + { + value: obj, + expected: obj + }, + { + value: nil, + expected: nil + }, + { + value: [ + { 'some-value' => 'value' } + ], + expected: [ + { 'some_value' => 'value' } + ] + } + ] + scenarios.each do |s| + result = JsonKeyTransform.underscore(s[:value]) + assert_equal s[:expected], result + end + end + end + end +end