sinclair 1.8.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +20 -6
  3. data/.rubocop.yml +5 -2
  4. data/Dockerfile +1 -1
  5. data/README.md +25 -1
  6. data/config/check_specs.yml +1 -0
  7. data/config/yardstick.yml +9 -1
  8. data/lib/sinclair/comparable/class_methods.rb +33 -0
  9. data/lib/sinclair/comparable.rb +47 -0
  10. data/lib/sinclair/config_builder.rb +4 -2
  11. data/lib/sinclair/configurable.rb +2 -0
  12. data/lib/sinclair/equals_checker.rb +110 -0
  13. data/lib/sinclair/matchers/add_class_method.rb +27 -36
  14. data/lib/sinclair/matchers/add_instance_method.rb +59 -59
  15. data/lib/sinclair/matchers/add_method.rb +33 -35
  16. data/lib/sinclair/matchers/change_class_method.rb +22 -16
  17. data/lib/sinclair/matchers/change_instance_method.rb +46 -16
  18. data/lib/sinclair/matchers.rb +2 -8
  19. data/lib/sinclair/method_builder/call_method_builder.rb +49 -0
  20. data/lib/sinclair/method_builder.rb +4 -1
  21. data/lib/sinclair/method_definition/call_definition.rb +52 -0
  22. data/lib/sinclair/method_definition/string_definition.rb +0 -2
  23. data/lib/sinclair/method_definition.rb +40 -24
  24. data/lib/sinclair/method_definitions.rb +21 -1
  25. data/lib/sinclair/options/builder.rb +8 -0
  26. data/lib/sinclair/options.rb +1 -13
  27. data/lib/sinclair/version.rb +1 -1
  28. data/lib/sinclair.rb +2 -0
  29. data/sinclair.gemspec +1 -1
  30. data/spec/integration/readme/sinclair/comparable_spec.rb +24 -0
  31. data/spec/integration/yard/sinclair/comparable_spec.rb +24 -0
  32. data/spec/integration/yard/sinclair/equals_checker_spec.rb +51 -0
  33. data/spec/integration/yard/sinclair/matchers/change_class_method_spec.rb +24 -0
  34. data/spec/integration/yard/sinclair/matchers/change_instance_method_spec.rb +40 -0
  35. data/spec/lib/sinclair/comparable_spec.rb +202 -0
  36. data/spec/lib/sinclair/equals_checker_spec.rb +148 -0
  37. data/spec/lib/sinclair/method_builder/call_method_builder_spec.rb +76 -0
  38. data/spec/lib/sinclair/method_builder_spec.rb +63 -20
  39. data/spec/lib/sinclair/method_definition/call_definition_spec.rb +36 -0
  40. data/spec/lib/sinclair/method_definition_spec.rb +64 -0
  41. data/spec/lib/sinclair/method_definitions_spec.rb +79 -0
  42. data/spec/lib/sinclair/options/builder_spec.rb +13 -0
  43. data/spec/lib/sinclair/options/class_methods_spec.rb +23 -8
  44. data/spec/support/sample_model.rb +19 -0
  45. data/spec/support/shared_examples/attribute_accessor.rb +103 -0
  46. metadata +21 -5
@@ -4,41 +4,39 @@ class Sinclair
4
4
  module Matchers
5
5
  # @api private
6
6
  #
