u-attributes 2.4.0 → 2.8.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: 62c6ad9683f0824be6a32648028f25a97ba88a10c7fbdd0b9a2ccfbfe275961b
4
- data.tar.gz: d110a1b4220af76a7f14753cb1ca5067428f06db026466e095d54a22ee46f86f
3
+ metadata.gz: 75a44be0f1b71d41abdfd3e9c81f67527760a4a8f0202818fd7f04782680922b
4
+ data.tar.gz: bbb26faf04daffd2e92b18eb4e363d85c7677ed5ed3fb09bda485d8b34c8296d
5
5
  SHA512:
6
- metadata.gz: 24703dcd81aa785fe00b77a741434ad84600243f4bb132d2bbbce2b5a310d4f2b931c2b6ea41e311a2bcf07876296bd03d8e6c39a92905bc9a4f745fdb30a094
7
- data.tar.gz: 8172811ddaf44a3099e66f63cea5aed99d5c1a63a77944dc1e98c16fa0378603498fbdfe0c77f7220244c8d7b977801a75bd01facc875fb05f0bd1aed0171cc5
6
+ metadata.gz: 9e7da00d4786086edc9607533ef9c9632b10dd2200ddee29a46551d311c8e5ee8c5549d161f8ea6881c95c529abbdd4a2c48fd28635464606a7877b0d94a6488
7
+ data.tar.gz: aa42ef9c78b7f60514ef062b78d1b6f4eb3589ce1d39ab13d6459d2a164e3dcfa8b4f54660598df76afe4b42b8087cf9060f57398609f59508952bfd49c0cda1
@@ -0,0 +1,27 @@
1
+
2
+ name: build
3
+ on: [push]
4
+ jobs:
5
+ test:
6
+ runs-on: ubuntu-latest
7
+ strategy:
8
+ matrix:
9
+ ruby: [2.2, 2.3, 2.4, 2.5, 2.6, 3.0]
10
+ steps:
11
+ - uses: actions/checkout@v2
12
+ - uses: ruby/setup-ruby@v1
13
+ with:
14
+ ruby-version: ${{ matrix.ruby }}
15
+ bundler-cache: true
16
+ - name: Test and generate coverage
17
+ run: bin/test
18
+ - name: Format coverage
19
+ if: ${{ matrix.ruby >= 3 }}
20
+ run: bin/prepare_coverage
21
+ - uses: paambaati/codeclimate-action@v2.7.5
22
+ if: ${{ matrix.ruby >= 3 }}
23
+ env:
24
+ CC_TEST_REPORTER_ID: 9015766fe58e0abd5ab58050ddfc67708e0b11430b3d3999ac17139ffcb4d3e7
25
+ with:
26
+ debug: true
27
+ coverageLocations: coverage/.resultset.json:simplecov
@@ -0,0 +1,8 @@
1
+ {
2
+ "cSpell.enabled": true,
3
+ "cSpell.ignoreWords": [
4
+ "paambaati",
5
+ "resultset",
6
+ "simplecov"
7
+ ]
8
+ }
data/Gemfile CHANGED
@@ -1,8 +1,8 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'u-case', '~> 4.0'
3
+ gem 'u-case', '~> 4.5', '>= 4.5.1'
4
4
 
5
- activemodel_version = ENV.fetch('ACTIVEMODEL_VERSION', '6.1')
5
+ activemodel_version = ENV['ACTIVEMODEL_VERSION']
6
6
 
7
7
  activemodel = case activemodel_version
8
8
  when '3.2' then '3.2.22'
@@ -11,26 +11,31 @@ activemodel = case activemodel_version
11
11
  when '4.2' then '4.2.11'
12
12
  when '5.0' then '5.0.7'
13
13
  when '5.1' then '5.1.7'
14
- when '5.2' then '5.2.3'
15
- when '6.0' then '6.0.0'
14
+ when '5.2' then '5.2.4'
15
+ when '6.0' then '6.0.3.4'
16
+ when '6.1' then '6.1.2'
16
17
  end
17
18
 
18
- if activemodel_version < '6.1'
19
- gem 'activemodel', activemodel, require: false
20
- gem 'activesupport', activemodel, require: false
21
- end
22
-
23
19
  simplecov_version =
24
20
  case RUBY_VERSION
