pax_global_header00006660000000000000000000000064140326320520014507gustar00rootroot0000000000000052 comment=587dc56146581cb036603c2e5211bd20db56f5b7 equatable-0.7.0/000077500000000000000000000000001403263205200134565ustar00rootroot00000000000000equatable-0.7.0/.editorconfig000066400000000000000000000002261403263205200161330ustar00rootroot00000000000000root = true [*.rb] charset = utf-8 end_of_line = lf insert_final_newline = true indent_style = space indent_size = 2 trim_trailing_whitespace = true equatable-0.7.0/.github/000077500000000000000000000000001403263205200150165ustar00rootroot00000000000000equatable-0.7.0/.github/FUNDING.yml000066400000000000000000000000241403263205200166270ustar00rootroot00000000000000github: piotrmurach equatable-0.7.0/.github/ISSUE_TEMPLATE.md000066400000000000000000000010461403263205200175240ustar00rootroot00000000000000### Are you in the right place? * For issues or feature requests file a GitHub issue in this repository * For general questions or discussion post on StackOverflow ### Describe the problem A brief description of the issue/feature. ### Steps to reproduce the problem ``` Your code here to reproduce the issue ``` ### Actual behaviour What happened? This could be a description, log output, error raised etc... ### Expected behaviour What did you expect to happen? ### Describe your environment * OS version: * Ruby version: * Equatable version: equatable-0.7.0/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000007561403263205200206270ustar00rootroot00000000000000### Describe the change What does this Pull Request do? ### Why are we doing this? Any related context as to why is this is a desirable change. ### Benefits How will the library improve? ### Drawbacks Possible drawbacks applying this change. ### Requirements - [ ] Tests written & passing locally? - [ ] Code style checked? - [ ] Rebased with `master` branch? - [ ] Documentation updated? - [ ] Changelog updated? equatable-0.7.0/.github/workflows/000077500000000000000000000000001403263205200170535ustar00rootroot00000000000000equatable-0.7.0/.github/workflows/ci.yml000066400000000000000000000026071403263205200201760ustar00rootroot00000000000000--- name: CI on: push: branches: - master paths-ignore: - "examples/**" - "*.md" pull_request: branches: - master paths-ignore: - "examples/**" - "*.md" jobs: tests: name: Ruby ${{ matrix.ruby }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: - ubuntu-latest ruby: - 2.3 - 2.4 - 2.5 - 2.6 - 3.0 - ruby-head - jruby-9.2.13.0 - jruby-head - truffleruby-head include: - ruby: 2.1 os: ubuntu-latest coverage: false bundler: 1 - ruby: 2.2 os: ubuntu-latest coverage: false bundler: 1 - ruby: 2.7 os: ubuntu-latest coverage: true bundler: latest env: COVERAGE: ${{ matrix.coverage }} COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} continue-on-error: ${{ endsWith(matrix.ruby, 'head') }} steps: - uses: actions/checkout@v2 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler: ${{ matrix.bundler }} - name: Install dependencies run: bundle install --jobs 4 --retry 3 - name: Run tests run: bundle exec rake ci equatable-0.7.0/.gitignore000066400000000000000000000002401403263205200154420ustar00rootroot00000000000000*.gem *.rbc .bundle .config .yardoc Gemfile.lock InstalledFiles _yardoc coverage doc/ lib/bundler/man pkg rdoc spec/reports test/tmp test/version_tmp tmp *.sw* equatable-0.7.0/.rspec000066400000000000000000000001001403263205200145620ustar00rootroot00000000000000--color --format documentation --require spec_helper --warnings equatable-0.7.0/.rubocop.yml000066400000000000000000000015511403263205200157320ustar00rootroot00000000000000AllCops: NewCops: enable Layout/FirstArrayElementIndentation: Enabled: false Layout/LineLength: Max: 82 Lint/AssignmentInCondition: Enabled: false Metrics/AbcSize: Max: 30 Metrics/BlockLength: CountComments: true Max: 25 IgnoredMethods: [] Exclude: - "spec/**/*" Metrics/ClassLength: Max: 1500 Metrics/CyclomaticComplexity: Enabled: false Metrics/MethodLength: Max: 20 Naming/MethodParameterName: Enabled: false Style/AccessorGrouping: Enabled: false Style/AsciiComments: Enabled: false Style/BlockDelimiters: Enabled: false Style/CommentedKeyword: Enabled: false Style/FormatStringToken: Enabled: false Style/Lambda: Enabled: false Style/LambdaCall: EnforcedStyle: braces Style/ParallelAssignment: Enabled: false Style/StringLiterals: EnforcedStyle: double_quotes Style/TrivialAccessors: Enabled: false equatable-0.7.0/CHANGELOG.md000066400000000000000000000021521403263205200152670ustar00rootroot00000000000000# Change log ## [v0.7.0] - 2021-04-05 ### Changed * Change gemspec to remove test and rake files to reduce gem size * Change to remove bundler as a dev dependency and relax rspec upper constraint * Change to require Ruby 2.0 or higher ## [v0.6.1] - 2019-06-26 ### Added * Add license key to gemspec ## [v0.6.0] - 2019-06-16 ### Added * Add dev dependencies to gemspec ### Changed * Change to limit Ruby >= 1.8.7 * Change gemspec to load files directly instead of git ## [v0.1.0] - 2012-12-09 * Initial implementation and release [v0.7.0]: https://github.com/piotrmurach/equatable/compare/v0.6.1...v0.7.0 [v0.6.1]: https://github.com/piotrmurach/equatable/compare/v0.6.0...v0.6.1 [v0.6.0]: https://github.com/piotrmurach/equatable/compare/v0.5.0...v0.6.0 [v0.5.0]: https://github.com/piotrmurach/equatable/compare/v0.4.0...v0.5.0 [v0.4.0]: https://github.com/piotrmurach/equatable/compare/v0.3.0...v0.4.0 [v0.3.0]: https://github.com/piotrmurach/equatable/compare/v0.2.0...v0.3.0 [v0.2.0]: https://github.com/piotrmurach/equatable/compare/v0.1.0...v0.2.0 [v0.1.0]: https://github.com/piotrmurach/equatable/compare/v0.1.0 equatable-0.7.0/CODE_OF_CONDUCT.md000066400000000000000000000062351403263205200162630ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at piotr@piotrmurach.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ equatable-0.7.0/Gemfile000066400000000000000000000004401403263205200147470ustar00rootroot00000000000000source "https://rubygems.org" gemspec gem "json", "2.4.1" if RUBY_VERSION == "2.0.0" group :development do if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.5.0") gem "coveralls_reborn", "~> 0.20.0" gem "simplecov", "~> 0.21.0" end gem "yardstick", "~> 0.9.9" end equatable-0.7.0/LICENSE.txt000066400000000000000000000020771403263205200153070ustar00rootroot00000000000000Copyright (c) 2012 Piotr Murach (piotrmurach.com) MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. equatable-0.7.0/README.md000066400000000000000000000102361403263205200147370ustar00rootroot00000000000000# Equatable [![Gem Version](https://badge.fury.io/rb/equatable.svg)][gem] [![Actions CI](https://github.com/piotrmurach/equatable/workflows/CI/badge.svg?branch=master)][gh_actions_ci] [![Build status](https://ci.appveyor.com/api/projects/status/lsb02nm0g4c6guiu?svg=true)][appveyor] [![Code Climate](https://codeclimate.com/github/piotrmurach/equatable/badges/gpa.svg)][codeclimate] [![Coverage Status](https://coveralls.io/repos/github/piotrmurach/equatable/badge.svg)][coverage] [![Inline docs](http://inch-ci.org/github/piotrmurach/equatable.svg?branch=master)][inchpages] [gem]: http://badge.fury.io/rb/equatable [gh_actions_ci]: https://github.com/piotrmurach/equatable/actions?query=workflow%3ACI [appveyor]: https://ci.appveyor.com/project/piotrmurach/equatable [codeclimate]: https://codeclimate.com/github/piotrmurach/equatable [coverage]: https://coveralls.io/github/piotrmurach/equatable [inchpages]: http://inch-ci.org/github/piotrmurach/equatable > Provide equality comparison methods for objects based on their attributes. By including this module, a class indicates that its instances have explicit general contracts for `==`, `eql?` and `hash` methods. Specifically the `eql?` contract requires that it implements an equivalence relation. By default, each instance of a class is equal only to itself. This is the right behaviour when you have distinct objects. However, it is the responsibility of any class to clearly define its equality. Failure to do so may prevent instances from behaving as expected when tested for uniqueness in Array#uniq or when used as Hash keys. ## Installation Add this line to your application's Gemfile: gem "equatable" And then execute: $ bundle Or install it yourself as: $ gem install equatable ## Usage It is assumed that your objects are value objects and the only values that affect equality comparison are the ones specified by your attribute readers. Each attribute reader should be a significant field in determining objects values. ```ruby class Point include Equatable attr_reader :x, :y def initialize(x, y) @x, @y = x, y end end point_1 = Point.new(1, 1) point_2 = Point.new(1, 1) point_3 = Point.new(1, 2) point_1 == point_2 # => true point_1.hash == point_2.hash # => true point_1.eql?(point_2) # => true point_1.equal?(point_2) # => false point_1 == point_3 # => false point_1.hash == point_3.hash # => false point_1.eql?(point_3) # => false point_1.equal?(point_3) # => false point_1.inspect # => "#" ``` ## Attributes It is important that the attribute readers should allow for performing deterministic computations on class instances. Therefore you should avoid specifying attributes that depend on unreliable resources like IP address that require network access. ## Subtypes **Equatable** ensures that any important property of a type holds for its subtypes. However, please note that adding an extra attribute reader to a subclass will violate the equivalence contract, namely, the superclass will be equal to the subclass but reverse won't be true. For example: ```ruby class ColorPoint < Point attr_reader :color def initialize(x, y, color) super(x, y) @color = color end end point = Point.new(1, 1) color_point = ColorPoint.new(1, 1, :red) point == color_point # => true color_point == point # => false point.hash == color_point.hash # => false point.eql?(color_point) # => false point.equal?(color_point) # => false ``` The `ColorPoint` class demonstrates that extending a class with extra value property does not preserve the `equals` contract. ## Contributing 1. Fork it 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request ## Code of Conduct Everyone interacting in the Equatable project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/piotrmurach/equatable/blob/master/CODE_OF_CONDUCT.md). ## Copyright Copyright (c) 2012 Piotr Murach. See LICENSE for further details. equatable-0.7.0/Rakefile000066400000000000000000000002631403263205200151240ustar00rootroot00000000000000# frozen_string_literal: true require "bundler/gem_tasks" FileList["tasks/**/*.rake"].each { |task| import task } desc "Run all specs" task ci: %w[spec] task default: [:spec] equatable-0.7.0/appveyor.yml000066400000000000000000000012361403263205200160500ustar00rootroot00000000000000--- skip_commits: files: - "examples/**" - "*.md" install: - SET PATH=C:\Ruby%ruby_version%\bin;%PATH% - gem install bundler -v '< 2.0' - bundle install before_test: - ruby -v - gem -v - bundle -v build: off test_script: - bundle exec rake ci environment: matrix: - ruby_version: "200" - ruby_version: "200-x64" - ruby_version: "21" - ruby_version: "21-x64" - ruby_version: "22" - ruby_version: "22-x64" - ruby_version: "23" - ruby_version: "23-x64" - ruby_version: "24" - ruby_version: "24-x64" - ruby_version: "25" - ruby_version: "25-x64" - ruby_version: "26" - ruby_version: "26-x64" equatable-0.7.0/equatable.gemspec000066400000000000000000000025331403263205200167710ustar00rootroot00000000000000# frozen_string_literal: true require_relative "lib/equatable/version" Gem::Specification.new do |spec| spec.name = "equatable" spec.version = Equatable::VERSION spec.authors = ["Piotr Murach"] spec.email = ["piotr@piotrmurach.com"] spec.summary = %q{Provide equality comparison methods for objects based on their attributes.} spec.description = %q{Provide equality comparison methods for objects based on their attributes by generating implementations for the ==, eql?, hash and inspect methods.} spec.homepage = "https://github.com/piotrmurach/equatable" spec.license = "MIT" if spec.respond_to?(:metadata=) spec.metadata = { "allowed_push_host" => "https://rubygems.org", "bug_tracker_uri" => "#{spec.homepage}/issues", "changelog_uri" => "#{spec.homepage}/blob/master/CHANGELOG.md", "documentation_uri" => "https://www.rubydoc.info/github/piotrmurach/equatable", "homepage_uri" => spec.homepage, "source_code_uri" => spec.homepage } end spec.files = Dir["lib/**/*"] spec.extra_rdoc_files = ["README.md", "CHANGELOG.md", "LICENSE.txt"] spec.require_paths = ["lib"] spec.required_ruby_version = Gem::Requirement.new(">= 2.0.0") spec.add_development_dependency "rake" spec.add_development_dependency "rspec", ">= 3.0" end equatable-0.7.0/examples/000077500000000000000000000000001403263205200152745ustar00rootroot00000000000000equatable-0.7.0/examples/point.rb000066400000000000000000000032061403263205200167530ustar00rootroot00000000000000# frozen_string_literal: true require_relative "../lib/equatable" # A point on a Cartesian plane class Point include Equatable attr_reader :x, :y def initialize(x, y) @x, @y = x, y end end # A coloured point on a Cartesian plane class ColorPoint < Point attr_reader :color def initialize(x, y, color) super(x, y) @color = color end end point1 = Point.new(1, 1) point2 = Point.new(1, 1) point3 = Point.new(2, 1) puts "** Comparison with equal **" puts "Point.new(1,1) == Point.new(1,1): #{point1 == point2}" puts "Point.new(1,1).hash == Point.new(1,1).hash: #{point1.hash == point2.hash}" puts "Point.new(1,1).eql?(Point.new(1,1)): #{point1.eql?(point2)}" puts "Point.new(1,1).equal?(Point.new(1,1)): #{point1.equal?(point2)}" puts "\n** Comparison with unequal **" puts "Point.new(1,1) == Point.new(2,1): #{point1 == point3}" puts "Point.new(1,1).hash == Point.new(2,1).hash: #{point1.hash == point3.hash}" puts "Point.new(1,1).eql?(Point.new(2,1)): #{point1.eql?(point3)}" puts "Point.new(1,1).equal?(Point.new(2,1)): #{point1.equal?(point3)}" point = Point.new(1, 1) color_point = ColorPoint.new(1, 1, :red) puts "\n** Comparison with subtype **" puts "Point.new(1,1) == ColorPoint.new(1,1,:red): #{point == color_point}" puts "ColorPoint.new(1,1,:red) == Point.new(1,1): #{color_point == point}" puts "Point.new(1,1).hash == ColorPoint.new(1,1,:red).hash: #{point.hash == color_point.hash}" puts "Point.new(1,1).eql?(ColorPoint.new(1,1,:red)): #{point.eql?(color_point)}" puts "Point.new(1,1).equal?(ColorPoint.new(1,1,:red)): #{point.equal?(color_point)}" puts "\n** Inspect **" puts "Point.new(1,1).inspect: #{point1.inspect}" equatable-0.7.0/lib/000077500000000000000000000000001403263205200142245ustar00rootroot00000000000000equatable-0.7.0/lib/equatable.rb000066400000000000000000000065701403263205200165240ustar00rootroot00000000000000# frozen_string_literal: true require "equatable/version" # Make it easy to define equality and hash methods. module Equatable # Hook into module inclusion. # # @param [Module] base # the module or class including Equatable # # @return [self] # # @api private def self.included(base) super base.extend(self) base.class_eval do include Methods define_methods end end # Holds all attributes used for comparison. # # @return [Array] # # @api private attr_reader :comparison_attrs # Objects that include this module are assumed to be value objects. # It is also assumed that the only values that affect the results of # equality comparison are the values of the object's attributes. # # @param [Array] *args # # @return [undefined] # # @api public def attr_reader(*args) super comparison_attrs.concat(args) end # Copy the comparison_attrs into the subclass. # # @param [Class] subclass # # @api private def inherited(subclass) super subclass.instance_variable_set(:@comparison_attrs, comparison_attrs.dup) end private # Define all methods needed for ensuring object's equality. # # @return [undefined] # # @api private def define_methods define_comparison_attrs define_compare define_hash define_inspect end # Define class instance #comparison_attrs as an empty array. # # @return [undefined] # # @api private def define_comparison_attrs instance_variable_set("@comparison_attrs", []) end # Define a #compare? method to check if the receiver is the same # as the other object. # # @return [undefined] # # @api private def define_compare define_method(:compare?) do |comparator, other| klass = self.class attrs = klass.comparison_attrs attrs.all? do |attr| other.respond_to?(attr) && send(attr).send(comparator, other.send(attr)) end end end # Define a hash method that ensures that the hash value is the same for # the same instance attributes and their corresponding values. # # @api private def define_hash define_method(:hash) do klass = self.class attrs = klass.comparison_attrs ([klass] + attrs.map { |attr| send(attr) }).hash end end # Define an inspect method that shows the class name and the values for the # instance's attributes. # # @return [undefined] # # @api private def define_inspect define_method(:inspect) do klass = self.class name = klass.name || klass.inspect attrs = klass.comparison_attrs "#<#{name}#{attrs.map { |attr| " #{attr}=#{send(attr).inspect}" }.join}>" end end # The equality methods module Methods # Compare two objects for equality based on their value # and being an instance of the given class. # # @param [Object] other # the other object in comparison # # @return [Boolean] # # @api public def eql?(other) instance_of?(other.class) && compare?(__method__, other) end # Compare two objects for equality based on their value # and being a subclass of the given class. # # @param [Object] other # the other object in comparison # # @return [Boolean] # # @api public def ==(other) other.is_a?(self.class) && compare?(__method__, other) end end # Methods end # Equatable equatable-0.7.0/lib/equatable/000077500000000000000000000000001403263205200161675ustar00rootroot00000000000000equatable-0.7.0/lib/equatable/version.rb000066400000000000000000000001101403263205200201710ustar00rootroot00000000000000# frozen_string_literal: true module Equatable VERSION = "0.7.0" end equatable-0.7.0/spec/000077500000000000000000000000001403263205200144105ustar00rootroot00000000000000equatable-0.7.0/spec/equatable/000077500000000000000000000000001403263205200163535ustar00rootroot00000000000000equatable-0.7.0/spec/equatable/eql_spec.rb000066400000000000000000000016771403263205200205060ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Equatable, "#eql?" do let(:name) { "Value" } let(:value) { 11 } let(:klass) { ::Class.new do include Equatable attr_reader :value def initialize(value) @value = value end end } let(:object) { klass.new(value) } subject { object.eql?(other) } context "with the same object" do let(:other) { object } it { is_expected.to eql(true) } it "is symmetric" do is_expected.to eql(other.eql?(object)) end end context "with an equivalent object" do let(:other) { object.dup } it { is_expected.to eql(true) } it "is symmetric" do is_expected.to eql(other.eql?(object)) end end context "with an equivalent object of a subclass" do let(:other) { ::Class.new(klass).new(value) } it { is_expected.to eql(false) } it "is symmetric" do is_expected.to eql(other.eql?(object)) end end end equatable-0.7.0/spec/equatable/equal_spec.rb000066400000000000000000000031651403263205200210260ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Equatable, "#==" do let(:name) { "Value" } let(:value) { 11 } let(:super_klass) { ::Class.new do include Equatable attr_reader :value def initialize(value) @value = value end end } let(:klass) { Class.new(super_klass) } let(:object) { klass.new(value) } subject { object == other } context "with the same object" do let(:other) { object } it { is_expected.to eql(true) } it "is symmetric" do is_expected.to eql(other == object) end end context "with an equivalent object" do let(:other) { object.dup } it { is_expected.to eql(true) } it "is symmetric" do is_expected.to eql(other == object) end end context "with an equivalent object of a subclass" do let(:other) { ::Class.new(klass).new(value) } it { is_expected.to eql(true) } it "is not symmetric" do # LSP, any equality for type should work for subtype but # not the other way is_expected.not_to eql(other == object) end end context "with an equivalent object of a superclass" do let(:other) { super_klass.new(value) } it { is_expected.to eql(false) } it "is not symmetric" do is_expected.not_to eql(other == object) end end context "with an object with a different interface" do let(:other) { Object.new } it { is_expected.to eql(false) } end context "with an object of another class" do let(:other) { Class.new.new } it { is_expected.to eql(false) } it "is symmetric" do is_expected.to eql(other == object) end end end equatable-0.7.0/spec/equatable/include_spec.rb000066400000000000000000000100051403263205200213310ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Equatable, "#include" do let(:name) { "Value" } let(:object) { described_class } context "without attributes" do let(:klass) { ::Class.new } subject { klass.new } before { allow(klass).to receive(:name).and_return(name) klass.send(:include, object) } it { is_expected.to respond_to(:compare?) } it { is_expected.to be_instance_of(klass) } it "has no attribute names" do expect(klass.comparison_attrs).to eq([]) end describe "#inspect" do it { expect(subject.inspect).to eql("#") } end describe "#hash" do it { expect(subject.hash).to eql([klass].hash) } end describe "#eql?" do context "when objects are similar" do let(:other) { subject.dup } it { expect(subject.eql?(other)).to eql(true) } end context "when objects are different" do let(:other) { double("other") } it { expect(subject.eql?(other)).to eql(false) } end end describe "#==" do context "when objects are similar" do let(:other) { subject.dup } it { expect(subject == other).to eql(true) } end context "when objects are different" do let(:other) { double("other") } it { expect(subject == other).to eql(false) } end end context "equivalence relation" do let(:other) { subject.dup } let(:another) { other.dup } it "is not equal to nil reference" do expect(subject.eql?(nil)).to eql(false) end it "is reflexive" do expect(subject.eql?(subject)).to eql(true) end it "is symmetric" do expect(subject.eql?(other)).to eql(other.eql?(subject)) end it "is transitive" do expect(subject.eql?(other) && other.eql?(another)) .to eql(subject.eql?(another)) end end end context "with attributes" do let(:value) { 11 } let(:klass) { ::Class.new do include Equatable attr_reader :value def initialize(value) @value = value end end } before { allow(klass).to receive(:name).and_return(name) } subject { klass.new(value) } it "dynamically defines #hash method" do expect(klass.method_defined?(:hash)).to eql(true) end it "dynamically defines #inspect method" do expect(klass.method_defined?(:inspect)).to eql(true) end it { is_expected.to respond_to(:compare?) } it { is_expected.to respond_to(:eql?) } it "has comparison attribute names" do expect(klass.comparison_attrs).to eq([:value]) end describe "#eql?" do context "when objects are similar" do let(:other) { subject.dup } it { expect(subject.eql?(other)).to eql(true) } end context "when objects are different" do let(:other) { double("other") } it { expect(subject.eql?(other)).to eql(false) } end end describe "#==" do context "when objects are similar" do let(:other) { subject.dup } it { expect(subject == other).to eql(true) } end context "when objects are different" do let(:other) { double("other") } it { expect(subject == other).to eql(false) } end end describe "#inspect" do it { expect(subject.inspect).to eql("#") } end describe "#hash" do it { expect(subject.hash).to eql(([klass] + [value]).hash) } end context "equivalence relation" do let(:other) { subject.dup } let(:another) { other.dup } it "is not equal to nil reference" do expect(subject.eql?(nil)).to eql(false) end it "is reflexive" do expect(subject.eql?(subject)).to eql(true) end it "is symmetric" do expect(subject.eql?(other)).to eql(other.eql?(subject)) end it "is transitive" do expect(subject.eql?(other) && other.eql?(another)) .to eql(subject.eql?(another)) end end end end equatable-0.7.0/spec/equatable/subclass_spec.rb000066400000000000000000000024751403263205200215410ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Equatable, "subclass" do let(:name) { "Value" } context "when subclass" do let(:value) { 11 } let(:klass) { ::Class.new do include Equatable attr_reader :value def initialize(value) @value = value end end } let(:subclass) { ::Class.new(klass) } subject { subclass.new(value) } before { allow(klass).to receive(:name).and_return(name) } it { expect(subclass.superclass).to eq(klass) } it { is_expected.to respond_to(:value) } describe "#inspect" do it { expect(subject.inspect).to eql("#") } end describe "#eql?" do context "when objects are similar" do let(:other) { subject.dup } it { expect(subject.eql?(other)).to eql(true) } end context "when objects are different" do let(:other) { double("other") } it { expect(subject.eql?(other)).to eql(false) } end end describe "#==" do context "when objects are similar" do let(:other) { subject.dup } it { expect(subject == other).to eql(true) } end context "when objects are different" do let(:other) { double("other") } it { expect(subject == other).to eql(false) } end end end end equatable-0.7.0/spec/spec_helper.rb000066400000000000000000000020041403263205200172220ustar00rootroot00000000000000# frozen_string_literal: true if ENV["COVERAGE"] == "true" require "simplecov" require "coveralls" SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([ SimpleCov::Formatter::HTMLFormatter, Coveralls::SimpleCov::Formatter ]) SimpleCov.start do command_name "spec" add_filter "spec" end end require "equatable" RSpec.configure do |config| config.expect_with :rspec do |expectations| expectations.include_chain_clauses_in_custom_matcher_descriptions = true end config.mock_with :rspec do |mocks| mocks.verify_partial_doubles = true end # Limits the available syntax to the non-monkey patched syntax that is recommended. config.disable_monkey_patching! # This setting enables warnings. It's recommended, but in some cases may # be too noisy due to issues in dependencies. config.warnings = true if config.files_to_run.one? config.default_formatter = "doc" end config.profile_examples = 2 config.order = :random Kernel.srand config.seed end equatable-0.7.0/tasks/000077500000000000000000000000001403263205200146035ustar00rootroot00000000000000equatable-0.7.0/tasks/console.rake000066400000000000000000000003441403263205200171120ustar00rootroot00000000000000# frozen_string_literal: true desc "Load gem inside irb console" task :console do require "irb" require "irb/completion" require File.join(__FILE__, "../../lib/equatable") ARGV.clear IRB.start end task c: %w[console] equatable-0.7.0/tasks/coverage.rake000066400000000000000000000003361403263205200172440ustar00rootroot00000000000000# frozen_string_literal: true desc "Measure code coverage" task :coverage do begin original, ENV["COVERAGE"] = ENV["COVERAGE"], "true" Rake::Task["spec"].invoke ensure ENV["COVERAGE"] = original end end equatable-0.7.0/tasks/spec.rake000066400000000000000000000003371403263205200164040ustar00rootroot00000000000000# frozen_string_literal: true begin require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:spec) do |spec| spec.pattern = "spec/**{,/*/**}/*_spec.rb" end rescue LoadError warn("Cannot load rspec task.") end