7
- # Commone methods for matchers
8
- module AddMethod
9
- # @api public
10
- #
11
- # Builds final matcher
12
- #
13
- # The matcher checks if a method was added
14
- # to a class or instance
15
- #
16
- # @param [target] target where the method will be added
17
- #
18
- # @return [Sinclair::Matchers::Base]
19
- #
20
- # @example
21
- # RSpec.configure do |config|
22
- # config.include Sinclair::Matchers
23
- # end
24
- #
25
- # class MyModel
26
- # end
27
- #
28
- # RSpec.describe 'my test' do
29
- # let(:klass) { Class.new(MyModel) }
30
- # let(:builder) { Sinclair.new(klass) }
31
- #
32
- # before do
33
- # builder.add_method(:class_name, 'self.class.name')
34
- # end
35
- #
36
- # it do
37
- # expect { builder.build }.to add_method(:class_name).to(klass)
38
- # end
39
- # end
40
- def to(target = nil)
41
- add_method_to_class.new(target, method_name)
7
+ # Common methods for matchers
8
+ class AddMethod < Base
9
+ class << self
10
+ private
11
+
12
+ # @api private
13
+ # @private
14
+ #
15
+ # Add a method to generate the final matcher
16
+ #
17
+ # @param name [String,Symbol] the name of the method
18
+ # @param matcher_class [Class<AddMethodTo>] The matcher class to be returned
19
+ #
20
+ # @return (see Sinclair#build)
21
+ #
22
+ # @!macro with_final_matcher
23
+ # @!method $1(target = nil)
24
+ # @api public
25
+ #
26
+ # Builds final matcher
27
+ #
28
+ # The matcher checks if a method was added
29
+ # to a class or instance
30
+ #
31
+ # @param [Class,Object] target where the method will be added
32
+ #
33
+ # @return [$2]
34
+ def with_final_matcher(name, matcher_class)
35
+ matcher = matcher_class
36
+ Sinclair.new(self).tap do |builder|
37
+ builder.add_method(name) { |target| matcher.new(target, method_name) }
38
+ end.build
39
+ end
42
40
  end
43
41
 
44
42
  # @abstract
@@ -7,15 +7,30 @@ class Sinclair
7
7
  #
8
8
  # AddInstanceMethod is able to build an instance of
9
9
  # {Sinclair::Matchers::ChangeClassMethodOn}
10
- class ChangeClassMethod < Base
11
- include AddMethod
12
-
13
- # @api public
10
+ class ChangeClassMethod < AddMethod
11
+ # @example Checking if a class method has changed
12
+ # RSpec.configure do |config|
13
+ # config.include Sinclair::Matchers
14
+ # end
15
+ #
16
+ # class MyModel
17
+ # end
14
18
  #
15
- # Builds final matcher
19
+ # RSpec.describe 'my test' do
20
+ # let(:builder) { Sinclair.new(klass) }
21
+ # let(:klass) { Class.new(MyModel) }
16
22
  #
17
- # @return [Sinclair::Matchers::ChangeClassMethodOn]
18
- alias on to
23
+ # before do
24
+ # builder.add_class_method(:the_method) { 10 }
25
+ # builder.build
26
+ # builder.add_class_method(:the_method) { 20 }
27
+ # end
28
+ #
29
+ # it do
30
+ # expect{ builder.build }.to change_class_method(:the_method).on(klass)
31
+ # end
32
+ # end
33
+ with_final_matcher :on, ChangeClassMethodOn
19
34
 
20
35
  private
21
36
 
@@ -28,15 +43,6 @@ class Sinclair
28
43
  'You should specify which class the method is being changed on' \
29
44
  "change_class_method(:#{method_name}).on(klass)"
30
45
  end
31
-
32
- # @private
33
- #
34
- # Class of the real matcher
35
- #
36
- # @return [Class<ChangeClassMethodOn>]
37
- def add_method_to_class
38
- ChangeClassMethodOn
39
- end
40
46
  end
41
47
  end
42
48
  end
@@ -7,15 +7,54 @@ class Sinclair
7
7
  #
8
8
  # AddInstanceMethod is able to build an instance of
9
9
  # {Sinclair::Matchers::ChangeInstanceMethodOn}
10
- class ChangeInstanceMethod < Base
11
- include AddMethod
12
-
13
- # @api public
10
+ class ChangeInstanceMethod < AddMethod
11
+ # @example Checking if an instance method has changed
12
+ # RSpec.configure do |config|
13
+ # config.include Sinclair::Matchers
14
+ # end
15
+ #
16
+ # class MyModel
17
+ # end
18
+ #
19
+ # RSpec.describe 'my test' do
20
+ # let(:builder) { Sinclair.new(klass) }
21
+ # let(:klass) { Class.new(MyModel) }
22
+ #
23
+ # before do
24
+ # builder.add_method(:the_method) { 10 }
25
+ # builder.build
26
+ # builder.add_method(:the_method) { 20 }
27
+ # end
28
+ #
29
+ # it do
30
+ # expect{ builder.build }.to change_method(:the_method).on(klass)
31
+ # end
32
+ # end
33
+ #
34
+ # @example Checking if an instance method has changed on an instance
35
+ # RSpec.configure do |config|
36
+ # config.include Sinclair::Matchers
37
+ # end
38
+ #
39
+ # class MyModel
40
+ # end
14
41
  #
