├── .rspec ├── lib ├── dont │ └── version.rb └── dont.rb ├── spec ├── spec_helper.rb ├── dont_with_warn_spec.rb ├── dont_with_exception_spec.rb └── dont_spec.rb ├── Gemfile ├── .gitignore ├── Rakefile ├── bin ├── setup └── console ├── .travis.yml ├── LICENSE.txt ├── dont.gemspec ├── CODE_OF_CONDUCT.md └── README.md /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | -------------------------------------------------------------------------------- /lib/dont/version.rb: -------------------------------------------------------------------------------- 1 | class Dont < Module 2 | VERSION = "0.2.2".freeze 3 | end 4 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) 2 | require "dont" 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in dont.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rspec/core/rake_task" 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task :default => :spec 7 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: ruby 3 | rvm: 4 | - 2.2.5 5 | - 2.3.3 6 | - ruby-head 7 | - jruby-9.1.5.0 8 | matrix: 9 | allow_failures: 10 | - rvm: ruby-head 11 | - rvm: jruby-9.1.5.0 12 | before_install: gem install bundler -v 1.13.6 13 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "dont" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start 15 | -------------------------------------------------------------------------------- /spec/dont_with_warn_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Dont::WithWarn do 4 | it "logs a warning via Kernal#warn" do 5 | klass = Class.new do 6 | include Dont::WithWarn 7 | 8 | def stuff; end 9 | dont_use :stuff 10 | end 11 | 12 | expect_any_instance_of(Kernel).to receive(:warn) 13 | .with(/^DEPRECATED:.+/) 14 | klass.new.stuff 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/dont_with_exception_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Dont::WithException do 4 | it "raises a Dont::DeprecationError" do 5 | klass = Class.new do 6 | include Dont::WithException 7 | 8 | def stuff; end 9 | dont_use :stuff 10 | end 11 | 12 | expect { 13 | klass.new.stuff 14 | }.to raise_error( 15 | Dont::DeprecationError, 16 | /DEPRECATED: Don't use #stuff/ 17 | ) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Maarten Claes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /dont.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'dont/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "dont" 8 | spec.version = Dont::VERSION 9 | spec.authors = ["Maarten Claes"] 10 | spec.email = ["maartencls@gmail.com"] 11 | 12 | spec.summary = %q{Mark methods as deprecated} 13 | spec.description = %q{Mark methods as deprecated} 14 | spec.homepage = "https://github.com/datacamp/dont" 15 | spec.license = "MIT" 16 | 17 | spec.files = `git ls-files -z`.split("\x0").reject do |f| 18 | f.match(%r{^(test|spec|features)/}) 19 | end 20 | spec.bindir = "exe" 21 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 22 | spec.require_paths = ["lib"] 23 | 24 | spec.add_dependency "dry-container", ">= 0.6" 25 | 26 | spec.add_development_dependency "bundler", "~> 1.13" 27 | spec.add_development_dependency "rake", "~> 10.0" 28 | spec.add_development_dependency "rspec", "~> 3.0" 29 | spec.add_development_dependency "sqlite3", "~> 1.3.12" 30 | spec.add_development_dependency "activerecord", "~> 4.2" 31 | end 32 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at maartencls@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dont 2 | 3 | [![Build Status](https://travis-ci.org/datacamp/dont.svg?branch=master)](https://travis-ci.org/datacamp/dont) [![Gem Version](https://badge.fury.io/rb/dont.svg)](https://badge.fury.io/rb/dont) 4 | 5 | Easily deprecate methods. 6 | 7 | ## Installation 8 | 9 | Add this line to your application's Gemfile: 10 | 11 | ```ruby 12 | gem 'dont' 13 | ``` 14 | 15 | And then execute: 16 | 17 | $ bundle 18 | 19 | Or install it yourself as: 20 | 21 | $ gem install dont 22 | 23 | ## Usage 24 | 25 | ```ruby 26 | class Shouter 27 | include Dont::WithWarn 28 | 29 | def shout(msg) 30 | msg.upcase 31 | end 32 | 33 | def scream(msg) 34 | shout(msg) 35 | end 36 | # Indicate that we want to deprecate the scream method, and that you should 37 | # use "shout" instead. The :use option can be any string or symbol. 38 | dont_use :scream, use: :shout 39 | end 40 | 41 | # Logs "DEPRECATED: Don't use Shouter#scream. It's deprecated in favor of shout.", 42 | # before executing the method. 43 | Shouter.new.scream("hello") 44 | ``` 45 | 46 | ### With a custom handler 47 | 48 | ```ruby 49 | # Register a deprecation handler. 50 | # Anything that responds to `.call(deprecation)` will work. 51 | LOGGER = Logger.new 52 | DeprecationLogger = Dont.register_handler(:logger, ->(deprecation) { LOGGER.warn(deprecation.message) }) 53 | class Shouter 54 | include DeprecationLogger 55 | 56 | def shout(msg) 57 | msg.upcase 58 | end 59 | 60 | def scream(msg) 61 | shout(msg) 62 | end 63 | dont_use :scream, use: :shout 64 | end 65 | 66 | # Logs "DEPRECATED: Don't use Shouter#scream. It's deprecated in favor of shout.", 67 | # before executing the method. 68 | Shouter.new.scream("hello") 69 | ``` 70 | 71 | ### Raising exceptions in development 72 | 73 | ``` 74 | # The :exception deprecation handler is provided by default. 75 | # It raises an exception whenever the method is called, which is handy in 76 | # test or development mode. 77 | class Person 78 | include Dont::WithException 79 | 80 | attr_accessor :firstname 81 | attr_accessor :first_name 82 | 83 | dont_use :firstname, use: :first_name 84 | end 85 | Person.new.firstname # => fails with Dont::DeprecationError 86 | ``` 87 | 88 | ### Using the Rails logger 89 | 90 | ```ruby 91 | # in config/initializers/dont.rb 92 | Dont.register_handler(:rails_logger, ->(deprecation) { 93 | Rails.logger.warn(deprecation.message) 94 | }) 95 | DontLogger = Dont.new(:rails_logger) 96 | 97 | # in app/models/comment.rb 98 | class Comment < ActiveRecord::Base 99 | include DontLogger # as defined in the initializer 100 | 101 | # We want to use `body` instead of `description` from now on 102 | alias_attribute :description, :body 103 | dont_use :description, use: :body 104 | end 105 | 106 | comment = Comment.new 107 | comment.description 108 | # => works, but with a deprecation warning: "DEPRECATED: Don't use Comment#description. It's deprecated in favor of body." 109 | 110 | ``` 111 | 112 | ## Development 113 | 114 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. 115 | 116 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). 117 | 118 | ## Contributing 119 | 120 | Bug reports and pull requests are welcome on GitHub at https://github.com/datacamp/dont. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. 121 | 122 | 123 | ## License 124 | 125 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). 126 | 127 | -------------------------------------------------------------------------------- /spec/dont_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "logger" 3 | require "sqlite3" 4 | require "active_record" 5 | 6 | describe Dont do 7 | 8 | before :all do 9 | @method_calls = [] 10 | Dont.register_handler(:method_logger, -> (depr) { 11 | @method_calls << "#{depr.subject.class.name}##{depr.old_method}" 12 | }) 13 | end 14 | 15 | it "has a version number" do 16 | expect(Dont::VERSION).not_to be nil 17 | end 18 | 19 | describe ".new(...)" do 20 | it "fails when used with an unknown handler" do 21 | expect { 22 | Class.new { include Dont.new(:deal_with_it) } 23 | }.to raise_error( 24 | Dont::MissingHandlerError, 25 | "Nothing registered with the key :deal_with_it" 26 | ) 27 | end 28 | end 29 | 30 | describe ".register_handler" do 31 | it "can be used for a custom handler" do 32 | logger = instance_double(Logger) 33 | Dont.register_handler(:log_deprecated_call, -> (depr) { 34 | logger.warn(depr.message) 35 | }) 36 | 37 | klass = Class.new do 38 | include Dont.new(:log_deprecated_call) 39 | 40 | def shout(msg) 41 | scream(msg) 42 | end 43 | dont_use :shout 44 | 45 | def yell(msg) 46 | scream(msg) 47 | end 48 | dont_use :yell, use: :scream 49 | 50 | def scream(msg) 51 | msg.upcase 52 | end 53 | end 54 | 55 | expect(logger).to receive(:warn) 56 | .with("DEPRECATED: Don't use #shout. It's deprecated.") 57 | expect(logger).to receive(:warn) 58 | .with("DEPRECATED: Don't use #yell. It's deprecated in favor of scream.") 59 | shouter = klass.new 60 | expect(shouter.shout("Welcome!")).to eq("WELCOME!") 61 | expect(shouter.yell("Welcome!")).to eq("WELCOME!") 62 | end 63 | end 64 | 65 | describe "ActiveRecord::Base" do 66 | before(:all) do 67 | # Define model before schema is defined. The attributes don't exist at 68 | # this point, but dont_use should still work 69 | # 70 | class Item < ActiveRecord::Base 71 | include Dont.new(:method_logger) 72 | dont_use :usable 73 | dont_use :usable? 74 | dont_use :usable= 75 | 76 | def old_name 77 | name 78 | end 79 | dont_use :old_name 80 | end 81 | 82 | ActiveRecord::Migration.verbose = false 83 | ActiveRecord::Base.establish_connection( 84 | adapter: "sqlite3", 85 | database: ":memory:" 86 | ) 87 | ActiveRecord::Schema.define(version: 1) do 88 | create_table :items do |t| 89 | t.text :name 90 | t.boolean :usable 91 | end 92 | end 93 | end 94 | 95 | it "still executes the original method correctly" do 96 | Item.create!(name: "Usable item", usable: true) 97 | expect(@method_calls).to include("Item#usable=") 98 | @method_calls.clear 99 | item = Item.last 100 | expect(item.usable).to eq(true) 101 | expect(item.usable?).to eq(true) 102 | expect(@method_calls).to eq(["Item#usable", "Item#usable?"]) 103 | 104 | item.usable = false 105 | item.save! 106 | item.reload 107 | expect(item.usable).to eq(false) 108 | expect(item.usable?).to eq(false) 109 | 110 | item.usable = nil 111 | item.save! 112 | item.reload 113 | expect(item.usable).to eq(nil) 114 | expect(item.usable?).to eq(nil) 115 | 116 | @method_calls.clear 117 | expect(item.old_name).to eq("Usable item") 118 | expect(@method_calls).to eq(["Item#old_name"]) 119 | end 120 | end 121 | 122 | describe Dont::Deprecation do 123 | it "generates a deprecation message" do 124 | [ 125 | [ 126 | Hash.new, "to_h", :to_h_v2, 127 | "DEPRECATED: Don't use Hash#to_h. It's deprecated in favor of to_h_v2." 128 | ], 129 | [ 130 | Array.new, "old_method", nil, 131 | "DEPRECATED: Don't use Array#old_method. It's deprecated." 132 | ], 133 | [ 134 | Array.new, "old_method", "", 135 | "DEPRECATED: Don't use Array#old_method. It's deprecated." 136 | ], 137 | ].each do |(obj, old, use, msg)| 138 | depr = described_class.new(subject: obj, old_method: old, new_method: use) 139 | expect(depr.message).to eq(msg) 140 | end 141 | end 142 | end 143 | end 144 | -------------------------------------------------------------------------------- /lib/dont.rb: -------------------------------------------------------------------------------- 1 | require "dont/version" 2 | require "dry-container" 3 | 4 | # Defines a `dont_use` method which can be used to deprecate methods. Whenever 5 | # the deprecated method is used the specified handler will get triggered. 6 | # 7 | # @example 8 | # 9 | # # Register a deprecation handler. 10 | # # Anything that responds to `.call(deprecation)` will work. 11 | # LOGGER = Logger.new 12 | # Dont.register_handler(:logger, ->(deprecation) { LOGGER.warn(deprecation.message) }) 13 | # class Shouter 14 | # include Dont.new(:logger) 15 | # 16 | # def shout(msg) 17 | # msg.upcase 18 | # end 19 | # 20 | # def scream(msg) 21 | # shout(msg) 22 | # end 23 | # dont_use :scream, use: :shout 24 | # end 25 | # 26 | # # Logs "DEPRECATED: Don't use Shouter#scream. It's deprecated in favor of 27 | # # shout.", before executing the method. 28 | # Shouter.new.scream("hello") 29 | # 30 | # 31 | # # The :exception deprecation handler is provided by default. 32 | # # It raises an exception whenever the method is called, which is handy in 33 | # # test or development mode. 34 | # class Person 35 | # include Dont.new(:exception) 36 | # 37 | # attr_accessor :firstname 38 | # attr_accessor :first_name 39 | # 40 | # dont_use :firstname, use: :first_name 41 | # end 42 | # Person.new.firstname # => fails with Dont::DeprecationError 43 | # 44 | class Dont < Module 45 | Error = Class.new(StandardError) 46 | DeprecationError = Class.new(Error) 47 | MissingHandlerError = Class.new(Error) 48 | 49 | def initialize(key) 50 | handler = Dont.fetch_handler(key) 51 | @implementation = ->(old_method, use: nil) { 52 | # The moment `dont_use` is called in ActiveRecord is before AR defines 53 | # the attributes in the model. So you get an error when calling 54 | # instance_method with the regular implementation. 55 | # 56 | # This hack determines if it's an ActiveRecord attribute or not, and 57 | # adapts the code. 58 | is_ar_attribute = defined?(ActiveRecord::Base) && 59 | ancestors.include?(ActiveRecord::Base) && 60 | !method_defined?(old_method) 61 | 62 | original = instance_method(old_method) unless is_ar_attribute 63 | define_method(old_method) do |*args| 64 | deprecation = Deprecation.new( 65 | subject: self, 66 | new_method: use, 67 | old_method: old_method, 68 | ) 69 | handler.call(deprecation) 70 | if is_ar_attribute 71 | if old_method =~ /=\z/ 72 | attr = old_method.to_s.sub(/=\z/, '') 73 | public_send(:[]=, attr, *args) 74 | else 75 | self[old_method.to_s.sub(/\?\z/, '')] 76 | end 77 | else 78 | original.bind(self).call(*args) 79 | end 80 | end 81 | } 82 | end 83 | 84 | def included(base) 85 | base.instance_exec(@implementation) do |impl| 86 | define_singleton_method(:dont_use, &impl) 87 | end 88 | end 89 | 90 | class << self 91 | def register_handler(key, callable) 92 | handlers.register(key, callable) 93 | end 94 | 95 | def fetch_handler(key) 96 | handlers.resolve(key) 97 | rescue Dry::Container::Error => e 98 | fail MissingHandlerError, e.message 99 | end 100 | 101 | protected 102 | 103 | def handlers 104 | @handlers ||= Dry::Container.new 105 | end 106 | end 107 | 108 | # Contains info about the deprecated method being called 109 | class Deprecation 110 | attr_reader :subject, :old_method, :new_method 111 | 112 | def initialize(subject:, old_method:, new_method: nil) 113 | @subject = subject 114 | @new_method = new_method 115 | @old_method = old_method 116 | end 117 | 118 | # A message saying that the old_method is deprecated. It also mentions the 119 | # new_method if provided. 120 | # 121 | # @return [String] 122 | def message 123 | klass = subject.class.name 124 | if new_method && !new_method.empty? 125 | "DEPRECATED: Don't use #{klass}##{old_method}. It's deprecated in favor of #{new_method}." 126 | else 127 | "DEPRECATED: Don't use #{klass}##{old_method}. It's deprecated." 128 | end 129 | end 130 | end 131 | 132 | register_handler(:exception, -> (deprecation) { fail Dont::DeprecationError, deprecation.message }) 133 | register_handler(:warn, -> (deprecation) { warn deprecation.message }) 134 | 135 | WithException = new(:exception) 136 | WithWarn = new(:warn) 137 | end 138 | --------------------------------------------------------------------------------