diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 69dccfc..d20ab56 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,7 +4,9 @@ updates: directory: "/" schedule: interval: "quarterly" - - package-ecosystem: "bundler" - directory: "/" - schedule: - interval: "quarterly" + # TODO: the bundler dependency updater doesn't seem to handle the Gemfile + # using the gemspec? Figure this out and re-enable, if possible + # - package-ecosystem: "bundler" + # directory: "/" + # schedule: + # interval: "quarterly" diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..39adbc9 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,56 @@ +# The behavior of RuboCop can be controlled via the .rubocop.yml +# configuration file. It makes it possible to enable/disable +# certain cops (checks) and to alter their behavior if they accept +# any parameters. The file can be placed either in your home +# directory or in some project directory. +# +# RuboCop will start looking for the configuration file in the directory +# where the inspected file is and continue its way up to the root directory. +# +# See https://docs.rubocop.org/rubocop/configuration +plugins: + - 'rubocop-rake' + - 'rubocop-rspec' + +Layout/LineLength: + Max: 120 + +Metrics/BlockLength: + Exclude: + - "spec/**/*.rb" + - "*.gemspec" + +Metrics/MethodLength: + Enabled: false + +Naming/FileName: + Exclude: + # This file is deliberately named with dashes, to match the package name + - 'lib/recursive-open-struct.rb' + +Style/CommentedKeyword: + Exclude: + - "spec/**/*.rb" + +Style/Documentation: + Enabled: false + +# Can't really disable this in the gemspec file (it is reported on line 1, but +# line 1 has the frozen string literal comment) +Gemspec/RequiredRubyVersion: + Enabled: false +# TODO: should I set a required ruby version? At the moment, the project policy +# is to officially support supported Ruby versions (and JRuby), but to not +# officially support no-longer-maintained Ruby versions. + +RSpec/ExampleLength: + Enabled: false +RSpec/NestedGroups: + Enabled: false +RSpec/MultipleExpectations: + Enabled: false + +# Not sure why this one still fails -- the specs are all in a +# recursive_open_struct directory. +RSpec/SpecFilePathFormat: + Enabled: false diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index fe7bdcb..0000000 --- a/.travis.yml +++ /dev/null @@ -1,34 +0,0 @@ ---- -language: ruby -rvm: - # No longer supported (but test anyways) - - 2.0.0 - - 2.1.10 - - 2.2.10 - - jruby-19mode - - 2.3.8 - - 2.4.10 - # Current stable supported by Travis - - 2.5.8 - - 2.6.6 - - 2.7.2 - - 3.0.0 - - jruby-9.1.9.0 - # Future - - ruby-head - - jruby-head - - truffleruby-head -sudo: false -matrix: - allow_failures: - # No longer supported - - rvm: 2.0.0 - - rvm: 2.1.10 - - rvm: 2.2.10 - - rvm: 2.3.8 - - rvm: 2.4.10 - - rvm: jruby-19mode - # Future - - rvm: ruby-head - - rvm: jruby-head - - rvm: truffleruby-head diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 011a348..863c191 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,3 +49,31 @@ When contributing code that changes behavior or fixes bugs, please include unit tests to cover the new behavior or to provide regression testing for bugs. Also, treat the unit tests as documentation --- make sure they are clean, clear, and concise, and well organized. + +## Testing and Development + +- This project uses RSpec to both document and test behavior. Please structure + your tests help someone understand what is being tested. +- It also uses rubocop to autoformat and lint the project. Run it locally + before committing. + +You can run both the test suite and the linter with the following commands: + +```sh +# install/update dependencies +bundle + +# run the test suite and linter +bundle exec rake + +# have the linter apply all safe fixes (eg, autoformatting) +bundle exec rake rubocop:autocorrect +``` + +## Release Process + +1. Update CHANGELOG.md and lib/recursive_open_struct/version.rb +2. Run `bundle exec rake update_authors` +3. Make a version release commit +4. Run the release task (it will tag, build, push code, push package): + `bundle exec rake release` diff --git a/Gemfile b/Gemfile index 851fabc..5f10ba8 100644 --- a/Gemfile +++ b/Gemfile @@ -1,2 +1,4 @@ +# frozen_string_literal: true + source 'https://rubygems.org' gemspec diff --git a/Rakefile b/Rakefile index 466148f..1fa3517 100644 --- a/Rakefile +++ b/Rakefile @@ -1,4 +1,4 @@ -# encoding: utf-8 +# frozen_string_literal: true require 'rubygems' require 'bundler/gem_tasks' @@ -9,23 +9,26 @@ RSpec::Core::RakeTask.new(:spec) do |spec| end namespace :spec do if RUBY_VERSION =~ /^1\.8/ - desc "Rspec code coverage (1.8.7)" + desc 'Rspec code coverage (1.8.7)' RSpec::Core::RakeTask.new(:coverage) do |spec| spec.pattern = 'spec/**/*_spec.rb' spec.rcov = true end else - desc "Rspec code coverage (1.9+)" + desc 'Rspec code coverage (1.9+)' task :coverage do ENV['COVERAGE'] = 'true' - Rake::Task["spec"].execute + Rake::Task['spec'].execute end end end +require 'rubocop/rake_task' +RuboCop::RakeTask.new + require 'rdoc/task' Rake::RDocTask.new do |rdoc| - version = File.exist?('VERSION') ? File.read('VERSION') : "" + version = File.exist?('VERSION') ? File.read('VERSION') : '' rdoc.rdoc_dir = 'rdoc' rdoc.title = "recursive-open-struct #{version}" @@ -33,28 +36,29 @@ Rake::RDocTask.new do |rdoc| rdoc.rdoc_files.include('lib/**/*.rb') end -task :default => :spec +task default: %i[spec rubocop] +desc 'Ensure all files have appropriate permissions before building a package' task :fix_permissions do - File.umask 0022 + File.umask 0o022 filelist = `git ls-files`.split("\n") - FileUtils.chmod 0644, filelist, :verbose => true - FileUtils.chmod 0755, ['lib','spec'], :verbose => true + FileUtils.chmod 0o644, filelist, verbose: true + FileUtils.chmod 0o755, %w[lib spec], verbose: true end -desc "Update the AUTHORS.txt file" +desc 'Update the AUTHORS.txt file' task :update_authors do authors = `git log --format="%aN <%aE>"|sort -f|uniq` File.open('AUTHORS.txt', 'w') do |f| f.write("Recursive-open-struct was written by these fine people:\n\n") - f.write(authors.split("\n").map { |a| "* #{a}" }.join( "\n" )) + f.write(authors.split("\n").map { |a| "* #{a}" }.join("\n")) f.write("\n") end end -task :build => [:update_authors, :fix_permissions] +task build: %i[update_authors fix_permissions] -desc "Run an interactive pry shell with ros required" +desc 'Run an interactive pry shell with ros required' task :pry do - sh "pry -I lib -r recursive-open-struct" + sh 'pry -I lib -r recursive-open-struct' end diff --git a/lib/recursive-open-struct.rb b/lib/recursive-open-struct.rb index 886fe8f..53f6ed6 100644 --- a/lib/recursive-open-struct.rb +++ b/lib/recursive-open-struct.rb @@ -1 +1,3 @@ +# frozen_string_literal: true + require 'recursive_open_struct' diff --git a/lib/recursive_open_struct.rb b/lib/recursive_open_struct.rb index a0d01ea..b20feea 100644 --- a/lib/recursive_open_struct.rb +++ b/lib/recursive_open_struct.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'ostruct' require 'recursive_open_struct/version' @@ -12,6 +14,7 @@ # TODO: `#*_as_a_hash` deprecated. Nested hashes can be referenced using # `#to_h`. +# rubocop:disable Metrics/ClassLength class RecursiveOpenStruct < OpenStruct include Dig if OpenStruct.public_instance_methods.include? :dig @@ -28,7 +31,9 @@ def self.default_options } end - def initialize(hash=nil, passed_options={}) + # rubocop:disable Lint/MissingSuper + # Intentionally doesn't call super and initializes +@table+ itself. + def initialize(hash = nil, passed_options = {}) hash = hash.to_h if [hash.is_a?(RecursiveOpenStruct), hash.is_a?(OpenStruct)].any? hash ||= {} @@ -40,6 +45,7 @@ def initialize(hash=nil, passed_options={}) @sub_elements = {} end + # rubocop:enable Lint/MissingSuper def marshal_load(attributes) hash, @options = attributes @@ -69,17 +75,17 @@ def to_h # TODO: deprecated, unsupported by OpenStruct. OpenStruct does not consider # itself to be a "kind of" Hash. - alias_method :to_hash, :to_h + alias to_hash to_h # Continue supporting older rubies -- JRuby 9.1.x.x is still considered # stable, but is based on Ruby # 2.3.x and so uses :modifiable instead of :modifiable?. Furthermore, if # :modifiable is private, then make :modifiable? private too. - if !OpenStruct.private_instance_methods.include?(:modifiable?) + unless OpenStruct.private_instance_methods.include?(:modifiable?) if OpenStruct.private_instance_methods.include?(:modifiable) - alias_method :modifiable?, :modifiable + alias modifiable? modifiable elsif OpenStruct.public_instance_methods.include?(:modifiable) - alias_method :modifiable?, :modifiable + alias modifiable? modifiable private :modifiable? end end @@ -89,7 +95,7 @@ def [](name) v = @table[key_name] if v.is_a?(Hash) @sub_elements[key_name] ||= _create_sub_element_(v, mutate_input_hash: true) - elsif v.is_a?(Array) and @options[:recurse_over_arrays] + elsif v.is_a?(Array) && @options[:recurse_over_arrays] @sub_elements[key_name] ||= recurse_over_array(v) @sub_elements[key_name] = recurse_over_array(@sub_elements[key_name]) else @@ -100,7 +106,7 @@ def [](name) if private_instance_methods.include?(:modifiable?) || public_instance_methods.include?(:modifiable?) def []=(name, value) key_name = _get_key_from_table_(name) - tbl = modifiable? # Ensure we are modifiable + tbl = modifiable? # Ensure we are modifiable @sub_elements.delete(key_name) tbl[key_name] = value end @@ -120,19 +126,20 @@ def respond_to_missing?(mid, include_private = false) # Adapted implementation of method_missing to accommodate the differences # between ROS and OS. + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/PerceivedComplexity def method_missing(mid, *args) len = args.length if mid =~ /^(.*)=$/ - if len != 1 - raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1) - end + raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1) if len != 1 + # self[$1.to_sym] = args[0] # modifiable?[new_ostruct_member!($1.to_sym)] = args[0] - new_ostruct_member!($1.to_sym) + new_ostruct_member!(::Regexp.last_match(1).to_sym) public_send(mid, args[0]) - elsif len == 0 + elsif len.zero? key = mid - key = $1 if key =~ /^(.*)_as_a_hash$/ + key = ::Regexp.last_match(1) if key =~ /^(.*)_as_a_hash$/ if @table.key?(_get_key_from_table_(key)) new_ostruct_member!(key) public_send(mid) @@ -147,6 +154,8 @@ def method_missing(mid, *args) raise err end end + # rubocop:enable Metrics/PerceivedComplexity + # rubocop:enable Metrics/AbcSize def freeze @table.each_key do |key| @@ -160,7 +169,7 @@ def freeze # 2.4.0. def new_ostruct_member(name) key_name = _get_key_from_table_(name) - unless self.singleton_class.method_defined?(name.to_sym) + unless singleton_class.method_defined?(name.to_sym) class << self; self; end.class_eval do define_method(name) do self[key_name] @@ -185,7 +194,12 @@ class << self; self; end.class_eval do def delete_field(name) sym = _get_key_from_table_(name) - singleton_class.__send__(:remove_method, sym, "#{sym}=") rescue NoMethodError # ignore if methods not yet generated. + begin + singleton_class.__send__(:remove_method, sym, "#{sym}=") + rescue StandardError + # ignore if methods not yet generated. + NoMethodError + end @sub_elements.delete(sym) @table.delete(sym) end @@ -203,8 +217,9 @@ def initialize_dup(orig) end def _get_key_from_table_(name) - return name.to_s if @table.has_key?(name.to_s) - return name.to_sym if @table.has_key?(name.to_sym) + return name.to_s if @table.key?(name.to_s) + return name.to_sym if @table.key?(name.to_sym) + name end @@ -222,5 +237,5 @@ def recurse_over_array(array) end array end - end +# rubocop:enable Metrics/ClassLength diff --git a/lib/recursive_open_struct/debug_inspect.rb b/lib/recursive_open_struct/debug_inspect.rb index c64cfab..3b4f0be 100644 --- a/lib/recursive_open_struct/debug_inspect.rb +++ b/lib/recursive_open_struct/debug_inspect.rb @@ -1,38 +1,46 @@ -module RecursiveOpenStruct::DebugInspect - def debug_inspect(io = STDOUT, indent_level = 0, recursion_limit = 12) - display_recursive_open_struct(io, @table, indent_level, recursion_limit) - end +# frozen_string_literal: true - def display_recursive_open_struct(io, ostrct_or_hash, indent_level, recursion_limit) +# rubocop:disable Metrics/AbcSize +# rubocop:disable Metrics/CyclomaticComplexity +# rubocop:disable Metrics/PerceivedComplexity +# rubocop:disable Style/StringConcatenation +class RecursiveOpenStruct < OpenStruct + module DebugInspect + def debug_inspect(io = $stdout, indent_level = 0, recursion_limit = 12) + display_recursive_open_struct(io, @table, indent_level, recursion_limit) + end - if recursion_limit <= 0 then - # protection against recursive structure (like in the tests) - io.puts ' '*indent_level + '(recursion limit reached)' - else - #puts ostrct_or_hash.inspect - if ostrct_or_hash.is_a?(self.class) then - ostrct_or_hash = ostrct_or_hash.marshal_dump - end + def display_recursive_open_struct(io, ostrct_or_hash, indent_level, recursion_limit) + if recursion_limit <= 0 + # protection against recursive structure (like in the tests) + io.puts ' ' * indent_level + '(recursion limit reached)' + else + # puts ostrct_or_hash.inspect + ostrct_or_hash = ostrct_or_hash.marshal_dump if ostrct_or_hash.is_a?(self.class) - # We'll display the key values like this : key = value - # to align display, we look for the maximum key length of the data that will be displayed - # (everything except hashes) - data_indent = ostrct_or_hash \ - .reject { |k, v| v.is_a?(self.class) || v.is_a?(Hash) } \ - .max {|a,b| a[0].to_s.length <=> b[0].to_s.length}[0].to_s.length - # puts "max length = #{data_indent}" + # We'll display the key values like this : key = value + # to align display, we look for the maximum key length of the data that will be displayed + # (everything except hashes) + data_indent = ostrct_or_hash \ + .reject { |_k, v| v.is_a?(self.class) || v.is_a?(Hash) } \ + .max { |a, b| a[0].to_s.length <=> b[0].to_s.length }[0].to_s.length + # puts "max length = #{data_indent}" - ostrct_or_hash.each do |key, value| - if (value.is_a?(self.class) || value.is_a?(Hash)) then - io.puts ' '*indent_level + key.to_s + '.' - display_recursive_open_struct(io, value, indent_level + 1, recursion_limit - 1) - else - io.puts ' '*indent_level + key.to_s + ' '*(data_indent - key.to_s.length) + ' = ' + value.inspect + ostrct_or_hash.each do |key, value| + if value.is_a?(self.class) || value.is_a?(Hash) + io.puts ' ' * indent_level + key.to_s + '.' + display_recursive_open_struct(io, value, indent_level + 1, recursion_limit - 1) + else + io.puts ' ' * indent_level + key.to_s + ' ' * (data_indent - key.to_s.length) + ' = ' + value.inspect + end end end - end - true + true + end end - end +# rubocop:enable Style/StringConcatenation +# rubocop:enable Metrics/PerceivedComplexity +# rubocop:enable Metrics/CyclomaticComplexity +# rubocop:enable Metrics/AbcSize diff --git a/lib/recursive_open_struct/deep_dup.rb b/lib/recursive_open_struct/deep_dup.rb index 8af870b..8f2742d 100644 --- a/lib/recursive_open_struct/deep_dup.rb +++ b/lib/recursive_open_struct/deep_dup.rb @@ -1,33 +1,40 @@ -require 'set' -class RecursiveOpenStruct::DeepDup - def initialize(opts={}) - @recurse_over_arrays = opts.fetch(:recurse_over_arrays, false) - @preserve_original_keys = opts.fetch(:preserve_original_keys, false) - end +# frozen_string_literal: true - def call(obj) - deep_dup(obj) - end +class RecursiveOpenStruct < OpenStruct + class DeepDup + def initialize(opts = {}) + @recurse_over_arrays = opts.fetch(:recurse_over_arrays, false) + @preserve_original_keys = opts.fetch(:preserve_original_keys, false) + end + + def call(obj) + deep_dup(obj) + end - private + private - def deep_dup(obj, visited=Set.new) - if obj.is_a?(Hash) - obj.each_with_object({}) do |(key, value), h| - h[@preserve_original_keys ? key : key.to_sym] = value_or_deep_dup(value, visited) - end - elsif obj.is_a?(Array) && @recurse_over_arrays - obj.each_with_object([]) do |value, arr| - value = value.is_a?(RecursiveOpenStruct) ? value.to_h : value - arr << value_or_deep_dup(value, visited) + # rubocop:disable Metrics/PerceivedComplexity + # rubocop:disable Metrics/CyclomaticComplexity + def deep_dup(obj, visited = Set.new) + if obj.is_a?(Hash) + obj.each_with_object({}) do |(key, value), h| + h[@preserve_original_keys ? key : key.to_sym] = value_or_deep_dup(value, visited) + end + elsif obj.is_a?(Array) && @recurse_over_arrays + obj.each_with_object([]) do |value, arr| + value = value.is_a?(RecursiveOpenStruct) ? value.to_h : value + arr << value_or_deep_dup(value, visited) + end + else + obj end - else - obj end - end + # rubocop:enable Metrics/PerceivedComplexity + # rubocop:enable Metrics/CyclomaticComplexity - def value_or_deep_dup(value, visited) - obj_id = value.object_id - visited.include?(obj_id) ? value : deep_dup(value, visited << obj_id) + def value_or_deep_dup(value, visited) + obj_id = value.object_id + visited.include?(obj_id) ? value : deep_dup(value, visited << obj_id) + end end end diff --git a/lib/recursive_open_struct/dig.rb b/lib/recursive_open_struct/dig.rb index 518e7b7..c47785c 100644 --- a/lib/recursive_open_struct/dig.rb +++ b/lib/recursive_open_struct/dig.rb @@ -1,8 +1,12 @@ +# frozen_string_literal: true + class RecursiveOpenStruct < OpenStruct + # Replaces +OpenStruct#dig+ to properly support treating nested values as + # RecursiveOpenStructs instead of returning the nested Hashes. + # + # This module is only added in when +OpenStruct#dig+ exists (the OpenStruct + # included in older Ruby versions didn't implement it) module Dig - - # Replaces +OpenStruct#dig+ to properly support treating nested values as - # RecursiveOpenStructs instead of returning the nested Hashes. def dig(name, *names) begin name = name.to_sym @@ -12,7 +16,7 @@ def dig(name, *names) name_val = self[name] - if names.length > 0 && name_val.respond_to?(:dig) + if !names.empty? && name_val.respond_to?(:dig) name_val.dig(*names) else name_val diff --git a/lib/recursive_open_struct/version.rb b/lib/recursive_open_struct/version.rb index 679563a..725da18 100644 --- a/lib/recursive_open_struct/version.rb +++ b/lib/recursive_open_struct/version.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + # Necessary since the top-level class/module is a class that inherits from # OpenStruct. require 'ostruct' class RecursiveOpenStruct < OpenStruct - VERSION = "2.1.0" + VERSION = '2.1.0' end diff --git a/recursive-open-struct.gemspec b/recursive-open-struct.gemspec index bc7928a..48302d6 100644 --- a/recursive-open-struct.gemspec +++ b/recursive-open-struct.gemspec @@ -1,20 +1,21 @@ -# -*- encoding: utf-8 -*- -name = "recursive-open-struct" -version = File.foreach(File.join(__dir__, "lib", "recursive_open_struct", "version.rb")) do |line| - /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1 +# frozen_string_literal: true + +name = 'recursive-open-struct' +version = File.foreach(File.join(__dir__, 'lib', 'recursive_open_struct', 'version.rb')) do |line| + /^\s*VERSION\s*=\s*'(.*)'/ =~ line and break Regexp.last_match(1) end Gem::Specification.new do |s| s.name = name s.version = version - s.authors = ["William (B.J.) Snow Orvis"] - s.email = "aetherknight@gmail.com" - s.date = Time.now.utc.strftime("%Y-%m-%d") - s.homepage = "https://github.com/aetherknight/recursive-open-struct" - s.licenses = ["MIT"] - - s.summary = "OpenStruct subclass that returns nested hash attributes as RecursiveOpenStructs" - s.description = <<-QUOTE .gsub(/^ /,'') + s.authors = ['William (B.J.) Snow Orvis'] + s.email = 'aetherknight@gmail.com' + s.date = Time.now.utc.strftime('%Y-%m-%d') + s.homepage = 'https://github.com/aetherknight/recursive-open-struct' + s.licenses = ['MIT'] + + s.summary = 'OpenStruct subclass that returns nested hash attributes as RecursiveOpenStructs' + s.description = <<-QUOTE.gsub(/^ /, '') RecursiveOpenStruct is a subclass of OpenStruct. It differs from OpenStruct in that it allows nested hashes to be treated in a recursive fashion. For example: @@ -25,22 +26,25 @@ Gem::Specification.new do |s| Also, nested hashes can still be accessed as hashes: ros.a_as_a_hash # { :b => 'c' } - QUOTE + QUOTE - s.files = `git ls-files lib`.split("\n") + ["AUTHORS.txt" , "CHANGELOG.md", "LICENSE.txt", "README.md"] - s.require_paths = ["lib"] + s.files = `git ls-files lib`.split("\n") + ['AUTHORS.txt', 'CHANGELOG.md', 'LICENSE.txt', 'README.md'] + s.require_paths = ['lib'] s.extra_rdoc_files = [ - "CHANGELOG.md", - "LICENSE.txt", - "README.md" + 'CHANGELOG.md', + 'LICENSE.txt', + 'README.md' ] - s.add_development_dependency('bundler', [">= 0"]) - s.add_development_dependency('pry', [">= 0"]) - s.add_development_dependency('rake', [">= 0"]) - s.add_development_dependency('rdoc', [">= 0"]) - s.add_development_dependency('rspec', "~> 3.2") - s.add_development_dependency('simplecov', [">= 0"]) + s.add_development_dependency('bundler', ['>= 2']) + s.add_development_dependency('pry', ['>= 0']) + s.add_development_dependency('rake', ['~>13.4']) + s.add_development_dependency('rdoc', ['~>7.2']) + s.add_development_dependency('rspec', '~> 3.13') + s.add_development_dependency('rubocop', ['~>1.86']) + s.add_development_dependency('rubocop-rake', ['~>0.7']) + s.add_development_dependency('rubocop-rspec', ['~>3.9']) + s.add_development_dependency('simplecov', ['>= 0']) s.add_dependency('ostruct') end diff --git a/spec/recursive_open_struct/debug_inspect_spec.rb b/spec/recursive_open_struct/debug_inspect_spec.rb index a8c2623..c1dd0f6 100644 --- a/spec/recursive_open_struct/debug_inspect_spec.rb +++ b/spec/recursive_open_struct/debug_inspect_spec.rb @@ -1,70 +1,46 @@ +# frozen_string_literal: true + require_relative '../spec_helper' require 'recursive_open_struct' describe RecursiveOpenStruct do - describe "#debug_inspect" do - before(:each) do - h1 = { :a => 'a'} - h2 = { :a => 'b', :h1 => h1 } + describe '#debug_inspect' do + subject(:ros) do + h1 = { a: 'a' } + h2 = { a: 'b', h1: h1 } h1[:h2] = h2 - @ros = RecursiveOpenStruct.new(h2) + + described_class.new(h2) end - it "should have a simple way of display" do - @output = <<-QUOTE -a = "b" -h1. - a = "a" - h2. - a = "b" - h1. - a = "a" - h2. - a = "b" - h1. - a = "a" - h2. - a = "b" - h1. - a = "a" - h2. - a = "b" - h1. - a = "a" - h2. - a = "b" - h1. - a = "a" - h2. - (recursion limit reached) - QUOTE - @io = StringIO.new - @ros.debug_inspect(@io) - expect(@io.string).to match /^a = "b"$/ - expect(@io.string).to match /^h1\.$/ - expect(@io.string).to match /^ a = "a"$/ - expect(@io.string).to match /^ h2\.$/ - expect(@io.string).to match /^ a = "b"$/ - expect(@io.string).to match /^ h1\.$/ - expect(@io.string).to match /^ a = "a"$/ - expect(@io.string).to match /^ h2\.$/ - expect(@io.string).to match /^ a = "b"$/ - expect(@io.string).to match /^ h1\.$/ - expect(@io.string).to match /^ a = "a"$/ - expect(@io.string).to match /^ h2\.$/ - expect(@io.string).to match /^ a = "b"$/ - expect(@io.string).to match /^ h1\.$/ - expect(@io.string).to match /^ a = "a"$/ - expect(@io.string).to match /^ h2\.$/ - expect(@io.string).to match /^ a = "b"$/ - expect(@io.string).to match /^ h1\.$/ - expect(@io.string).to match /^ a = "a"$/ - expect(@io.string).to match /^ h2\.$/ - expect(@io.string).to match /^ a = "b"$/ - expect(@io.string).to match /^ h1\.$/ - expect(@io.string).to match /^ a = "a"$/ - expect(@io.string).to match /^ h2\.$/ - expect(@io.string).to match /^ \(recursion limit reached\)$/ + it 'has a simple way of display' do + io = StringIO.new + ros.debug_inspect(io) + expect(io.string).to match(/^a = "b"$/) + expect(io.string).to match(/^h1\.$/) + expect(io.string).to match(/^ a = "a"$/) + expect(io.string).to match(/^ h2\.$/) + expect(io.string).to match(/^ a = "b"$/) + expect(io.string).to match(/^ h1\.$/) + expect(io.string).to match(/^ a = "a"$/) + expect(io.string).to match(/^ h2\.$/) + expect(io.string).to match(/^ a = "b"$/) + expect(io.string).to match(/^ h1\.$/) + expect(io.string).to match(/^ a = "a"$/) + expect(io.string).to match(/^ h2\.$/) + expect(io.string).to match(/^ a = "b"$/) + expect(io.string).to match(/^ h1\.$/) + expect(io.string).to match(/^ a = "a"$/) + expect(io.string).to match(/^ h2\.$/) + expect(io.string).to match(/^ a = "b"$/) + expect(io.string).to match(/^ h1\.$/) + expect(io.string).to match(/^ a = "a"$/) + expect(io.string).to match(/^ h2\.$/) + expect(io.string).to match(/^ a = "b"$/) + expect(io.string).to match(/^ h1\.$/) + expect(io.string).to match(/^ a = "a"$/) + expect(io.string).to match(/^ h2\.$/) + expect(io.string).to match(/^ \(recursion limit reached\)$/) end end end diff --git a/spec/recursive_open_struct/indifferent_access_spec.rb b/spec/recursive_open_struct/indifferent_access_spec.rb index c62400c..fdb60fd 100644 --- a/spec/recursive_open_struct/indifferent_access_spec.rb +++ b/spec/recursive_open_struct/indifferent_access_spec.rb @@ -1,109 +1,114 @@ +# frozen_string_literal: true + require_relative '../spec_helper' require 'recursive_open_struct' describe RecursiveOpenStruct do - let(:value) { 'foo' } - let(:symbol) { :bar } - let(:new_value) { 'bar' } - let(:new_symbol) { :foo } - describe 'indifferent access' do - let(:hash) { {:foo => value, 'bar' => symbol} } + subject(:hash_ros) { described_class.new(hash, hash_ros_opts) } + + let(:hash) { { :foo => value, 'bar' => :bar } } let(:hash_ros_opts) { {} } - subject(:hash_ros) { RecursiveOpenStruct.new(hash, hash_ros_opts) } - context 'setting value with method' do - before(:each) do - subject.foo = value - end + describe 'setting value with method' do + let(:value) { 'foo' } - it('allows getting with method') { expect(subject.foo).to be value } - it('allows getting with symbol') { expect(subject[:foo]).to be value } - it('allows getting with string') { expect(subject['foo']).to be value } + before do + hash_ros.foo = value + end + it('allows getting with method') { expect(hash_ros.foo).to be value } + it('allows getting with symbol') { expect(hash_ros[:foo]).to be value } + it('allows getting with string') { expect(hash_ros['foo']).to be value } end - context 'setting value with symbol' do - before(:each) do - subject[:foo] = value - end + describe 'setting value with symbol' do + let(:value) { 'foo' } - it('allows getting with method') { expect(subject.foo).to be value } - it('allows getting with symbol') { expect(subject[:foo]).to be value } - it('allows getting with string') { expect(subject['foo']).to be value } + before do + hash_ros[:foo] = value + end + it('allows getting with method') { expect(hash_ros.foo).to be value } + it('allows getting with symbol') { expect(hash_ros[:foo]).to be value } + it('allows getting with string') { expect(hash_ros['foo']).to be value } end - context 'setting value with string' do - before(:each) do - subject['foo'] = value - end + describe 'setting value with string' do + let(:value) { 'foo' } - it('allows getting with method') { expect(subject.foo).to be value } - it('allows getting with symbol') { expect(subject[:foo]).to be value } - it('allows getting with string') { expect(subject['foo']).to be value } + before do + hash_ros['foo'] = value + end + it('allows getting with method') { expect(hash_ros.foo).to be value } + it('allows getting with symbol') { expect(hash_ros[:foo]).to be value } + it('allows getting with string') { expect(hash_ros['foo']).to be value } end - context 'overwriting values' do - context 'set with method' do - before(:each) do - subject.foo = value + describe 'overwriting values' do + let(:value) { 'foo' } + let(:new_value) { 'bar' } + + describe 'set with method' do + before do + hash_ros.foo = value end it('overrides with symbol') do - subject[:foo] = new_value - expect(subject.foo).to be new_value + hash_ros[:foo] = new_value + expect(hash_ros.foo).to be new_value end it('overrides with string') do - subject['foo'] = new_value - expect(subject.foo).to be new_value + hash_ros['foo'] = new_value + expect(hash_ros.foo).to be new_value end end - context 'set with symbol' do - before(:each) do - subject[:foo] = value + describe 'set with symbol' do + before do + hash_ros[:foo] = value end it('overrides with method') do - subject.foo = new_value - expect(subject[:foo]).to be new_value + hash_ros.foo = new_value + expect(hash_ros[:foo]).to be new_value end it('overrides with string') do - subject['foo'] = new_value - expect(subject[:foo]).to be new_value + hash_ros['foo'] = new_value + expect(hash_ros[:foo]).to be new_value end end - context 'set with string' do - before(:each) do - subject['foo'] = value + describe 'set with string' do + before do + hash_ros['foo'] = value end it('overrides with method') do - subject.foo = new_value - expect(subject['foo']).to be new_value + hash_ros.foo = new_value + expect(hash_ros['foo']).to be new_value end it('overrides with symbol') do - subject[:foo] = new_value - expect(subject['foo']).to be new_value + hash_ros[:foo] = new_value + expect(hash_ros['foo']).to be new_value end end - context 'set with hash' do + describe 'set with hash' do it('overrides with method') do hash_ros.foo = new_value expect(hash_ros[:foo]).to be new_value - + new_symbol = :foo hash_ros.bar = new_symbol expect(hash_ros['bar']).to be new_symbol end it('overrides with symbol') do + new_symbol = :foo hash_ros[:bar] = new_symbol expect(hash_ros['bar']).to be new_symbol end @@ -115,12 +120,14 @@ end context 'when preserve_original_keys is not enabled' do - context 'transforms original keys to symbols' do - subject(:recursive) { RecursiveOpenStruct.new(recursive_hash, recurse_over_arrays: true) } - let(:recursive_hash) { {:foo => [ {'bar' => [ { 'foo' => :bar} ] } ] } } - let(:symbolized_recursive_hash) { {:foo => [ {:bar => [ { :foo => :bar} ] } ] } } - let(:symbolized_modified_hash) { {:foo => [ {:bar => [ { :foo => :foo} ] } ] } } - let(:symbolized_hash) { Hash[hash.map{|(k,v)| [k.to_sym,v]}] } + # rubocop:disable RSpec/MultipleMemoizedHelpers + describe 'transforms original keys to symbols' do + subject(:recursive) { described_class.new(recursive_hash, recurse_over_arrays: true) } + + let(:recursive_hash) { { foo: [{ 'bar' => [{ 'foo' => :bar }] }] } } + let(:symbolized_recursive_hash) { { foo: [{ bar: [{ foo: :bar }] }] } } + let(:symbolized_modified_hash) { { foo: [{ bar: [{ foo: :foo }] }] } } + let(:symbolized_hash) { Hash[hash.map { |(k, v)| [k.to_sym, v] }] } specify 'after initialization' do expect(hash_ros.to_h).to eq symbolized_hash @@ -135,15 +142,20 @@ expect(recursive.to_h).to eq symbolized_modified_hash end end + # rubocop:enable RSpec/MultipleMemoizedHelpers end context 'when preserve_original_keys is enabled' do - context 'preserves the original keys' do - subject(:recursive) { RecursiveOpenStruct.new(recursive_hash, recurse_over_arrays: true, preserve_original_keys: true) } - let(:recursive_hash) { {:foo => [ {'bar' => [ { 'foo' => :bar} ] } ] } } - let(:modified_hash) { {:foo => [ {'bar' => [ { 'foo' => :foo} ] } ] } } + # rubocop:disable RSpec/MultipleMemoizedHelpers + describe 'preserves the original keys' do + subject(:recursive) do + described_class.new(recursive_hash, recurse_over_arrays: true, preserve_original_keys: true) + end + + let(:recursive_hash) { { foo: [{ 'bar' => [{ 'foo' => :bar }] }] } } + let(:modified_hash) { { foo: [{ 'bar' => [{ 'foo' => :foo }] }] } } - let(:hash_ros_opts) { { preserve_original_keys: true }} + let(:hash_ros_opts) { { preserve_original_keys: true } } specify 'after initialization' do expect(hash_ros.to_h).to eq hash @@ -158,33 +170,32 @@ expect(recursive.to_h).to eq modified_hash end end + # rubocop:enable RSpec/MultipleMemoizedHelpers end context 'when undefined method' do context 'when raise_on_missing is enabled' do - subject(:recursive) { RecursiveOpenStruct.new(recursive_hash, raise_on_missing: true) } - let(:recursive_hash) { {:foo => [ {'bar' => [ { 'foo' => :bar} ] } ] } } + subject(:recursive) { described_class.new(recursive_hash, raise_on_missing: true) } + + let(:recursive_hash) { { foo: [{ 'bar' => [{ 'foo' => :bar }] }] } } specify 'raises NoMethodError' do - expect { + expect do recursive.undefined_method - }.to raise_error(NoMethodError) + end.to raise_error(NoMethodError) end end context 'when raise_on_missing is disabled' do - context 'preserves the original keys' do - subject(:recursive) { RecursiveOpenStruct.new(recursive_hash) } - let(:recursive_hash) { {:foo => [ {'bar' => [ { 'foo' => :bar} ] } ] } } + subject(:recursive) { described_class.new(recursive_hash) } - specify 'returns nil' do - expect(recursive.undefined_method).to be_nil - end + let(:recursive_hash) { { foo: [{ 'bar' => [{ 'foo' => :bar }] }] } } + + specify 'returns nil' do + expect(recursive.undefined_method).to be_nil end end end - end - end end diff --git a/spec/recursive_open_struct/open_struct_behavior_spec.rb b/spec/recursive_open_struct/open_struct_behavior_spec.rb index 1739171..2fb0b71 100644 --- a/spec/recursive_open_struct/open_struct_behavior_spec.rb +++ b/spec/recursive_open_struct/open_struct_behavior_spec.rb @@ -1,16 +1,20 @@ +# frozen_string_literal: true + require_relative '../spec_helper' require 'recursive_open_struct' describe RecursiveOpenStruct do + subject(:ros) { described_class.new(hash) } + let(:hash) { {} } - subject(:ros) { RecursiveOpenStruct.new(hash) } - describe "behavior it inherits from OpenStruct" do + describe 'behavior it inherits from OpenStruct' do context 'when not initialized from anything' do - subject(:ros) { RecursiveOpenStruct.new } - it "can represent arbitrary data objects" do - ros.blah = "John Smith" - expect(ros.blah).to eq "John Smith" + subject(:ros) { described_class.new } + + it 'can represent arbitrary data objects' do + ros.blah = 'John Smith' + expect(ros.blah).to eq 'John Smith' end it 'returns nil for missing attributes' do @@ -20,6 +24,7 @@ context 'when initialized with nil' do let(:hash) { nil } + it 'returns nil for missing attributes' do expect(ros.foo).to be_nil end @@ -31,28 +36,29 @@ end end - context "when initialized from a hash" do - let(:hash) { { :asdf => 'John Smith' } } + context 'when initialized from a hash' do + let(:hash) { { asdf: 'John Smith' } } - context 'that contains symbol keys' do - it "turns those symbol keys into method names" do - expect(ros.asdf).to eq "John Smith" + context 'when it contains symbol keys' do + it 'turns those symbol keys into method names' do + expect(ros.asdf).to eq 'John Smith' end end - it "can modify an existing key" do - ros.asdf = "George Washington" - expect(ros.asdf).to eq "George Washington" + it 'can modify an existing key' do + ros.asdf = 'George Washington' + expect(ros.asdf).to eq 'George Washington' end - context 'that contains string keys' do + context 'when it contains string keys' do let(:hash) { { 'asdf' => 'John Smith' } } - it "turns those string keys into method names" do - expect(ros.asdf).to eq "John Smith" + + it 'turns those string keys into method names' do + expect(ros.asdf).to eq 'John Smith' end end - context 'that contains keys that mirror existing private methods' do + context 'when it contains keys that mirror existing private methods' do let(:hash) { { test: :foo, rand: 'not a number' } } # https://github.com/aetherknight/recursive-open-struct/issues/42 @@ -63,83 +69,85 @@ expect(ros.test).to eq :foo expect(ros.rand).to eq 'not a number' end - end - context 'that contains keys that mirror existing public methods inherited from Object' do + context 'when it contains keys that mirror existing public methods inherited from Object' do let(:hash) { { method: :something } } + it 'handles subscript notation without calling the existing methods' do expect(ros[:method]).to eq :something expect(ros['method']).to eq :something end end - if [/\A([0-9]+)\.([0-9]+)\.([0-9]+)\z/.match(RUBY_VERSION)].tap { |l| m = l[0] ; l[0] = (m[1].to_i >= 2 && m[2].to_i >= 4) }.first + if [/\A([0-9]+)\.([0-9]+)\.([0-9]+)\z/.match(RUBY_VERSION)].tap do |l| + m = l[0] + l[0] = (m[1].to_i >= 2 && m[2].to_i >= 4) + end.first context 'when Ruby 2.4.0 or newer' do specify 'new_ostruct_member! is private' do - expect { + expect do ros.new_ostruct_member!(:bonsoir) - }.to raise_error(NoMethodError) - # OpenStruct.new().new_ostruct_member!(:foo) + end.to raise_error(NoMethodError) + # OpenStruct.new().new_ostruct_member!(:foo) end end end - end + describe 'handling of arbitrary attributes' do + subject(:ros) { described_class.new } - describe "handling of arbitrary attributes" do - subject { RecursiveOpenStruct.new } - before(:each) do - subject.blah = "John Smith" + before do + ros.blah = 'John Smith' end - describe "#respond?" do - it { expect(subject).to respond_to :blah } - it { expect(subject).to respond_to :blah= } - it { expect(subject).to_not respond_to :asdf } - it { expect(subject).to_not respond_to :asdf= } + describe '#respond?' do + it { expect(ros).to respond_to :blah } + it { expect(ros).to respond_to :blah= } + it { expect(ros).not_to respond_to :asdf } + it { expect(ros).not_to respond_to :asdf= } end # describe #respond? - describe "#methods" do - it { expect(subject.methods.map(&:to_sym)).to include :blah } - it { expect(subject.methods.map(&:to_sym)).to include :blah= } - it { expect(subject.methods.map(&:to_sym)).to_not include :asdf } - it { expect(subject.methods.map(&:to_sym)).to_not include :asdf= } + describe '#methods' do + it { expect(ros.methods.map(&:to_sym)).to include :blah } + it { expect(ros.methods.map(&:to_sym)).to include :blah= } + it { expect(ros.methods.map(&:to_sym)).not_to include :asdf } + it { expect(ros.methods.map(&:to_sym)).not_to include :asdf= } end # describe #methods end # describe handling of arbitrary attributes - describe "handling of freezing" do - let(:hash) { { :asdf => 'John Smith' } } + describe 'handling of freezing' do + let(:hash) { { asdf: 'John Smith' } } before do ros.freeze end - it "can read existing keys" do + it 'can read existing keys' do expect(ros.asdf).to eq 'John Smith' end - it "cannot write new keys" do + it 'cannot write new keys' do expect { ros.new_key = 'new_value' }.to raise_error FrozenError end - it "cannot write existing keys" do + it 'cannot write existing keys' do expect { ros.asdf = 'new_value' }.to raise_error FrozenError end - context "with recursive structure" do - let(:hash) { { :key => { :subkey => 42 } } } + context 'with recursive structure' do + let(:hash) { { key: { subkey: 42 } } } - it "can read existing sub-elements" do + it 'can read existing sub-elements' do expect(ros.key.subkey).to eq 42 end - it "can write new sub-elements" do + it 'can write new sub-elements' do expect { ros.key.new_subkey = 43 }.not_to raise_error end - it "can write existing sub-elements" do + it 'can write existing sub-elements' do expect { ros.key.subkey = 43 }.not_to raise_error end end diff --git a/spec/recursive_open_struct/ostruct_2_0_0_spec.rb b/spec/recursive_open_struct/ostruct_2_0_0_spec.rb index ae038bc..3f9554e 100644 --- a/spec/recursive_open_struct/ostruct_2_0_0_spec.rb +++ b/spec/recursive_open_struct/ostruct_2_0_0_spec.rb @@ -1,111 +1,106 @@ +# frozen_string_literal: true + require_relative '../spec_helper' require 'recursive_open_struct' describe RecursiveOpenStruct do + subject(:ros) { described_class.new(hash) } - let(:hash) { {:foo => 'foo', 'bar' => :bar} } - subject(:ros) { RecursiveOpenStruct.new(hash) } - - describe "OpenStruct 2.0+ methods" do + let(:hash) { { :foo => 'foo', 'bar' => :bar } } - context "Hash style setter" do - - it "method exists" do - expect(ros.respond_to?('[]=')).to be_truthy + describe 'OpenStruct 2.0+ methods' do + describe '#[]=' do + it 'method exists' do + expect(ros).to respond_to '[]=' end - it "changes the value" do + it 'changes the value' do ros[:foo] = :foo - ros.foo = :foo + expect(ros.foo).to be :foo end - end - context "delete_field" do + describe '#delete_field' do + before { ros.delete_field :foo } - before(:each) { ros.delete_field :foo } - - it "removes the value" do + it 'removes the value' do expect(ros.foo).to be_nil - expect(ros.to_h).to_not include(:foo) + expect(ros.to_h).not_to include(:foo) end - it "removes the getter method" do - is_expected.to_not respond_to :foo + it 'removes the getter method' do + expect(ros).not_to respond_to :foo end - it "removes the setter method" do - expect(ros.respond_to? 'foo=').to be_falsey + it 'removes the setter method' do + expect(ros).not_to respond_to 'foo=' end - it "works with indifferent access" do - expect(ros.delete_field :bar).to eq :bar - is_expected.to_not respond_to :bar - is_expected.to_not respond_to 'bar=' + it 'works with indifferent access' do + expect(ros.delete_field(:bar)).to eq :bar + expect(ros).not_to respond_to :bar + expect(ros).not_to respond_to 'bar=' expect(ros.to_h).to be_empty end - end - context "eql?" do + describe '#eql?' do subject(:new_ros) { ros.dup } - context "with identical ROS" do + context 'with identical ROS' do subject { ros } - it { is_expected.to be_eql ros } + + it { is_expected.to eql ros } end - context "with similar ROS" do - subject { RecursiveOpenStruct.new(hash) } - it { is_expected.to be_eql ros } + context 'with similar ROS' do + subject { described_class.new(hash) } + + it { is_expected.to eql ros } end - context "with same Hash" do - subject { RecursiveOpenStruct.new(hash, recurse_over_arrays: true) } - it { is_expected.to be_eql ros } + context 'with same Hash' do + subject { described_class.new(hash, recurse_over_arrays: true) } + + it { is_expected.to eql ros } end - context "with duplicated ROS" do - subject { ros.dup } + context 'with duplicated ROS' do + subject(:duped_ros) { ros.dup } - it "fails on different value" do - subject.foo = 'bar' - is_expected.not_to be_eql ros + it 'fails on different value' do + duped_ros.foo = 'bar' + expect(duped_ros).not_to eql ros end - it "fails on missing field" do - subject.delete_field :bar - is_expected.not_to be_eql ros + it 'fails on missing field' do + duped_ros.delete_field :bar + expect(duped_ros).not_to eql ros end - it "fails on added field" do - subject.baz = :baz - is_expected.not_to be_eql ros + it 'fails on added field' do + duped_ros.baz = :baz + expect(duped_ros).not_to eql ros end - end - end - context "hash" do - it "calculates table hash" do + describe '#hash' do + it 'calculates table hash' do expect(ros.hash).to eq(ros.instance_variable_get('@table').hash) end - end - context "each_pair" do - it "iterates over hash keys, with keys as symbol" do + describe '#each_pair' do + it 'iterates over hash keys, with keys as symbol' do ros_pairs = [] - ros.each_pair {|k,v| ros_pairs << [k,v]} + ros.each_pair { |k, v| ros_pairs << [k, v] } hash_pairs = [] - {:foo => 'foo', :bar => :bar}.each_pair {|k,v| hash_pairs << [k,v]} + { foo: 'foo', bar: :bar }.each_pair { |k, v| hash_pairs << [k, v] } - expect(ros_pairs).to match (hash_pairs) + expect(ros_pairs).to match(hash_pairs) end end - end # describe OpenStruct 2.0+ methods - end diff --git a/spec/recursive_open_struct/ostruct_2_3_0_spec.rb b/spec/recursive_open_struct/ostruct_2_3_0_spec.rb index f05865e..6e7e5d3 100644 --- a/spec/recursive_open_struct/ostruct_2_3_0_spec.rb +++ b/spec/recursive_open_struct/ostruct_2_3_0_spec.rb @@ -1,49 +1,57 @@ +# frozen_string_literal: true + require_relative '../spec_helper' require 'recursive_open_struct' +# rubocop:disable Style/SingleArgumentDig describe RecursiveOpenStruct do - describe "OpenStruct 2.3.0+ methods" do - describe "#dig" do + describe 'OpenStruct 2.3.0+ methods' do + describe '#dig' do # We only care when Ruby supports `#dig`. if OpenStruct.public_instance_methods.include? :dig - context "recurse_over_arrays: false" do - subject { RecursiveOpenStruct.new(a: { b: 2, c: ["doo", "bee", { inner: "one"}]}) } + context 'when recurse_over_arrays: false' do + subject(:ros) { described_class.new(a: { b: 2, c: ['doo', 'bee', { inner: 'one' }] }) } - describe "OpenStruct-like behavior" do - it { expect(subject.dig(:a, :b)).to eq 2 } - it { expect(subject.dig(:a, :c, 0)).to eq "doo" } - it { expect(subject.dig(:a, :c, 2, :inner)).to eq "one" } + describe 'OpenStruct-like behavior' do + it { expect(ros.dig(:a, :b)).to eq 2 } + it { expect(ros.dig(:a, :c, 0)).to eq 'doo' } + it { expect(ros.dig(:a, :c, 2, :inner)).to eq 'one' } end - describe "recursive behavior" do + describe 'recursive behavior' do it { - expect(subject.dig(:a)).to eq RecursiveOpenStruct.new( - { b: 2, c: ["doo", "bee", { inner: "one"}]} + expect(ros.dig(:a)).to eq described_class.new( + { b: 2, c: ['doo', 'bee', { inner: 'one' }] } ) } - it { expect(subject.dig(:a, :c, 2)).to eq({inner: "one"}) } + + it { expect(ros.dig(:a, :c, 2)).to eq({ inner: 'one' }) } end end - context "recurse_over_arrays: true" do - subject { RecursiveOpenStruct.new({a: { b: 2, c: ["doo", "bee", { inner: "one"}]}}, recurse_over_arrays: true) } + context 'when recurse_over_arrays: true' do + subject(:ros) do + described_class.new({ a: { b: 2, c: ['doo', 'bee', { inner: 'one' }] } }, recurse_over_arrays: true) + end - describe "OpenStruct-like behavior" do - it { expect(subject.dig(:a, :b)).to eq 2 } - it { expect(subject.dig(:a, :c, 0)).to eq "doo" } - it { expect(subject.dig(:a, :c, 2, :inner)).to eq "one" } + describe 'OpenStruct-like behavior' do + it { expect(ros.dig(:a, :b)).to eq 2 } + it { expect(ros.dig(:a, :c, 0)).to eq 'doo' } + it { expect(ros.dig(:a, :c, 2, :inner)).to eq 'one' } end - describe "recursive behavior" do + describe 'recursive behavior' do it { - expect(subject.dig(:a)).to eq RecursiveOpenStruct.new( - { b: 2, c: ["doo", "bee", { inner: "one"}]} + expect(ros.dig(:a)).to eq described_class.new( + { b: 2, c: ['doo', 'bee', { inner: 'one' }] } ) } - it { expect(subject.dig(:a, :c, 2)).to eq RecursiveOpenStruct.new(inner: "one") } + + it { expect(ros.dig(:a, :c, 2)).to eq described_class.new(inner: 'one') } end end end end # describe #dig end # describe OpenStruct 2.3+ methods end +# rubocop:enable Style/SingleArgumentDig diff --git a/spec/recursive_open_struct/recursion_and_subclassing_spec.rb b/spec/recursive_open_struct/recursion_and_subclassing_spec.rb index ec40890..4d5a458 100644 --- a/spec/recursive_open_struct/recursion_and_subclassing_spec.rb +++ b/spec/recursive_open_struct/recursion_and_subclassing_spec.rb @@ -1,13 +1,15 @@ +# frozen_string_literal: true + require_relative '../spec_helper' require 'recursive_open_struct' describe RecursiveOpenStruct do - describe "subclassing RecursiveOpenStruct" do - let(:subclass) { Class.new(RecursiveOpenStruct) } + describe 'subclassing RecursiveOpenStruct' do + subject(:rossc) { subclass.new({ one: [{ two: :three }] }, recurse_over_arrays: true) } - subject(:rossc) { subclass.new({ :one => [{:two => :three}] }, recurse_over_arrays: true) } + let(:subclass) { Class.new(described_class) } - specify "nested objects use the subclass of the parent" do + specify 'nested objects use the subclass of the parent' do expect(rossc.one.first.class).to eq subclass end end diff --git a/spec/recursive_open_struct/recursion_spec.rb b/spec/recursive_open_struct/recursion_spec.rb index 4b094d5..393c8db 100644 --- a/spec/recursive_open_struct/recursion_spec.rb +++ b/spec/recursive_open_struct/recursion_spec.rb @@ -1,34 +1,37 @@ +# frozen_string_literal: true + +# rubocop:disable Security/MarshalLoad require_relative '../spec_helper' require 'recursive_open_struct' describe RecursiveOpenStruct do + describe 'recursive behavior' do + subject(:ros) { described_class.new(h) } - describe "recursive behavior" do - let(:h) { { :blah => { :another => 'value' } } } - subject(:ros) { RecursiveOpenStruct.new(h) } + let(:h) { { blah: { another: 'value' } } } - it "can convert the entire hash tree back into a hash" do + it 'can convert the entire hash tree back into a hash' do blank_obj = Object.new - h = {:asdf => 'John Smith', :foo => [{:bar => blank_obj}, {:baz => nil}]} - ros = RecursiveOpenStruct.new(h) + h = { asdf: 'John Smith', foo: [{ bar: blank_obj }, { baz: nil }] } + ros = described_class.new(h) expect(ros.to_h).to eq h expect(ros.to_hash).to eq h end - it "returns accessed hashes as RecursiveOpenStructs instead of hashes" do - expect(subject.blah.another).to eq 'value' + it 'returns accessed hashes as RecursiveOpenStructs instead of hashes' do + expect(ros.blah.another).to eq 'value' end - it "handles subscript notation the same way as dotted notation" do - expect(subject.blah.another).to eq subject[:blah].another + it 'handles subscript notation the same way as dotted notation' do + expect(ros.blah.another).to eq ros[:blah].another end - it "uses #key_as_a_hash to return key as a Hash" do - expect(subject.blah_as_a_hash).to eq({ :another => 'value' }) + it 'uses #key_as_a_hash to return key as a Hash' do + expect(ros.blah_as_a_hash).to eq({ another: 'value' }) end - it "handles sub-element replacement with dotted notation before member setup" do + it 'handles sub-element replacement with dotted notation before member setup' do expect(ros[:blah][:another]).to eql 'value' expect(ros.methods).not_to include(:blah) @@ -37,319 +40,320 @@ expect(ros.blah.changed).to eql 'backing' end - it "handles being dump then loaded by Marshal" do - foo_struct = [RecursiveOpenStruct.new] - bar_struct = RecursiveOpenStruct.new(foo: foo_struct) + it 'handles being dump then loaded by Marshal' do + foo_struct = [described_class.new] + bar_struct = described_class.new(foo: foo_struct) serialized = Marshal.dump(bar_struct) expect(Marshal.load(serialized).foo).to eq(foo_struct) end - describe "handling loops in the original Hashes" do - let(:h1) { { :a => 'a'} } - let(:h2) { { :a => 'b', :h1 => h1 } } - before(:each) { h1[:h2] = h2 } + describe 'handling loops in the original Hashes' do + subject(:ros) { described_class.new(h) } + + let(:nested_h) { { a: 'a' } } + let(:h) { { a: 'b', nested_h: nested_h } } - subject { RecursiveOpenStruct.new(h2) } + before { nested_h[:h] = h } - it { expect(subject.h1.a).to eq 'a' } - it { expect(subject.h1.h2.a).to eq 'b' } - it { expect(subject.h1.h2.h1.a).to eq 'a' } - it { expect(subject.h1.h2.h1.h2.a).to eq 'b' } - it { expect(subject.h1).to eq subject.h1.h2.h1 } - it { expect(subject.h1).to_not eq subject.h1.h2 } + it { expect(ros.nested_h.a).to eq 'a' } + it { expect(ros.nested_h.h.a).to eq 'b' } + it { expect(ros.nested_h.h.nested_h.a).to eq 'a' } + it { expect(ros.nested_h.h.nested_h.h.a).to eq 'b' } + it { expect(ros.nested_h).to eq ros.nested_h.h.nested_h } + it { expect(ros.nested_h).not_to eq ros.nested_h.h } end # describe handling loops in the origin Hashes - it "can modify a key of a sub-element" do - h = { - :blah => { - :blargh => 'Brad' - } - } - ros = RecursiveOpenStruct.new(h) - ros.blah.blargh = "Janet" + it 'can modify a key of a sub-element' do + h = { blah: { blargh: 'Brad' } } + ros = described_class.new(h) + ros.blah.blargh = 'Janet' - expect(ros.blah.blargh).to eq "Janet" + expect(ros.blah.blargh).to eq 'Janet' end describe 'subscript mutation notation' do + let(:diff) { { different: 'thing' } } + it 'handles the basic case' do - subject[:blah] = 12345 - expect(subject.blah).to eql 12345 + ros[:blah] = 12_345 + expect(ros.blah).to be 12_345 end it 'recurses properly' do - subject[:blah][:another] = 'abc' - expect(subject.blah.another).to eql 'abc' - expect(subject.blah_as_a_hash).to eql({ :another => 'abc' }) + ros[:blah][:another] = 'abc' + expect(ros.blah.another).to eql 'abc' + expect(ros.blah_as_a_hash).to eql({ another: 'abc' }) end - let(:diff){ { :different => 'thing' } } - it 'can replace the entire hash' do - expect(subject.to_h).to eql(h) - subject[:blah] = diff - expect(subject.to_h).to eql({ :blah => diff }) + expect(ros.to_h).to eql(h) + ros[:blah] = diff + expect(ros.to_h).to eql({ blah: diff }) end it 'updates sub-element cache' do - expect(subject.blah.different).to be_nil - subject[:blah] = diff - expect(subject.blah.different).to eql 'thing' - expect(subject.blah_as_a_hash).to eql(diff) + expect(ros.blah.different).to be_nil + ros[:blah] = diff + expect(ros.blah.different).to eql 'thing' + expect(ros.blah_as_a_hash).to eql(diff) end end - context "after a sub-element has been modified" do + context 'when a sub-element has been modified' do + subject(:ros) { described_class.new(hash) } + let(:hash) do - { :blah => { :blargh => "Brad" }, :some_array => [ 1, 2, 3] } + { blah: { blargh: 'Brad' }, some_array: [1, 2, 3] } end let(:updated_hash) do - { :blah => { :blargh => "Janet" }, :some_array => [ 1, 2, 3] } + { blah: { blargh: 'Janet' }, some_array: [1, 2, 3] } end - subject { RecursiveOpenStruct.new(hash) } - - before(:each) { subject.blah.blargh = "Janet" } + before { ros.blah.blargh = 'Janet' } - describe ".to_h" do - it "returns a hash tree that contains those modifications" do - expect(subject.to_h).to eq updated_hash + describe '.to_h' do + it 'returns a hash tree that contains those modifications' do + expect(ros.to_h).to eq updated_hash end - specify "modifying the returned hash tree does not modify the ROS" do - subject.to_h[:blah][:blargh] = "Dr Scott" + specify 'modifying the returned hash tree does not modify the ROS' do + ros.to_h[:blah][:blargh] = 'Dr Scott' - expect(subject.blah.blargh).to eq "Janet" + expect(ros.blah.blargh).to eq 'Janet' end end - it "does not mutate the original hash tree passed to the constructor" do + it 'does not mutate the original hash tree passed to the constructor' do expect(hash[:blah][:blargh]).to eq 'Brad' end - it "limits the deep-copy to the initial hash tree" do - subject.some_array[0] = 4 + it 'limits the deep-copy to the initial hash tree' do + ros.some_array[0] = 4 expect(hash[:some_array][0]).to eq 4 end - describe "#dup" do - let(:duped_subject) { subject.dup } + describe '#dup' do + let(:duped_subject) { ros.dup } - it "preserves sub-element modifications" do - expect(duped_subject.blah.blargh).to eq subject.blah.blargh + it 'preserves sub-element modifications' do + expect(duped_subject.blah.blargh).to eq ros.blah.blargh end it "allows the copy's sub-elements to be modified independently from the original's" do - expect(subject.blah.blargh).to eq "Janet" + expect(ros.blah.blargh).to eq 'Janet' - duped_subject.blah.blargh = "Dr. Scott" + duped_subject.blah.blargh = 'Dr. Scott' - expect(subject.blah.blargh).to eq "Janet" - expect(duped_subject.blah.blargh).to eq "Dr. Scott" + expect(ros.blah.blargh).to eq 'Janet' + expect(duped_subject.blah.blargh).to eq 'Dr. Scott' end end end - context "when memoizing and then modifying entire recursive structures" do - subject do - RecursiveOpenStruct.new( - { :blah => original_blah }, :recurse_over_arrays => true) + context 'when memoizing and then modifying entire recursive structures' do + subject(:ros) do + described_class.new( + { blah: original_blah }, recurse_over_arrays: true + ) end - before(:each) { subject.blah } # enforce memoization + before { ros.blah } # enforce memoization - context "when modifying an entire Hash" do - let(:original_blah) { { :a => 'A', :b => 'B' } } - let(:new_blah) { { :something_new => "C" } } + context 'when modifying an entire Hash' do + let(:original_blah) { { a: 'A', b: 'B' } } + let(:new_blah) { { something_new: 'C' } } - before(:each) { subject.blah = new_blah } + before { ros.blah = new_blah } - it "returns the modified value instead of the memoized one" do - expect(subject.blah.something_new).to eq "C" + it 'returns the modified value instead of the memoized one' do + expect(ros.blah.something_new).to eq 'C' end - specify "the old value no longer exists" do - expect(subject.blah.a).to be_nil + specify 'the old value no longer exists' do + expect(ros.blah.a).to be_nil end end - context "when modifying an entire Array" do + context 'when modifying an entire Array' do let(:original_blah) { [1, 2, 3] } - it "returns the modified value instead of the memoized one" do + it 'returns the modified value instead of the memoized one' do new_blah = [4, 5, 6] - subject.blah = new_blah - expect(subject.blah).to eq new_blah + ros.blah = new_blah + expect(ros.blah).to eq new_blah end end end describe 'recursing over arrays' do - let(:blah_list) { [ { :foo => '1' }, { :foo => '2' }, 'baz' ] } - let(:h) { { :blah => blah_list } } - - context "when dump and loaded by Marshal" do - let(:test) { RecursiveOpenStruct.new(h, :recurse_over_arrays => true) } - subject { Marshal.load(Marshal.dump(test))} - - it { expect(subject.blah.length).to eq 3 } - it { expect(subject.blah[0].foo).to eq '1' } - it { expect(subject.blah[1].foo).to eq '2' } - it { expect(subject.blah_as_a_hash).to eq blah_list } - it { expect(subject.blah[2]).to eq 'baz' } + let(:blah_list) { [{ foo: '1' }, { foo: '2' }, 'baz'] } + let(:h) { { blah: blah_list } } + + context 'when dump and loaded by Marshal' do + subject(:ros) { Marshal.load(Marshal.dump(test)) } + + let(:test) { described_class.new(h, recurse_over_arrays: true) } + + it { expect(ros.blah.length).to eq 3 } + it { expect(ros.blah[0].foo).to eq '1' } + it { expect(ros.blah[1].foo).to eq '2' } + it { expect(ros.blah_as_a_hash).to eq blah_list } + it { expect(ros.blah[2]).to eq 'baz' } end - context "when recursing over arrays is enabled" do - subject { RecursiveOpenStruct.new(h, :recurse_over_arrays => true) } + context 'when recursing over arrays is enabled' do + subject(:ros) { described_class.new(h, recurse_over_arrays: true) } - it { expect(subject.blah.length).to eq 3 } - it { expect(subject.blah[0].foo).to eq '1' } - it { expect(subject.blah[1].foo).to eq '2' } - it { expect(subject.blah_as_a_hash).to eq blah_list } - it { expect(subject.blah[2]).to eq 'baz' } + it { expect(ros.blah.length).to eq 3 } + it { expect(ros.blah[0].foo).to eq '1' } + it { expect(ros.blah[1].foo).to eq '2' } + it { expect(ros.blah_as_a_hash).to eq blah_list } + it { expect(ros.blah[2]).to eq 'baz' } - context "when an inner value changes" do - let(:updated_blah_list) { [ { :foo => '1' }, { :foo => 'Dr Scott' }, 'baz' ] } - let(:updated_h) { { :blah => updated_blah_list } } + context 'when an inner value changes' do + let(:updated_blah_list) { [{ foo: '1' }, { foo: 'Dr Scott' }, 'baz'] } + let(:updated_h) { { blah: updated_blah_list } } - before(:each) { subject.blah[1].foo = "Dr Scott" } + before { ros.blah[1].foo = 'Dr Scott' } - it "Retains changes across Array lookups" do - expect(subject.blah[1].foo).to eq "Dr Scott" + it 'Retains changes across Array lookups' do + expect(ros.blah[1].foo).to eq 'Dr Scott' end - it "propagates the changes through to .to_h across Array lookups" do - expect(subject.to_h).to eq({ - :blah => [ { :foo => '1' }, { :foo => "Dr Scott" }, 'baz' ] - }) + it 'propagates the changes through to .to_h across Array lookups' do + expect(ros.to_h).to eq({ + blah: [{ foo: '1' }, { foo: 'Dr Scott' }, 'baz'] + }) end - it "deep-copies hashes within Arrays" do - subject.to_h[:blah][1][:foo] = "Rocky" + it 'deep-copies hashes within Arrays' do + ros.to_h[:blah][1][:foo] = 'Rocky' - expect(subject.blah[1].foo).to eq "Dr Scott" - end - - it "does not mutate the input hash passed to the constructor" do - expect(h[:blah][1][:foo]).to eq '2' + expect(ros.blah[1].foo).to eq 'Dr Scott' end - it "the deep copy recurses over Arrays as well" do + it 'does not mutate the input hash passed to the constructor (works when recursing over arrays too)' do expect(h[:blah][1][:foo]).to eq '2' end - describe "#dup" do - let(:duped_subject) { subject.dup } + describe '#dup' do + let(:duped_subject) { ros.dup } - it "preserves sub-element modifications" do - expect(duped_subject.blah[1].foo).to eq subject.blah[1].foo + it 'preserves sub-element modifications' do + expect(duped_subject.blah[1].foo).to eq ros.blah[1].foo end it "allows the copy's sub-elements to be modified independently from the original's" do - duped_subject.blah[1].foo = "Rocky" + duped_subject.blah[1].foo = 'Rocky' - expect(duped_subject.blah[1].foo).to eq "Rocky" - expect(subject.blah[1].foo).to eq "Dr Scott" + expect(duped_subject.blah[1].foo).to eq 'Rocky' + expect(ros.blah[1].foo).to eq 'Dr Scott' end end end - context "when array is nested deeper" do - let(:deep_hash) { { :foo => { :blah => blah_list } } } - subject { RecursiveOpenStruct.new(deep_hash, :recurse_over_arrays => true) } + context 'when array is nested deeper' do + subject(:ros) { described_class.new(deep_hash, recurse_over_arrays: true) } - it { expect(subject.foo.blah.length).to eq 3 } - it "Retains changes across Array lookups" do - subject.foo.blah[1].foo = "Dr Scott" - expect(subject.foo.blah[1].foo).to eq "Dr Scott" - end + let(:deep_hash) { { foo: { blah: blah_list } } } + + it { expect(ros.foo.blah.length).to eq 3 } + it 'Retains changes across Array lookups' do + ros.foo.blah[1].foo = 'Dr Scott' + expect(ros.foo.blah[1].foo).to eq 'Dr Scott' + end end - context "when array is in an array" do - let(:haah) { { :blah => [ blah_list ] } } - subject { RecursiveOpenStruct.new(haah, :recurse_over_arrays => true) } + context 'when array is in an array' do + subject(:ros) { described_class.new(haah, recurse_over_arrays: true) } - it { expect(subject.blah.length).to eq 1 } - it { expect(subject.blah[0].length).to eq 3 } - it "Retains changes across Array lookups" do - subject.blah[0][1].foo = "Dr Scott" + let(:haah) { { blah: [blah_list] } } - expect(subject.blah[0][1].foo).to eq "Dr Scott" - end + it { expect(ros.blah.length).to eq 1 } + it { expect(ros.blah[0].length).to eq 3 } - end + it 'Retains changes across Array lookups' do + ros.blah[0][1].foo = 'Dr Scott' + expect(ros.blah[0][1].foo).to eq 'Dr Scott' + end + end end # when recursing over arrays is enabled - context "when recursing over arrays is disabled" do - subject { RecursiveOpenStruct.new(h) } + context 'when recursing over arrays is disabled' do + subject(:ros) { described_class.new(h) } - it { expect(subject.blah.length).to eq 3 } - it { expect(subject.blah[0]).to eq({ :foo => '1' }) } - it { expect(subject.blah[0][:foo]).to eq '1' } + it { expect(ros.blah.length).to eq 3 } + it { expect(ros.blah[0]).to eq({ foo: '1' }) } + it { expect(ros.blah[0][:foo]).to eq '1' } end # when recursing over arrays is disabled describe 'modifying an array and recursing over it' do + subject(:ros) { described_class.new(h, recurse_over_arrays: true) } + let(:h) { {} } - subject { RecursiveOpenStruct.new(h, recurse_over_arrays: true) } context 'when adding an array with hashes into the tree' do - before(:each) do - subject.mystery = {} - subject.mystery.science = [{ theatre: 9000 }] + before do + ros.mystery = {} + ros.mystery.science = [{ theatre: 9000 }] end it "ROS's it" do - expect(subject.mystery.science[0].theatre).to eq 9000 + expect(ros.mystery.science[0].theatre).to eq 9000 end end context 'when appending a hash to an array' do - before(:each) do - subject.mystery = {} - subject.mystery.science = [] - subject.mystery.science << { theatre: 9000 } + before do + ros.mystery = {} + ros.mystery.science = [] + ros.mystery.science << { theatre: 9000 } end it "ROS's it" do - expect(subject.mystery.science[0].theatre).to eq 9000 + expect(ros.mystery.science[0].theatre).to eq 9000 end - specify "the changes show up in .to_h" do - expect(subject.to_h).to eq({ mystery: { science: [{theatre: 9000}]}}) + specify 'the changes show up in .to_h' do + expect(ros.to_h).to eq({ mystery: { science: [{ theatre: 9000 }] } }) end - end - context 'after appending a hash to an array' do - before(:each) do - subject.mystery = {} - subject.mystery.science = [] - subject.mystery.science[0] = {} + specify 'and the new ROS/hash can have new values set' do + ros.mystery.science[0].gizmoplex = 9000 + expect(ros.mystery.science[0].gizmoplex).to eq 9000 end + end - it "can have new values be set" do - expect do - subject.mystery.science[0].theatre = 9000 - end.to_not raise_error + context 'when assigning a hash to an array' do + before do + ros.mystery = {} + ros.mystery.science = [] + ros.mystery.science[0] = {} + end - expect(subject.mystery.science[0].theatre).to eq 9000 + it 'can have new values be set' do + ros.mystery.science[0].theatre = 9000 + expect(ros.mystery.science[0].theatre).to eq 9000 end end end # modifying an array and then recursing end # recursing over arrays describe 'nested nil values' do - let(:h) { { foo: { bar: nil }} } + let(:h) { { foo: { bar: nil } } } + it 'returns nil' do - expect(subject.foo.bar).to be_nil + expect(ros.foo.bar).to be_nil end it 'returns a hash with the key and a nil value' do - expect(subject.to_hash).to eq({ foo: { bar: nil }}) + expect(ros.to_hash).to eq({ foo: { bar: nil } }) end end # nested nil values end # recursive behavior end +# rubocop:enable Security/MarshalLoad diff --git a/spec/recursive_open_struct/wrapping_spec.rb b/spec/recursive_open_struct/wrapping_spec.rb index 7a6f2e4..6d23d8b 100644 --- a/spec/recursive_open_struct/wrapping_spec.rb +++ b/spec/recursive_open_struct/wrapping_spec.rb @@ -1,17 +1,20 @@ +# frozen_string_literal: true + require_relative '../spec_helper' require 'recursive_open_struct' describe RecursiveOpenStruct do describe 'wrapping RecursiveOpenStruct' do - let(:h) { { :blah => { :another => 'value' } } } - subject(:ros) { RecursiveOpenStruct.new(RecursiveOpenStruct.new(h)) } + subject(:ros) { described_class.new(described_class.new(h)) } + + let(:h) { { blah: { another: 'value' } } } it 'can convert the entire hash tree back into a hash' do expect(ros.to_h).to eq h end it 'can access the flat keys' do - expect(ros.blah).to be_a(RecursiveOpenStruct) + expect(ros.blah).to be_a(described_class) end it 'can access the nested keys' do @@ -25,15 +28,16 @@ end describe 'wrapping OpenStruct' do - let(:h) { { :blah => { :another => 'value' } } } - subject(:ros) { RecursiveOpenStruct.new(OpenStruct.new(h)) } + subject(:ros) { described_class.new(OpenStruct.new(h)) } + + let(:h) { { blah: { another: 'value' } } } it 'can convert the entire hash tree back into a hash' do expect(ros.to_h).to eq h end it 'can access the flat keys' do - expect(ros.blah).to be_a(RecursiveOpenStruct) + expect(ros.blah).to be_a(described_class) end it 'can access the nested keys' do @@ -47,16 +51,17 @@ end describe 'wrapping a subclass' do - let(:h) { { :blah => { :another => 'value' } } } - let(:subclass) { Class.new(RecursiveOpenStruct) } subject(:ros) { subclass.new(subclass.new(h)) } + let(:h) { { blah: { another: 'value' } } } + let(:subclass) { Class.new(described_class) } + it 'can convert the entire hash tree back into a hash' do expect(ros.to_h).to eq h end it 'can access the flat keys' do - expect(ros.blah).to be_a(RecursiveOpenStruct) + expect(ros.blah).to be_a(described_class) end it 'can access the nested keys' do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f07057e..c913d7b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) $LOAD_PATH.unshift(File.dirname(__FILE__)) require 'rspec' @@ -10,10 +12,10 @@ # Requires supporting files with custom matchers and macros, etc, # in ./support/ and its subdirectories. -Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f} +Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].sort.each { |f| require f } RSpec.configure do |config| config.run_all_when_everything_filtered = true config.filter_run :focus -# config.expect_with(:rspec) { |c| c.syntax = :should } + # config.expect_with(:rspec) { |c| c.syntax = :should } end