u-attributes 1.0.0 → 2.0.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/.gitignore +1 -0
- data/.travis.sh +9 -2
- data/.travis.yml +4 -3
- data/Gemfile +1 -1
- data/LICENSE.txt +2 -2
- data/README.md +285 -234
- data/Rakefile +5 -5
- data/bin/console +4 -4
- data/lib/micro/attributes.rb +58 -40
- data/lib/micro/attributes/diff.rb +52 -0
- data/lib/micro/attributes/features.rb +82 -17
- data/lib/micro/attributes/features/activemodel_validations.rb +17 -23
- data/lib/micro/attributes/features/diff.rb +2 -50
- data/lib/micro/attributes/features/initialize.rb +5 -0
- data/lib/micro/attributes/features/initialize/strict.rb +39 -0
- data/lib/micro/attributes/macros.rb +25 -23
- data/lib/micro/attributes/utils.rb +19 -0
- data/lib/micro/attributes/version.rb +1 -1
- data/lib/micro/attributes/with.rb +9 -13
- data/u-attributes.gemspec +14 -10
- metadata +42 -8
- data/Gemfile.lock +0 -29
- data/lib/micro/attributes/attributes_utils.rb +0 -21
- data/lib/micro/attributes/features/strict_initialize.rb +0 -43
data/Rakefile
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rake/testtask'
|
3
3
|
|
4
4
|
Rake::TestTask.new(:test) do |t|
|
5
|
-
t.libs <<
|
6
|
-
t.libs <<
|
7
|
-
t.test_files = FileList[
|
5
|
+
t.libs << 'test'
|
6
|
+
t.libs << 'lib'
|
7
|
+
t.test_files = FileList['test/**/*_test.rb']
|
8
8
|
end
|
9
9
|
|
10
10
|
task :default => :test
|
data/bin/console
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'micro/attributes'
|
5
5
|
|
6
6
|
# You can add fixtures and/or initialization code here to make experimenting
|
7
7
|
# with your gem easier. You can also use a different console, if you like.
|
8
8
|
|
9
9
|
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
-
# require
|
10
|
+
# require 'pry'
|
11
11
|
# Pry.start
|
12
12
|
|
13
|
-
require
|
13
|
+
require 'irb'
|
14
14
|
IRB.start(__FILE__)
|
data/lib/micro/attributes.rb
CHANGED
@@ -1,65 +1,39 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require "micro/attributes/attributes_utils"
|
5
|
-
require "micro/attributes/macros"
|
6
|
-
require "micro/attributes/features"
|
3
|
+
require 'kind'
|
7
4
|
|
8
5
|
module Micro
|
9
6
|
module Attributes
|
7
|
+
require 'micro/attributes/version'
|
8
|
+
require 'micro/attributes/utils'
|
9
|
+
require 'micro/attributes/diff'
|
10
|
+
require 'micro/attributes/macros'
|
11
|
+
require 'micro/attributes/features'
|
12
|
+
|
10
13
|
def self.included(base)
|
11
14
|
base.extend(::Micro::Attributes.const_get(:Macros))
|
12
15
|
|
13
16
|
base.class_eval do
|
14
|
-
private_class_method :
|
15
|
-
private_class_method :__attributes_def, :__attributes_set
|
16
|
-
private_class_method :__attribute_reader, :__attribute_set
|
17
|
+
private_class_method :__attributes, :__attribute_set, :__attribute_reader
|
17
18
|
end
|
18
19
|
|
19
20
|
def base.inherited(subclass)
|
20
|
-
subclass.
|
21
|
+
subclass.__attributes_set_after_inherit__(self.__attributes_data__)
|
22
|
+
|
21
23
|
subclass.extend ::Micro::Attributes.const_get('Macros::ForSubclasses'.freeze)
|
22
24
|
end
|
23
25
|
end
|
24
26
|
|
25
|
-
def self.
|
26
|
-
|
27
|
-
end
|
28
|
-
|
29
|
-
def self.to_initialize!(diff: false, activemodel_validations: false)
|
30
|
-
features(*Features.options(:strict_initialize, diff, activemodel_validations))
|
27
|
+
def self.without(*names)
|
28
|
+
Features.without(names)
|
31
29
|
end
|
32
30
|
|
33
31
|
def self.with(*names)
|
34
32
|
Features.with(names)
|
35
33
|
end
|
36
34
|
|
37
|
-
def self.
|
38
|
-
|
39
|
-
end
|
40
|
-
|
41
|
-
def self.features(*names)
|
42
|
-
names.empty? ? Features.all : Features.with(names)
|
43
|
-
end
|
44
|
-
|
45
|
-
protected def attributes=(arg)
|
46
|
-
self.class
|
47
|
-
.attributes_data(AttributesUtils.hash_argument!(arg))
|
48
|
-
.each { |name, value| __attribute_set(name, value) }
|
49
|
-
|
50
|
-
__attributes.freeze
|
51
|
-
end
|
52
|
-
|
53
|
-
private def __attributes
|
54
|
-
@__attributes ||= {}
|
55
|
-
end
|
56
|
-
|
57
|
-
private def __attribute_set(name, value)
|
58
|
-
__attributes[name] = instance_variable_set("@#{name}", value) if attribute?(name)
|
59
|
-
end
|
60
|
-
|
61
|
-
def attributes
|
62
|
-
__attributes
|
35
|
+
def self.with_all_features
|
36
|
+
Features.all
|
63
37
|
end
|
64
38
|
|
65
39
|
def attribute?(name)
|
@@ -79,5 +53,49 @@ module Micro
|
|
79
53
|
|
80
54
|
raise NameError, "undefined attribute `#{name}"
|
81
55
|
end
|
56
|
+
|
57
|
+
def attributes(*names)
|
58
|
+
return __attributes if names.empty?
|
59
|
+
|
60
|
+
names.each_with_object({}) do |name, memo|
|
61
|
+
memo[name] = attribute(name) if attribute?(name)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
protected
|
66
|
+
|
67
|
+
def attributes=(arg)
|
68
|
+
hash = Utils.stringify_hash_keys(arg)
|
69
|
+
|
70
|
+
__attributes_set(hash, self.class.__attributes_data__)
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def __attributes
|
76
|
+
@__attributes ||= {}
|
77
|
+
end
|
78
|
+
|
79
|
+
def __attribute_set(name, value)
|
80
|
+
__attributes[name] = instance_variable_set("@#{name}", value) if attribute?(name)
|
81
|
+
end
|
82
|
+
|
83
|
+
def __attributes_set(hash, att_data)
|
84
|
+
att_data.each do |key, default|
|
85
|
+
value = hash[key]
|
86
|
+
|
87
|
+
final_value =
|
88
|
+
if default.respond_to?(:call)
|
89
|
+
callable = default.is_a?(Proc) ? default : default.method(:call)
|
90
|
+
callable.arity > 0 ? callable.call(value) : callable.call
|
91
|
+
else
|
92
|
+
value || default
|
93
|
+
end
|
94
|
+
|
95
|
+
__attribute_set(key, final_value)
|
96
|
+
end
|
97
|
+
|
98
|
+
__attributes.freeze
|
99
|
+
end
|
82
100
|
end
|
83
101
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Micro::Attributes
|
4
|
+
module Diff
|
5
|
+
class Changes
|
6
|
+
TO = 'to'.freeze
|
7
|
+
FROM = 'from'.freeze
|
8
|
+
FROM_TO_ERROR = 'pass the attribute name with the :from and :to values'.freeze
|
9
|
+
|
10
|
+
attr_reader :from, :to, :differences
|
11
|
+
|
12
|
+
def initialize(from:, to:)
|
13
|
+
raise ArgumentError, "expected an instance of #{from.class}" unless to.is_a?(from.class)
|
14
|
+
@from, @to = from, to
|
15
|
+
@differences = diff(from.attributes, to.attributes).freeze
|
16
|
+
end
|
17
|
+
|
18
|
+
def empty?
|
19
|
+
@differences.empty?
|
20
|
+
end
|
21
|
+
alias_method :blank?, :empty?
|
22
|
+
|
23
|
+
def present?
|
24
|
+
!empty?
|
25
|
+
end
|
26
|
+
|
27
|
+
def changed?(name = nil, from: nil, to: nil)
|
28
|
+
if name.nil?
|
29
|
+
return present? if from.nil? && to.nil?
|
30
|
+
raise ArgumentError, FROM_TO_ERROR
|
31
|
+
elsif from.nil? && to.nil?
|
32
|
+
differences.has_key?(name.to_s)
|
33
|
+
else
|
34
|
+
result = @differences[name.to_s]
|
35
|
+
result ? result[FROM] == from && result[TO] == to : false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def diff(from_attributes, to_attributes)
|
42
|
+
@from_attributes, @to_attributes = from_attributes, to_attributes
|
43
|
+
@from_attributes.each_with_object({}) do |(from_key, from_val), acc|
|
44
|
+
to_value = @to_attributes[from_key]
|
45
|
+
acc[from_key] = {FROM => from_val, TO => to_value}.freeze if from_val != to_value
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private_constant :TO, :FROM, :FROM_TO_ERROR
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -1,18 +1,33 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require 'micro/attributes/with'
|
4
4
|
|
5
5
|
module Micro
|
6
6
|
module Attributes
|
7
7
|
module Features
|
8
|
-
|
8
|
+
extend self
|
9
|
+
|
10
|
+
STRICT_INITIALIZE = 'strict_initialize'.freeze
|
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
|
9
24
|
|
10
25
|
OPTIONS = {
|
11
26
|
# Features
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
27
|
+
DIFF => With::Diff,
|
28
|
+
INITIALIZE => With::Initialize,
|
29
|
+
STRICT_INITIALIZE => With::StrictInitialize,
|
30
|
+
ACTIVEMODEL_VALIDATIONS => With::ActiveModelValidations,
|
16
31
|
# Combinations
|
17
32
|
'diff:initialize' => With::DiffAndInitialize,
|
18
33
|
'diff:strict_initialize' => With::DiffAndStrictInitialize,
|
@@ -23,24 +38,74 @@ module Micro
|
|
23
38
|
'activemodel_validations:diff:strict_initialize' => With::ActiveModelValidationsAndDiffAndStrictInitialize
|
24
39
|
}.freeze
|
25
40
|
|
26
|
-
private_constant :OPTIONS
|
41
|
+
private_constant :OPTIONS, :INVALID_NAME
|
27
42
|
|
28
|
-
def
|
29
|
-
|
43
|
+
def all
|
44
|
+
@all ||= self.with(ALL)
|
30
45
|
end
|
31
46
|
|
32
|
-
def
|
33
|
-
|
34
|
-
|
35
|
-
|
47
|
+
def with(args)
|
48
|
+
valid_names!(args) do |names|
|
49
|
+
delete_initialize_if_has_strict_initialize(names)
|
50
|
+
|
51
|
+
OPTIONS.fetch(names.sort.join(':'))
|
52
|
+
end
|
36
53
|
end
|
37
54
|
|
38
|
-
def
|
39
|
-
|
40
|
-
|
41
|
-
|
55
|
+
def without(args)
|
56
|
+
valid_names!(args) do |names_to_exclude|
|
57
|
+
names = except_options(names_to_exclude)
|
58
|
+
names.empty? ? ::Micro::Attributes : self.with(names)
|
42
59
|
end
|
43
60
|
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def fetch_feature_name(name)
|
65
|
+
return name unless name.is_a?(Hash)
|
66
|
+
|
67
|
+
STRICT_INITIALIZE if name[:initialize] == :strict
|
68
|
+
end
|
69
|
+
|
70
|
+
def normalize_names(args)
|
71
|
+
names = Array(args).dup
|
72
|
+
|
73
|
+
last_feature = fetch_feature_name(names.pop)
|
74
|
+
|
75
|
+
features = names.empty? ? [last_feature] : names + [last_feature]
|
76
|
+
features.map! { |name| name.to_s.downcase }
|
77
|
+
features.uniq
|
78
|
+
end
|
79
|
+
|
80
|
+
def valid_names?(names)
|
81
|
+
names.all? { |name| ALL.include?(name) }
|
82
|
+
end
|
83
|
+
|
84
|
+
def valid_names!(args)
|
85
|
+
names = normalize_names(args)
|
86
|
+
|
87
|
+
raise ArgumentError, INVALID_NAME if names.empty? || !valid_names?(names)
|
88
|
+
|
89
|
+
yield(names)
|
90
|
+
end
|
91
|
+
|
92
|
+
def an_initialize?(name)
|
93
|
+
name == INITIALIZE || name == STRICT_INITIALIZE
|
94
|
+
end
|
95
|
+
|
96
|
+
def delete_initialize_if_has_strict_initialize(names)
|
97
|
+
return unless names.include?(STRICT_INITIALIZE)
|
98
|
+
|
99
|
+
names.delete_if { |name| name == INITIALIZE }
|
100
|
+
end
|
101
|
+
|
102
|
+
def except_options(names_to_exclude)
|
103
|
+
(ALL - names_to_exclude).tap do |names|
|
104
|
+
names.delete_if { |name| an_initialize?(name) } if names_to_exclude.include?(INITIALIZE)
|
105
|
+
|
106
|
+
delete_initialize_if_has_strict_initialize(names)
|
107
|
+
end
|
108
|
+
end
|
44
109
|
end
|
45
110
|
end
|
46
111
|
end
|
@@ -3,36 +3,30 @@
|
|
3
3
|
module Micro::Attributes
|
4
4
|
module Features
|
5
5
|
module ActiveModelValidations
|
6
|
-
@@__active_model_required = false
|
7
|
-
@@__active_model_load_error = false
|
8
|
-
|
9
|
-
V32 = '3.2'
|
10
|
-
|
11
6
|
def self.included(base)
|
12
|
-
|
13
|
-
|
14
|
-
require 'active_model'
|
15
|
-
rescue LoadError => e
|
16
|
-
@@__active_model_load_error = true
|
17
|
-
end
|
18
|
-
@@__active_model_required = true
|
19
|
-
end
|
7
|
+
begin
|
8
|
+
require 'active_model'
|
20
9
|
|
21
|
-
unless @@__active_model_load_error
|
22
10
|
base.send(:include, ::ActiveModel::Validations)
|
11
|
+
base.extend(ClassMethods)
|
12
|
+
rescue LoadError
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
def __call_after_attribute_set__(attr_name, options)
|
18
|
+
validate, validates = options.values_at(:validate, :validates)
|
23
19
|
|
24
|
-
if
|
25
|
-
|
26
|
-
def initialize(arg)
|
27
|
-
self.attributes=arg
|
28
|
-
run_validations!
|
29
|
-
end
|
30
|
-
RUBY
|
31
|
-
end
|
20
|
+
self.validate(validate) if validate
|
21
|
+
self.validates(attr_name, validates) if validates
|
32
22
|
end
|
33
23
|
end
|
34
24
|
|
35
|
-
|
25
|
+
private
|
26
|
+
|
27
|
+
def __call_after_micro_attribute
|
28
|
+
run_validations! if respond_to?(:run_validations!, true)
|
29
|
+
end
|
36
30
|
end
|
37
31
|
end
|
38
32
|
end
|
@@ -3,59 +3,11 @@
|
|
3
3
|
module Micro::Attributes
|
4
4
|
module Features
|
5
5
|
module Diff
|
6
|
-
class Changes
|
7
|
-
TO = 'to'.freeze
|
8
|
-
FROM = 'from'.freeze
|
9
|
-
FROM_TO_ERROR = 'pass the attribute name with the :from and :to values'.freeze
|
10
|
-
|
11
|
-
attr_reader :from, :to, :differences
|
12
|
-
|
13
|
-
def initialize(from:, to:)
|
14
|
-
raise ArgumentError, "expected an instance of #{from.class}" unless to.is_a?(from.class)
|
15
|
-
@from, @to = from, to
|
16
|
-
@differences = diff(from.attributes, to.attributes).freeze
|
17
|
-
end
|
18
|
-
|
19
|
-
def empty?
|
20
|
-
@differences.empty?
|
21
|
-
end
|
22
|
-
alias_method :blank?, :empty?
|
23
|
-
|
24
|
-
def present?
|
25
|
-
!empty?
|
26
|
-
end
|
27
|
-
|
28
|
-
def changed?(name = nil, from: nil, to: nil)
|
29
|
-
if name.nil?
|
30
|
-
return present? if from.nil? && to.nil?
|
31
|
-
raise ArgumentError, FROM_TO_ERROR
|
32
|
-
elsif from.nil? && to.nil?
|
33
|
-
differences.has_key?(name.to_s)
|
34
|
-
else
|
35
|
-
result = @differences[name.to_s]
|
36
|
-
result ? result[FROM] == from && result[TO] == to : false
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
private
|
41
|
-
|
42
|
-
def diff(from_attributes, to_attributes)
|
43
|
-
@from_attributes, @to_attributes = from_attributes, to_attributes
|
44
|
-
@from_attributes.each_with_object({}) do |(from_key, from_val), acc|
|
45
|
-
to_value = @to_attributes[from_key]
|
46
|
-
acc[from_key] = {FROM => from_val, TO => to_value}.freeze if from_val != to_value
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
private_constant :TO, :FROM, :FROM_TO_ERROR
|
51
|
-
end
|
52
|
-
|
53
6
|
def diff_attributes(to)
|
54
|
-
return Changes.new(from: self, to: to) if to.is_a?(::Micro::Attributes)
|
7
|
+
return Micro::Attributes::Diff::Changes.new(from: self, to: to) if to.is_a?(::Micro::Attributes)
|
8
|
+
|
55
9
|
raise ArgumentError, "#{to.inspect} must implement Micro::Attributes"
|
56
10
|
end
|
57
|
-
|
58
|
-
private_constant :Changes
|
59
11
|
end
|
60
12
|
end
|
61
13
|
end
|