diff --git a/.github/workflows/versionci.yml b/.github/workflows/versionci.yml index 2d673e7..8fe9f17 100644 --- a/.github/workflows/versionci.yml +++ b/.github/workflows/versionci.yml @@ -12,9 +12,14 @@ jobs: matrix: ruby: ["3.2.0", "3.2.7", "3.3.0", "3.3.7"] steps: - - uses: actions/checkout@v3 - - uses: ruby/setup-ruby@v1 + - name: Checkout code + uses: actions/checkout@v4 + - name: Setup Ruby + uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true - - run: bundle exec rspec + - name: Run Rubocop + run: bundle exec rubocop --force-exclusion --autocorrect --fail-level autocorrect + - name: Run Tests + run: bundle exec rspec diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..961073d --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,50 @@ +AllCops: + TargetRubyVersion: 3.3.0 + NewCops: enable + Exclude: + - 'bin/**/*' + - 'vendor/**/*' + - 'tmp/**/*' + - 'coverage/**/*' + +Gemspec/RequiredRubyVersion: + Enabled: false + +Layout/LineLength: + Max: 120 + Exclude: + - 'decanter.gemspec' + +Lint/UnderscorePrefixedVariableName: + Exclude: + - 'lib/decanter/core.rb' + +Metrics/AbcSize: + Max: 20 + Exclude: + - 'lib/decanter/core.rb' + +Metrics/BlockLength: + Exclude: + - 'spec/**/*_spec.rb' + - 'decanter.gemspec' + +Metrics/CyclomaticComplexity: + Exclude: + - 'lib/decanter/core.rb' + +Metrics/MethodLength: + Max: 25 + +Metrics/ModuleLength: + Max: 205 + +Naming/PredicateName: + Exclude: + - 'lib/decanter/core.rb' + +Style/Documentation: + Enabled: false + +Style/StringLiterals: + EnforcedStyle: single_quotes \ No newline at end of file diff --git a/Gemfile b/Gemfile index 8a08ac9..edc12c3 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,15 @@ +# frozen_string_literal: true + source 'https://rubygems.org' # Specify your gem's dependencies in decanter.gemspec gemspec + +group :development, :test do + gem 'bundler', '~> 2.4.22' + gem 'dotenv' + gem 'rake', '~> 12.0' + gem 'rspec-rails', '~> 3.9' + gem 'rubocop', '~> 1.59' + gem 'simplecov', '~> 0.15.1' +end diff --git a/Gemfile.lock b/Gemfile.lock index b9f6b37..297cce9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - decanter (5.0.0) + decanter (5.1.0) actionpack (>= 7.1.3.2) activesupport rails (>= 7.1.3.2) @@ -84,6 +84,7 @@ GEM minitest (>= 5.1) mutex_m tzinfo (~> 2.0) + ast (2.4.3) base64 (0.2.0) bigdecimal (3.1.7) builder (3.2.4) @@ -105,6 +106,8 @@ GEM rdoc (>= 4.0.0) reline (>= 0.4.2) json (2.7.2) + language_server-protocol (3.17.0.4) + lint_roller (1.1.0) loofah (2.22.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) @@ -131,6 +134,11 @@ GEM racc (~> 1.4) nokogiri (1.16.4-x86_64-linux) racc (~> 1.4) + parallel (1.26.3) + parser (3.3.7.4) + ast (~> 2.4.1) + racc + prism (1.4.0) psych (5.1.2) stringio racc (1.7.3) @@ -171,9 +179,11 @@ GEM rake (>= 12.2) thor (~> 1.0, >= 1.2.2) zeitwerk (~> 2.6) + rainbow (3.1.1) rake (12.3.3) rdoc (6.6.3.1) psych (>= 4.0.0) + regexp_parser (2.10.0) reline (0.5.5) io-console (~> 0.5) rspec-core (3.9.3) @@ -193,6 +203,21 @@ GEM rspec-mocks (~> 3.9.0) rspec-support (~> 3.9.0) rspec-support (3.9.4) + rubocop (1.75.2) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.44.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.44.0) + parser (>= 3.3.7.2) + prism (~> 1.4) + ruby-progressbar (1.13.0) simplecov (0.15.1) docile (~> 1.1.0) json (>= 1.8, < 3) @@ -203,6 +228,9 @@ GEM timeout (0.4.3) tzinfo (2.0.6) concurrent-ruby (~> 1.0) + unicode-display_width (3.1.4) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) webrick (1.8.1) websocket-driver (0.7.7) base64 @@ -221,6 +249,7 @@ DEPENDENCIES dotenv rake (~> 12.0) rspec-rails (~> 3.9) + rubocop (~> 1.59) simplecov (~> 0.15.1) BUNDLED WITH diff --git a/Rakefile b/Rakefile index 2995527..bd7a008 100644 --- a/Rakefile +++ b/Rakefile @@ -1 +1,10 @@ -require "bundler/gem_tasks" +# frozen_string_literal: true + +require 'bundler/gem_tasks' +require 'rspec/core/rake_task' +require 'rubocop/rake_task' + +RSpec::Core::RakeTask.new(:spec) +RuboCop::RakeTask.new + +task default: %i[spec rubocop] diff --git a/decanter.gemspec b/decanter.gemspec index 8da6fad..74d3606 100644 --- a/decanter.gemspec +++ b/decanter.gemspec @@ -1,3 +1,5 @@ +# frozen_string_literal: true + lib = File.expand_path('lib', __dir__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'decanter/version' @@ -20,19 +22,17 @@ Gem::Specification.new do |spec| spec.metadata['allowed_push_host'] = 'https://rubygems.org' - spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } + spec.files = `git ls-files -z`.split("\x0").reject do |f| + f.match(%r{^(test|spec|features)/}) + end spec.bindir = 'exe' spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ['lib'] - spec.add_dependency 'rails', '>= 7.1.3.2' spec.add_dependency 'actionpack', '>= 7.1.3.2' spec.add_dependency 'activesupport' + spec.add_dependency 'rails', '>= 7.1.3.2' spec.add_dependency 'rails-html-sanitizer', '>= 1.0.4' - spec.add_development_dependency 'bundler', '~> 2.4.22' - spec.add_development_dependency 'dotenv' - spec.add_development_dependency 'rake', '~> 12.0' - spec.add_development_dependency 'rspec-rails', '~> 3.9' - spec.add_development_dependency 'simplecov', '~> 0.15.1' + spec.metadata['rubygems_mfa_required'] = 'true' end diff --git a/lib/decanter.rb b/lib/decanter.rb index 989173e..2447059 100644 --- a/lib/decanter.rb +++ b/lib/decanter.rb @@ -1,23 +1,24 @@ +# frozen_string_literal: true + require 'active_support/all' module Decanter - class << self - def decanter_for(klass_or_sym) decanter_name = case klass_or_sym when Class - klass_or_sym.name + "#{klass_or_sym.name}Decanter" when Symbol - klass_or_sym.to_s.singularize.camelize + "#{klass_or_sym.to_s.singularize.camelize}Decanter" else - raise ArgumentError.new("cannot lookup decanter for #{klass_or_sym} with class #{klass_or_sym.class}") - end + 'Decanter' + raise ArgumentError, + "cannot lookup decanter for #{klass_or_sym} with class #{klass_or_sym.class}" + end begin decanter_name.constantize - rescue - raise NameError.new("uninitialized constant #{decanter_name}") + rescue StandardError + raise NameError, "uninitialized constant #{decanter_name}" end end @@ -29,22 +30,21 @@ def decanter_from(klass_or_string) when String begin klass_or_string.constantize - rescue - raise NameError.new("uninitialized constant #{klass_or_string}") + rescue StandardError + raise NameError, "uninitialized constant #{klass_or_string}" end else - raise ArgumentError.new("cannot find decanter from #{klass_or_string} with class #{klass_or_string.class}") + raise ArgumentError, + "cannot find decanter from #{klass_or_string} with class #{klass_or_string.class}" end - unless constant.ancestors.include? Decanter::Base - raise ArgumentError.new("#{constant.name} is not a decanter") - end + raise ArgumentError, "#{constant.name} is not a decanter" unless constant.ancestors.include? Decanter::Base constant end def configuration - @config ||= Decanter::Configuration.new + @configuration ||= Decanter::Configuration.new end def config @@ -61,4 +61,4 @@ def config require 'decanter/extensions' require 'decanter/exceptions' require 'decanter/parser' -require 'decanter/railtie' if defined?(::Rails) +require 'decanter/railtie' if defined?(Rails) diff --git a/lib/decanter/base.rb b/lib/decanter/base.rb index c45f0c1..bcac4fd 100644 --- a/lib/decanter/base.rb +++ b/lib/decanter/base.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'decanter/core' require 'decanter/collection_detection' diff --git a/lib/decanter/collection_detection.rb b/lib/decanter/collection_detection.rb index a5d47e3..03b9f85 100644 --- a/lib/decanter/collection_detection.rb +++ b/lib/decanter/collection_detection.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Decanter module CollectionDetection def self.included(base) @@ -15,7 +17,12 @@ def decant(args, **options) # leveraging the approach used in the [fast JSON API gem](https://github.com/Netflix/fast_jsonapi#collection-serialization) def collection?(args, collection_option = nil) - raise(ArgumentError, "#{name}: Unknown collection option value: #{collection_option}") unless [true, false, nil].include? collection_option + unless [ + true, false, nil + ].include? collection_option + raise(ArgumentError, + "#{name}: Unknown collection option value: #{collection_option}") + end return collection_option unless collection_option.nil? diff --git a/lib/decanter/configuration.rb b/lib/decanter/configuration.rb index 1799cf9..caf47fb 100644 --- a/lib/decanter/configuration.rb +++ b/lib/decanter/configuration.rb @@ -1,6 +1,7 @@ +# frozen_string_literal: true + module Decanter class Configuration - attr_accessor :strict, :log_unhandled_keys def initialize diff --git a/lib/decanter/core.rb b/lib/decanter/core.rb index 5e44f67..e538a71 100644 --- a/lib/decanter/core.rb +++ b/lib/decanter/core.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'action_controller' module Decanter @@ -49,12 +51,13 @@ def has_one(assoc, **options) } end - def ignore(*args) - keys_to_ignore.push(*args).map!(&:to_sym) + def ignore(*) + keys_to_ignore.push(*).map!(&:to_sym) end def strict(mode) - raise(ArgumentError, "#{name}: Unknown strict value #{mode}") unless [:ignore, true, false].include? mode + raise(ArgumentError, "#{name}: Unknown strict value #{mode}") unless [:ignore, true, + false].include? mode @strict_mode = mode end @@ -80,8 +83,7 @@ def decant(args) def default_keys # return keys with provided default value when key is not defined within incoming args default_result = default_value_inputs - .map { |input| [input[:key], input[:options][DEFAULT_VALUE_KEY]] } - .to_h + .to_h { |input| [input[:key], input[:options][DEFAULT_VALUE_KEY]] } # parse handled default values, including keys # with defaults not already managed by handled_keys @@ -132,8 +134,8 @@ def unhandled_keys(args) handlers.keys.flatten.uniq - keys_to_ignore - handlers.values - .select { |handler| handler[:type] != :input } - .map { |handler| "#{handler[:name]}_attributes".to_sym } + .reject { |handler| handler[:type] == :input } + .map { |handler| :"#{handler[:name]}_attributes" } return {} unless unhandled_keys.any? @@ -142,7 +144,8 @@ def unhandled_keys(args) p "#{name} ignoring unhandled keys: #{unhandled_keys.join(', ')}." if log_unhandled_keys_mode {} when true - raise(UnhandledKeysError, "#{name} received unhandled keys: #{unhandled_keys.join(', ')}.") + raise(UnhandledKeysError, + "#{name} received unhandled keys: #{unhandled_keys.join(', ')}.") else args.select { |key| unhandled_keys.include? key.to_sym } end @@ -153,7 +156,7 @@ def handled_keys(args) inputs, assocs = handlers.values.partition { |handler| handler[:type] == :input } {}.merge( # Inputs - inputs.select { |handler| (arg_keys & handler[:name]).any? } + inputs.select { |handler| arg_keys.intersect?(handler[:name]) } .reduce({}) { |memo, handler| memo.merge handle_input(handler, args) } ).merge( # Associations @@ -163,13 +166,13 @@ def handled_keys(args) def handle(handler, args) values = args.values_at(*handler[:name]) - values = values.length == 1 ? values.first : values + values = values.first if values.length == 1 send("handle_#{handler[:type]}", handler, values) end def handle_input(handler, args) values = args.values_at(*handler[:name]) - values = values.length == 1 ? values.first : values + values = values.first if values.length == 1 parse(handler[:key], handler[:parsers], values, handler[:options]) end @@ -178,7 +181,7 @@ def handle_association(handler, args) handler, handler.merge({ key: handler[:options].fetch(:key, "#{handler[:name]}_attributes").to_sym, - name: "#{handler[:name]}_attributes".to_sym + name: :"#{handler[:name]}_attributes" }) ] @@ -188,10 +191,11 @@ def handle_association(handler, args) when 0 {} when 1 - _handler = assoc_handlers.detect { |_handler| args.has_key?(_handler[:name]) } + _handler = assoc_handlers.detect { |_handler| args.key?(_handler[:name]) } send("handle_#{_handler[:type]}", _handler, args[_handler[:name]]) else - raise ArgumentError, "Handler #{handler[:name]} matches multiple keys: #{assoc_handler_names}." + raise ArgumentError, + "Handler #{handler[:name]} matches multiple keys: #{assoc_handler_names}." end end @@ -216,7 +220,7 @@ def handle_has_one(handler, values) end def decanter_for_handler(handler) - if specified_decanter = handler[:options][:decanter] + if (specified_decanter = handler[:options][:decanter]) Decanter.decanter_from(specified_decanter) else Decanter.decanter_for(handler[:assoc]) @@ -225,7 +229,11 @@ def decanter_for_handler(handler) def parse(key, parsers, value, options) return { key => value } unless parsers - raise ArgumentError, "No value for required argument: #{key}" if options[:required] && value_missing?(value) + + if options[:required] && value_missing?(value) + raise ArgumentError, + "No value for required argument: #{key}" + end parser_classes = Parser.parsers_for(parsers) Parser.compose_parsers(parser_classes).parse(key, value, options) diff --git a/lib/decanter/exceptions.rb b/lib/decanter/exceptions.rb index 1729381..66a91b9 100644 --- a/lib/decanter/exceptions.rb +++ b/lib/decanter/exceptions.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Decanter class Error < StandardError; end class UnhandledKeysError < Error; end diff --git a/lib/decanter/extensions.rb b/lib/decanter/extensions.rb index b2eb73c..315697b 100644 --- a/lib/decanter/extensions.rb +++ b/lib/decanter/extensions.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Decanter module Extensions def self.included(base) @@ -6,12 +8,12 @@ def self.included(base) def decant_update(args, **options) self.attributes = self.class.decant(args, options) - self.save(context: options[:context]) + save(context: options[:context]) end def decant_update!(args, **options) self.attributes = self.class.decant(args, options) - self.save!(context: options[:context]) + save!(context: options[:context]) end def decant(args, **options) @@ -20,17 +22,17 @@ def decant(args, **options) module ClassMethods def decant_create(args, **options) - self.new(decant(args, options)) - .save(context: options[:context]) + new(decant(args, options)) + .save(context: options[:context]) end def decant_new(args, **options) - self.new(decant(args, options)) + new(decant(args, options)) end def decant_create!(args, **options) - self.new(decant(args, options)) - .save!(context: options[:context]) + new(decant(args, options)) + .save!(context: options[:context]) end def decant(args, options = {}) diff --git a/lib/decanter/parser.rb b/lib/decanter/parser.rb index 03d6e82..0f97b9d 100644 --- a/lib/decanter/parser.rb +++ b/lib/decanter/parser.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative 'parser/utils' module Decanter @@ -15,7 +17,8 @@ def parsers_for(klass_or_syms) # Composes multiple parsers into a single parser def compose_parsers(parsers) - raise ArgumentError.new('expects an array') unless parsers.is_a? Array + raise ArgumentError, 'expects an array' unless parsers.is_a? Array + composed_parser = Class.new(Decanter::Parser::ComposeParser) composed_parser.parsers(parsers) composed_parser @@ -31,7 +34,8 @@ def klass_or_sym_to_str(klass_or_sym) when Symbol symbol_to_string(klass_or_sym) else - raise ArgumentError.new("cannot lookup parser for #{klass_or_sym} with class #{klass_or_sym.class}") + raise ArgumentError, + "cannot lookup parser for #{klass_or_sym} with class #{klass_or_sym.class}" end.concat('Parser') end @@ -40,7 +44,7 @@ def parser_constantize(parser_str) # safe_constantize returns nil if match not found parser_str.safe_constantize || concat_str(parser_str).safe_constantize || - raise(NameError.new("cannot find parser #{parser_str}")) + raise(NameError, "cannot find parser #{parser_str}") end # expand to include preparsers diff --git a/lib/decanter/parser/array_parser.rb b/lib/decanter/parser/array_parser.rb index a5c9fc0..9f2ff72 100644 --- a/lib/decanter/parser/array_parser.rb +++ b/lib/decanter/parser/array_parser.rb @@ -1,18 +1,22 @@ +# frozen_string_literal: true + module Decanter module Parser class ArrayParser < Base - - DUMMY_VALUE_KEY = '_'.freeze + DUMMY_VALUE_KEY = '_' parser do |val, options| next if val.nil? - raise Decanter::ParseError.new 'Expects an array' unless val.is_a? Array + raise Decanter::ParseError, 'Expects an array' unless val.is_a? Array + # Fetch parser classes for provided keys parse_each = options.fetch(:parse_each, :pass) item_parsers = Parser.parsers_for(Array.wrap(parse_each)) - unless item_parsers.all? { |parser| parser <= ValueParser || parser <= PassParser } - raise Decanter::ParseError.new 'parser(s) for array items must subclass either ValueParser or PassParser' + unless item_parsers.all? { |parser| parser <= ValueParser || parser <= PassParser } + raise Decanter::ParseError, + 'parser(s) for array items must subclass either ValueParser or PassParser' end + # Compose supplied parsers item_parser = Parser.compose_parsers(item_parsers) # Parse all values diff --git a/lib/decanter/parser/base.rb b/lib/decanter/parser/base.rb index b98f1cf..0d2b821 100644 --- a/lib/decanter/parser/base.rb +++ b/lib/decanter/parser/base.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative 'core' module Decanter @@ -7,4 +9,3 @@ class Base end end end - diff --git a/lib/decanter/parser/boolean_parser.rb b/lib/decanter/parser/boolean_parser.rb index ff666e4..6a5183f 100644 --- a/lib/decanter/parser/boolean_parser.rb +++ b/lib/decanter/parser/boolean_parser.rb @@ -1,12 +1,15 @@ +# frozen_string_literal: true + module Decanter module Parser class BooleanParser < ValueParser - allow TrueClass, FalseClass - parser do |val, options| - next if (val.nil? || val === '') - [1, '1'].include?(val) || !!/^true$/i.match?(val.to_s) + parser do |val, _options| + next if val.nil? || val == '' + + true_values = [1, '1', true, 'true', 'TRUE', 'True'] + true_values.include?(val) || val.to_s.downcase == 'true' end end end diff --git a/lib/decanter/parser/compose_parser.rb b/lib/decanter/parser/compose_parser.rb index d57d63d..3a5ec48 100644 --- a/lib/decanter/parser/compose_parser.rb +++ b/lib/decanter/parser/compose_parser.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative 'core' # A parser that composes the results of multiple parsers. @@ -5,14 +7,14 @@ module Decanter module Parser class ComposeParser < Base + def self._parse(name, value, options = {}) + raise Decanter::ParseError, 'Must have parsers' unless @parsers - def self._parse(name, value, options={}) - raise Decanter::ParseError.new('Must have parsers') unless @parsers # Call each parser on the result of the previous one. initial_result = { name => value } @parsers.reduce(initial_result) do |result, parser| - result.keys.reduce({}) do |acc, key| - acc.merge(parser.parse(key, result[key], options)) + result.keys.reduce({}) do |acc, key| + acc.merge(parser.parse(key, result[key], options)) end end end @@ -20,8 +22,6 @@ def self._parse(name, value, options={}) def self.parsers(parsers) @parsers = parsers end - end end end - diff --git a/lib/decanter/parser/core.rb b/lib/decanter/parser/core.rb index f185ee8..e82d824 100644 --- a/lib/decanter/parser/core.rb +++ b/lib/decanter/parser/core.rb @@ -1,19 +1,19 @@ +# frozen_string_literal: true + module Decanter module Parser module Core - def self.included(base) base.extend(ClassMethods) end module ClassMethods - - def _parse(name, value, options={}) + def _parse(name, value, options = {}) { name => @parser.call(value, options) } end # Check if allowed, parse if not - def parse(name, value, options={}) + def parse(name, value, options = {}) if allowed?(value) { name => value } else @@ -43,7 +43,7 @@ def preparsers # Check for allowed classes def allowed?(value) - @allowed && @allowed.any? { |allowed| value.is_a? allowed } + @allowed&.any? { |allowed| value.is_a? allowed } end end end diff --git a/lib/decanter/parser/date_parser.rb b/lib/decanter/parser/date_parser.rb index 8af7f08..5ce8054 100644 --- a/lib/decanter/parser/date_parser.rb +++ b/lib/decanter/parser/date_parser.rb @@ -1,15 +1,16 @@ +# frozen_string_literal: true + module Decanter module Parser class DateParser < ValueParser - allow Date parser do |val, options| - next if (val.nil? || val === '') + next if val.nil? || val == '' + parse_format = options.fetch(:parse_format, '%m/%d/%Y') ::Date.strptime(val, parse_format) end end end end - diff --git a/lib/decanter/parser/datetime_parser.rb b/lib/decanter/parser/datetime_parser.rb index 08dbb99..14f5f29 100644 --- a/lib/decanter/parser/datetime_parser.rb +++ b/lib/decanter/parser/datetime_parser.rb @@ -1,11 +1,13 @@ +# frozen_string_literal: true + module Decanter module Parser class DateTimeParser < ValueParser - allow DateTime parser do |val, options| - next if (val.nil? || val === '') + next if val.nil? || val == '' + parse_format = options.fetch(:parse_format, '%m/%d/%Y %I:%M:%S %p') ::DateTime.strptime(val, parse_format) end diff --git a/lib/decanter/parser/float_parser.rb b/lib/decanter/parser/float_parser.rb index 37af36f..09be529 100644 --- a/lib/decanter/parser/float_parser.rb +++ b/lib/decanter/parser/float_parser.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Decanter module Parser class FloatParser < ValueParser @@ -5,8 +7,9 @@ class FloatParser < ValueParser allow Float, Integer - parser do |val, options| - next if (val.nil? || val === '') + parser do |val, _options| + next if val.nil? || val == '' + val.scan(REGEX).join.try(:to_f) end end diff --git a/lib/decanter/parser/hash_parser.rb b/lib/decanter/parser/hash_parser.rb index 2f7ef77..dd658d6 100644 --- a/lib/decanter/parser/hash_parser.rb +++ b/lib/decanter/parser/hash_parser.rb @@ -1,18 +1,22 @@ +# frozen_string_literal: true + require_relative 'core' module Decanter module Parser class HashParser < Base - def self._parse(name, value, options={}) + def self._parse(name, value, options = {}) validate_hash(@parser.call(name, value, options)) end - private def self.validate_hash(parsed) - parsed.is_a?(Hash) ? parsed : - raise(ArgumentError.new("Result of HashParser #{self.name} was #{parsed} when it must be a hash.")) + unless parsed.is_a?(Hash) + raise ArgumentError, + "Result of HashParser #{name} was #{parsed} when it must be a hash." + end + + parsed end end end end - diff --git a/lib/decanter/parser/integer_parser.rb b/lib/decanter/parser/integer_parser.rb index 6069980..18ab225 100644 --- a/lib/decanter/parser/integer_parser.rb +++ b/lib/decanter/parser/integer_parser.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Decanter module Parser class IntegerParser < ValueParser @@ -5,11 +7,14 @@ class IntegerParser < ValueParser allow Integer - parser do |val, options| - next if (val.nil? || val === '') - val.is_a?(Float) ? - val.to_i : + parser do |val, _options| + next if val.nil? || val == '' + + if val.is_a?(Float) + val.to_i + else val.scan(REGEX).join.try(:to_i) + end end end end diff --git a/lib/decanter/parser/pass_parser.rb b/lib/decanter/parser/pass_parser.rb index 0cfeea7..ee61616 100644 --- a/lib/decanter/parser/pass_parser.rb +++ b/lib/decanter/parser/pass_parser.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + module Decanter module Parser class PassParser < Base + parser do |val, _options| + next if val.nil? || val == '' - parser do |val, options| - next if (val.nil? || val == '') val end end diff --git a/lib/decanter/parser/phone_parser.rb b/lib/decanter/parser/phone_parser.rb index 1d4064a..13cc618 100644 --- a/lib/decanter/parser/phone_parser.rb +++ b/lib/decanter/parser/phone_parser.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Decanter module Parser class PhoneParser < ValueParser @@ -5,8 +7,9 @@ class PhoneParser < ValueParser allow Integer - parser do |val, options| - next if (val.nil? || val === '') + parser do |val, _options| + next if val.nil? || val == '' + val.scan(REGEX).join.to_s end end diff --git a/lib/decanter/parser/string_parser.rb b/lib/decanter/parser/string_parser.rb index 457be7c..7422b90 100644 --- a/lib/decanter/parser/string_parser.rb +++ b/lib/decanter/parser/string_parser.rb @@ -1,9 +1,12 @@ +# frozen_string_literal: true + module Decanter module Parser class StringParser < ValueParser - parser do |val, options| - next if (val.nil? || val === '') + parser do |val, _options| + next if val.nil? || val == '' next val if val.is_a? String + val.to_s end end diff --git a/lib/decanter/parser/utils.rb b/lib/decanter/parser/utils.rb index c2d74a8..f2836f7 100644 --- a/lib/decanter/parser/utils.rb +++ b/lib/decanter/parser/utils.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Decanter module Parser module Utils @@ -24,7 +26,7 @@ def camelize_str(klass_or_sym) end def concat_str(parser_str) - 'Decanter::Parser::'.concat(parser_str) + "Decanter::Parser::#{parser_str}" end end end diff --git a/lib/decanter/parser/value_parser.rb b/lib/decanter/parser/value_parser.rb index 4ac5cea..0590d5a 100644 --- a/lib/decanter/parser/value_parser.rb +++ b/lib/decanter/parser/value_parser.rb @@ -1,20 +1,18 @@ +# frozen_string_literal: true + require_relative 'core' module Decanter module Parser class ValueParser < Base - - def self._parse(name, value, options={}) - self.validate_singularity(value) - super(name, value, options) + def self._parse(name, value, options = {}) + validate_singularity(value) + super end - private - def self.validate_singularity(value) - raise Decanter::ParseError.new 'Expects a single value' if value.is_a? Array + raise Decanter::ParseError, 'Expects a single value' if value.is_a? Array end end end end - diff --git a/lib/decanter/railtie.rb b/lib/decanter/railtie.rb index 96cb494..b34719f 100644 --- a/lib/decanter/railtie.rb +++ b/lib/decanter/railtie.rb @@ -1,21 +1,24 @@ -require 'decanter' +# frozen_string_literal: true -class Decanter::Railtie < Rails::Railtie +require 'decanter' - initializer 'decanter.active_record' do - ActiveSupport.on_load :active_record do - require 'decanter/extensions' - Decanter::Extensions::ActiveRecordExtensions.enable! +module Decanter + class Railtie < Rails::Railtie + initializer 'decanter.active_record' do + ActiveSupport.on_load :active_record do + require 'decanter/extensions' + Decanter::Extensions::ActiveRecordExtensions.enable! + end end - end - initializer 'decanter.parser.autoload', :before => :set_autoload_paths do |app| - app.config.autoload_paths << Rails.root.join("lib/decanter/parsers") - end + initializer 'decanter.parser.autoload', before: :set_autoload_paths do |app| + app.config.autoload_paths << Rails.root.join('lib/decanter/parsers') + end - generators do |app| - Rails::Generators.configure!(app.config.generators) - Rails::Generators.hidden_namespaces.uniq! - require 'generators/rails/resource_override' + generators do |app| + Rails::Generators.configure!(app.config.generators) + Rails::Generators.hidden_namespaces.uniq! + require 'generators/rails/resource_override' + end end end diff --git a/lib/decanter/version.rb b/lib/decanter/version.rb index 7a560e6..823511c 100644 --- a/lib/decanter/version.rb +++ b/lib/decanter/version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Decanter - VERSION = '5.0.0'.freeze + VERSION = '5.1.0' end diff --git a/lib/generators/decanter/install_generator.rb b/lib/generators/decanter/install_generator.rb index 0044ffe..fe4ae1d 100644 --- a/lib/generators/decanter/install_generator.rb +++ b/lib/generators/decanter/install_generator.rb @@ -1,12 +1,14 @@ +# frozen_string_literal: true + module Decanter module Generators class InstallGenerator < Rails::Generators::Base - source_root File.expand_path('../templates', __FILE__) + source_root File.expand_path('templates', __dir__) - desc "Creates a Decanter initializer in your application." + desc 'Creates a Decanter initializer in your application.' def copy_initializer - copy_file "initializer.rb", "config/initializers/decanter.rb" + copy_file 'initializer.rb', 'config/initializers/decanter.rb' end end end diff --git a/lib/generators/decanter/templates/initializer.rb b/lib/generators/decanter/templates/initializer.rb index b83f95b..7c4861f 100644 --- a/lib/generators/decanter/templates/initializer.rb +++ b/lib/generators/decanter/templates/initializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + Decanter.config do |config| config.strict = true end diff --git a/lib/generators/rails/decanter_generator.rb b/lib/generators/rails/decanter_generator.rb index 38a4562..3e5f1f0 100644 --- a/lib/generators/rails/decanter_generator.rb +++ b/lib/generators/rails/decanter_generator.rb @@ -1,16 +1,19 @@ +# frozen_string_literal: true + module Rails module Generators class DecanterGenerator < NamedBase - source_root File.expand_path('../templates', __FILE__) + source_root File.expand_path('templates', __dir__) check_class_collision suffix: 'Decanter' - ASSOCIATION_TYPES = [:has_many, :has_one, :belongs_to] + ASSOCIATION_TYPES = %i[has_many has_one belongs_to].freeze argument :attributes, type: :array, default: [], banner: 'field:type field:type' class_option :parent, type: :string, desc: 'The parent class for the generated decanter' def create_decanter_file - template 'decanter.rb.erb', File.join('app/decanters', class_path, "#{file_name}_decanter.rb") + template 'decanter.rb.erb', + File.join('app/decanters', class_path, "#{file_name}_decanter.rb") end private diff --git a/lib/generators/rails/parser_generator.rb b/lib/generators/rails/parser_generator.rb index 62b28de..4f6f4ef 100644 --- a/lib/generators/rails/parser_generator.rb +++ b/lib/generators/rails/parser_generator.rb @@ -1,13 +1,16 @@ +# frozen_string_literal: true + module Rails module Generators class ParserGenerator < NamedBase - source_root File.expand_path('../templates', __FILE__) + source_root File.expand_path('templates', __dir__) check_class_collision suffix: 'Parser' class_option :parent, type: :string, desc: 'The parent class for the generated parser' def create_parser_file - template 'parser.rb.erb', File.join('lib/decanter/parsers', class_path, "#{file_name}_parser.rb") + template 'parser.rb.erb', + File.join('lib/decanter/parsers', class_path, "#{file_name}_parser.rb") end private diff --git a/lib/generators/rails/resource_override.rb b/lib/generators/rails/resource_override.rb index 349c5c5..fb40a61 100644 --- a/lib/generators/rails/resource_override.rb +++ b/lib/generators/rails/resource_override.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails/generators' require 'rails/generators/rails/resource/resource_generator' diff --git a/spec/decanter/decanter_collection_detection_spec.rb b/spec/decanter/decanter_collection_detection_spec.rb index 36bb7a6..ec1a97a 100644 --- a/spec/decanter/decanter_collection_detection_spec.rb +++ b/spec/decanter/decanter_collection_detection_spec.rb @@ -1,19 +1,21 @@ +# frozen_string_literal: true + require 'spec_helper' describe Decanter::CollectionDetection do - let(:base_decanter) { + let(:base_decanter) do stub_const('BaseDecanter', Class.new) - } + end - let(:decanter) { + let(:decanter) do stub_const('TripDecanter', base_decanter.new) TripDecanter.class_eval { include Decanter::CollectionDetection } - } + end let(:args) { { destination: 'Hawaii' } } - before(:each) { + before(:each) do allow(base_decanter).to receive(:decant) - } + end describe '#decant' do context 'when args are a single hash' do @@ -35,7 +37,7 @@ end context 'and args are not a collection' do - let(:args) { { "0": [{ destination: 'Hawaii' }] } } + let(:args) { { '0': [{ destination: 'Hawaii' }] } } it 'calls decant on the entire element' do decanter.decant(args) expect(base_decanter).to have_received(:decant).once.with(args) diff --git a/spec/decanter/decanter_core_spec.rb b/spec/decanter/decanter_core_spec.rb index 42f89ce..4412630 100644 --- a/spec/decanter/decanter_core_spec.rb +++ b/spec/decanter/decanter_core_spec.rb @@ -1,16 +1,18 @@ +# frozen_string_literal: true + require 'spec_helper' describe Decanter::Core do let(:dummy) { Class.new { include Decanter::Core } } before(:each) do - Decanter::Core.class_variable_set(:@@handlers, {}) - Decanter::Core.class_variable_set(:@@strict_mode, {}) + Decanter::Core.instance_variable_set(:@handlers, {}) + Decanter::Core.instance_variable_set(:@strict_mode, {}) end after(:each) do - Decanter::Core.class_variable_set(:@@handlers, {}) - Decanter::Core.class_variable_set(:@@strict_mode, {}) + Decanter::Core.instance_variable_set(:@handlers, {}) + Decanter::Core.instance_variable_set(:@strict_mode, {}) end describe '#input' do @@ -28,7 +30,7 @@ let(:name) { %i[first_name last_name] } it 'adds a handler for the provided name' do - expect(dummy.handlers.has_key?(name)).to be true + expect(dummy.handlers.key?(name)).to be true end it 'raises an error if multiple values are passed without a parser' do @@ -67,13 +69,13 @@ describe '#has_one' do let(:assoc) { :profile } - let(:name) { ["#{assoc}_attributes".to_sym] } + let(:name) { [:"#{assoc}_attributes"] } let(:options) { {} } before(:each) { dummy.has_one(assoc, **options) } it 'adds a handler for the association' do - expect(dummy.handlers.has_key?(assoc)).to be true + expect(dummy.handlers.key?(assoc)).to be true end it 'the handler has type :has_one' do @@ -107,13 +109,13 @@ describe '#has_many' do let(:assoc) { :profile } - let(:name) { ["#{assoc}_attributes".to_sym] } + let(:name) { [:"#{assoc}_attributes"] } let(:options) { {} } before(:each) { dummy.has_many(assoc, **options) } it 'adds a handler for the assoc' do - expect(dummy.handlers.has_key?(assoc)).to be true + expect(dummy.handlers.key?(assoc)).to be true end it 'the handler has type :has_many' do @@ -230,28 +232,28 @@ def self.name 'PctParser' end - end.tap do |parser| - parser.pre :float - parser.parser do |val, _options| - val / 100 - end end) + PctParser.pre :float + PctParser.parser do |val, _options| + val / 100 + end + Object.const_set('KeyValueSplitterParser', Class.new(Decanter::Parser::HashParser) do def self.name 'KeyValueSplitterParser' end - end.tap do |parser| - parser.parser do |_name, val, _options| - item_delimiter = ',' - pair_delimiter = ':' - val.split(item_delimiter).reduce({}) do |memo, pair| - memo.merge(Hash[*pair.split(pair_delimiter)]) - end - end end) + KeyValueSplitterParser.parser do |_name, val, _options| + item_delimiter = ',' + pair_delimiter = ':' + val.split(item_delimiter).reduce({}) do |memo, pair| + memo.merge(Hash[*pair.split(pair_delimiter)]) + end + end + let(:key) { :afloat } let(:val) { 8.0 } @@ -299,7 +301,10 @@ def self.name let(:args) { { foo: :bar, 'baz' => 'foo' } } context 'when there are no unhandled keys' do - before(:each) { allow(dummy).to receive(:handlers).and_return({ foo: { type: :input }, baz: { type: :input } }) } + before(:each) do + allow(dummy).to receive(:handlers).and_return({ foo: { type: :input }, + baz: { type: :input } }) + end it 'returns an empty hash' do expect(dummy.unhandled_keys(args)).to match({}) @@ -333,14 +338,18 @@ def self.name it 'logs the unhandled keys' do dummy.strict :ignore - expect { dummy.unhandled_keys(args) }.to output(/ignoring unhandled keys: foo, baz/).to_stdout + expect do + dummy.unhandled_keys(args) + end.to output(/ignoring unhandled keys: foo, baz/).to_stdout end context 'and log_unhandled_keys mode is false' do it 'does not log the unhandled keys' do dummy.strict :ignore dummy.log_unhandled_keys false - expect { dummy.unhandled_keys(args) }.not_to output(/ignoring unhandled keys: foo, baz/).to_stdout + expect do + dummy.unhandled_keys(args) + end.not_to output(/ignoring unhandled keys: foo, baz/).to_stdout end end end @@ -500,13 +509,13 @@ def self.name end context 'when there is a matching key for _attributes' do - let(:args) { { "#{assoc}_attributes".to_sym => 'bar', :baz => 'foo' } } + let(:args) { { "#{assoc}_attributes": 'bar', baz: 'foo' } } it 'calls handler_has_one with the _attributes handler and args' do dummy.handle_association(handler, args) expect(dummy) .to have_received(:handle_has_one) - .with(hash_including(name: "#{assoc}_attributes".to_sym), args[:profile_attributes]) + .with(hash_including(name: :"#{assoc}_attributes"), args[:profile_attributes]) end end @@ -524,7 +533,7 @@ def self.name end context 'when there are multiple matching keys' do - let(:args) { { "#{assoc}_attributes".to_sym => 'bar', assoc => 'foo' } } + let(:args) { { :"#{assoc}_attributes" => 'bar', assoc => 'foo' } } it 'raises an argument error' do expect { dummy.handle_association(handler, args) } @@ -788,7 +797,7 @@ def self.name describe 'required_input_keys_present?' do let(:is_required) { true } - let(:args) { { "title": 'RubyConf' } } + let(:args) { { title: 'RubyConf' } } let(:input_hash) do { key: 'foo', diff --git a/spec/decanter/decanter_extensions_spec.rb b/spec/decanter/decanter_extensions_spec.rb index e26b322..a9d6f01 100644 --- a/spec/decanter/decanter_extensions_spec.rb +++ b/spec/decanter/decanter_extensions_spec.rb @@ -1,9 +1,10 @@ +# frozen_string_literal: true + require 'spec_helper' describe Decanter::Extensions do - describe '#decant' do - let(:args) { { } } + let(:args) { {} } let(:decanter) { class_double('Decanter::Base', decant: true) } context 'when a decanter is specified' do @@ -31,7 +32,7 @@ end context 'when the decanter is not specified' do - let(:options) { { } } + let(:options) { {} } before(:each) do allow(Decanter).to receive(:decanter_for).and_return(decanter) @@ -70,14 +71,16 @@ shared_examples 'a decanter update' do |strict| let(:args) { { foo: 'bar' } } - before(:each) { strict ? dummy_instance.decant_update!(args) : dummy_instance.decant_update(args) } + before(:each) do + strict ? dummy_instance.decant_update!(args) : dummy_instance.decant_update(args) + end it 'sets the attributes on the model with the results from the decanter' do expect(dummy_instance).to have_received(:attributes=).with(args) end it "calls #{strict ? 'save!' : 'save'} on the model" do - expect(dummy_instance).to have_received( strict ? :save! : :save ) + expect(dummy_instance).to have_received(strict ? :save! : :save) end end @@ -85,14 +88,16 @@ let(:args) { { foo: 'bar' } } context 'with no context' do - before(:each) { strict ? dummy_class.decant_create!(args) : dummy_class.decant_create(args) } + before(:each) do + strict ? dummy_class.decant_create!(args) : dummy_class.decant_create(args) + end it 'sets the attributes on the model with the results from the decanter' do expect(dummy_class).to have_received(:new).with(args) end it "calls #{strict ? 'save!' : 'save'} on the model" do - expect(dummy_instance).to have_received( strict ? :save! : :save ) + expect(dummy_instance).to have_received(strict ? :save! : :save) end end end diff --git a/spec/decanter/decanter_spec.rb b/spec/decanter/decanter_spec.rb index 5e1532e..b75aac4 100644 --- a/spec/decanter/decanter_spec.rb +++ b/spec/decanter/decanter_spec.rb @@ -1,109 +1,97 @@ +# frozen_string_literal: true + require 'spec_helper' describe Decanter do - before(:all) do Object.const_set('FooDecanter', - Class.new(Decanter::Base) do - def self.name - 'FooDecanter' - end - end - ) + Class.new(Decanter::Base) do + def self.name + 'FooDecanter' + end + end) end describe '#decanter_from' do - context 'for a string' do - context 'when a corresponding decanter exists' do - let(:foo) { 'FooDecanter' } it 'returns the decanter' do - expect(Decanter::decanter_from(foo)).to eq FooDecanter + expect(Decanter.decanter_from(foo)).to eq FooDecanter end end context 'when a corresponding class does not exist' do - let(:foo) { 'FoobarDecanter' } it 'raises a name error' do - expect { Decanter::decanter_from(foo) } + expect { Decanter.decanter_from(foo) } .to raise_error(NameError, "uninitialized constant #{foo}") end end context 'when a corresponding class exists but it is not a decanter' do - let(:foo) { String } it 'raises an argument error' do - expect { Decanter::decanter_from(foo) } + expect { Decanter.decanter_from(foo) } .to raise_error(ArgumentError, "#{foo.name} is not a decanter") end end end context 'for a class' do - context 'when a corresponding decanter exists' do - let(:foo) { FooDecanter } it 'returns the decanter' do - expect(Decanter::decanter_from(foo)).to eq FooDecanter + expect(Decanter.decanter_from(foo)).to eq FooDecanter end end context 'when a corresponding class does not exist' do - let(:foo) { Class.new } it 'raises a name error' do - expect { Decanter::decanter_from(foo) } + expect { Decanter.decanter_from(foo) } .to raise_error(ArgumentError, "#{foo.name} is not a decanter") end end context 'when a corresponding class exists but it is not a decanter' do - let(:foo) { String } it 'raises an argument error' do - expect { Decanter::decanter_from(foo) } + expect { Decanter.decanter_from(foo) } .to raise_error(ArgumentError, "#{foo.name} is not a decanter") end end end context 'for a symbol' do - let(:foo) { :foo } it 'raises an argument error' do - expect { Decanter::decanter_from(foo) } + expect { Decanter.decanter_from(foo) } .to raise_error(ArgumentError, "cannot find decanter from #{foo} with class #{foo.class}") end end end describe '#decanter_for' do - context 'for a string' do - let(:foo) { 'Foo' } it 'raises an argument error' do - expect { Decanter::decanter_for(foo) } - .to raise_error(ArgumentError, "cannot lookup decanter for #{foo} with class #{foo.class}") + expect { Decanter.decanter_for(foo) } + .to raise_error(ArgumentError, + "cannot lookup decanter for #{foo} with class #{foo.class}") end end context 'for a class' do context 'when a corresponding decanter does not exist' do - let(:foo) do Class.new do def self.name @@ -113,13 +101,12 @@ def self.name end it 'raises a name error' do - expect { Decanter::decanter_for(foo) } - .to raise_error(NameError, "uninitialized constant #{foo.name.concat('Decanter')}") + expect { Decanter.decanter_for(foo) } + .to raise_error(NameError, "uninitialized constant #{foo.name}Decanter") end end context 'when a corresponding decanter exists' do - let(:foo) do Class.new do def self.name @@ -129,42 +116,41 @@ def self.name end it 'returns the decanter' do - expect(Decanter::decanter_for(foo)).to eq FooDecanter + expect(Decanter.decanter_for(foo)).to eq FooDecanter end context 'and the class name is a frozen string' do - let(:foo) { + let(:foo) do Class.new do def self.name - 'Foo'.freeze # ActiveRecord classes might have a frozen name (Rails 5.2.3) + 'Foo' # ActiveRecord classes might have a frozen name (Rails 5.2.3) end end - } + end it 'returns the decanter' do - expect(Decanter::decanter_for(foo)).to eq FooDecanter + expect(Decanter.decanter_for(foo)).to eq FooDecanter end end end end context 'for a symbol' do - let(:foo) { :foobar } context 'when a corresponding decanter does not exist' do it 'raises a name error' do - expect { Decanter::decanter_for(foo) } - .to raise_error(NameError, "uninitialized constant #{foo.to_s.capitalize.concat('Decanter')}") + expect { Decanter.decanter_for(foo) } + .to raise_error(NameError, + "uninitialized constant #{foo.to_s.capitalize}Decanter") end end context 'when a corresponding decanter exists' do - let(:foo) { :foo } it 'returns the decanter' do - expect(Decanter::decanter_for(foo)).to eq FooDecanter + expect(Decanter.decanter_for(foo)).to eq FooDecanter end end end diff --git a/spec/decanter/parser/array_parser_spec.rb b/spec/decanter/parser/array_parser_spec.rb index 6145f6d..94f7889 100644 --- a/spec/decanter/parser/array_parser_spec.rb +++ b/spec/decanter/parser/array_parser_spec.rb @@ -1,7 +1,8 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'ArrayParser' do - let(:name) { :foo } let(:parser) { Decanter::Parser::ArrayParser } @@ -9,32 +10,34 @@ describe '#parse' do context 'with an empty array' do it 'returns an empty array' do - expect(parser.parse(name, [])).to match({name => []}) + expect(parser.parse(name, [])).to match({ name => [] }) end end context 'with an array of "empty" values' do it 'returns an empty array' do - expect(parser.parse(name, [''])).to match({name => []}) + expect(parser.parse(name, [''])).to match({ name => [] }) end end context 'with no parse_each option' do it 'defaults to PassParser' do - expect(parser.parse(name, [1, '2'])).to match({name => [1, '2']}) + expect(parser.parse(name, [1, '2'])).to match({ name => [1, '2'] }) end end context 'with parse_each option' do context 'with single parser' do it 'applies the parser' do - expect(parser.parse(name, [1, 2, 3], parse_each: :string)).to match({name => ['1', '2', '3']}) + expect(parser.parse(name, [1, 2, 3], + parse_each: :string)).to match({ name => %w[1 2 3] }) end end context 'with multiple parsers' do it 'applies all parsers' do - expect(parser.parse(name, [0, 1], parse_each: [:boolean, :string])).to eq({name => ['false', 'true']}) + expect(parser.parse(name, [0, 1], + parse_each: %i[boolean string])).to eq({ name => %w[false true] }) end end @@ -54,22 +57,22 @@ context 'with a non-array argument' do it 'raises an exception' do expect { parser.parse(name, 123) } - .to raise_error(Decanter::ParseError) + .to raise_error(Decanter::ParseError) end end - # Note: this follows example above, + # NOTE: this follows example above, # but it's still worth testing since it departs from the behavior of other parsers. context 'with empty string' do it 'raises an exception' do expect { parser.parse(name, '') } - .to raise_error(Decanter::ParseError) + .to raise_error(Decanter::ParseError) end end context 'with nil' do it 'returns nil' do - expect(parser.parse(name, nil)).to match({name => nil}) + expect(parser.parse(name, nil)).to match({ name => nil }) end end end diff --git a/spec/decanter/parser/boolean_parser_spec.rb b/spec/decanter/parser/boolean_parser_spec.rb index 3f0493c..57d60c7 100644 --- a/spec/decanter/parser/boolean_parser_spec.rb +++ b/spec/decanter/parser/boolean_parser_spec.rb @@ -1,18 +1,18 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'BooleanParser' do - let(:parser) { Decanter::Parser::BooleanParser } describe '#parse' do - trues = [ ['number', 1], ['string', 1], ['boolean', true], - ['string', 'true'], - ['string', 'True'], - ['string', 'truE'] + %w[string true], + %w[string True], + %w[string truE] ] falses = [ @@ -29,7 +29,7 @@ context 'returns true for' do trues.each do |cond| it "#{cond[0]}: #{cond[1]}" do - expect(parser.parse(name, cond[1])).to match({name => true}) + expect(parser.parse(name, cond[1])).to match({ name => true }) end end end @@ -37,20 +37,20 @@ context 'returns false for' do falses.each do |cond| it "#{cond[0]}: #{cond[1]}" do - expect(parser.parse(name, cond[1])).to match({name => false}) + expect(parser.parse(name, cond[1])).to match({ name => false }) end end end context 'with empty string' do it 'returns nil' do - expect(parser.parse(name, '')).to match({name => nil}) + expect(parser.parse(name, '')).to match({ name => nil }) end end context 'with nil' do it 'returns nil' do - expect(parser.parse(name, nil)).to match({name => nil}) + expect(parser.parse(name, nil)).to match({ name => nil }) end end diff --git a/spec/decanter/parser/date_parser_spec.rb b/spec/decanter/parser/date_parser_spec.rb index 1b8e4cc..41779dc 100644 --- a/spec/decanter/parser/date_parser_spec.rb +++ b/spec/decanter/parser/date_parser_spec.rb @@ -1,7 +1,8 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'DateParser' do - let(:name) { :foo } let(:parser) { Decanter::Parser::DateParser } @@ -9,7 +10,7 @@ describe '#parse' do context 'with a valid date string of default form ' do it 'returns the date' do - expect(parser.parse(name, '2/21/1990')).to match({name => Date.new(1990,2,21)}) + expect(parser.parse(name, '2/21/1990')).to match({ name => Date.new(1990, 2, 21) }) end end @@ -22,25 +23,27 @@ context 'with a date' do it 'returns the date' do - expect(parser.parse(name, Date.new(1990,2,21))).to match({name => Date.new(1990,2,21)}) + expect(parser.parse(name, + Date.new(1990, 2, 21))).to match({ name => Date.new(1990, 2, 21) }) end end context 'with a valid date string and custom format' do it 'returns the date' do - expect(parser.parse(name, '2-21-1990', parse_format: '%m-%d-%Y')).to match({name => Date.new(1990,2,21)}) + expect(parser.parse(name, '2-21-1990', + parse_format: '%m-%d-%Y')).to match({ name => Date.new(1990, 2, 21) }) end end context 'with empty string' do it 'returns nil' do - expect(parser.parse(name, '')).to match({name => nil}) + expect(parser.parse(name, '')).to match({ name => nil }) end end context 'with nil' do it 'returns nil' do - expect(parser.parse(name, nil)).to match({name => nil}) + expect(parser.parse(name, nil)).to match({ name => nil }) end end diff --git a/spec/decanter/parser/datetime_parser_spec.rb b/spec/decanter/parser/datetime_parser_spec.rb index 087004f..68de67a 100644 --- a/spec/decanter/parser/datetime_parser_spec.rb +++ b/spec/decanter/parser/datetime_parser_spec.rb @@ -1,7 +1,8 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'DateTimeParser' do - let(:name) { :foo } let(:parser) { Decanter::Parser::DateTimeParser } @@ -9,7 +10,9 @@ describe '#parse' do context 'with a valid datetime string of default form ' do it 'returns the datetime' do - expect(parser.parse(name, '2/21/1990 04:15:16 PM')).to match({name => DateTime.new(1990,2,21,16,15,16)}) + expect(parser.parse(name, + '2/21/1990 04:15:16 PM')).to match({ name => DateTime.new(1990, 2, 21, + 16, 15, 16) }) end end @@ -22,25 +25,29 @@ context 'with a datetime' do it 'returns the datetime' do - expect(parser.parse(name, DateTime.new(1990,2,21))).to match({name => DateTime.new(1990,2,21)}) + expect(parser.parse(name, + DateTime.new(1990, 2, + 21))).to match({ name => DateTime.new(1990, 2, 21) }) end end context 'with a valid date string and custom format' do it 'returns the date' do - expect(parser.parse(name, '2-21-1990', parse_format: '%m-%d-%Y')).to match({name => DateTime.new(1990,2,21)}) + expect(parser.parse(name, '2-21-1990', + parse_format: '%m-%d-%Y')).to match({ name => DateTime.new(1990, 2, + 21) }) end end context 'with empty string' do it 'returns nil' do - expect(parser.parse(name, '')).to match({name => nil}) + expect(parser.parse(name, '')).to match({ name => nil }) end end context 'with nil' do it 'returns nil' do - expect(parser.parse(name, nil)).to match({name => nil}) + expect(parser.parse(name, nil)).to match({ name => nil }) end end diff --git a/spec/decanter/parser/float_parser_spec.rb b/spec/decanter/parser/float_parser_spec.rb index dfee2ae..f0b37c5 100644 --- a/spec/decanter/parser/float_parser_spec.rb +++ b/spec/decanter/parser/float_parser_spec.rb @@ -1,7 +1,8 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'FloatParser' do - let(:name) { :foo } let(:parser) { Decanter::Parser::FloatParser } diff --git a/spec/decanter/parser/hash_parser_spec.rb b/spec/decanter/parser/hash_parser_spec.rb index 0aa481d..7f56644 100644 --- a/spec/decanter/parser/hash_parser_spec.rb +++ b/spec/decanter/parser/hash_parser_spec.rb @@ -1,15 +1,16 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'HashParser' do - - let(:parser) { + let(:parser) do # Mock parser just passes value through Class.new(Decanter::Parser::HashParser) do parser do |_name, val, _options| val end end - } + end context 'when the result is a hash' do it 'returns the hash directly' do @@ -20,7 +21,7 @@ context 'when the result is not a hash' do it 'raises an argument error' do expect { parser.parse(:first_name, 'bar') } - .to raise_error(ArgumentError, "Result of HashParser was bar when it must be a hash.") + .to raise_error(ArgumentError, 'Result of HashParser was bar when it must be a hash.') end end end diff --git a/spec/decanter/parser/integer_parser_spec.rb b/spec/decanter/parser/integer_parser_spec.rb index 1ae448e..7e2a9c8 100644 --- a/spec/decanter/parser/integer_parser_spec.rb +++ b/spec/decanter/parser/integer_parser_spec.rb @@ -1,7 +1,8 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'IntegerParser' do - let(:name) { :foo } let(:parser) { Decanter::Parser::IntegerParser } diff --git a/spec/decanter/parser/parser_spec.rb b/spec/decanter/parser/parser_spec.rb index 00d3403..29cb313 100644 --- a/spec/decanter/parser/parser_spec.rb +++ b/spec/decanter/parser/parser_spec.rb @@ -1,39 +1,34 @@ +# frozen_string_literal: true + require 'spec_helper' describe Decanter::Parser do - before(:all) do - Object.const_set('FooParser', - Class.new(Decanter::Parser::ValueParser) do - def self.name - 'FooParser' - end - end - ) + Class.new(Decanter::Parser::ValueParser) do + def self.name + 'FooParser' + end + end) Object.const_set('BarParser', - Class.new(Decanter::Parser::ValueParser) do - def self.name - 'BarParser' - end - end.tap do |parser| - parser.pre :date, :float - end - ) + Class.new(Decanter::Parser::ValueParser) do + def self.name + 'BarParser' + end + end) + BarParser.pre :date, :float + Object.const_set('CheckInFieldDataParser', - Class.new(Decanter::Parser::ValueParser) do - def self.name - 'BarParser' - end - end.tap do |parser| - parser.pre :date, :float - end - ) + Class.new(Decanter::Parser::ValueParser) do + def self.name + 'BarParser' + end + end) + CheckInFieldDataParser.pre :date, :float end describe '#parsers_for' do - subject { Decanter::Parser.parsers_for(:bar) } let(:_data) { Decanter::Parser.parsers_for(:check_in_field_data) } diff --git a/spec/decanter/parser/pass_parser_spec.rb b/spec/decanter/parser/pass_parser_spec.rb index 68155a4..d396337 100644 --- a/spec/decanter/parser/pass_parser_spec.rb +++ b/spec/decanter/parser/pass_parser_spec.rb @@ -1,32 +1,33 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'PassParser' do - let(:name) { :foo } let(:parser) { Decanter::Parser::PassParser } describe '#parse' do it 'lets anything through' do - expect(parser.parse(name, '(12)2-21/19.90')).to match({name =>'(12)2-21/19.90'}) + expect(parser.parse(name, '(12)2-21/19.90')).to match({ name => '(12)2-21/19.90' }) end context 'with empty string' do it 'returns nil' do - expect(parser.parse(name, '')).to match({name => nil}) + expect(parser.parse(name, '')).to match({ name => nil }) end end context 'with nil' do it 'returns nil' do - expect(parser.parse(name, nil)).to match({name => nil}) + expect(parser.parse(name, nil)).to match({ name => nil }) end end context 'with array value' do it 'returns the array value' do - expect(parser.parse(name, ['123'])).to match({name => ['123']}) - expect(parser.parse(name, [])).to match({name => []}) + expect(parser.parse(name, ['123'])).to match({ name => ['123'] }) + expect(parser.parse(name, [])).to match({ name => [] }) end end end diff --git a/spec/decanter/parser/phone_parser_spec.rb b/spec/decanter/parser/phone_parser_spec.rb index bee3a76..b1b1eb9 100644 --- a/spec/decanter/parser/phone_parser_spec.rb +++ b/spec/decanter/parser/phone_parser_spec.rb @@ -1,25 +1,26 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'PhoneParser' do - let(:name) { :foo } let(:parser) { Decanter::Parser::PhoneParser } describe '#parse' do it 'strips all non-numbers from value and returns a string' do - expect(parser.parse(:foo, '(12)2-21/19.90')).to match({:foo =>'122211990'}) + expect(parser.parse(:foo, '(12)2-21/19.90')).to match({ foo: '122211990' }) end context 'with empty string' do it 'returns nil' do - expect(parser.parse(name, '')).to match({name => nil}) + expect(parser.parse(name, '')).to match({ name => nil }) end end context 'with nil' do it 'returns nil' do - expect(parser.parse(name, nil)).to match({name => nil}) + expect(parser.parse(name, nil)).to match({ name => nil }) end end diff --git a/spec/decanter/parser/string_parser_spec.rb b/spec/decanter/parser/string_parser_spec.rb index 77eb7ff..15e483a 100644 --- a/spec/decanter/parser/string_parser_spec.rb +++ b/spec/decanter/parser/string_parser_spec.rb @@ -1,7 +1,8 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'StringParser' do - let(:name) { :foo } let(:parser) { Decanter::Parser::StringParser } @@ -9,25 +10,25 @@ describe '#parse' do context 'with integer' do it 'returns string' do - expect(parser.parse(name, 8)).to match({name => '8'}) + expect(parser.parse(name, 8)).to match({ name => '8' }) end end context 'with string' do it 'returns a string' do - expect(parser.parse(name, 'bar')).to match({name => 'bar'}) + expect(parser.parse(name, 'bar')).to match({ name => 'bar' }) end end context 'with empty string' do it 'returns nil' do - expect(parser.parse(name, '')).to match({name => nil}) + expect(parser.parse(name, '')).to match({ name => nil }) end end context 'with nil' do it 'returns nil' do - expect(parser.parse(name, nil)).to match({name => nil}) + expect(parser.parse(name, nil)).to match({ name => nil }) end end diff --git a/spec/decanter/parser/value_parser_spec.rb b/spec/decanter/parser/value_parser_spec.rb index 24c4745..c94eb16 100644 --- a/spec/decanter/parser/value_parser_spec.rb +++ b/spec/decanter/parser/value_parser_spec.rb @@ -1,20 +1,20 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'ValueParser' do - - let(:parser) { + let(:parser) do # Mock parser just passes value through Class.new(Decanter::Parser::ValueParser) do parser do |val, _options| val end end - } + end context 'when the result is a single value' do it 'returns a hash with the value keyed under the name' do expect(parser.parse(:foo, 'bar')).to match({ foo: 'bar' }) end end - end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 0a834d8..6621596 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,10 +1,12 @@ +# frozen_string_literal: true + require 'dotenv' Dotenv.load # Report Coverage to Code Climate require 'simplecov' SimpleCov.start -$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) +$LOAD_PATH.unshift File.expand_path('../lib', __dir__) require 'decanter' RSpec.configure do |config|