diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..2fd1771
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,15 @@
+version: 2
+
+updates:
+
+  # Maintain dependencies for GitHub Actions
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      interval: "daily"
+
+  # Maintain dependencies for Ruby's Bundler
+  - package-ecosystem: "bundler"
+    directory: "/"
+    schedule:
+      interval: "daily"
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 0000000..5a7a77b
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,31 @@
+name: Main
+on:
+  push:
+    branches:
+      - main
+  pull_request:
+    branches:
+      - main
+jobs:
+  test:
+    name: 'CI Tests'
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [ubuntu-latest]
+        # Due to https://github.com/actions/runner/issues/849, we have to use quotes for '3.0'
+        ruby: [jruby, truffleruby, 2.5, 2.6, 2.7, '3.0', head]
+    runs-on: ${{ matrix.os }}
+    steps:
+      - uses: actions/checkout@v2.3.4
+      - uses: ruby/setup-ruby@v1
+        with:
+          ruby-version: ${{ matrix.ruby }}
+          bundler-cache: true
+      - run: bundle exec rspec
+      - uses: codecov/codecov-action@v1.5.0
+        with:
+          name: ${{ matrix.ruby }}
+          file: ./coverage/coverage.xml
+      - run: bundle exec rubocop
+        if: matrix.ruby == '3.0'
diff --git a/.gitignore b/.gitignore
index 3604ac0..b1261ee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,4 +6,6 @@ pkg
 doc
 .yardoc
 coverage
-vendor
\ No newline at end of file
+vendor
+.ruby-gemset
+.ruby-version
diff --git a/.rubocop.yml b/.rubocop.yml
new file mode 100644
index 0000000..d75a94f
--- /dev/null
+++ b/.rubocop.yml
@@ -0,0 +1,2 @@
+inherit_gem:
+  panolint: rubocop.yml
diff --git a/.ruby-gemset b/.ruby-gemset
deleted file mode 100644
index 960d9cf..0000000
--- a/.ruby-gemset
+++ /dev/null
@@ -1 +0,0 @@
-docile
diff --git a/.ruby-version b/.ruby-version
deleted file mode 100644
index 9304515..0000000
--- a/.ruby-version
+++ /dev/null
@@ -1 +0,0 @@
-ruby-2.1.0
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 85909c2..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-language: ruby
-cache: bundler
-rvm:
-  - ruby-head
-  - 2.1.2
-  - 2.1.1
-  - 2.1.0
-  - 2.0.0
-  - 1.9.3
-  - 1.9.2
-  - 1.8.7
-  - ree
-  - jruby-head
-  - jruby-19mode
-  - jruby-18mode
-  - rbx-2
-matrix:
-  allow_failures:
-    - rvm: ruby-head
-    - rvm: jruby-head
-  fast_finish: true
diff --git a/Gemfile b/Gemfile
index aabeeca..4ebdeca 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,4 +1,26 @@
-source 'https://rubygems.org'
+# frozen_string_literal: true
+
+source "https://rubygems.org"
+
+# CI-only dependencies go here
+if ENV["CI"] == "true" # rubocop:disable Style/IfUnlessModifier
+  gem "simplecov-cobertura", require: false, group: "test"
+end
 
 # Specify gem's dependencies in docile.gemspec
 gemspec
+
+group :test do
+  gem "rspec", "~> 3.10"
+  gem "simplecov", require: false
+end
+
+# Excluded from CI except on latest MRI Ruby, to reduce compatibility burden
+group :checks do
+  gem "panolint", github: "panorama-ed/panolint", branch: "main"
+end
+
+# Optional, only used locally to release to rubygems.org
+group :release, optional: true do
+  gem "rake"
+end
diff --git a/HISTORY.md b/HISTORY.md
index 22ac340..2e3c82b 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,5 +1,71 @@
 # HISTORY
 