25
- when /\A2.[23]/ then '~> 0.17.1'
21
+ when /\A2.[23]/ then '0.17.1'
26
22
  when /\A2.4/ then '~> 0.18.5'
27
- else '~> 0.19'
23
+ else '~> 0.21.2'
28
24
  end
29
25
 
30
26
  group :test do
31
- gem 'minitest', activemodel_version < '4.1' ? '~> 4.2' : '~> 5.0'
27
+ if activemodel_version
28
+ gem 'activesupport', activemodel, require: false
29
+ gem 'activemodel', activemodel, require: false
30
+ gem 'minitest', activemodel_version < '4.1' ? '~> 4.2' : '~> 5.0'
31
+ else
32
+ gem 'minitest', '~> 5.0'
33
+ end
34
+
32
35
  gem 'simplecov', simplecov_version, require: false
33
36
  end
34
37
 
38
+ gem 'rake', '~> 13.0'
39
+
35
40
  # Specify your gem's dependencies in u-attributes.gemspec
36
41
  gemspec
data/README.md CHANGED
@@ -1,19 +1,19 @@
1
1
  <p align="center">
2
2
  <img src="./assets/u-attributes_logo_v1.png" alt='Create "immutable" objects. No setters, just getters!'>
3
3
 
4
- <p align="center"><i>Create "immutable" objects. No setters, just getters!</i></p>
4
+ <p align="center"><i>Create "immutable" objects with no setters, just getters.</i></p>
5
5
  <br>
6
6
  </p>
7
7
 
8
8
  <p align="center">
9
- <img src="https://img.shields.io/badge/ruby-2.2+-ruby.svg?colorA=99004d&colorB=cc0066" alt="Ruby">
9
+ <img src="https://img.shields.io/badge/ruby->%3D%202.2.0-ruby.svg?colorA=99004d&colorB=cc0066" alt="Ruby">
10
10
 
11
11
  <a href="https://rubygems.org/gems/u-attributes">
12
12
  <img alt="Gem" src="https://img.shields.io/gem/v/u-attributes.svg?style=flat-square">
13
13
  </a>
14
14
 
15
- <a href="https://travis-ci.com/serradura/u-attributes">
16
- <img alt="Build Status" src="https://travis-ci.com/serradura/u-attributes.svg?branch=main">
15
+ <a href="https://github.com/serradura/u-attributes/actions/workflows/ci.yml">
16
+ <img alt="Build Status" src="https://github.com/serradura/u-attributes/actions/workflows/ci.yml/badge.svg">
17
17
  </a>
18
18
 
19
19
  <a href="https://codeclimate.com/github/serradura/u-attributes/maintainability">
@@ -25,8 +25,16 @@
25
25
  </a>
26
26
  </p>
27
27
 