15
- # Builds final matcher
42
+ # RSpec.describe 'my test' do
43
+ # let(:builder) { Sinclair.new(klass) }
44
+ # let(:instance) { klass.new }
45
+ # let(:klass) { Class.new(MyModel) }
16
46
  #
17
- # @return [Sinclair::Matchers::ChangeInstanceMethodOn]
18
- alias on to
47
+ # before do
48
+ # builder.add_method(:the_method) { 10 }
49
+ # builder.build
50
+ # builder.add_method(:the_method) { 20 }
51
+ # end
52
+ #
53
+ # it do
54
+ # expect{ builder.build }.to change_method(:the_method).on(instance)
55
+ # end
56
+ # end
57
+ with_final_matcher :on, ChangeInstanceMethodOn
19
58
 
20
59
  private
21
60
 
@@ -28,15 +67,6 @@ class Sinclair
28
67
  'You should specify which instance the method is being changed on' \
29
68
  "change_method(:#{method_name}).on(instance)"
30
69
  end
31
-
32
- # @private
33
- #
34
- # Class of the real matcher
35
- #
36
- # @return [Class<Sinclair::Matchers::Base>]
37
- def add_method_to_class
38
- ChangeInstanceMethodOn
39
- end
40
70
  end
41
71
  end
42
72
  end
@@ -6,8 +6,6 @@ class Sinclair
6
6
  #
7
7
  # Matchers module will have the DSL to be included in RSpec in order to have
8
8
  # access to the matchers
9
- #
10
- # @example (see Sinclair::Matchers::AddMethod#to)
11
9
  module Matchers
12
10
  autoload :Base, 'sinclair/matchers/base'
13
11
  autoload :AddInstanceMethod, 'sinclair/matchers/add_instance_method'
@@ -25,7 +23,6 @@ class Sinclair
25
23
 
26
24
  # DSL to AddInstanceMethod
27
25
  #
28
- # @example (see Sinclair::Matchers)
29
26
  # @example (see Sinclair::Matchers::AddInstanceMethod#to)
30
27
  #
31
28
  # @return [AddInstanceMethod] RSpec Matcher
@@ -35,7 +32,6 @@ class Sinclair
35
32
 
36
33
  # DSL to AddClassMethod
37
34
  #
38
- # @example (see Sinclair::Matchers)
39
35
  # @example (see Sinclair::Matchers::AddClassMethod#to)
40
36
  #
41
37
  # @return [AddClassMethod] RSpec Matcher
@@ -45,8 +41,7 @@ class Sinclair
45
41
 
46
42
  # DSL to ChangeInstanceMethod
47
43
  #
48
- # @example (see Sinclair::Matchers)
49
- # @example (see Sinclair::Matchers::ChangeInstanceMethod#to)
44
+ # @example (see Sinclair::Matchers::ChangeInstanceMethod#on)
50
45
  #
51
46
  # @return [ChangeInstanceMethod] RSpec Matcher
52
47
  def change_method(method_name)
@@ -55,8 +50,7 @@ class Sinclair
55
50
 
56
51
  # DSL to ChangeClassMethod
57
52
  #
58
- # @example (see Sinclair::Matchers)
59
- # @example (see Sinclair::Matchers::ChangeClassMethod#to)
53
+ # @example (see Sinclair::Matchers::ChangeClassMethod#on)
60
54
  #
61
55
  # @return [ChangeClassMethod] RSpec Matcher
