u-attributes 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|