28
- This gem allows you to define "immutable" objects, and your objects will have only getters and no setters.
29
- So, if you change [[1](#with_attribute)] [[2](#with_attributes)] some object attribute, you will have a new object instance. That is, you transform the object instead of modifying it.
28
+ This gem allows you to define "immutable" objects, when using it your objects will only have getters and no setters.
29
+ So, if you change [[1](#with_attribute)] [[2](#with_attributes)] an attribute of the object, you’ll have a new object instance. That is, you transform the object instead of modifying it.
30
+
31
+ ## Documentation <!-- omit in toc -->
32
+
33
+ Version | Documentation
34
+ ---------- | -------------
35
+ unreleased | https://github.com/serradura/u-case/blob/main/README.md
36
+ 2.8.0 | https://github.com/serradura/u-case/blob/v2.x/README.md
37
+ 1.2.0 | https://github.com/serradura/u-case/blob/v1.x/README.md
30
38
 
31
39
  # Table of contents <!-- omit in toc -->
32
40
  - [Installation](#installation)
@@ -85,7 +93,8 @@ gem 'u-attributes'
85
93
 
86
94
  | u-attributes | branch | ruby | activemodel |
87
95
  | -------------- | ------- | -------- | ------------- |
88
- | 2.4.0 | main | >= 2.2.0 | >= 3.2, < 6.1 |
96
+ | unreleased | main | >= 2.2.0 | >= 3.2, < 7 |
97
+ | 2.8.0 | v2.x | >= 2.2.0 | >= 3.2, < 7 |
89
98
  | 1.2.0 | v1.x | >= 2.2.0 | >= 3.2, < 6.1 |
90
99
 
91
100
  > **Note**: The activemodel is an optional dependency, this module [can be enabled](#activemodelvalidation-extension) to validate the attributes.
@@ -618,7 +627,7 @@ The method `Micro::Attributes.with()` will raise an exception if no arguments/fe
618
627
 
619
628
  ```ruby
620
629
  class Job
621
- include Micro::Attributes.with() # ArgumentError (Invalid feature name! Available options: :activemodel_validations, :diff, :initialize, :keys_as_symbol)
630
+ include Micro::Attributes.with() # ArgumentError (Invalid feature name! Available options: :accept, :activemodel_validations, :diff, :initialize, :keys_as_symbol)
622
631
  end
623
632
  ```
624
633
 
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ # Borrowed from https://gist.github.com/qortex/7e7c49f3731391a91ee898336183acef
4
+
5
+ # Temporary hack to get CodeClimate to work with SimpleCov 0.18 JSON format until issue is fixed
6
+ # upstream: https://github.com/codeclimate/test-reporter/issues/413
7
+
8
+ require "json"
9
+
10
+ filename = "coverage/.resultset.json"
11
+ contents = JSON.parse(File.read(filename))
12
+
13
+ def remove_lines_key(obj)
14
+ case obj
15
+ when Hash
16
+ obj.transform_values do |val|
17
+ val.is_a?(Hash) && val.key?("lines") ? val["lines"] : remove_lines_key(val)
18
+ end
19
+ else
20
+ obj
21
+ end
22
+ end
23
+
24
+ # overwrite
25
+ File.write(filename, JSON.generate(remove_lines_key(contents)))
26
+
27
+ puts Dir['coverage/.*.json']
data/bin/test ADDED
@@ -0,0 +1,48 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ RUBY_V=$(ruby -v)
6
+
7
+ function reset_gemfile_and_test {
8
+ rm Gemfile.lock
9
+
10
+ eval "$1 bundle update"
11
+ eval "$1 bundle exec rake test"
12
+ }
13
+
14
+ function test_with_activemodel {
15
+ reset_gemfile_and_test "ACTIVEMODEL_VERSION=$1"
16
+ }
17
+
18
+ RUBY_2_2345="ruby 2.[2345]."
19
+ RUBY_2_234="ruby 2.[234]."
20
+ RUBY_2_567="ruby 2.[567]."
21
+ RUBY_2_2="ruby 2.2."
22
+ RUBY_3_X="ruby 3.0."
23
+
24
+ if [[ $RUBY_V =~ $RUBY_2_2345 ]]; then
25
+ if [[ $RUBY_V =~ $RUBY_2_234 ]]; then
26
+ reset_gemfile_and_test
27
+ fi
28
+
29
+ if [[ $RUBY_V =~ $RUBY_2_2 ]]; then
30
+ test_with_activemodel "3.2"
31
+ fi
32
+
33
+ test_with_activemodel "4.0"
34
+ test_with_activemodel "4.1"
35
+ test_with_activemodel "4.2"
36
+ test_with_activemodel "5.0"
37
+ test_with_activemodel "5.1"
38
+ test_with_activemodel "5.2"
39
+ fi
40
+
41
+ if [[ $RUBY_V =~ $RUBY_2_567 ]] || [[ $RUBY_V =~ $RUBY_3_X ]]; then
42
+ gem install bundler -v ">= 2" --no-doc
43
+
44
+ reset_gemfile_and_test
45
+
46
+ test_with_activemodel "6.0"
47
+ test_with_activemodel "6.1"
48
+ fi
@@ -12,7 +12,7 @@ module Micro::Attributes
12
12
  def initialize(from:, to:)
13
13
  @from_class = from.class
14
14
 
15
- @from, @to = from, Kind::Of.(@from_class, to)
15
+ @from, @to = from, Kind.of(@from_class, to)
16
16
 
17
17
  @from_key, @to_key =
18
18
  @from_class.attributes_access == :symbol ? FROM_TO_SYM : FROM_TO_STR
@@ -35,17 +35,17 @@ module Micro::Attributes
35
35
 
36
36
  raise ArgumentError, FROM_TO_ERROR
37
37
  elsif from.nil? && to.nil?
38
- differences.has_key?(key_access(name))
38
+ differences.has_key?(key_transform(name))
39
39
  else
40
- result = @differences[key_access(name)]
40
+ result = @differences[key_transform(name)]
41
41
  result ? result[@from_key] == from && result[@to_key] == to : false
42
42
  end
43
43
  end
44
44
 
45
45
  private
46
46
 
47
- def key_access(key)
48
- @from_class.__attribute_key__(key)
47
+ def key_transform(key)
48
+ @from_class.__attribute_key_transform__(key)
49
49
  end
50
50
 
51
51
  def diff(from_attributes, to_attributes)
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro::Attributes
4
+ module Features
5
+ module Accept
6
+
7
+ module Strict
8
+ ATTRIBUTES_REJECTED = "One or more attributes were rejected. Errors:\n".freeze
9
+
10
+ def __call_after_attributes_assign
11
+ return unless attributes_errors?
12
+
13
+ __raise_error_if_found_attributes_errors
14
+ end
15
+
16
+ def __raise_error_if_found_attributes_errors
17
+ raise ArgumentError, [
18
+ ATTRIBUTES_REJECTED,
19
+ attributes_errors.map { |key, msg| "* #{key.inspect} #{msg}" }.join("\n")
20
+ ].join
21
+ end
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro::Attributes
4
+ module Features
5
+ module Accept
6
+ def attributes_errors
7
+ @__attributes_errors
8
+ end
9
+
10
+ def attributes_errors?
11
+ !@__attributes_errors.empty?
12
+ end
13
+
14
+ def rejected_attributes
15
+ @__rejected_attributes ||= attributes_errors.keys
16
+ end
17
+
18
+ def accepted_attributes
19
+ @__accepted_attributes ||= defined_attributes - rejected_attributes
20
+ end
21
+
22
+ def rejected_attributes?
23
+ attributes_errors?
24
+ end
25
+
26
+ def accepted_attributes?
27
+ !rejected_attributes?
28
+ end
29
+
30
+ private
31
+
32
+ def __call_before_attributes_assign
33
+ @__attributes_errors = {}
34
+ end
35
+
36
+ KeepProc = -> validation_data { validation_data[0] == :accept && validation_data[1] == Proc }
37
+
38
+ def __attribute_assign(key, init_hash, attribute_data)
39
+ validation_data = attribute_data[1]
40
+
41
+ value_to_assign = FetchValueToAssign.(init_hash, init_hash[key], attribute_data, KeepProc.(validation_data))
42
+
43
+ value = __attributes[key] = instance_variable_set("@#{key}", value_to_assign)
44
+
45
+ __attribute_accept_or_reject(key, value, validation_data) if !validation_data.empty?
46
+ end
47
+
48
+ def __attribute_accept_or_reject(key, value, validation_data)
49
+ context = Context.with(key, value, validation_data)
50
+
51
+ error_msg = context.rejection_message(Validate.call(context))
52
+
53
+ @__attributes_errors[key] = error_msg if error_msg
54
+ end
55
+
56
+ Context = Struct.new(:key, :value, :validation, :expected, :allow_nil, :rejection) do
57
+ def self.with(key, value, data)
58
+ new(key, value, data[0], data[1], data[2], data[3])
59
+ end
60
+
61
+ def allow_nil?
62
+ allow_nil && value.nil?
63
+ end
64
+
65
+ def accept?
66
+ validation == :accept
67
+ end
68
+
69
+ def rejection_message(default_msg)
70
+ return unless default_msg
71
+
72
+ return default_msg unless rejection || expected.respond_to?(:rejection_message)
73
+
74
+ rejection_msg = rejection || expected.rejection_message
75
+
76
+ return rejection_msg unless rejection_msg.is_a?(Proc)
77
+
78
+ rejection_msg.arity == 0 ? rejection_msg.call : rejection_msg.call(key)
79
+ end
80
+ end
81
+
82
+ module Validate
83
+ module Callable
84
+ MESSAGE = 'is invalid'.freeze
85
+
86
+ def self.call?(exp); exp.respond_to?(:call); end
87
+ def self.call(exp, val); exp.call(val); end
88
+ def self.accept_failed(_exp); MESSAGE; end
89
+ def self.reject_failed(_exp); MESSAGE; end
90
+ end
91
+
92
+ module KindOf
93
+ def self.call?(exp); exp.is_a?(Class) || exp.is_a?(Module); end
94
+ def self.call(exp, val); val.kind_of?(exp); end
95
+ def self.accept_failed(exp); "expected to be a kind of #{exp}"; end
96
+ def self.reject_failed(exp); "expected to not be a kind of #{exp}"; end
97
+ end
98
+
99
+ module Predicate
100
+ QUESTION_MARK = '?'.freeze
101
+
102
+ def self.call?(exp); exp.is_a?(Symbol) && exp.to_s.end_with?(QUESTION_MARK); end
103
+ def self.call(exp, val); val.public_send(exp); end
104
+ def self.accept_failed(exp); "expected to be #{exp}"; end
105
+ def self.reject_failed(exp); "expected to not be #{exp}"; end
106
+ end
107
+
108
+ def self.with(expected)
109
+ return Callable if Callable.call?(expected)
110
+ return KindOf if KindOf.call?(expected)
111
+ return Predicate if Predicate.call?(expected)
112
+ end
113
+
114
+ def self.call(context)
115
+ return if context.allow_nil?
116
+
117
+ validate = self.with(expected = context.expected)
118
+
119
+ return unless validate
120
+
121
+ truthy = validate.call(expected, context.value)
122
+
123
+ return truthy ? nil : validate.accept_failed(expected) if context.accept?
124
+
125
+ validate.reject_failed(expected) if truthy
126
+ end
127
+ end
128
+
129
+ private_constant :KeepProc, :Context, :Validate
130
+ end
131
+ end
132
+ end
@@ -3,13 +3,45 @@
3
3
  module Micro::Attributes
4
4
  module Features
5
5
  module ActiveModelValidations
6
- def self.included(base)
7
- begin
8
- require 'active_model'
6
+ module Standard
7
+ private def __call_after_attributes_assign
8
+ run_validations!
9
+ end
10
+ end
9
11
 
10
- base.send(:include, ::ActiveModel::Validations)
11
- base.extend(ClassMethods)
12
- rescue LoadError
12
+ module CheckActivemodelValidationErrors
13
+ private def __check_activemodel_validation_errors
14
+ return if errors.blank?
15
+
16
+ errors_hash = errors.to_hash
17
+
18
+ defined_attributes.each do |key|
19
+ value = Utils::Hashes.assoc(errors_hash, key)
20
+
21
+ @__attributes_errors[key] = value.join(', ') if value.present?
22
+ end
23
+ end
24
+ end
25
+
26
+ module WithAccept
27
+ include CheckActivemodelValidationErrors
28
+
29
+ private def __call_after_attributes_assign
30
+ run_validations! unless attributes_errors?
31
+
32
+ __check_activemodel_validation_errors
33
+ end
34
+ end
35
+
36
+ module WithAcceptStrict
37
+ include CheckActivemodelValidationErrors
38
+
39
+ private def __call_after_attributes_assign
40
+ __raise_error_if_found_attributes_errors if attributes_errors?
41
+
42
+ run_validations!
43
+
44
+ __check_activemodel_validation_errors
13
45
  end
14
46
  end
15
47
 
@@ -22,11 +54,23 @@ module Micro::Attributes
22
54
  end
23
55
  end
24
56
 
25
- private
57
+ def self.included(base)
58
+ begin
59
+ require 'active_model'
60
+
61
+ base.send(:include, ::ActiveModel::Validations)
62
+ base.extend(ClassMethods)
26
63
 
27
- def __call_after_micro_attribute
28
- run_validations! if respond_to?(:run_validations!, true)
64
+ case
65
+ when base <= Features::Accept::Strict then base.send(:include, WithAcceptStrict)
66
+ when base <= Features::Accept then base.send(:include, WithAccept)
67
+ else base.send(:include, Standard)
68
+ end
69
+ rescue LoadError
29
70
  end
71
+ end
72
+
73
+ private_constant :Standard, :CheckActivemodelValidationErrors, :WithAccept, :WithAcceptStrict
30
74
  end
31
75
  end
32
76
  end