62
56
  def change_class_method(method_name)
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Sinclair
4
+ class MethodBuilder
5
+ # @api private
6
+ # @author darthjee
7
+ #
8
+ # Build a method based on a {MethodDefinition::CallDefinition}
9
+ class CallMethodBuilder < Base
10
+ # Builds the method
11
+ #
12
+ # @return [NilClass]
13
+ def build
14
+ klass.module_eval(code_line, __FILE__, __LINE__ + 1)
15
+ end
16
+
17
+ private
18
+
19
+ # @api private
20
+ # @private
21
+ #
22
+ # String to be evaluated when building the method
23
+ #
24
+ # This can be {code_string} or {class_code_string}
25
+ # @return (see MethodDefinition::CallDefinition#code_string)
26
+ def code_line
27
+ instance? ? code_string : class_code_string
28
+ end
29
+
30
+ delegate :code_string, :class_code_string, to: :definition
31
+
32
+ # @method code_string
33
+ # @private
34
+ # @api private
35
+ #
36
+ # Delegated from {MethodDefinition::CallDefinition}
37
+ #
38
+ # @see MethodDefinition::CallDefinition#code_string
39
+ # @return [String]
40
+
41
+ # @method class_code_string
42
+ # @private
43
+ # @api private
44
+ #
45
+ # @see MethodDefinition::CallDefinition#class_code_string
46
+ # @return [String]
47
+ end
48
+ end
49
+ end
@@ -9,6 +9,7 @@ class Sinclair
9
9
  autoload :Base, 'sinclair/method_builder/base'
10
10
  autoload :StringMethodBuilder, 'sinclair/method_builder/string_method_builder'
11
11
  autoload :BlockMethodBuilder, 'sinclair/method_builder/block_method_builder'
12
+ autoload :CallMethodBuilder, 'sinclair/method_builder/call_method_builder'
12
13
 
13
14
  CLASS_METHOD = :class
14
15
  INSTANCE_METHOD = :instance
@@ -53,8 +54,10 @@ class Sinclair
53
54
  def build_from_definition(definition, type)
54
55
  if definition.string?
55
56
  StringMethodBuilder.new(klass, definition, type: type).build
56
- else
57
+ elsif definition.block?
57
58
  BlockMethodBuilder.new(klass, definition, type: type).build
59
+ else
60
+ CallMethodBuilder.new(klass, definition, type: type).build
58
61
  end
59
62
  end
60
63
  end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Sinclair
4
+ class MethodDefinition
5
+ # @api private
6
+ # @author darthjee
7
+ #
8
+ # Define a call of method to e done within the class
9
+ class CallDefinition < MethodDefinition
10
+ # @param method_name [Symbol] method to be called
11
+ # @param arguments [Array<Symbol,String>] parameters to be passed as
12
+ # arguments to the call
13
+ def initialize(method_name, *arguments)
14
+ @arguments = arguments
15
+ super(method_name)
16
+ end
17
+
18
+ default_value :block?, false
19
+ default_value :string?, false
20
+
21
+ # String to be executed within the class
22
+ # @return [String]
23
+ def code_string
24
+ "#{name} :#{arguments.join(', :')}"
25
+ end
26
+
27
+ # String to be executed within the class running code to change the class itself
28
+ #
29
+ # @see code_string
30
+ # @return [String]
31
+ def class_code_string
32
+ <<-CODE
33
+ class << self
34
+ #{code_string}
35
+ end
36
+ CODE
37
+ end
38
+
39
+ private
40
+
41
+ attr_reader :arguments
42
+
43
+ # @method arguments
44
+ # @api private
45
+ # @private
46
+ #
47
+ # parameters to be passed as arguments to the call
48
+ #
49
+ # @return [Array<Symbol,String>]
50
+ end
51
+ end
52
+ end
@@ -5,8 +5,6 @@ class Sinclair
5
5
  # @api private
6
6
  # @author darthjee
7
7
  #
8
- # @abstract
9
- #
10
8
  # Define an instance method from string
11
9
  class StringDefinition < MethodDefinition
12
10
  # @param name [String,Symbol] name of the method
@@ -11,6 +11,7 @@ class Sinclair
11
11
  autoload :BlockHelper, 'sinclair/method_definition/block_helper'
12
12
  autoload :BlockDefinition, 'sinclair/method_definition/block_definition'
13
13
  autoload :StringDefinition, 'sinclair/method_definition/string_definition'
14
+ autoload :CallDefinition, 'sinclair/method_definition/call_definition'
14
15
 
15
16
  # @method name
16
17
  #
@@ -24,31 +25,46 @@ class Sinclair
24
25
  cached: false
