sinclair 1.7.0 → 1.9.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4e9a5068ba9c9c0f3def716decd2cea72fbdea0e6a3255d2bd0d324462de4ef5
4
- data.tar.gz: c067ae53d0320738e785675306dc7f8848851a3adb964217bd30593c15eaedbf
3
+ metadata.gz: 23c4aa7f5d3c743f783dc6196e0799bbef94fa2c4f42825379c4cc61aad8e9ec
4
+ data.tar.gz: 4aa6481202521cc4c0cf8b0210e798a8d56344a43c42e41157ae7a3121ea67a4
5
5
  SHA512:
6
- metadata.gz: ddc575979d46b47983aa57083243e674ac1da951e1e8407b98ee188a17e60e7683d5a73ad829914e34d076764970fb93484f9da67b767a89b301f47d081aad47
7
- data.tar.gz: 0052e1e9b9e86758cf2b35c215509d6d4946b5e8bd0fde994b2e8121b83807de04d8c4e84d3fa3ee47565e4a07f89313f0ef871d6156f93ebdd7e6c8df0a83c5
6
+ metadata.gz: 3b07675d7768ea9db9e858498863fec96959e209b2bf9596f78e4f57ff2134f286723d8d7d3c42b9be5f8efc907602dee83dc3d0914473cc73c163a65362e84f
7
+ data.tar.gz: 8f596032dfad280158eeeb5a4a74400386f960010b86480608864d81d9c017ad6fdcd8fcabf01d97ef9987a333719561f59d883840514fc707d076872760d195
data/.circleci/config.yml CHANGED
@@ -7,8 +7,12 @@ workflows:
7
7
  filters:
8
8
  tags:
9
9
  only: /.*/
10
+ - checks:
11
+ filters:
12
+ tags:
13
+ only: /.*/
10
14
  - build-and-release:
11
- requires: [test]
15
+ requires: [test, checks]
12
16
  filters:
13
17
  tags:
14
18
  only: /\d+\.\d+\.\d+/
@@ -18,7 +22,7 @@ workflows:
18
22
  jobs:
19
23
  test:
20
24
  docker:
21
- - image: darthjee/circleci_ruby_270:1.1.0
25
+ - image: darthjee/circleci_ruby_270:1.2.0
22
26
  environment:
23
27
  PROJECT: sinclair
24
28
  steps:
@@ -32,12 +36,22 @@ jobs:
32
36
  - run:
33
37
  name: RSpec
34
38
  command: bundle exec rspec
35
- - run:
36
- name: Rubocop
37
- command: rubocop
38
39
  - run:
39
40
  name: Coverage Test Report
40
41
  command: cc-test-reporter after-build --exit-code $?
42
+ checks:
43
+ docker:
44
+ - image: darthjee/circleci_ruby_270:1.2.0
45
+ environment:
46
+ PROJECT: sinclair
47
+ steps:
48
+ - checkout
49
+ - run:
50
+ name: Bundle Install
51
+ command: bundle install
52
+ - run:
53
+ name: Rubocop
54
+ command: rubocop
41
55
  - run:
42
56
  name: Yardstick coverage check
43
57
  command: bundle exec rake verify_measurements
@@ -52,7 +66,7 @@ jobs:
52
66
  command: check_specs
53
67
  build-and-release:
54
68
  docker:
55
- - image: darthjee/circleci_ruby_270:1.1.0
69
+ - image: darthjee/circleci_ruby_270:1.2.0
56
70
  environment:
57
71
  PROJECT: sinclair
58
72
  steps:
data/.rubocop.yml CHANGED
@@ -10,7 +10,7 @@ Metrics/BlockLength:
10
10
  - 'spec/support/**/*.rb'
11
11
  - '*.gemspec'
12
12
 
13
- Metrics/LineLength:
13
+ Layout/LineLength:
14
14
  Max: 100
15
15
 
16
16
  Lint/AmbiguousBlockAssociation:
@@ -20,6 +20,9 @@ Lint/AmbiguousBlockAssociation:
20
20
  RSpec/AlignLeftLetBrace:
21
21
  Enabled: true
22
22
 
23
+ RSpec/PredicateMatcher:
24
+ Exclude:
25
+ - 'spec/integration/yard/**/*_spec.rb'
23
26
 
24
27
  RSpec/DescribedClass:
25
28
  Exclude:
data/Dockerfile CHANGED
@@ -1,6 +1,6 @@
1
1
  FROM darthjee/scripts:0.3.1 as scripts
2
2
 
3
- FROM darthjee/ruby_270:1.1.0 as base
3
+ FROM darthjee/ruby_270:1.2.0 as base
4
4
 
5
5
  COPY --chown=app:app ./ /home/app/app/
6
6
 
data/README.md CHANGED
@@ -13,9 +13,11 @@ This gem helps the creation of complex gems/concerns
13
13
  that enables creation of methods on the fly through class
14
14
  methods
15
15
 