+## [Unreleased changes](http://github.com/ms-ati/docile/compare/v1.4.0...main)
+
+## [v1.4.0 (May 12, 2021)](http://github.com/ms-ati/docile/compare/v1.3.5...v1.4.0)
+
+  - Special thanks to Matt Schreiber (@tomeon):
+    - Short-circuit to calling #instance_exec directly on the DSL object (prior to
+      constructing a proxy object) when the DSL object and block context object are
+      identical (*Sorry it took over a year to review and merge this!*)
+  - Renamed default branch from master to main, see: https://github.com/github/renaming
+  - Temporarily removed YARD doc configuration, to replace after
+    migration to Github Actions
+  - Removed support for all EOL Rubies < 2.6
+  - Migrated CI from Travis to Github Actions
+  - Special thanks (again!) to Taichi Ishitani (@taichi-ishitani):
+    - Use more reliable codecov github action (via simplecov-cobertura)
+      rather than less reliable codecov gem
+    - Enable bundle caching in github action setup-ruby 
+  - Added Rubocop, and configured it to run in CI
+  - Added Dependabot, and configured it to run daily
+  - Added SECURITY.md for vulnerability reporting policy
+
+## [v1.3.5 (Jan 13, 2021)](http://github.com/ms-ati/docile/compare/v1.3.4...v1.3.5)
+
+  - Special thanks to Jochen Seeber (@jochenseeber):
+    - Fix remaining delegation on Ruby 2.7 (PR #62)
+  - Remove support for Ruby 1.8.7 and REE, because they
+    [are no longer runnable on Travis CI](https://travis-ci.community/t/ruby-1-8-7-and-ree-builds-broken-by-ssl-certificate-failure/10866)
+  - Announce that continued support for any EOL Ruby versions (that is, versions
+    prior to Ruby 2.5 as of Jan 13 2021) will be decided on **Feb 1, 2021**
+    based on comments to [issue #58](https://github.com/ms-ati/docile/issues/58)
+
+## [v1.3.4 (Dec 22, 2020)](http://github.com/ms-ati/docile/compare/v1.3.3...v1.3.4)
+
+  - Special thanks to Benoit Daloze (@eregon):
+    - Fix delegation on Ruby 2.7 (issues #45 and #44, PR #52)
+
+## [v1.3.3 (Dec 18, 2020)](http://github.com/ms-ati/docile/compare/v1.3.2...v1.3.3)
+
+  - Special thanks (again!) to Taichi Ishitani (@taichi-ishitani):
+    - Fix keyword arg warnings on Ruby 2.7 (issue #44, PR #45)
+    - Filter Docile's source files from backtrace (issue #35, PR #36)
+
+## [v1.3.2 (Jun 12, 2019)](http://github.com/ms-ati/docile/compare/v1.3.1...v1.3.2)
+
+  - Special thanks (again!) to Taichi Ishitani (@taichi-ishitani):
+    - Fix for DSL object is replaced when #dsl_eval is nested (#33, PR #34)
+
+## [v1.3.1 (May 24, 2018)](http://github.com/ms-ati/docile/compare/v1.3.0...v1.3.1)
+
+  - Special thanks to Taichi Ishitani (@taichi-ishitani):
+    - Fix for when DSL object is also the block's context (#30)
+
+## [v1.3.0 (Feb 7, 2018)](http://github.com/ms-ati/docile/compare/v1.2.0...v1.3.0)
+
+  - Allow helper methods in block's context to call DSL methods
+  - Add SemVer release policy explicitly
+  - Standardize on double-quoted string literals
+  - Workaround some more Travis CI shenanigans
+
+## [v1.2.0 (Jan 11, 2018)](http://github.com/ms-ati/docile/compare/v1.1.5...v1.2.0)
+
+  - Special thanks to Christina Koller (@cmkoller)
+    - add DSL evaluation returning *return value of the block* (see `.dsl_eval_with_block_return`)
+  - add an example to README
+  - keep travis builds passing on old ruby versions
+
 ## [v1.1.5 (Jun 15, 2014)](http://github.com/ms-ati/docile/compare/v1.1.4...v1.1.5)
 
   - as much as possible, loosen version restrictions on development dependencies
diff --git a/LICENSE b/LICENSE
index 26593ee..947ed7b 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 The MIT License (MIT)
 
-Copyright (c) 2012-2014 Marc Siegel
+Copyright (c) 2012-2021 Marc Siegel
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 098e0a4..070aaaa 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,15 @@
 # Docile
-[![Gem Version](https://badge.fury.io/rb/docile.png)](http://badge.fury.io/rb/docile)
-[![Build Status](https://travis-ci.org/ms-ati/docile.png)](https://travis-ci.org/ms-ati/docile)
-[![Dependency Status](https://gemnasium.com/ms-ati/docile.png)](https://gemnasium.com/ms-ati/docile)
-[![Code Climate](https://codeclimate.com/github/ms-ati/docile.png)](https://codeclimate.com/github/ms-ati/docile)
-[![Coverage Status](https://coveralls.io/repos/ms-ati/docile/badge.png)](https://coveralls.io/r/ms-ati/docile)
-[![Inline docs](http://inch-ci.org/github/ms-ati/docile.png)](http://inch-ci.org/github/ms-ati/docile)
-[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/ms-ati/docile/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
+
+[![Gem Version](https://img.shields.io/gem/v/docile.svg)](https://rubygems.org/gems/docile)
+[![Gem Downloads](https://img.shields.io/gem/dt/docile.svg)](https://rubygems.org/gems/docile)
+
+[![Join the chat at https://gitter.im/ms-ati/docile](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ms-ati/docile?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+[![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://rubydoc.info/github/ms-ati/docile)
+[![Docs Coverage](http://inch-ci.org/github/ms-ati/docile.png)](http://inch-ci.org/github/ms-ati/docile)
+
+[![Build Status](https://github.com/ms-ati/docile/actions/workflows/main.yml/badge.svg)](https://github.com/ms-ati/docile/actions/workflows/main.yml)
+[![Code Coverage](https://img.shields.io/codecov/c/github/ms-ati/docile.svg)](https://codecov.io/github/ms-ati/docile)
+[![Maintainability](https://api.codeclimate.com/v1/badges/79ca631bc123f7b83b34/maintainability)](https://codeclimate.com/github/ms-ati/docile/maintainability)
 
 Ruby makes it possible to create very expressive **Domain Specific
 Languages**, or **DSL**'s for short. However, it requires some deep knowledge and
@@ -20,7 +24,7 @@ coding a bit more docile...
 
 ## Usage
 
-### Basic
+### Basic: Ruby [Array](http://ruby-doc.org/core-3.0.0/Array.html) as DSL
 
 Let's say that we want to make a DSL for modifying Array objects.
 Wouldn't it be great if we could just treat the methods of Array as a DSL?
@@ -45,7 +49,80 @@ end
 
 Easy!
 
-### Advanced
+### Next step: Allow helper methods to call DSL methods
+
+What if, in our use of the methods of Array as a DSL, we want to extract
+helper methods which in turn call DSL methods?
+
+```ruby
+def pop_sum_and_push(n)
+  sum = 0
+  n.times { sum += pop }
+  push sum
+end
+
+Docile.dsl_eval([]) do
+  push 5
+  push 6
+  pop_sum_and_push(2)
+end
+#=> [11]
+```
+
+Without Docile, you may find this sort of code extraction to be more
+challenging.
+
+### Wait! Can't I do that with just `instance_eval` or `instance_exec`?
+
+Good question!
+
+In short: **No**. 
+
+Not if you want the code in the block to be able to refer to anything
+the block would normally have access to from the surrounding context.
+
+Let's be very specific. Docile internally uses `instance_exec` (see [execution.rb#26](lib/docile/execution.rb#L26)), adding a small layer to support referencing *local variables*, *instance variables*, and *methods* from the _block's context_ **or** the target _object's context_, interchangeably. This is "**the hard part**", where most folks making a DSL in Ruby throw up their hands.
+
+For example:
+
+```ruby
+class ContextOfBlock
+  def example_of_contexts
+    @block_instance_var = 1
+    block_local_var = 2
+
+    with_array do
+      push @block_instance_var
+      push block_local_var
+      pop
+      push block_sees_this_method 
+    end
+  end
+  
+  def block_sees_this_method
+    3
+  end  
+
+  def with_array(&block)
+    {
+      docile: Docile.dsl_eval([], &block),
+      instance_eval: ([].instance_eval(&block) rescue $!),
+      instance_exec: ([].instance_exec(&block) rescue $!)
+    }  
+  end
+end
+
+ContextOfBlock.new.example_of_contexts
+#=> {
+      :docile=>[1, 3],
+      :instance_eval=>#<NameError: undefined local variable or method `block_sees_this_method' for [nil]:Array>,
+      :instance_exec=>#<NameError: undefined local variable or method `block_sees_this_method' for [nil]:Array>
+    }
+```
+
+As you can see, it won't be possible to call methods or access instance variables defined in the block's context using just the raw `instance_eval` or `instance_exec` methods. And in fact, Docile goes further, making it easy to maintain this support even in multi-layered DSLs.
+
+### Build a Pizza
 
 Mutating (changing) an Array instance is fine, but what usually makes a good DSL is a [Builder Pattern][2].
 
@@ -83,7 +160,7 @@ PizzaBuilder.new.cheese.pepperoni.sauce(:extra).build
 
 Then implement your DSL like this:
 
-``` ruby
+```ruby
 def pizza(&block)
   Docile.dsl_eval(PizzaBuilder.new, &block).build
 end
@@ -93,6 +170,38 @@ It's just that easy!
 
 [2]: http://stackoverflow.com/questions/328496/when-would-you-use-the-builder-pattern  "Builder Pattern"
 
+### Multi-level and Recursive DSLs
+
+Docile is a very easy way to write a multi-level DSL in Ruby, even for
+a [recursive data structure such as a tree][4]:
+
+```ruby
+Person = Struct.new(:name, :mother, :father)
+
+person {
+  name 'John Smith'
+  mother {
+    name 'Mary Smith'
+  }
+  father {
+    name 'Tom Smith'
+    mother {
+      name 'Jane Smith'
+    }
+  }
+}
+
+#=> #<struct Person name="John Smith",
+#                   mother=#<struct Person name="Mary Smith", mother=nil, father=nil>,
+#                   father=#<struct Person name="Tom Smith",
+#                                          mother=#<struct Person name="Jane Smith", mother=nil, father=nil>,
+#                                          father=nil>>
+```
+
+See the full [person tree example][4] for details.
+
+[4]: https://gist.github.com/ms-ati/2bb17bdf10a430faba98
+
 ### Block parameters
 
 Parameters can be passed to the DSL block.
@@ -153,18 +262,18 @@ end
 
 [3]: http://www.sinatrarb.com "Sinatra"
 
-### Functional-Style DSL Objects
+### Functional-Style Immutable DSL Objects
 
 Sometimes, you want to use an object as a DSL, but it doesn't quite fit the
 [imperative](http://en.wikipedia.org/wiki/Imperative_programming) pattern shown
 above.
 
 Instead of methods like
-[Array#push](http://www.ruby-doc.org/core-2.0/Array.html#method-i-push), which
+[Array#push](http://www.ruby-doc.org/core-3.0.0/Array.html#method-i-push), which
 modifies the object at hand, it has methods like
-[String#reverse](http://www.ruby-doc.org/core-2.0/String.html#method-i-reverse),
+[String#reverse](http://www.ruby-doc.org/core-3.0.0/String.html#method-i-reverse),
 which returns a new object without touching the original. Perhaps it's even
-[frozen](http://www.ruby-doc.org/core-2.0/Object.html#method-i-freeze) in
+[frozen](http://www.ruby-doc.org/core-3.0.0/Object.html#method-i-freeze) in
 order to enforce [immutability](http://en.wikipedia.org/wiki/Immutable_object).
 
 Wouldn't it be great if we could just treat these methods as a DSL as well?
@@ -192,6 +301,33 @@ end
 
 All set!
 
+### Accessing the block's return value
+
+Sometimes you might want to access the return value of your provided block,
+as opposed to the DSL object itself. In these cases, use
+`dsl_eval_with_block_return`. It behaves exactly like `dsl_eval`, but returns
+the output from executing the block, rather than the DSL object.
+
+```ruby
+arr = []
+with_array(arr) do
+  push "a"
+  push "b"
+  push "c"
+  length
+end
+#=> 3
+
+arr
+#=> ["a", "b", "c"]
+```
+
+```ruby
+def with_array(arr=[], &block)
+  Docile.dsl_eval_with_block_return(arr, &block)
+end
+```
+
 ## Features
 
   1.  Method lookup falls back from the DSL object to the block's context
@@ -215,9 +351,19 @@ $ gem install docile
 
 ## Status
 
-Works on [all ruby versions since 1.8.7](https://github.com/ms-ati/docile/blob/master/.travis.yml), or so Travis CI [tells us](https://travis-ci.org/ms-ati/docile).
+Works on [all currently supported ruby versions](https://github.com/ms-ati/docile/blob/master/.github/workflows/main.yml),
+or so [Github Actions](https://github.com/ms-ati/docile/actions)
+tells us.
 
-Used by some pretty cool gems to implement their DSLs, notably including [SimpleCov](https://github.com/colszowka/simplecov). Keep an eye out for new gems using Docile at the [Ruby Toolbox](https://www.ruby-toolbox.com/projects/docile).
+Used by some pretty cool gems to implement their DSLs, notably including
+[SimpleCov](https://github.com/colszowka/simplecov). Keep an eye out for new
+gems using Docile at the
+[Ruby Toolbox](https://www.ruby-toolbox.com/projects/docile).
+
+## Release Policy
+
+Docile releases follow
+[Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html).
 
 ## Note on Patches/Pull Requests
 
@@ -232,10 +378,33 @@ Used by some pretty cool gems to implement their DSLs, notably including [Simple
       commit by itself I can ignore when I pull)
   * Send me a pull request. Bonus points for topic branches.
 
-## Copyright & License
+## Releasing
 
-Copyright (c) 2012-2014 Marc Siegel.
+To make a new release of `Dile` to
+[RubyGems](https://rubygems.org/gems/docile), first install the release
+dependencies (e.g. `rake`) as follows:
 
-Licensed under the [MIT License](http://choosealicense.com/licenses/mit/), see [LICENSE](LICENSE) for details.
+```shell
+bundle config set --local with 'release'
+bundle install
+```
+
+Then carry out these steps:
+
+1. Update `HISTORY.md`:
+    - Add an entry for the upcoming version _x.y.z_
+    - Move content from _Unreleased_ to the upcoming version _x.y.z_
+    - Commit with title `Update HISTORY.md for x.y.z`
+
+2. Update `lib/docile/version.rb`
+    - Replace with upcoming version _x.y.z_
+    - Commit with title `Bump version to x.y.z`
+
+3. `bundle exec rake release`
+
+## Copyright & License
 
+Copyright (c) 2012-2021 Marc Siegel.
 
+Licensed under the [MIT License](http://choosealicense.com/licenses/mit/),
+see [LICENSE](LICENSE) for details.
diff --git a/Rakefile b/Rakefile
index 5528825..83e2b67 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,28 +1,14 @@
-require 'rake/clean'
-require 'bundler/gem_tasks'
-require 'rspec/core/rake_task'
-require File.expand_path('on_what', File.dirname(__FILE__))
+# frozen_string_literal: true
+
+require "rake/clean"
+require "bundler/gem_tasks"
+require "rspec/core/rake_task"
 
 # Default task for `rake` is to run rspec
-task :default => [:spec]
+task default: [:spec]
 
 # Use default rspec rake task
 RSpec::Core::RakeTask.new
 
 # Configure `rake clobber` to delete all generated files
-CLOBBER.include('pkg', 'doc', 'coverage')
-
-# To limit needed compatibility with versions of dependencies, only configure
-#   yard doc generation when *not* on Travis, JRuby, or 1.8
-if !on_travis? && !on_jruby? && !on_1_8?
-  require 'github/markup'
-  require 'redcarpet'
-  require 'yard'
-  require 'yard/rake/yardoc_task'
-
-  YARD::Rake::YardocTask.new do |t|
-    OTHER_PATHS = %w()
-    t.files   = ['lib/**/*.rb', OTHER_PATHS]
-    t.options = %w(--markup-provider=redcarpet --markup=markdown --main=README.md)
-  end
-end
+CLOBBER.include("pkg", "doc", "coverage")
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..393190a
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,18 @@
+# Security Policy
+
+## Supported Versions
+
+Use this section to tell people about which versions of your project are
+currently being supported with security updates.
+
+| Version | Supported          |
+| ------- | ------------------ |
+| 1.3.x   | :white_check_mark: |
+| < 1.3   | :x:                |
+
+## Reporting a Vulnerability
+
+At this time, security issues and vulnerabilities in Docile should
+be reported like any other issue. Please create an issue in the
+[public issue tracker](https://github.com/ms-ati/docile/issues) on
+Github.
diff --git a/checksums.yaml.gz b/checksums.yaml.gz
deleted file mode 100644
index bf864f2..0000000
Binary files a/checksums.yaml.gz and /dev/null differ
diff --git a/docile.gemspec b/docile.gemspec
index 4ebc077..7ac3c35 100644
--- a/docile.gemspec
+++ b/docile.gemspec
@@ -1,43 +1,29 @@
-require File.expand_path('on_what', File.dirname(__FILE__))
-$:.push File.expand_path('../lib', __FILE__)
-require 'docile/version'
+# frozen_string_literal: true
+
+require_relative "lib/docile/version"
 
 Gem::Specification.new do |s|
-  s.name        = 'docile'
+  s.name        = "docile"
   s.version     = Docile::VERSION
-  s.author      = 'Marc Siegel'
-  s.email       = 'marc@usainnov.com'
-  s.homepage    = 'https://ms-ati.github.io/docile/'
-  s.summary     = 'Docile keeps your Ruby DSLs tame and well-behaved'
-  s.description = 'Docile turns any Ruby object into a DSL. Especially useful with the Builder pattern.'
-  s.license     = 'MIT'
-
-  # Files included in the gem
-  s.files         = `git ls-files`.split("\n")
-  s.test_files    = `git ls-files -- {test,spec,features}/*`.split("\n")
-  s.executables   = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
-  s.require_paths = %w(lib)
-
-  # Specify oldest supported Ruby version
-  s.required_ruby_version = '>= 1.8.7'
+  s.author      = "Marc Siegel"
+  s.email       = "marc@usainnov.com"
+  s.homepage    = "https://ms-ati.github.io/docile/"
+  s.summary     = "Docile keeps your Ruby DSLs tame and well-behaved."
+  s.description = "Docile treats the methods of a given ruby object as a DSL " \
+                  "(domain specific language) within a given block. \n\n"      \
+                  "Killer feature: you can also reference methods, instance "  \
+                  "variables, and local variables from the original (non-DSL) "\
+                  "context within the block. \n\n"                             \
+                  "Docile releases follow Semantic Versioning as defined at "  \
+                  "semver.org."
+  s.license     = "MIT"
 
-  # Run rspec tests from rake
-  s.add_development_dependency 'rake'
-  s.add_development_dependency 'rspec', '~> 3.0.0'
+  # Specify oldest supported Ruby version (2.5 to support JRuby 9.2.17.0)
+  s.required_ruby_version = ">= 2.5.0"
 
-  # NOTE: needed for Travis builds on 1.8, but can't yet reproduce failure locally
-  s.add_development_dependency 'mime-types', '~> 1.25.1' if on_1_8?
-
-  # To limit needed compatibility with versions of dependencies, only configure
-  #   yard doc generation when *not* on Travis, JRuby, or 1.8
-  if !on_travis? && !on_jruby? && !on_1_8?
-    # Github flavored markdown in YARD documentation
-    # http://blog.nikosd.com/2011/11/github-flavored-markdown-in-yard.html
-    s.add_development_dependency 'yard'
-    s.add_development_dependency 'redcarpet'
-    s.add_development_dependency 'github-markup'
+  # Files included in the gem
+  s.files = `git ls-files -z`.split("\x0").reject do |f|
+    f.match(%r{^(test|spec|features)/})
   end
-
-  # Coveralls test coverage tool, basically hosted SimpleCov
-  s.add_development_dependency 'coveralls'
+  s.require_paths = ["lib"]
 end
diff --git a/lib/docile.rb b/lib/docile.rb
index dc51096..90e6e84 100644
--- a/lib/docile.rb
+++ b/lib/docile.rb
@@ -1,7 +1,10 @@
-require 'docile/version'
-require 'docile/execution'
-require 'docile/fallback_context_proxy'
-require 'docile/chaining_fallback_context_proxy'
+# frozen_string_literal: true
+
+require "docile/version"
+require "docile/execution"
+require "docile/fallback_context_proxy"
+require "docile/chaining_fallback_context_proxy"
+require "docile/backtrace_filter"
 
 # Docile keeps your Ruby DSLs tame and well-behaved.
 module Docile
@@ -43,8 +46,53 @@ module Docile
     exec_in_proxy_context(dsl, FallbackContextProxy, *args, &block)
     dsl
   end
+
+  ruby2_keywords :dsl_eval if respond_to?(:ruby2_keywords, true)
   module_function :dsl_eval
 
+  # Execute a block in the context of an object whose methods represent the
+  # commands in a DSL, and return *the block's return value*.
+  #
+  # @note Use with an *imperative* DSL (commands modify the context object)
+  #
+  # Use this method to execute an *imperative* DSL, which means that:
+  #
+  #   1. Each command mutates the state of the DSL context object
+  #   2. The return value of each command is ignored
+  #   3. The final return value is the original context object
+  #
+  # @example Use a String as a DSL
+  #   Docile.dsl_eval_with_block_return("Hello, world!") do
+  #     reverse!
+  #     upcase!
+  #     first
+  #   end
+  #   #=> "!"
+  #
+  # @example Use an Array as a DSL
+  #   Docile.dsl_eval_with_block_return([]) do
+  #     push "a"
+  #     push "b"
+  #     pop
+  #     push "c"
+  #     length
+  #   end
+  #   #=> 2
+  #
+  # @param dsl   [Object] context object whose methods make up the DSL
+  # @param args  [Array]  arguments to be passed to the block
+  # @param block [Proc]   the block of DSL commands to be executed against the
+  #                         `dsl` context object
+  # @return      [Object] the return value from executing the block
+  def dsl_eval_with_block_return(dsl, *args, &block)
+    exec_in_proxy_context(dsl, FallbackContextProxy, *args, &block)
+  end
+
+  if respond_to?(:ruby2_keywords, true)
+    ruby2_keywords :dsl_eval_with_block_return
+  end
+  module_function :dsl_eval_with_block_return
+
   # Execute a block in the context of an immutable object whose methods,
   # and the methods of their return values, represent the commands in a DSL.
   #
@@ -80,5 +128,7 @@ module Docile
   def dsl_eval_immutable(dsl, *args, &block)
     exec_in_proxy_context(dsl, ChainingFallbackContextProxy, *args, &block)
   end
+
+  ruby2_keywords :dsl_eval_immutable if respond_to?(:ruby2_keywords, true)
   module_function :dsl_eval_immutable
 end
diff --git a/lib/docile/backtrace_filter.rb b/lib/docile/backtrace_filter.rb
new file mode 100644
index 0000000..3d7a0d8
--- /dev/null
+++ b/lib/docile/backtrace_filter.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Docile
+  # @api private
+  #
+  # This is used to remove entries pointing to Docile's source files
+  # from {Exception#backtrace} and {Exception#backtrace_locations}.
+  #
+  # If {NoMethodError} is caught then the exception object will be extended
+  # by this module to add filter functionalities.
+  module BacktraceFilter
+    FILTER_PATTERN = %r{/lib/docile/}.freeze
+
+    def backtrace
+      super.reject { |trace| trace =~ FILTER_PATTERN }
+    end
+
+    if ::Exception.public_method_defined?(:backtrace_locations)
+      def backtrace_locations
+        super.reject { |location| location.absolute_path =~ FILTER_PATTERN }
+      end
+    end
+  end
+end
diff --git a/lib/docile/chaining_fallback_context_proxy.rb b/lib/docile/chaining_fallback_context_proxy.rb
index 20d0b63..4fea047 100644
--- a/lib/docile/chaining_fallback_context_proxy.rb
+++ b/lib/docile/chaining_fallback_context_proxy.rb
@@ -1,4 +1,6 @@
-require 'docile/fallback_context_proxy'
+# frozen_string_literal: true
+
+require "docile/fallback_context_proxy"
 
 module Docile
   # @api private
@@ -10,11 +12,16 @@ module Docile
   # objects.
   #
   # @see Docile.dsl_eval_immutable
+  #
+  # rubocop:disable Style/MissingRespondToMissing
   class ChainingFallbackContextProxy < FallbackContextProxy
     # Proxy methods as in {FallbackContextProxy#method_missing}, replacing
     # `receiver` with the returned value.
     def method_missing(method, *args, &block)
       @__receiver__ = super(method, *args, &block)
     end
+
+    ruby2_keywords :method_missing if respond_to?(:ruby2_keywords, true)
   end
-end
\ No newline at end of file
+  # rubocop:enable Style/MissingRespondToMissing
+end
diff --git a/lib/docile/execution.rb b/lib/docile/execution.rb
index f5696b9..e8a6408 100644
--- a/lib/docile/execution.rb
+++ b/lib/docile/execution.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 module Docile
   # @api private
   #
@@ -15,21 +17,37 @@ module Docile
     # @param block      [Proc]   the block of DSL commands to be executed
     # @return           [Object] the return value of the block
     def exec_in_proxy_context(dsl, proxy_type, *args, &block)
-      block_context = eval('self', block.binding)
+      block_context = eval("self", block.binding) # rubocop:disable Style/EvalWithLocation
+
+      # Use #equal? to test strict object identity (assuming that this dictum
+      # from the Ruby docs holds: "[u]nlike ==, the equal? method should never
+      # be overridden by subclasses as it is used to determine object
+      # identity")
+      return dsl.instance_exec(*args, &block) if dsl.equal?(block_context)
+
       proxy_context = proxy_type.new(dsl, block_context)
       begin
         block_context.instance_variables.each do |ivar|
           value_from_block = block_context.instance_variable_get(ivar)
           proxy_context.instance_variable_set(ivar, value_from_block)
         end
+
         proxy_context.instance_exec(*args, &block)
       ensure
+        if block_context.respond_to?(:__docile_undo_fallback__)
+          block_context.send(:__docile_undo_fallback__)
+        end
+
         block_context.instance_variables.each do |ivar|
+          next unless proxy_context.instance_variables.include?(ivar)
+
           value_from_dsl_proxy = proxy_context.instance_variable_get(ivar)
           block_context.instance_variable_set(ivar, value_from_dsl_proxy)
         end
       end
     end
+
+    ruby2_keywords :exec_in_proxy_context if respond_to?(:ruby2_keywords, true)
     module_function :exec_in_proxy_context
   end
-end
\ No newline at end of file
+end
diff --git a/lib/docile/fallback_context_proxy.rb b/lib/docile/fallback_context_proxy.rb
index a865d93..18a2e0e 100644
--- a/lib/docile/fallback_context_proxy.rb
+++ b/lib/docile/fallback_context_proxy.rb
@@ -1,4 +1,6 @@
-require 'set'
+# frozen_string_literal: true
+
+require "set"
 
 module Docile
   # @api private
@@ -13,14 +15,20 @@ module Docile
   # This is useful for implementing DSL evaluation in the context of an object.
   #
   # @see Docile.dsl_eval
+  #
+  # rubocop:disable Style/MissingRespondToMissing
   class FallbackContextProxy
     # The set of methods which will **not** be proxied, but instead answered
     # by this object directly.
     NON_PROXIED_METHODS = Set[:__send__, :object_id, :__id__, :==, :equal?,
-                              :'!', :'!=', :instance_exec, :instance_variables,
+                              :!, :!=, :instance_exec, :instance_variables,
                               :instance_variable_get, :instance_variable_set,
                               :remove_instance_variable]
 
+    # The set of methods which will **not** fallback from the block's context
+    # to the dsl object.
+    NON_FALLBACK_METHODS = Set[:class, :self, :respond_to?, :instance_of?]
+
     # The set of instance variables which are local to this object and hidden.
     # All other instance variables will be copied in and out of this object
     # from the scope in which this proxy was created.
@@ -38,16 +46,44 @@ module Docile
     def initialize(receiver, fallback)
       @__receiver__ = receiver
       @__fallback__ = fallback
+
+      # Enables calling DSL methods from helper methods in the block's context
+      unless fallback.respond_to?(:method_missing)
+        # NOTE: We could switch to {#define_singleton_method} on current Rubies
+        singleton_class = (class << fallback; self; end)
+
+        # instrument {#method_missing} on the block's context to fallback to
+        # the DSL object. This allows helper methods in the block's context to
+        # contain calls to methods on the DSL object.
+        singleton_class.
+          send(:define_method, :method_missing) do |method, *args, &block|
+            m = method.to_sym
+            if !NON_FALLBACK_METHODS.member?(m) &&
+               !fallback.respond_to?(m) &&
+               receiver.respond_to?(m)
+              receiver.__send__(method.to_sym, *args, &block)
+            else
+              super(method, *args, &block)
+            end
+          end
+
+        if singleton_class.respond_to?(:ruby2_keywords, true)
+          singleton_class.send(:ruby2_keywords, :method_missing)
+        end
+
+        # instrument a helper method to remove the above instrumentation
+        singleton_class.
+          send(:define_method, :__docile_undo_fallback__) do
+            singleton_class.send(:remove_method, :method_missing)
+            singleton_class.send(:remove_method, :__docile_undo_fallback__)
+          end
+      end
     end
 
     # @return [Array<Symbol>]  Instance variable names, excluding
     #                            {NON_PROXIED_INSTANCE_VARIABLES}
-    #
-    # @note on Ruby 1.8.x, the instance variable names are actually of
-    #   type `String`.
     def instance_variables
-      # Ruby 1.8.x returns string names, convert to symbols for compatibility
-      super.select { |v| !NON_PROXIED_INSTANCE_VARIABLES.include?(v.to_sym) }
+      super.reject { |v| NON_PROXIED_INSTANCE_VARIABLES.include?(v) }
     end
 
     # Proxy all methods, excluding {NON_PROXIED_METHODS}, first to `receiver`
@@ -56,8 +92,16 @@ module Docile
       if @__receiver__.respond_to?(method.to_sym)
         @__receiver__.__send__(method.to_sym, *args, &block)
       else
-        @__fallback__.__send__(method.to_sym, *args, &block)
+        begin
+          @__fallback__.__send__(method.to_sym, *args, &block)
+        rescue NoMethodError => e
+          e.extend(BacktraceFilter)
+          raise e
+        end
       end
     end
+
+    ruby2_keywords :method_missing if respond_to?(:ruby2_keywords, true)
   end
+  # rubocop:enable Style/MissingRespondToMissing
 end
diff --git a/lib/docile/version.rb b/lib/docile/version.rb
index 9614b34..02b3354 100644
--- a/lib/docile/version.rb
+++ b/lib/docile/version.rb
@@ -1,4 +1,6 @@
+# frozen_string_literal: true
+
 module Docile
   # The current version of this library
-  VERSION = '1.1.5'
+  VERSION = "1.4.0"
 end
diff --git a/metadata.yml b/metadata.yml
deleted file mode 100644
index 4196e9e..0000000
--- a/metadata.yml
+++ /dev/null
@@ -1,151 +0,0 @@
---- !ruby/object:Gem::Specification
-name: docile
-version: !ruby/object:Gem::Version
-  version: 1.1.5
-platform: ruby
-authors:
-- Marc Siegel
-autorequire: 
-bindir: bin
-cert_chain: []
-date: 2014-06-15 00:00:00.000000000 Z
-dependencies:
-- !ruby/object:Gem::Dependency
-  name: rake
-  requirement: !ruby/object:Gem::Requirement
-    requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: '0'
-  type: :development
-  prerelease: false
-  version_requirements: !ruby/object:Gem::Requirement
-    requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: '0'
-- !ruby/object:Gem::Dependency
-  name: rspec
-  requirement: !ruby/object:Gem::Requirement
-    requirements:
-    - - "~>"
-      - !ruby/object:Gem::Version
-        version: 3.0.0
-  type: :development
-  prerelease: false
-  version_requirements: !ruby/object:Gem::Requirement
-    requirements:
-    - - "~>"
-      - !ruby/object:Gem::Version
-        version: 3.0.0
-- !ruby/object:Gem::Dependency
-  name: yard
-  requirement: !ruby/object:Gem::Requirement
-    requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: '0'
-  type: :development
-  prerelease: false
-  version_requirements: !ruby/object:Gem::Requirement
-    requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: '0'
-- !ruby/object:Gem::Dependency
-  name: redcarpet
-  requirement: !ruby/object:Gem::Requirement
-    requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: '0'
-  type: :development
-  prerelease: false
-  version_requirements: !ruby/object:Gem::Requirement
-    requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: '0'
-- !ruby/object:Gem::Dependency
-  name: github-markup
-  requirement: !ruby/object:Gem::Requirement
-    requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: '0'
-  type: :development
-  prerelease: false
-  version_requirements: !ruby/object:Gem::Requirement
-    requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: '0'
-- !ruby/object:Gem::Dependency
-  name: coveralls
-  requirement: !ruby/object:Gem::Requirement
-    requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: '0'
-  type: :development
-  prerelease: false
-  version_requirements: !ruby/object:Gem::Requirement
-    requirements:
-    - - ">="
-      - !ruby/object:Gem::Version
-        version: '0'
-description: Docile turns any Ruby object into a DSL. Especially useful with the Builder
-  pattern.
-email: marc@usainnov.com
-executables: []
-extensions: []
-extra_rdoc_files: []
-files:
-- ".gitignore"
-- ".rspec"
-- ".ruby-gemset"
-- ".ruby-version"
-- ".travis.yml"
-- ".yardopts"
-- Gemfile
-- HISTORY.md
-- LICENSE
-- README.md
-- Rakefile
-- docile.gemspec
-- lib/docile.rb
-- lib/docile/chaining_fallback_context_proxy.rb
-- lib/docile/execution.rb
-- lib/docile/fallback_context_proxy.rb
-- lib/docile/version.rb
-- on_what.rb
-- spec/docile_spec.rb
-- spec/spec_helper.rb
-homepage: https://ms-ati.github.io/docile/
-licenses:
-- MIT
-metadata: {}
-post_install_message: 
-rdoc_options: []
-require_paths:
-- lib
-required_ruby_version: !ruby/object:Gem::Requirement
-  requirements:
-  - - ">="
-    - !ruby/object:Gem::Version
-      version: 1.8.7
-required_rubygems_version: !ruby/object:Gem::Requirement
-  requirements:
-  - - ">="
-    - !ruby/object:Gem::Version
-      version: '0'
-requirements: []
-rubyforge_project: 
-rubygems_version: 2.2.2
-signing_key: 
-specification_version: 4
-summary: Docile keeps your Ruby DSLs tame and well-behaved
-test_files:
-- spec/docile_spec.rb
-- spec/spec_helper.rb
-has_rdoc: 
diff --git a/on_what.rb b/on_what.rb
deleted file mode 100644
index b1ffa28..0000000
--- a/on_what.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# NOTE: Very simple tests for what system we are on, extracted for sharing
-#   between Rakefile, gemspec, and spec_helper. Not for use in actual library.
-
-def on_travis?
-  ENV['CI'] == 'true'
-end
-
-def on_jruby?
-  (defined?(RUBY_ENGINE) && 'jruby' == RUBY_ENGINE)
-end
-
-def on_1_8?
-  RUBY_VERSION.start_with? '1.8'
-end
\ No newline at end of file
diff --git a/spec/docile_spec.rb b/spec/docile_spec.rb
deleted file mode 100644
index 33bc896..0000000
--- a/spec/docile_spec.rb
+++ /dev/null
@@ -1,339 +0,0 @@
-require 'spec_helper'
-require 'singleton'
-
-describe Docile do
-
-  describe '.dsl_eval' do
-
-    context 'when DSL context object is an Array' do
-      let(:array) { [] }
-      let!(:result) { execute_dsl_against_array }
-
-      def execute_dsl_against_array
-        Docile.dsl_eval(array) do
-          push 1
-          push 2
-          pop
-          push 3
-        end
-      end
-
-      it 'executes the block against the DSL context object' do
-        expect(array).to eq([1, 3])
-      end
-
-      it 'returns the DSL object after executing block against it' do
-        expect(result).to eq(array)
-      end
-
-      it "doesn't proxy #__id__" do
-        Docile.dsl_eval(array) { expect(__id__).not_to eq(array.__id__) }
-      end
-
-      it "raises NoMethodError if the DSL object doesn't implement the method" do
-        expect { Docile.dsl_eval(array) { no_such_method } }.to raise_error(NoMethodError)
-      end
-    end
-
-    Pizza = Struct.new(:cheese, :pepperoni, :bacon, :sauce)
-
-    class PizzaBuilder
-      def cheese(v=true);    @cheese    = v; end
-      def pepperoni(v=true); @pepperoni = v; end
-      def bacon(v=true);     @bacon     = v; end
-      def sauce(v=nil);      @sauce     = v; end
-      def build
-        Pizza.new(!!@cheese, !!@pepperoni, !!@bacon, @sauce)
-      end
-    end
-
-    context 'when DSL context object is a Builder pattern' do
-      let(:builder) { PizzaBuilder.new }
-      let(:result) { execute_dsl_against_builder_and_call_build }
-
-      def execute_dsl_against_builder_and_call_build
-        @sauce = :extra
-        Docile.dsl_eval(builder) do
-          bacon
-          cheese
-          sauce @sauce
-        end.build
-      end
-
-      it 'returns correctly built object' do
-        expect(result).to eq(Pizza.new(true, false, true, :extra))
-      end
-    end
-
-    class InnerDSL
-      def initialize; @b = 'b'; end
-      attr_accessor :b
-    end
-
-    class OuterDSL
-      def initialize; @a = 'a'; end
-      attr_accessor :a
-
-      def inner(&block)
-        Docile.dsl_eval(InnerDSL.new, &block)
-      end
-
-      def inner_with_params(param, &block)
-        Docile.dsl_eval(InnerDSL.new, param, :foo, &block)
-      end
-    end
-
-    def outer(&block)
-      Docile.dsl_eval(OuterDSL.new, &block)
-    end
-
-    context 'when given parameters for the DSL block' do
-      def parameterized(*args, &block)
-        Docile.dsl_eval(OuterDSL.new, *args, &block)
-      end
-
-      it 'passes parameters to the block' do
-        parameterized(1,2,3) do |x,y,z|
-          expect(x).to eq(1)
-          expect(y).to eq(2)
-          expect(z).to eq(3)
-        end
-      end
-
-      it 'finds parameters before methods' do
-        parameterized(1) { |a| expect(a).to eq(1) }
-      end
-
-      it 'find outer dsl parameters in inner dsl scope' do
-        parameterized(1,2,3) do |a,b,c|
-          inner_with_params(c) do |d,e|
-            expect(a).to eq(1)
-            expect(b).to eq(2)
-            expect(c).to eq(3)
-            expect(d).to eq(c)
-            expect(e).to eq(:foo)
-          end
-        end
-      end
-    end
-
-    class DSLWithNoMethod
-      def initialize(b); @b = b; end
-      attr_accessor :b
-      def push_element
-        @b.push 1
-      end
-    end
-
-    context 'when DSL have NoMethod error inside' do
-      it 'raise error from nil' do
-        Docile.dsl_eval(DSLWithNoMethod.new(nil)) do
-          expect { push_element }.to raise_error(NoMethodError, /undefined method `push' (for|on) nil:NilClass/)
-        end
-      end
-    end
-
-    context 'when DSL blocks are nested' do
-
-      context 'method lookup' do
-        it 'finds method of outer dsl in outer dsl scope' do
-          outer { expect(a).to eq('a') }
-        end
-
-        it 'finds method of inner dsl in inner dsl scope' do
-          outer { inner { expect(b).to eq('b') } }
-        end
-
-        it 'finds method of outer dsl in inner dsl scope' do
-          outer { inner { expect(a).to eq('a') } }
-        end
-
-        it "finds method of block's context in outer dsl scope" do
-          def c; 'c'; end
-          outer { expect(c).to eq('c') }
-        end
-
-        it "finds method of block's context in inner dsl scope" do
-          def c; 'c'; end
-          outer { inner { expect(c).to eq('c') } }
-        end
-
-        it 'finds method of outer dsl in preference to block context' do
-          def a; 'not a'; end
-          outer { expect(a).to eq('a') }
-          outer { inner { expect(a).to eq('a') } }
-        end
-      end
-
-      context 'local variable lookup' do
-        it 'finds local variable from block context in outer dsl scope' do
-          foo = 'foo'
-          outer { expect(foo).to eq('foo') }
-        end
-
-        it 'finds local variable from block definition in inner dsl scope' do
-          bar = 'bar'
-          outer { inner { expect(bar).to eq('bar') } }
-        end
-      end
-
-      context 'instance variable lookup' do
-        it 'finds instance variable from block definition in outer dsl scope' do
-          @iv1 = 'iv1'; outer { expect(@iv1).to eq('iv1') }
-        end
-
-        it "proxies instance variable assignments in block in outer dsl scope back into block's context" do
-          @iv1 = 'foo'; outer { @iv1 = 'bar' }; expect(@iv1).to eq('bar')
-        end
-
-        it 'finds instance variable from block definition in inner dsl scope' do
-          @iv2 = 'iv2'; outer { inner { expect(@iv2).to eq('iv2') } }
-        end
-
-        it "proxies instance variable assignments in block in inner dsl scope back into block's context" do
-          @iv2 = 'foo'; outer { inner { @iv2 = 'bar' } }; expect(@iv2).to eq('bar')
-        end
-      end
-
-    end
-
-    context 'when DSL context object is a Dispatch pattern' do
-      class DispatchScope
-        def params
-          { :a => 1, :b => 2, :c => 3 }
-        end
-      end
-
-      class MessageDispatch
-        include Singleton
-
-        def initialize
-          @responders = {}
-        end
-
-        def add_responder path, &block
-          @responders[path] = block
-        end
-
-        def dispatch path, request
-          Docile.dsl_eval(DispatchScope.new, request, &@responders[path])
-        end
-      end
-
-      def respond(path, &block)
-        MessageDispatch.instance.add_responder(path, &block)
-      end
-
-      def send_request(path, request)
-        MessageDispatch.instance.dispatch(path, request)
-      end
-
-      it 'dispatches correctly' do
-        @first = @second = nil
-
-        respond '/path' do |request|
-          @first = request
-        end
-
-        respond '/new_bike' do |bike|
-          @second = "Got a new #{bike}"
-        end
-
-        def x(y) ; "Got a #{y}"; end
-        respond '/third' do |third|
-          expect(x(third)).to eq('Got a third thing')
-        end
-
-        fourth = nil
-        respond '/params' do |arg|
-          fourth = params[arg]
-        end
-
-        send_request '/path', 1
-        send_request '/new_bike', 'ten speed'
-        send_request '/third', 'third thing'
-        send_request '/params', :b
-
-        expect(@first).to eq(1)
-        expect(@second).to eq('Got a new ten speed')
-        expect(fourth).to eq(2)
-      end
-
-    end
-
-  end
-
-  describe '.dsl_eval_immutable' do
-
-    context 'when DSL context object is a frozen String' do
-      let(:original) { "I'm immutable!".freeze }
-      let!(:result) { execute_non_mutating_dsl_against_string }
-
-      def execute_non_mutating_dsl_against_string
-        Docile.dsl_eval_immutable(original) do
-          reverse
-          upcase
-        end
-      end
-
-      it "doesn't modify the original string" do
-        expect(original).to eq("I'm immutable!")
-      end
-
-      it 'chains the commands in the block against the DSL context object' do
-        expect(result).to eq("!ELBATUMMI M'I")
-      end
-    end
-
-    context 'when DSL context object is a number' do
-      let(:original) { 84.5 }
-      let!(:result) { execute_non_mutating_dsl_against_number }
-
-      def execute_non_mutating_dsl_against_number
-        Docile.dsl_eval_immutable(original) do
-          fdiv(2)
-          floor
-        end
-      end
-
-      it 'chains the commands in the block against the DSL context object' do
-        expect(result).to eq(42)
-      end
-    end
-  end
-
-end
-
-describe Docile::FallbackContextProxy do
-
-  describe '#instance_variables' do
-    subject { create_fcp_and_set_one_instance_variable.instance_variables }
-    let(:expected_type_of_names) { type_of_ivar_names_on_this_ruby }
-    let(:actual_type_of_names) { subject.first.class }
-    let(:excluded) { Docile::FallbackContextProxy::NON_PROXIED_INSTANCE_VARIABLES }
-
-    def create_fcp_and_set_one_instance_variable
-      fcp = Docile::FallbackContextProxy.new(nil, nil)
-      fcp.instance_variable_set(:@foo, 'foo')
-      fcp
-    end
-
-    def type_of_ivar_names_on_this_ruby
-      @a = 1
-      instance_variables.first.class
-    end
-
-    it 'returns proxied instance variables' do
-      expect(subject.map(&:to_sym)).to include(:@foo)
-    end
-
-    it "doesn't return non-proxied instance variables" do
-      expect(subject.map(&:to_sym)).not_to include(*excluded)
-    end
-
-    it 'preserves the type (String or Symbol) of names on this ruby version' do
-      expect(actual_type_of_names).to eq(expected_type_of_names)
-    end
-  end
-
-end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
deleted file mode 100644
index 756491a..0000000
--- a/spec/spec_helper.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-require File.expand_path('on_what', File.dirname(File.dirname(__FILE__)))
-
-begin
-  require 'simplecov'
-  require 'coveralls'
-
-  # On Ruby 1.9+ use SimpleCov and publish to Coveralls.io
-  if !on_1_8?
-    SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
-      SimpleCov::Formatter::HTMLFormatter,
-      Coveralls::SimpleCov::Formatter
-    ]
-    SimpleCov.start do
-      add_filter '/spec/'    # exclude test code
-      add_filter '/vendor/'  # exclude gems which are vendored on Travis CI
-    end
-
-    # Remove Docile, which was required by SimpleCov, to require again later
-    Object.send(:remove_const, :Docile)
-    $LOADED_FEATURES.reject! { |f| f =~ /\/docile\// }
-  end
-rescue LoadError
-  warn 'warning: simplecov/coveralls gems not found; skipping coverage'
-end
-
-lib_dir = File.join(File.dirname(File.dirname(__FILE__)), 'lib')
-$LOAD_PATH.unshift lib_dir unless $LOAD_PATH.include? lib_dir
-
-# Require Docile again, now with coverage enabled on 1.9+
-require 'docile'