25
26
  }.freeze
26
27
 
27
- # Builds a method that will return the same value always
28
- #
29
- # @return [Symbol]
30
- def self.default_value(method_name, value)
31
- define_method(method_name) { value }
32
- end
28
+ class << self
29
+ # Builds a method that will return the same value always
30
+ #
31
+ # @return [Symbol]
32
+ def default_value(method_name, value)
33
+ define_method(method_name) { value }
34
+ end
33
35
 
34
- # @param name [String,Symbol] name of the method
35
- # @param code [String] code to be evaluated as method
36
- # @param block [Proc] block with code to be added as method
37
- # @param options [Hash] Options of construction
38
- # @option options cached [Boolean] Flag telling to create a block
39
- # with cache
40
- #
41
- # builds a method definition based on arguments
42
- #
43
- # when block is given, returns a {BlockDefinition} and
44
- # returns a {StringDefinition} otherwise
45
- #
46
- # @return [Base]
47
- def self.from(name, code = nil, **options, &block)
48
- if block
49
- BlockDefinition.new(name, **options, &block)
50
- else
51
- StringDefinition.new(name, code, **options)
36
+ # @param name [String,Symbol] name of the method
37
+ # @param code [String] code to be evaluated as method
38
+ # @param block [Proc] block with code to be added as method
39
+ # @param options [Hash] Options of construction
40
+ # @option options cached [Boolean] Flag telling to create a block
41
+ # with cache
42
+ #
43
+ # builds a method definition based on arguments
44
+ #
45
+ # when block is given, returns a {BlockDefinition} and
46
+ # returns a {StringDefinition} otherwise
47
+ #
48
+ # @return [Base]
49
+ def from(name, code = nil, **options, &block)
50
+ if block
51
+ BlockDefinition.new(name, **options, &block)
52
+ else
53
+ StringDefinition.new(name, code, **options)
54
+ end
55
+ end
56
+
57
+ # creates a definition
58
+ #
59
+ # The creation is based on type which will be used to infer
60
+ # which subclass of {Sinclair::MethodDefinition} to be used
61
+ #
62
+ # @param type [Symbol] the method definition type
63
+ #
64
+ # @return [Sinclair::MethodDefinition] an instance of a subclass
65
+ def for(type, *args, **options, &block)
66
+ klass = const_get("#{type}_definition".camelize)
67
+ klass.new(*args, **options, &block)
52
68
  end
53
69
  end
54
70
 
@@ -10,6 +10,8 @@ class Sinclair
10
10
 
11
11
  # Builds and adds new definition
12
12
  #
13
+ # The type is decided based in the arguments
14
+ #
13
15
  # @param name [String,Symbol] method name
14
16
  # @param options [Hash] Options of construction
15
17
  # @option options cached [Boolean] Flag telling to create
@@ -21,11 +23,29 @@ class Sinclair
21
23
  # @overload add(definition_class, name, **options, &block)
22
24
  # @param block [Proc] block to be ran as method
23
25
  #
24
- # @return MethodDefinitions
26
+ # @return [Array<MethodDefinition>]
25
27
  def add(name, code = nil, **options, &block)
26
28
  definitions << MethodDefinition.from(name, code, **options, &block)
27
29
  end
28
30
 
31
+ # Builds and adds new definition
32
+ #
33
+ # The type is decided based on the argument +type+
34
+ #
35
+ # @param type [Symbol] type of definition
36
+ # - :string -> {MethodDefinition::StringDefinition}
37
+ # - :block -> {MethodDefinition::BlockDefinition}
38
+ # - :call -> {MethodDefinition::CallDefinition}
39
+ # @param options [Hash] Options of construction
40
+ # @option options cached [Boolean] Flag telling to create
41
+ # a method with cache
42
+ # @param block [Proc] block to be ran as method
43
+ #
44
+ # @return [Array<MethodDefinition>]
45
+ def add_definition(type, *args, **options, &block)
46
+ definitions << MethodDefinition.for(type, *args, **options, &block)
47
+ end
48
+
29
49
  private
30
50
 
31
51
  # @private
@@ -36,6 +36,7 @@ class Sinclair
36
36
  # @return (see Sinclair#build)
