u-attributes 2.2.0 → 2.7.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 +4 -4
- data/.travis.sh +33 -19
- data/.travis.yml +12 -7
- data/Gemfile +15 -10
- data/README.md +192 -43
- data/lib/micro/attributes.rb +70 -20
- data/lib/micro/attributes/diff.rb +26 -14
- data/lib/micro/attributes/features.rb +128 -75
- data/lib/micro/attributes/features/accept.rb +132 -0
- data/lib/micro/attributes/features/accept/strict.rb +26 -0
- data/lib/micro/attributes/features/activemodel_validations.rb +53 -9
- data/lib/micro/attributes/features/initialize.rb +3 -8
- data/lib/micro/attributes/features/keys_as_symbol.rb +31 -0
- data/lib/micro/attributes/macros.rb +146 -27
- data/lib/micro/attributes/utils.rb +42 -22
- data/lib/micro/attributes/version.rb +1 -1
- data/test.sh +7 -3
- data/u-attributes.gemspec +5 -6
- metadata +14 -14
- data/assets/u-attributes_logo_v1.png +0 -0
- data/lib/micro/attributes/with.rb +0 -100
data/lib/micro/attributes.rb
CHANGED
@@ -15,7 +15,8 @@ module Micro
|
|
15
15
|
|
16
16
|
base.class_eval do
|
17
17
|
private_class_method :__attributes, :__attribute_reader
|
18
|
-
private_class_method :__attribute_assign, :
|
18
|
+
private_class_method :__attribute_assign, :__attributes_groups
|
19
|
+
private_class_method :__attributes_required_add, :__attributes_data_to_assign
|
19
20
|
end
|
20
21
|
|
21
22
|
def base.inherited(subclass)
|
@@ -37,8 +38,8 @@ module Micro
|
|
37
38
|
Features.all
|
38
39
|
end
|
39
40
|
|
40
|
-
def attribute?(name)
|
41
|
-
self.class.attribute?(name)
|
41
|
+
def attribute?(name, include_all = false)
|
42
|
+
self.class.attribute?(name, include_all)
|
42
43
|
end
|
43
44
|
|
44
45
|
def attribute(name)
|
@@ -52,59 +53,108 @@ module Micro
|
|
52
53
|
def attribute!(name, &block)
|
53
54
|
attribute(name) { |name| return block ? block[name] : name }
|
54
55
|
|
55
|
-
raise NameError,
|
56
|
+
raise NameError, __attribute_access_error_message(name)
|
57
|
+
end
|
58
|
+
|
59
|
+
def defined_attributes(option = nil)
|
60
|
+
return self.class.attributes_by_visibility if option == :by_visibility
|
61
|
+
|
62
|
+
@defined_attributes ||= self.class.attributes
|
56
63
|
end
|
57
64
|
|
58
65
|
def attributes(*names)
|
59
66
|
return __attributes if names.empty?
|
60
67
|
|
61
|
-
names.
|
62
|
-
|
68
|
+
options = names.last.is_a?(Hash) ? names.pop : Kind::Empty::HASH
|
69
|
+
|
70
|
+
names.flatten!
|
71
|
+
|
72
|
+
without_option = Array(options.fetch(:without, Kind::Empty::ARRAY))
|
73
|
+
|
74
|
+
keys = names.empty? ? defined_attributes - without_option.map { |value| __attribute_key(value) } : names - without_option
|
75
|
+
|
76
|
+
data = keys.each_with_object({}) { |key, memo| memo[key] = attribute(key) if attribute?(key) }
|
77
|
+
|
78
|
+
with_option = Array(options.fetch(:with, Kind::Empty::ARRAY))
|
79
|
+
|
80
|
+
unless with_option.empty?
|
81
|
+
extra = with_option.each_with_object({}) { |key, memo| memo[__attribute_key(key)] = public_send(key) }
|
82
|
+
|
83
|
+
data.merge!(extra)
|
63
84
|
end
|
64
|
-
end
|
65
85
|
|
66
|
-
|
67
|
-
@defined_attributes ||= self.class.attributes
|
86
|
+
Utils::Hashes.keys_as(options[:keys_as], data)
|
68
87
|
end
|
69
88
|
|
70
89
|
protected
|
71
90
|
|
72
91
|
def attributes=(arg)
|
73
|
-
hash =
|
92
|
+
hash = self.class.__attributes_keys_transform__(arg)
|
74
93
|
|
75
94
|
__attributes_missing!(hash)
|
76
95
|
|
96
|
+
__call_before_attributes_assign
|
77
97
|
__attributes_assign(hash)
|
98
|
+
__call_after_attributes_assign
|
99
|
+
|
100
|
+
__attributes
|
78
101
|
end
|
79
102
|
|
80
103
|
private
|
81
104
|
|
105
|
+
def __call_before_attributes_assign; end
|
106
|
+
def __call_after_attributes_assign; end
|
107
|
+
|
82
108
|
def extract_attributes_from(other)
|
83
109
|
Utils::ExtractAttribute.from(other, keys: defined_attributes)
|
84
110
|
end
|
85
111
|
|
112
|
+
def __attribute_access_error_message(name)
|
113
|
+
return "tried to access a private attribute `#{name}" if attribute?(name, true)
|
114
|
+
|
115
|
+
"undefined attribute `#{name}"
|
116
|
+
end
|
117
|
+
|
118
|
+
def __attribute_key(value)
|
119
|
+
self.class.__attribute_key_transform__(value)
|
120
|
+
end
|
121
|
+
|
86
122
|
def __attributes
|
87
123
|
@__attributes ||= {}
|
88
124
|
end
|
89
125
|
|
90
|
-
FetchValueToAssign = -> (value,
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
126
|
+
FetchValueToAssign = -> (value, attribute_data, keep_proc = false) do
|
127
|
+
default = attribute_data[0]
|
128
|
+
|
129
|
+
value_to_assign =
|
130
|
+
if default.is_a?(Proc) && !keep_proc
|
131
|
+
default.arity > 0 ? default.call(value) : default.call
|
132
|
+
else
|
133
|
+
value.nil? ? default : value
|
134
|
+
end
|
135
|
+
|
136
|
+
return value_to_assign unless to_freeze = attribute_data[2]
|
137
|
+
return value_to_assign.freeze if to_freeze == true
|
138
|
+
return value_to_assign.dup.freeze if to_freeze == :after_dup
|
139
|
+
return value_to_assign.clone.freeze if to_freeze == :after_clone
|
140
|
+
|
141
|
+
raise NotImplementedError
|
96
142
|
end
|
97
143
|
|
98
144
|
def __attributes_assign(hash)
|
99
|
-
self.class.__attributes_data__.each do |name,
|
100
|
-
__attribute_assign(name,
|
145
|
+
self.class.__attributes_data__.each do |name, attribute_data|
|
146
|
+
__attribute_assign(name, hash[name], attribute_data) if attribute?(name, true)
|
101
147
|
end
|
102
148
|
|
103
149
|
__attributes.freeze
|
104
150
|
end
|
105
151
|
|
106
|
-
def __attribute_assign(name,
|
107
|
-
|
152
|
+
def __attribute_assign(name, initialize_value, attribute_data)
|
153
|
+
value_to_assign = FetchValueToAssign.(initialize_value, attribute_data)
|
154
|
+
|
155
|
+
ivar_value = instance_variable_set("@#{name}", value_to_assign)
|
156
|
+
|
157
|
+
__attributes[name] = ivar_value if attribute_data[3] == :public
|
108
158
|
end
|
109
159
|
|
110
160
|
MISSING_KEYWORD = 'missing keyword'.freeze
|
@@ -3,15 +3,20 @@
|
|
3
3
|
module Micro::Attributes
|
4
4
|
module Diff
|
5
5
|
class Changes
|
6
|
-
|
7
|
-
|
6
|
+
FROM_TO_SYM = [:from, :to].freeze
|
7
|
+
FROM_TO_STR = ['from'.freeze, 'to'.freeze].freeze
|
8
8
|
FROM_TO_ERROR = 'pass the attribute name with the :from and :to values'.freeze
|
9
9
|
|
10
10
|
attr_reader :from, :to, :differences
|
11
11
|
|
12
12
|
def initialize(from:, to:)
|
13
|
-
|
14
|
-
|
13
|
+
@from_class = from.class
|
14
|
+
|
15
|
+
@from, @to = from, Kind.of(@from_class, to)
|
16
|
+
|
17
|
+
@from_key, @to_key =
|
18
|
+
@from_class.attributes_access == :symbol ? FROM_TO_SYM : FROM_TO_STR
|
19
|
+
|
15
20
|
@differences = diff(from.attributes, to.attributes).freeze
|
16
21
|
end
|
17
22
|
|
@@ -27,26 +32,33 @@ module Micro::Attributes
|
|
27
32
|
def changed?(name = nil, from: nil, to: nil)
|
28
33
|
if name.nil?
|
29
34
|
return present? if from.nil? && to.nil?
|
35
|
+
|
30
36
|
raise ArgumentError, FROM_TO_ERROR
|
31
37
|
elsif from.nil? && to.nil?
|
32
|
-
differences.has_key?(name
|
38
|
+
differences.has_key?(key_transform(name))
|
33
39
|
else
|
34
|
-
result = @differences[name
|
35
|
-
result ? result[
|
40
|
+
result = @differences[key_transform(name)]
|
41
|
+
result ? result[@from_key] == from && result[@to_key] == to : false
|
36
42
|
end
|
37
43
|
end
|
38
44
|
|
39
45
|
private
|
40
46
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
47
|
+
def key_transform(key)
|
48
|
+
@from_class.__attribute_key_transform__(key)
|
49
|
+
end
|
50
|
+
|
51
|
+
def diff(from_attributes, to_attributes)
|
52
|
+
@from_attributes, @to_attributes = from_attributes, to_attributes
|
53
|
+
|
54
|
+
@from_attributes.each_with_object({}) do |(from_key, from_val), acc|
|
55
|
+
to_value = @to_attributes[from_key]
|
56
|
+
|
57
|
+
acc[from_key] = {@from_key => from_val, @to_key => to_value}.freeze if from_val != to_value
|
58
|
+
end
|
46
59
|
end
|
47
|
-
end
|
48
60
|
|
49
|
-
private_constant :
|
61
|
+
private_constant :FROM_TO_SYM, :FROM_TO_STR, :FROM_TO_ERROR
|
50
62
|
end
|
51
63
|
end
|
52
64
|
end
|
@@ -1,111 +1,164 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'micro/attributes/with'
|
4
|
-
|
5
3
|
module Micro
|
6
4
|
module Attributes
|
5
|
+
module With
|
6
|
+
end
|
7
|
+
|
7
8
|
module Features
|
8
|
-
|
9
|
+
require 'micro/attributes/features/diff'
|
10
|
+
require 'micro/attributes/features/accept'
|
11
|
+
require 'micro/attributes/features/accept/strict'
|
12
|
+
require 'micro/attributes/features/initialize'
|
13
|
+
require 'micro/attributes/features/initialize/strict'
|
14
|
+
require 'micro/attributes/features/keys_as_symbol'
|
15
|
+
require 'micro/attributes/features/activemodel_validations'
|
9
16
|
|
10
|
-
|
11
|
-
|
12
|
-
ALL_VISIBLE = [
|
13
|
-
DIFF = 'diff'.freeze,
|
14
|
-
INITIALIZE = 'initialize'.freeze,
|
15
|
-
ACTIVEMODEL_VALIDATIONS = 'activemodel_validations'.freeze
|
16
|
-
].sort.freeze
|
17
|
-
|
18
|
-
ALL = (ALL_VISIBLE + [STRICT_INITIALIZE]).sort.freeze
|
19
|
-
|
20
|
-
INVALID_NAME = [
|
21
|
-
'Invalid feature name! Available options: ',
|
22
|
-
ALL_VISIBLE.map { |feature_name| ":#{feature_name}" }.join(', ')
|
23
|
-
].join
|
24
|
-
|
25
|
-
OPTIONS = {
|
26
|
-
# Features
|
27
|
-
DIFF => With::Diff,
|
28
|
-
INITIALIZE => With::Initialize,
|
29
|
-
STRICT_INITIALIZE => With::StrictInitialize,
|
30
|
-
ACTIVEMODEL_VALIDATIONS => With::ActiveModelValidations,
|
31
|
-
# Combinations
|
32
|
-
'diff:initialize' => With::DiffAndInitialize,
|
33
|
-
'diff:strict_initialize' => With::DiffAndStrictInitialize,
|
34
|
-
'activemodel_validations:diff' => With::ActiveModelValidationsAndDiff,
|
35
|
-
'activemodel_validations:initialize' => With::ActiveModelValidationsAndInitialize,
|
36
|
-
'activemodel_validations:strict_initialize' => With::ActiveModelValidationsAndStrictInitialize,
|
37
|
-
'activemodel_validations:diff:initialize' => With::ActiveModelValidationsAndDiffAndInitialize,
|
38
|
-
'activemodel_validations:diff:strict_initialize' => With::ActiveModelValidationsAndDiffAndStrictInitialize
|
39
|
-
}.freeze
|
40
|
-
|
41
|
-
private_constant :OPTIONS, :INVALID_NAME
|
17
|
+
extend self
|
42
18
|
|
43
|
-
|
44
|
-
|
19
|
+
module Name
|
20
|
+
ALL = [
|
21
|
+
DIFF = 'diff'.freeze,
|
22
|
+
ACCEPT = 'accept'.freeze,
|
23
|
+
INITIALIZE = 'initialize'.freeze,
|
24
|
+
KEYS_AS_SYMBOL = 'keys_as_symbol'.freeze,
|
25
|
+
ACTIVEMODEL_VALIDATIONS = 'activemodel_validations'.freeze
|
26
|
+
].sort.freeze
|
45
27
|
end
|
46
28
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
29
|
+
module Options
|
30
|
+
KEYS = [
|
31
|
+
DIFF = 'Diff'.freeze,
|
32
|
+
INIT = 'Initialize'.freeze,
|
33
|
+
ACCEPT = 'Accept'.freeze,
|
34
|
+
INIT_STRICT = 'InitializeStrict'.freeze,
|
35
|
+
ACCEPT_STRICT = 'AcceptStrict'.freeze,
|
36
|
+
KEYS_AS_SYMBOL = 'KeysAsSymbol'.freeze,
|
37
|
+
AM_VALIDATIONS = 'ActiveModelValidations'.freeze
|
38
|
+
].sort.freeze
|
39
|
+
|
40
|
+
KEYS_TO_FEATURES = {
|
41
|
+
DIFF => Features::Diff,
|
42
|
+
INIT => Features::Initialize,
|
43
|
+
ACCEPT => Features::Accept,
|
44
|
+
INIT_STRICT => Features::Initialize::Strict,
|
45
|
+
ACCEPT_STRICT => Features::Accept::Strict,
|
46
|
+
KEYS_AS_SYMBOL => Features::KeysAsSymbol,
|
47
|
+
AM_VALIDATIONS => Features::ActiveModelValidations
|
48
|
+
}.freeze
|
49
|
+
|
50
|
+
NAMES_TO_KEYS = {
|
51
|
+
Name::DIFF => DIFF,
|
52
|
+
Name::ACCEPT => ACCEPT,
|
53
|
+
Name::INITIALIZE => INIT,
|
54
|
+
Name::KEYS_AS_SYMBOL => KEYS_AS_SYMBOL,
|
55
|
+
Name::ACTIVEMODEL_VALIDATIONS => AM_VALIDATIONS
|
56
|
+
}.freeze
|
57
|
+
|
58
|
+
INIT_INIT_STRICT = "#{INIT}_#{INIT_STRICT}".freeze
|
59
|
+
ACCEPT_ACCEPT_STRICT = "#{ACCEPT}_#{ACCEPT_STRICT}".freeze
|
60
|
+
|
61
|
+
BuildKey = -> combination do
|
62
|
+
combination.sort.join('_')
|
63
|
+
.sub(INIT_INIT_STRICT, INIT_STRICT)
|
64
|
+
.sub(ACCEPT_ACCEPT_STRICT, ACCEPT_STRICT)
|
52
65
|
end
|
53
|
-
end
|
54
66
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
67
|
+
KEYS_TO_MODULES = begin
|
68
|
+
combinations = (1..KEYS.size).map { |n| KEYS.combination(n).to_a }.flatten(1).sort_by { |i| "#{i.size}#{i.join}" }
|
69
|
+
combinations.delete_if { |combination| combination.include?(INIT_STRICT) && !combination.include?(INIT) }
|
70
|
+
combinations.delete_if { |combination| combination.include?(ACCEPT_STRICT) && !combination.include?(ACCEPT) }
|
71
|
+
combinations.each_with_object({}) do |combination, features|
|
72
|
+
included = [
|
73
|
+
'def self.included(base)',
|
74
|
+
' base.send(:include, ::Micro::Attributes)',
|
75
|
+
combination.map { |key| " base.send(:include, ::#{KEYS_TO_FEATURES[key].name})" },
|
76
|
+
'end'
|
77
|
+
].flatten.join("\n")
|
78
|
+
|
79
|
+
key = BuildKey.call(combination)
|
80
|
+
|
81
|
+
With.const_set(key, Module.new.tap { |mod| mod.instance_eval(included) })
|
82
|
+
|
83
|
+
features[key] = With.const_get(key, false)
|
84
|
+
end.freeze
|
59
85
|
end
|
60
|
-
end
|
61
86
|
|
62
|
-
|
87
|
+
ACTIVEMODEL_VALIDATION = 'activemodel_validation'.freeze
|
88
|
+
|
89
|
+
def self.fetch_key(arg)
|
90
|
+
if arg.is_a?(Hash)
|
91
|
+
return ACCEPT_STRICT if arg[:accept] == :strict
|
92
|
+
|
93
|
+
INIT_STRICT if arg[:initialize] == :strict
|
94
|
+
else
|
95
|
+
str = String(arg)
|
63
96
|
|
64
|
-
|
65
|
-
return name unless name.is_a?(Hash)
|
97
|
+
name = str == ACTIVEMODEL_VALIDATION ? Name::ACTIVEMODEL_VALIDATIONS : str
|
66
98
|
|
67
|
-
|
99
|
+
KEYS_TO_MODULES.key?(name) ? name : NAMES_TO_KEYS[name]
|
100
|
+
end
|
68
101
|
end
|
69
102
|
|
70
|
-
|
71
|
-
|
103
|
+
INVALID_NAME = [
|
104
|
+
'Invalid feature name! Available options: ',
|
105
|
+
Name::ALL.map { |feature_name| ":#{feature_name}" }.join(', ')
|
106
|
+
].join
|
107
|
+
|
108
|
+
def self.fetch_keys(args)
|
109
|
+
keys = Array(args).dup.map { |name| fetch_key(name) }
|
72
110
|
|
73
|
-
|
111
|
+
raise ArgumentError, INVALID_NAME if keys.empty? || !(keys - KEYS).empty?
|
74
112
|
|
75
|
-
|
76
|
-
features.map! { |name| name.to_s.downcase }
|
77
|
-
features.uniq
|
113
|
+
yield(keys)
|
78
114
|
end
|
79
115
|
|
80
|
-
def
|
81
|
-
|
116
|
+
def self.remove_base_if_has_strict(keys)
|
117
|
+
keys.delete_if { |key| key == INIT } if keys.include?(INIT_STRICT)
|
118
|
+
keys.delete_if { |key| key == ACCEPT } if keys.include?(ACCEPT_STRICT)
|
82
119
|
end
|
83
120
|
|
84
|
-
def
|
85
|
-
|
121
|
+
def self.without_keys(keys_to_exclude)
|
122
|
+
keys = (KEYS - keys_to_exclude)
|
123
|
+
keys.delete_if { |key| key == INIT || key == INIT_STRICT } if keys_to_exclude.include?(INIT)
|
124
|
+
keys.delete_if { |key| key == ACCEPT || key == ACCEPT_STRICT } if keys_to_exclude.include?(ACCEPT)
|
125
|
+
keys
|
126
|
+
end
|
86
127
|
|
87
|
-
|
128
|
+
def self.fetch_module_by_keys(combination)
|
129
|
+
key = BuildKey.call(combination)
|
88
130
|
|
89
|
-
|
131
|
+
KEYS_TO_MODULES.fetch(key)
|
90
132
|
end
|
91
133
|
|
92
|
-
|
93
|
-
|
94
|
-
|
134
|
+
private_constant :KEYS_TO_FEATURES, :NAMES_TO_KEYS, :INVALID_NAME
|
135
|
+
private_constant :INIT_INIT_STRICT, :ACCEPT_ACCEPT_STRICT, :BuildKey
|
136
|
+
end
|
137
|
+
|
138
|
+
def all
|
139
|
+
@all ||= self.with(Options::KEYS)
|
140
|
+
end
|
95
141
|
|
96
|
-
|
97
|
-
|
142
|
+
def with(names)
|
143
|
+
Options.fetch_keys(names) do |keys|
|
144
|
+
Options.remove_base_if_has_strict(keys)
|
98
145
|
|
99
|
-
|
146
|
+
Options.fetch_module_by_keys(keys)
|
100
147
|
end
|
148
|
+
end
|
101
149
|
|
102
|
-
|
103
|
-
|
104
|
-
|
150
|
+
def without(names)
|
151
|
+
Options.fetch_keys(names) do |keys|
|
152
|
+
keys_to_fetch = Options.without_keys(keys)
|
105
153
|
|
106
|
-
|
107
|
-
|
154
|
+
return ::Micro::Attributes if keys_to_fetch.empty?
|
155
|
+
|
156
|
+
Options.fetch_module_by_keys(keys_to_fetch)
|
108
157
|
end
|
158
|
+
end
|
159
|
+
|
160
|
+
private_constant :Name
|
109
161
|
end
|
162
|
+
|
110
163
|
end
|
111
164
|
end
|