16
+ Next release: [1.10.0](https://github.com/darthjee/sinclair/compare/1.9.0...master)
17
+
16
18
  Yard Documentation
17
19
  -------------------
18
- [https://www.rubydoc.info/gems/sinclair/1.7.0](https://www.rubydoc.info/gems/sinclair/1.7.0)
20
+ [https://www.rubydoc.info/gems/sinclair/1.9.0](https://www.rubydoc.info/gems/sinclair/1.9.0)
19
21
 
20
22
  Installation
21
23
  ---------------
@@ -369,6 +371,12 @@ hash
369
371
  MyConfigurable.config.host # returns 'interstella.art'
370
372
  MyConfigurable.config.port # returns 5555
371
373
 
374
+ # Configurable enables options that can be passed
375
+ MyConfigurable.as_options.host # returns 'interstella.art'
376
+
377
+ # Configurable enables options that can be passed with custom values
378
+ MyConfigurable.as_options(host: 'other').host # returns 'other'
379
+
372
380
  MyConfigurable.reset_config
373
381
 
374
382
  MyConfigurable.config.host # returns nil
@@ -469,6 +477,28 @@ Options allows projects to have an easy to configure option object
469
477
  ConnectionOptions.new(invalid: 10) # raises Sinclair::Exception::InvalidOptions
470
478
  ```
471
479
 
480
+ ### Sinclair::Comparable
481
+ Comparable allows a class to implement quickly a `==` method comparing given attributes
482
+
483
+ ```ruby
484
+ class SampleModel
485
+ include Sinclair::Comparable
486
+
487
+ comparable_by :name
488
+ attr_reader :name, :age
489
+
490
+ def initialize(name: nil, age: nil)
491
+ @name = name
492
+ @age = age
493
+ end
494
+ end
495
+
496
+ model1 = model_class.new(name: 'jack', age: 21)
497
+ model2 = model_class.new(name: 'jack', age: 23)
498
+
499
+ model1 == model2 # returns true
500
+ ```
501
+
472
502
  RSspec matcher
473
503
  ---------------
474
504
 
@@ -553,3 +583,4 @@ Projects Using
553
583
 
554
584
  - [Arstotzka](https://github.com/darthjee/arstotzka)
555
585
  - [Azeroth](https://github.com/darthjee/azeroth)
586
+ - [Magicka](https://github.com/darthjee/magicka)
@@ -1,4 +1,5 @@
1
1
  ignore:
2
+ - lib/sinclair/comparable/class_methods.rb
2
3
  - lib/sinclair/matchers/add_method_to.rb
3
4
  - lib/sinclair/matchers/add_method.rb
4
5
  - lib/sinclair/version.rb
data/config/yardstick.yml CHANGED
@@ -19,6 +19,7 @@ rules:
19
19
  - Sinclair::Configurable#config
20
20
  - Sinclair::Configurable#reset_config
21
21
  - Sinclair::Configurable#configure
22
+ - Sinclair::EqualsChecker#initialize
22
23
  - Sinclair::Options#==
23
24
  - Sinclair::OptionsParser#options
24
25
  - Sinclair::OptionsParser#options_object
@@ -37,6 +38,7 @@ rules:
37
38
  - Sinclair::ConfigFactory#initialize
38
39
  - Sinclair::EnvSettable::Builder#initialize
39
40
  - Sinclair::Exception::InvalidOptions#initialize
41
+ - Sinclair::EqualsChecker#initialize
40
42
  - Sinclair::InputHash#initialize
41
43
  - Sinclair::Matchers::AddInstanceMethodTo#initialize
42
44
  - Sinclair::Matchers::AddClassMethodTo#initialize
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Sinclair
4
+ module Comparable
5
+ # @api public
6
+ # @author darthjee
7
+ #
8
+ # Class methods of {Sinclair::Comparable}
9
+ #
10
+ # @example (see Sinclair::Comparable)
11
+ module ClassMethods
12
+ # Adds fields to the comparison algorythim
13
+ #
14
+ # @param attributes [Array<Symbol>] attributes to be added to comparison
15
+ #
16
+ # @see Sinclair::EqualsChecker
17
+ # @return (see Sinclair::EqualsChecker#add)
18
+ # @example (see Sinclair::Comparable)
19
+ def comparable_by(*attributes)
20
+ equals_checker.add(*attributes)
21
+ end
22
+
23
+ # @api private
24
+ #
25
+ # Returns a comparable configured for the class
26
+ #
27
+ # @return [Sinclair::EqualsChecker]
28
+ def equals_checker
29
+ @equals_checker ||= Sinclair::EqualsChecker.new
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sinclair/comparable/class_methods'
4
+
5
+ class Sinclair
6
+ # @api public
7
+ # @author darthjee
8
+ #
9
+ # Concern to be added on classes for easy +==+ comparison
10
+ #
11
+ # @example
12
+ # class SampleModel
13
+ # include Sinclair::Comparable
14
+ #
15
+ # comparable_by :name
16
+ # attr_reader :name, :age
17
+ #
18
+ # def initialize(name: nil, age: nil)
19
+ # @name = name
20
+ # @age = age
21
+ # end
22
+ # end
23
+ #
24
+ # model1 = model_class.new(name: 'jack', age: 21)
25
+ # model2 = model_class.new(name: 'jack', age: 23)
26
+ #
27
+ # model1 == model2 # returns true
28
+ module Comparable
29
+ extend ActiveSupport::Concern
30
+
31
+ # Checks if an instance of a comparable is equals another
32
+ #
33
+ # @param other [Object] an object that should be equal to comparable
34
+ #
35
+ # @return [Boolean]
36
+ # @example (see Sinclair::Comparable)
37
+ def ==(other)
38
+ klass = self.class
39
+ superklass = klass.superclass
40
+
41
+ return false unless klass.equals_checker.match?(self, other)
42
+ return true unless superklass.include?(Sinclair::Comparable)
43
+
44
+ superklass.equals_checker.match?(self, other)
45
+ end
46
+ end
47
+ end
@@ -63,10 +63,10 @@ class Sinclair
63
63
  # conf.password :some_password
64
64
  # end
65
65
  #
66
- # options = LoginConfigurable.config.options
66
+ # options = LoginConfigurable.config.as_options
67
67
  #
68
- # config.options.username # returns :some_username
69
- # config.options.password # returns :some_password
68
+ # config.as_options.username # returns :some_username
69
+ # config.as_options.password # returns :some_password
70
70
  #
71
71
  # @example returning custom options
72
72
  # LoginConfigurable.configure do |conf|
@@ -74,13 +74,13 @@ class Sinclair
74
74
  # conf.password :some_password
75
75
  # end
76
76
  #
77
- # options = LoginConfigurable.config.options(
77
+ # options = LoginConfigurable.config.as_options(
78
78
  # password: :correct_password
79
79
  # )
80
80
  #
81
- # config.options.username # returns :some_username
82
- # config.options.password # returns :correct_password
83
- def options(options_hash = {})
81
+ # config.as_options.username # returns :some_username
82
+ # config.as_options.password # returns :correct_password
83
+ def as_options(options_hash = {})
84
84
  self.class.options_class.new(to_hash.merge(options_hash))
85
85
  end
86
86
  end
@@ -75,9 +75,11 @@ class Sinclair
75
75
  # @todo get rid of @config_attributes when only
76
76
  # Sinclair::ConfigClass are accepted
77
77
  def method_included?(method_name)
78
+ klass = @config.class
79
+
78
80
  @config_attributes.include?(method_name) ||
79
- @config.class.is_a?(Sinclair::ConfigClass) &&
80
- @config.class.config_attributes.include?(method_name)
81
+ klass.is_a?(Sinclair::ConfigClass) &&
82
+ klass.config_attributes.include?(method_name)
81
83
  end
82
84
  end
83
85
  end
@@ -105,7 +105,7 @@ class Sinclair
105
105
  #
106
106
  # @return [Class<Sinclair::Options>]
107
107
  def options_class
108
- @options_class ||= Class.new(Sinclair::Options)
108
+ @options_class ||= Class.new(superclass.try(:options_class) || Options)
109
109
  end
110
110
  end
111
111
  end
@@ -9,8 +9,8 @@ class Sinclair
9
9
  # By extending Configurable, class receives the methods public
10
10
  # {ConfigFactory#config .config}, {ConfigFactory#reset_config .reset_config}
11
11
  # and {ConfigFactory#configure .configure}
12
- # and the private methods {#configurable_with .configurable_with}
13
- # and {#configurable_by .configurable_by}
12
+ # and the private methods {#configurable_with .configurable_with},
13
+ # {#configurable_by .configurable_by} and {#as_options .as_options}
14
14
  #
15
15
  # @see ConfigFactory
16
16
  # @see ConfigBuilder
@@ -78,13 +78,13 @@ class Sinclair
78
78
  # @see ConfigFactory#configure
79
79
  delegate :config, :reset_config, :configure, to: :config_factory
80
80
 
81
- # @method options(options_hash = {})
81
+ # @method as_options(options_hash = {})
82
82
  # @api public
83
83
  #
84
- # @param (see Sinclair::Config#options)
85
- # @return (see Sinclair::Config#options)
86
- # @example (see Sinclair::Config#options)
87
- delegate :options, to: :config
84
+ # @param (see Sinclair::Config#as_options)
85
+ # @return (see Sinclair::Config#as_options)
86
+ # @example (see Sinclair::Config#as_options)
87
+ delegate :as_options, to: :config
88
88
 
89
89
  protected
90
90
 
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Sinclair
4
+ # @api public
5
+ # @author darthjee
6
+ #
7
+ # Class responsible for checking if two instances of a class are the equals
8
+ #
9
+ # @example regular usage
10
+ # class SampleModel
11
+ # def initialize(name: nil, age: nil)
12
+ # @name = name
13
+ # @age = age
14
+ # end
15
+ #
16
+ # protected
17
+ #
18
+ # attr_reader :name
19
+ #
20
+ # private
21
+ #
22
+ # attr_reader :age
23
+ # end
24
+ #
25
+ # class OtherModel < SampleModel
26
+ # end
27
+ #
28
+ # checker = Sinclair::EqualsChecker.new(:name, :age)
29
+ #
30
+ # model1 = SampleModel.new(name: 'jack', age: 21)
31
+ # model2 = SampleModel.new(name: 'rose', age: 23)
32
+ #
33
+ # checker.match?(model1, model2) # returns false
34
+ #
35
+ # @example similar models
36
+ # checker = Sinclair::EqualsChecker.new(:name, :age)
37
+ #
38
+ # model1 = SampleModel.new(name: 'jack', age: 21)
39
+ # model2 = SampleModel.new(name: 'jack', age: 21)
40
+ #
41
+ # checker.match?(model1, model2) # returns true
42
+ #
43
+ # @example different classes
44
+ # checker = Sinclair::EqualsChecker.new(:name, :age)
45
+ #
46
+ # model1 = SampleModel.new(name: 'jack', age: 21)
47
+ # model2 = OtherModel.new(name: 'jack', age: 21)
48
+ #
49
+ # checker.match?(model1, model2) # returns false
50
+ class EqualsChecker
51
+ # @param attributes [Array<Symbol,String>] list of relevant attributes
52
+ def initialize(*attributes)
53
+ @attributes = Set.new(attributes.flatten)
54
+ end
55
+
56
+ # Adds new fields to equals checker
57
+ #
58
+ # @param attributes [Array<Symbol,String>] list of relevant attributes
59
+ #
60
+ # @return [Set<Symbol,String>]
61
+ #
62
+ # @example adding fields to equal checker
63
+ # checker = Sinclair::EqualsChecker.new(:name)
64
+ #
65
+ # model1 = SampleModel.new(name: 'jack', age: 21)
66
+ # model2 = SampleModel.new(name: 'jack', age: 22)
67
+ #
68
+ # checker.match?(model1, model2) # returns true
69
+ #
70
+ # checker.add(:age)
71
+ #
72
+ # checker.match?(model1, model2) # returns false
73
+ def add(*attributes)
74
+ @attributes += Set.new(attributes.flatten)
75
+ end
76
+
77
+ # Returns if 2 objects are equals
78
+ #
79
+ # The check takes into consideration:
80
+ # - They should be instances of the same class
81
+ # - Their attributes (method calls) should return the same value,
82
+ # for the configured attributes
83
+ # - Public and private attributes are checked
84
+ #
85
+ # @example (see Sinclair::EqualsChecker)
86
+ #
87
+ # @return [TrueClass,FalseClass]
88
+ def match?(model, other)
89
+ return false unless model.class == other.class
90
+
91
+ attributes.all? do |attr|
92
+ model.send(attr) == other.send(attr)
93
+ end
94
+ end
95
+
96
+ private
97
+
98
+ attr_reader :attributes
99
+
100
+ # @private
101
+ # @api private
102
+ # @method attributes
103
+ #
104
+ # attributes relevant for checking difference
105
+ #
106
+ # @return [Set<Symbol,String>]
107
+ end
108
+ end
@@ -11,10 +11,10 @@ class Sinclair
11
11
  # This is used with a RSpec DSL method
12
12
  # change_class_method(method_name).on(class_object)
13
13
  class ChangeClassMethodOn < ChangeMethodOn
14
- # @param [Class] klass
14
+ # @param target [Class]
15
15
  # Class where the class method should be added to
16
16
  #
17
- # @param method_name [SYmbol,String] method name
17
+ # @param method_name [Symbol,String] method name
18
18
  def initialize(target, method_name)
19
19
  @klass = target
20
20
  super(method_name)
@@ -4,7 +4,7 @@ require 'set'
4
4
 
5
5
  class Sinclair
6
6
  class Options
7
- # Class Methods for {Sinclai::Options}
7
+ # Class Methods for {Sinclair::Options}
8
8
  module ClassMethods
9
9
  # @api private
10
10
  #
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Sinclair
4
- VERSION = '1.7.0'
4
+ VERSION = '1.9.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
@@ -25,7 +25,17 @@ describe Sinclair::Configurable do
25
25
  .to eq(5555)
26
26
  end
27
27
 
28
- context 'when #rest_config is called' do
28
+ it 'enables options to be returned' do
29
+ expect(MyConfigurable.as_options.host)
30
+ .to eq('interstella.art')
31
+ end
32
+
33
+ it 'enables custom options to be returned' do
34
+ expect(MyConfigurable.as_options(host: 'other').host)
35
+ .to eq('other')
36
+ end
37
+
38
+ context 'when #reset_config is called' do
29
39
  before do
30
40
  MyConfigurable.reset_config
31
41
  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
@@ -38,12 +38,12 @@ describe Sinclair::Config do
38
38
  end
39
39
 
40
40
  it 'returns options with correct values' do
41
- expect(config.options.username).to eq(:some_username)
42
- expect(config.options.password).to eq(:some_password)
41
+ expect(config.as_options.username).to eq(:some_username)
42
+ expect(config.as_options.password).to eq(:some_password)
43
43
  end
44
44
 
45
45
  context 'when merging with given attributes' do
46
- subject(:options) { config.options(password: :correct_password) }
46
+ subject(:options) { config.as_options(password: :correct_password) }
47
47
 
48
48
  it 'returns options with custom values' do
49
49
  expect(options.username).to eq(:some_username)
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Sinclair::EqualsChecker do
6
+ describe '#yard' do
7
+ describe '#match?' do
8
+ it 'regular usage' do
9
+ checker = Sinclair::EqualsChecker.new(:name, :age)
10
+
11
+ model1 = SampleModel.new(name: 'jack', age: 21)
12
+ model2 = SampleModel.new(name: 'rose', age: 23)
13
+
14
+ expect(checker.match?(model1, model2)).to be_falsey
15
+ end
16
+
17
+ it 'similar models' do
18
+ checker = Sinclair::EqualsChecker.new(:name, :age)
19
+
20
+ model1 = SampleModel.new(name: 'jack', age: 21)
21
+ model2 = SampleModel.new(name: 'jack', age: 21)
22
+
23
+ expect(checker.match?(model1, model2)).to be_truthy
24
+ end
25
+
26
+ it 'different classes' do
27
+ checker = Sinclair::EqualsChecker.new(:name, :age)
28
+
29
+ model1 = SampleModel.new(name: 'jack', age: 21)
30
+ model2 = OtherModel.new(name: 'jack', age: 21)
31
+
32
+ expect(checker.match?(model1, model2)).to be_falsey
33
+ end
34
+ end
35
+
36
+ describe '#add' do
37
+ it 'adding fields to equal checker' do
38
+ checker = Sinclair::EqualsChecker.new(:name)
39
+
40
+ model1 = SampleModel.new(name: 'jack', age: 21)
41
+ model2 = SampleModel.new(name: 'jack', age: 22)
42
+
43
+ expect(checker.match?(model1, model2)).to be_truthy
44
+
45
+ checker.add(:age)
46
+
47
+ expect(checker.match?(model1, model2)).to be_falsey
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,202 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Sinclair::Comparable do
6
+ let(:model_class) do
7
+ Class.new(SampleModel) do
8
+ include Sinclair::Comparable
9
+ end
10
+ end
11
+
12
+ let(:attributes) { %i[] }
13
+ let(:model1_class) { model_class }
14
+ let(:model2_class) { model_class }
15
+ let(:model1) { model1_class.new(**model1_attributes) }
16
+ let(:model2) { model2_class.new(**model2_attributes) }
17
+
18
+ let(:model1_attributes) { { name: name1, age: age1 } }
19
+ let(:model2_attributes) { { name: name2, age: age2 } }
20
+ let(:name1) { SecureRandom.hex(10) }
21
+ let(:name2) { SecureRandom.hex(10) }
22
+ let(:age1) { Random.rand(10..20) }
23
+ let(:age2) { Random.rand(21..50) }
24
+
25
+ describe '.comparable_by' do
26
+ let(:model2_class) { model1_class }
27
+
28
+ context 'when no field was present' do
29
+ it 'adds the field for comparison' do
30
+ expect { model1_class.comparable_by(:name) }
31
+ .to change { model1 == model2 }
32
+ .from(true).to(false)
33
+ end
34
+ end
35
+
36
+ context 'when there was a field present' do
37
+ let(:name2) { name1 }
38
+
39
+ before { model1_class.comparable_by(:name) }
40
+
41
+ it 'adds the field for comparison' do
42
+ expect { model1_class.comparable_by(:age) }
43
+ .to change { model1 == model2 }
44
+ .from(true).to(false)
45
+ end
46
+ end
47
+
48
+ context 'when there was a field present and it made a non match' do
49
+ let(:age2) { age1 }
50
+
51
+ before { model1_class.comparable_by(:name) }
52
+
53
+ it 'adds the field for comparison without forgeting the previous' do
54
+ expect { model1_class.comparable_by(:age) }
55
+ .not_to change { model1 == model2 }
56
+ .from(false)
57
+ end
58
+ end
59
+
60
+ context 'when the class parent class adds a field' do
61
+ let(:model1_class) { Class.new(model_class) }
62
+ let(:model2_class) { model1_class }
63
+
64
+ it 'takes the field into consideration' do
65
+ expect { model_class.comparable_by(:name) }
66
+ .to change { model1 == model2 }
67
+ .from(true).to(false)
68
+ end
69
+
70
+ context 'when we add a field to the class itself' do
71
+ let(:name2) { name1 }
72
+
73
+ before { model_class.comparable_by(:name) }
74
+
75
+ it 'takes all fields into consideration' do
76
+ expect { model1_class.comparable_by(:age) }
77
+ .to change { model1 == model2 }
78
+ .from(true).to(false)
79
+ end
80
+ end
81
+
82
+ context 'when we add a field to the parent class after' do
83
+ let(:name2) { name1 }
84
+
85
+ before { model1_class.comparable_by(:name) }
86
+
87
+ it 'takes all fields into consideration' do
88
+ expect { model_class.comparable_by(:age) }
89
+ .to change { model1 == model2 }
90
+ .from(true).to(false)
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ describe '#==' do
97
+ before do
98
+ model1_class.comparable_by(attributes)
99
+ model2_class.comparable_by(attributes)
100
+ end
101
+
102
+ context 'when the attributes is empty' do
103
+ context 'when they are different classes and attributes are the same' do
104
+ let(:model2_class) { Class.new(model1_class) }
105
+ let(:name2) { name1 }
106
+ let(:age2) { age1 }
107
+
108
+ it do
109
+ expect(model1).not_to eq(model2)
110
+ end
111
+ end
112
+
113
+ context 'when the models have the same attributes' do
114
+ let(:name2) { name1 }
115
+ let(:age2) { age1 }
116
+
117
+ it do
118
+ expect(model1).to eq(model2)
119
+ end
120
+ end
121
+
122
+ context 'when the models have very different attributes' do
123
+ it do
124
+ expect(model1).to eq(model2)
125
+ end
126
+ end
127
+ end
128
+
129
+ context 'when the attributes is missing just one attribute' do
130
+ let(:attributes) { %i[name] }
131
+
132
+ context 'when they are different classes and attributes are the same' do
133
+ let(:model2_class) { Class.new(model1_class) }
134
+ let(:name2) { name1 }
135
+ let(:age2) { age1 }
136
+
137
+ it do
138
+ expect(model1).not_to eq(model2)
139
+ end
140
+ end
141
+
142
+ context 'when the models have a non listed different attribute' do
143
+ let(:name2) { name1 }
144
+
145
+ it do
146
+ expect(model1).to eq(model2)
147
+ end
148
+ end
149
+
150
+ context 'when the models have a listed different attribute' do
151
+ let(:age2) { age1 }
152
+
153
+ it do
154
+ expect(model1).not_to eq(model2)
155
+ end
156
+ end
157
+
158
+ context 'when the models have very different attributes' do
159
+ it do
160
+ expect(model1).not_to eq(model2)
161
+ end
162
+ end
163
+ end
164
+
165
+ context 'when all attributes are included' do
166
+ let(:attributes) { %i[name age] }
167
+
168
+ context 'when they are different classes and attributes are the same' do
169
+ let(:model2_class) { Class.new(model1_class) }
170
+ let(:name2) { name1 }
171
+ let(:age2) { age1 }
172
+
173
+ it do
174
+ expect(model1).not_to eq(model2)
175
+ end
176
+ end
177
+
178
+ context 'when the models have the same attributes' do
179
+ let(:name2) { name1 }
180
+ let(:age2) { age1 }
181
+
182
+ it do
183
+ expect(model1).to eq(model2)
184
+ end
185
+ end
186
+
187
+ context 'when the models have a listed different attribute' do
188
+ let(:name) { name1 }
189
+
190
+ it do
191
+ expect(model1).not_to eq(model2)
192
+ end
193
+ end
194
+
195
+ context 'when the models have very different attributes' do
196
+ it do
197
+ expect(model1).not_to eq(model2)
198
+ end
199
+ end
200
+ end
201
+ end
202
+ end
@@ -109,29 +109,29 @@ describe Sinclair::Config do
109
109
  end
110
110
  end
111
111
 
112
- describe '#options' do
112
+ describe '#as_options' do
113
113
  let(:expected_options) do
114
114
  klass.options_class.new(username: :user, password: nil)
115
115
  end
116
116
 
117
+ let(:builder) do
118
+ Sinclair::ConfigBuilder.new(config, :username, :password)
119
+ end
120
+
117
121
  before do
118
122
  klass.add_configs(:password, username: :user)
119
123
  end
120
124
 
121
125
  it do
122
- expect(config.options).to be_a(Sinclair::Options)
126
+ expect(config.as_options).to be_a(Sinclair::Options)
123
127
  end
124
128
 
125
129
  it 'returns an option with default values' do
126
- expect(config.options)
130
+ expect(config.as_options)
127
131
  .to eq(expected_options)
128
132
  end
129
133
 
130
134
  context 'when config has been changed' do
131
- let(:builder) do
132
- Sinclair::ConfigBuilder.new(config, :username, :password)
133
- end
134
-
135
135
  let(:expected_options) do
136
136
  klass.options_class.new(
137
137
  username: :other_user, password: :some_password
@@ -144,7 +144,7 @@ describe Sinclair::Config do
144
144
  end
145
145
 
146
146
  it 'returns an option with values from config' do
147
- expect(config.options)
147
+ expect(config.as_options)
148
148
  .to eq(expected_options)
149
149
  end
150
150
  end
@@ -157,7 +157,7 @@ describe Sinclair::Config do
157
157
  end
158
158
 
159
159
  it 'returns merged options' do
160
- expect(config.options(password: :some_password))
160
+ expect(config.as_options(password: :some_password))
161
161
  .to eq(expected_options)
162
162
  end
163
163
  end
@@ -170,9 +170,23 @@ describe Sinclair::Config do
170
170
  end
171
171
 
172
172
  it 'returns merged options' do
173
- expect(config.options('password' => :some_password))
173
+ expect(config.as_options('password' => :some_password))
174
174
  .to eq(expected_options)
175
175
  end
176
+
177
+ context 'when the config is changed' do
178
+ let(:options) { config.as_options }
179
+
180
+ it 'changes the option returned' do
181
+ expect { builder.username :other_user }
182
+ .to change { config.as_options.username }
183
+ end
184
+
185
+ it 'changes the option previously returned' do
186
+ expect { builder.username :other_user }
187
+ .not_to change(options, :username)
188
+ end
189
+ end
176
190
  end
177
191
  end
178
192
  end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Sinclair::EqualsChecker do
6
+ subject(:checker) { described_class.new(attributes) }
7
+
8
+ let(:attributes) { %i[] }
9
+
10
+ let(:model1_class) { SampleModel }
11
+ let(:model2_class) { SampleModel }
12
+ let(:model1) { model1_class.new(**model1_attributes) }
13
+ let(:model2) { model2_class.new(**model2_attributes) }
14
+
15
+ let(:model1_attributes) { { name: name1, age: age1 } }
16
+ let(:model2_attributes) { { name: name2, age: age2 } }
17
+ let(:name1) { SecureRandom.hex(10) }
18
+ let(:name2) { SecureRandom.hex(10) }
19
+ let(:age1) { Random.rand(10..20) }
20
+ let(:age2) { Random.rand(21..50) }
21
+
22
+ describe 'match?' do
23
+ context 'when the attributes is empty' do
24
+ context 'when they are different classes and attributes are the same' do
25
+ let(:model2_class) { Class.new(SampleModel) }
26
+ let(:name2) { name1 }
27
+ let(:age2) { age1 }
28
+
29
+ it do
30
+ expect(checker).not_to be_match(model1, model2)
31
+ end
32
+ end
33
+
34
+ context 'when the models have the same attributes' do
35
+ let(:name2) { name1 }
36
+ let(:age2) { age1 }
37
+
38
+ it do
39
+ expect(checker).to be_match(model1, model2)
40
+ end
41
+ end
42
+
43
+ context 'when the models have very different attributes' do
44
+ it do
45
+ expect(checker).to be_match(model1, model2)
46
+ end
47
+ end
48
+ end
49
+
50
+ context 'when the attributes is missing just one attribute' do
51
+ let(:attributes) { %i[name] }
52
+
53
+ context 'when they are different classes and attributes are the same' do
54
+ let(:model2_class) { Class.new(SampleModel) }
55
+ let(:name2) { name1 }
56
+ let(:age2) { age1 }
57
+
58
+ it do
59
+ expect(checker).not_to be_match(model1, model2)
60
+ end
61
+ end
62
+
63
+ context 'when the models have a non listed different attribute' do
64
+ let(:name2) { name1 }
65
+
66
+ it do
67
+ expect(checker).to be_match(model1, model2)
68
+ end
69
+ end
70
+
71
+ context 'when the models have a listed different attribute' do
72
+ let(:age2) { age1 }
73
+
74
+ it do
75
+ expect(checker).not_to be_match(model1, model2)
76
+ end
77
+ end
78
+
79
+ context 'when the models have very different attributes' do
80
+ it do
81
+ expect(checker).not_to be_match(model1, model2)
82
+ end
83
+ end
84
+ end
85
+
86
+ context 'when all attributes are included' do
87
+ let(:attributes) { %i[name age] }
88
+
89
+ context 'when they are different classes and attributes are the same' do
90
+ let(:model2_class) { Class.new(SampleModel) }
91
+ let(:name2) { name1 }
92
+ let(:age2) { age1 }
93
+
94
+ it do
95
+ expect(checker).not_to be_match(model1, model2)
96
+ end
97
+ end
98
+
99
+ context 'when the models have the same attributes' do
100
+ let(:name2) { name1 }
101
+ let(:age2) { age1 }
102
+
103
+ it do
104
+ expect(checker).to be_match(model1, model2)
105
+ end
106
+ end
107
+
108
+ context 'when the models have a listed different attribute' do
109
+ let(:name) { name1 }
110
+
111
+ it do
112
+ expect(checker).not_to be_match(model1, model2)
113
+ end
114
+ end
115
+
116
+ context 'when the models have very different attributes' do
117
+ it do
118
+ expect(checker).not_to be_match(model1, model2)
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ describe '#add' do
125
+ let(:attributes) { [:name] }
126
+ let(:new_attributes) { [:age] }
127
+
128
+ context 'when the new field has different values' do
129
+ let(:name2) { name1 }
130
+
131
+ it 'uses the new field to the match' do
132
+ expect { checker.add(new_attributes) }
133
+ .to change { checker.match?(model1, model2) }
134
+ .from(true).to(false)
135
+ end
136
+ end
137
+
138
+ context 'when the old field has different values' do
139
+ let(:age2) { age1 }
140
+
141
+ it 'uses the new field to the match' do
142
+ expect { checker.add(new_attributes) }
143
+ .not_to change { checker.match?(model1, model2) }
144
+ .from(false)
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SampleModel
4
+ def initialize(name: nil, age: nil)
5
+ @name = name
6
+ @age = age
7
+ end
8
+
9
+ protected
10
+
11
+ attr_reader :name
12
+
13
+ private
14
+
15
+ attr_reader :age
16
+ end
17
+
18
+ class OtherModel < SampleModel
19
+ end
@@ -161,5 +161,60 @@ shared_examples 'a config class with .add_configs method' do
161
161
  .to(klass.options_class)
162
162
  end
163
163
  end
164
+
165
+ context 'when there is a child class' do
166
+ let(:code_block) { proc { klass.add_configs(name: 'Bob') } }
167
+
168
+ it 'adds attributes to child class' do
169
+ expect(&code_block)
170
+ .to change(child_klass, :config_attributes)
171
+ .from([]).to(%i[name])
172
+ end
173
+
174
+ it 'adds attributes to child options class' do
175
+ expect(&code_block)
176
+ .to add_method(:name).to(child_klass)
177
+ end
178
+
179
+ context 'when child class already has attributes' do
180
+ before do
181
+ child_klass.add_configs('email')
182
+ end
183
+
184
+ it 'adds new attributes to child class' do
185
+ expect(&code_block)
186
+ .to change(child_klass, :config_attributes)
187
+ .from([:email]).to(%i[name email])
188
+ end
189
+ end
190
+ end
191
+
192
+ context 'when there is a parent class' do
193
+ let(:code_block) do
194
+ proc { child_klass.add_configs(name: 'Bob') }
195
+ end
196
+
197
+ it 'does not add attributes to parent class' do
198
+ expect(&code_block)
199
+ .not_to change(klass, :config_attributes)
200
+ end
201
+
202
+ it 'does not add attributes to child options class' do
203
+ expect(&code_block)
204
+ .not_to add_method(:name).to(klass)
205
+ end
206
+
207
+ context 'when parent already has attributes' do
208
+ before do
209
+ klass.config_attributes(:email, 'username')
210
+ end
211
+
212
+ it 'adds only attributes that had not been defined before' do
213
+ expect(&code_block)
214
+ .to change(child_klass, :config_attributes)
215
+ .from(%i[email username]).to(%i[email username name])
216
+ end
217
+ end
218
+ end
164
219
  end
165
220
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sinclair
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.0
4
+ version: 1.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - DarthJee
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-08-31 00:00:00.000000000 Z
11
+ date: 2023-01-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - '='
32
32
  - !ruby/object:Gem::Version
33
- version: 2.3.20
33
+ version: 2.3.25
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - '='
39
39
  - !ruby/object:Gem::Version
40
- version: 2.3.20
40
+ version: 2.3.25
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: pry
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -272,6 +272,8 @@ files:
272
272
  - config/yardstick.yml
273
273
  - docker-compose.yml
274
274
  - lib/sinclair.rb
275
+ - lib/sinclair/comparable.rb
276
+ - lib/sinclair/comparable/class_methods.rb
275
277
  - lib/sinclair/config.rb
276
278
  - lib/sinclair/config/methods_builder.rb
277
279
  - lib/sinclair/config_builder.rb
@@ -280,6 +282,7 @@ files:
280
282
  - lib/sinclair/configurable.rb
281
283
  - lib/sinclair/env_settable.rb
282
284
  - lib/sinclair/env_settable/builder.rb
285
+ - lib/sinclair/equals_checker.rb
283
286
  - lib/sinclair/exception.rb
284
287
  - lib/sinclair/input_hash.rb
285
288
  - lib/sinclair/matchers.rb
@@ -314,18 +317,21 @@ files:
314
317
  - sinclair.jpg
315
318
  - spec/integration/readme/my_class_spec.rb
316
319
  - spec/integration/readme/my_model_spec.rb
320
+ - spec/integration/readme/sinclair/comparable_spec.rb
317
321
  - spec/integration/readme/sinclair/configurable_spec.rb
318
322
  - spec/integration/readme/sinclair/env_settable_spec.rb
319
323
  - spec/integration/readme/sinclair/matchers_spec.rb
320
324
  - spec/integration/readme/sinclair/options_spec.rb
321
325
  - spec/integration/readme/sinclair_spec.rb
322
326
  - spec/integration/yard/my_builder_spec.rb
327
+ - spec/integration/yard/sinclair/comparable_spec.rb
323
328
  - spec/integration/yard/sinclair/config_builder_spec.rb
324
329
  - spec/integration/yard/sinclair/config_class_spec.rb
325
330
  - spec/integration/yard/sinclair/config_factory_spec.rb
326
331
  - spec/integration/yard/sinclair/config_spec.rb
327
332
  - spec/integration/yard/sinclair/configurable_spec.rb
328
333
  - spec/integration/yard/sinclair/env_settable_spec.rb
334
+ - spec/integration/yard/sinclair/equals_checker_spec.rb
329
335
  - spec/integration/yard/sinclair/input_hash_spec.rb
330
336
  - spec/integration/yard/sinclair/matchers/add_class_method_spec.rb
331
337
  - spec/integration/yard/sinclair/matchers/add_class_method_to_spec.rb
@@ -334,6 +340,7 @@ files:
334
340
  - spec/integration/yard/sinclair/options_parser_spec.rb
335
341
  - spec/integration/yard/sinclair/options_spec.rb
336
342
  - spec/integration/yard/sinclair_spec.rb
343
+ - spec/lib/sinclair/comparable_spec.rb
337
344
  - spec/lib/sinclair/config/methods_builder_spec.rb
338
345
  - spec/lib/sinclair/config_builder_spec.rb
339
346
  - spec/lib/sinclair/config_class_spec.rb
@@ -342,6 +349,7 @@ files:
342
349
  - spec/lib/sinclair/configurable_spec.rb
343
350
  - spec/lib/sinclair/env_settable/builder_spec.rb
344
351
  - spec/lib/sinclair/env_settable_spec.rb
352
+ - spec/lib/sinclair/equals_checker_spec.rb
345
353
  - spec/lib/sinclair/exception/invalid_options_spec.rb
346
354
  - spec/lib/sinclair/input_hash_spec.rb
347
355
  - spec/lib/sinclair/matchers/add_class_method_spec.rb
@@ -404,6 +412,7 @@ files:
404
412
  - spec/support/models/server_config.rb
405
413
  - spec/support/models/service_client.rb
406
414
  - spec/support/models/validator_builder.rb
415
+ - spec/support/sample_model.rb
407
416
  - spec/support/shared_examples/class_method_definition.rb
408
417
  - spec/support/shared_examples/config.rb
409
418
  - spec/support/shared_examples/config_factory.rb
@@ -427,7 +436,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
427
436
  - !ruby/object:Gem::Version
428
437
  version: '0'
429
438
  requirements: []
430
- rubygems_version: 3.3.20
439
+ rubygems_version: 3.3.25
431
440
  signing_key:
432
441
  specification_version: 4
433
442
  summary: Gem for easy concern creation