37
37
  def build
38
38
  add_all_methods
39
+ add_filds_to_equals
39
40
 
40
41
  super
41
42
  end
@@ -60,6 +61,13 @@ class Sinclair
60
61
  klass.allow(option)
61
62
  end
62
63
  end
64
+
65
+ # Add the fields to equals comparation
66
+ #
67
+ # @return (see Sinclair::EqualsChecker#add)
68
+ def add_filds_to_equals
69
+ klass.comparable_by(*attributes.keys)
70
+ end
63
71
  end
64
72
  end
65
73
  end
@@ -24,6 +24,7 @@ class Sinclair
24
24
  autoload :ClassMethods, 'sinclair/options/class_methods'
25
25
 
26
26
  extend ClassMethods
27
+ include Comparable
27
28
 
28
29
  # @param options [Hash] hash with options (see {.options}, {.with_options})
29
30
  # @example (see Options)
@@ -59,19 +60,6 @@ class Sinclair
59
60
  end
60
61
  end
61
62
 
62
- # returns if other equals to self
63
- #
64
- # @param other [Object] object to be compared
65
- #
66
- # @return [TrueClass,FalseClass]
67
- def ==(other)
68
- return false unless self.class == other.class
69
-
70
- allowed_options.all? do |name|
71
- public_send(name) == other.public_send(name)
72
- end
73
- end
74
-
75
63
  private
76
64
 
77
65
  delegate :allowed_options, :skip_validation?, :invalid_options_in, to: :class
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Sinclair
4
- VERSION = '1.8.0'
4
+ VERSION = '1.10.0'
5
5
  end
data/lib/sinclair.rb CHANGED
@@ -88,8 +88,10 @@ class Sinclair
88
88
  autoload :ConfigClass, 'sinclair/config_class'
89
89
  autoload :ConfigFactory, 'sinclair/config_factory'
90
90
  autoload :Configurable, 'sinclair/configurable'
91
+ autoload :Comparable, 'sinclair/comparable'
91
92
  autoload :EnvSettable, 'sinclair/env_settable'
92
93
  autoload :Exception, 'sinclair/exception'
94
+ autoload :EqualsChecker, 'sinclair/equals_checker'
93
95
  autoload :InputHash, 'sinclair/input_hash'
94
96
  autoload :MethodBuilder, 'sinclair/method_builder'
95
97
  autoload :MethodDefinition, 'sinclair/method_definition'
data/sinclair.gemspec CHANGED
@@ -21,7 +21,7 @@ Gem::Specification.new do |gem|
21
21
 
22
22
  gem.add_runtime_dependency 'activesupport', '~> 5.2.0'
23
23
 
24
- gem.add_development_dependency 'bundler', '2.3.20'
24
+ gem.add_development_dependency 'bundler', '2.3.25'
25
25
  gem.add_development_dependency 'pry', '0.14.1'
26
26
  gem.add_development_dependency 'pry-nav', '1.0.0'
27
27
  gem.add_development_dependency 'rake', '13.0.1'
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Sinclair::Comparable do
6
+ describe '#readme' do
7
+ describe '.comparable_by?' do
8
+ let(:model_class) do
9
+ Class.new(SampleModel) do
10
+ include Sinclair::Comparable
11
+
12
+ comparable_by :name
13
+ end
14
+ end
15
+
16
+ it 'regular usage' do
17
+ model1 = model_class.new(name: 'jack', age: 21)
18
+ model2 = model_class.new(name: 'jack', age: 23)
19
+
20
+ expect(model1 == model2).to be_truthy
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Sinclair::Comparable do
6
+ describe '#yard' do
7
+ describe '.comparable_by?' do
8
+ let(:model_class) do
9
+ Class.new(SampleModel) do
10
+ include Sinclair::Comparable
11
+
12
+ comparable_by :name
13
+ end
14
+ end
15
+
16
+ it 'regular usage' do
17
+ model1 = model_class.new(name: 'jack', age: 21)
18
+ model2 = model_class.new(name: 'jack', age: 23)
19
+
20
+ expect(model1 == model2).to be_truthy
21
+ end
22
+ end
23
+ end
24
+ end