u-attributes 1.2.0 → 2.2.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.
data/Rakefile CHANGED
@@ -1,10 +1,10 @@
1
- require "bundler/gem_tasks"
2
- require "rake/testtask"
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
3
 
4
4
  Rake::TestTask.new(:test) do |t|
5
- t.libs << "test"
6
- t.libs << "lib"
7
- t.test_files = FileList["test/**/*_test.rb"]
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
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "bundler/setup"
4
- require "micro/attributes"
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 "pry"
10
+ # require 'pry'
11
11
  # Pry.start
12
12
 
13
- require "irb"
13
+ require 'irb'
14
14
  IRB.start(__FILE__)
@@ -1,35 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "micro/attributes/version"
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 :__attributes_data, :__attributes
15
- private_class_method :__attributes_def, :__attributes_set
16
- private_class_method :__attribute_reader, :__attribute_set
17
+ private_class_method :__attributes, :__attribute_reader
18
+ private_class_method :__attribute_assign, :__attributes_data_to_assign
17
19
  end
18
20
 
19
21
  def base.inherited(subclass)
20
- subclass.attributes(self.attributes_data({}))
22
+ subclass.__attributes_set_after_inherit__(self.__attributes_data__)
23
+
21
24
  subclass.extend ::Micro::Attributes.const_get('Macros::ForSubclasses'.freeze)
22
25
  end
23
26
  end
24
27
 
25
- def self.to_initialize(diff: false, activemodel_validations: false)
26
- features(*Features.options(:initialize, diff, activemodel_validations))
27
- end
28
-
29
- def self.to_initialize!(diff: false, activemodel_validations: false)
30
- features(*Features.options(:strict_initialize, diff, activemodel_validations))
31
- end
32
-
33
28
  def self.without(*names)
34
29
  Features.without(names)
35
30
  end
@@ -38,28 +33,26 @@ module Micro
38
33
  Features.with(names)
39
34
  end
40
35
 
41
- def self.feature(name)
42
- self.with(name)
36
+ def self.with_all_features
37
+ Features.all
43
38
  end
44
39
 
45
- def self.features(*names)
46
- names.empty? ? Features.all : Features.with(names)
40
+ def attribute?(name)
41
+ self.class.attribute?(name)
47
42
  end
48
43
 
49
- protected def attributes=(arg)
50
- self.class
51
- .attributes_data(AttributesUtils.hash_argument!(arg))
52
- .each { |name, value| __attribute_set(name, value) }
44
+ def attribute(name)
45
+ return unless attribute?(name)
53
46
 
54
- __attributes.freeze
55
- end
47
+ value = public_send(name)
56
48
 
57
- private def __attributes
58
- @__attributes ||= {}
49
+ block_given? ? yield(value) : value
59
50
  end
60
51
 
61
- private def __attribute_set(name, value)
62
- __attributes[name] = instance_variable_set("@#{name}", value) if attribute?(name)
52
+ def attribute!(name, &block)
53
+ attribute(name) { |name| return block ? block[name] : name }
54
+
55
+ raise NameError, "undefined attribute `#{name}"
63
56
  end
64
57
 
65
58
  def attributes(*names)
@@ -70,22 +63,68 @@ module Micro
70
63
  end
71
64
  end
72
65
 
73
- def attribute?(name)
74
- self.class.attribute?(name)
66
+ def defined_attributes
67
+ @defined_attributes ||= self.class.attributes
75
68
  end
76
69
 
77
- def attribute(name)
78
- return unless attribute?(name)
70
+ protected
79
71
 
80
- value = public_send(name)
72
+ def attributes=(arg)
73
+ hash = Utils::Hashes.stringify_keys(arg)
81
74
 
82
- block_given? ? yield(value) : value
83
- end
75
+ __attributes_missing!(hash)
84
76
 
85
- def attribute!(name, &block)
86
- attribute(name) { |name| return block ? block[name] : name }
77
+ __attributes_assign(hash)
78
+ end
87
79
 
88
- raise NameError, "undefined attribute `#{name}"
89
- end
80
+ private
81
+
82
+ def extract_attributes_from(other)
83
+ Utils::ExtractAttribute.from(other, keys: defined_attributes)
84
+ end
85
+
86
+ def __attributes
87
+ @__attributes ||= {}
88
+ end
89
+
90
+ FetchValueToAssign = -> (value, default) do
91
+ if default.is_a?(Proc)
92
+ default.arity > 0 ? default.call(value) : default.call
93
+ else
94
+ value.nil? ? default : value
95
+ end
96
+ end
97
+
98
+ def __attributes_assign(hash)
99
+ self.class.__attributes_data__.each do |name, default|
100
+ __attribute_assign(name, FetchValueToAssign.(hash[name], default)) if attribute?(name)
101
+ end
102
+
103
+ __attributes.freeze
104
+ end
105
+
106
+ def __attribute_assign(name, value)
107
+ __attributes[name] = instance_variable_set("@#{name}", value)
108
+ end
109
+
110
+ MISSING_KEYWORD = 'missing keyword'.freeze
111
+ MISSING_KEYWORDS = 'missing keywords'.freeze
112
+
113
+ def __attributes_missing!(hash)
114
+ required_keys = self.class.__attributes_required__
115
+
116
+ return if required_keys.empty?
117
+
118
+ missing_keys = required_keys.map { |name| ":#{name}" if !hash.key?(name) }
119
+ missing_keys.compact!
120
+
121
+ return if missing_keys.empty?
122
+
123
+ label = missing_keys.size == 1 ? MISSING_KEYWORD : MISSING_KEYWORDS
124
+
125
+ raise ArgumentError, "#{label}: #{missing_keys.join(', ')}"
126
+ end
127
+
128
+ private_constant :FetchValueToAssign, :MISSING_KEYWORD, :MISSING_KEYWORDS
90
129
  end
91
130
  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,22 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "micro/attributes/with"
3
+ require 'micro/attributes/with'
4
4
 
5
5
  module Micro
6
6
  module Attributes
7
7
  module Features
8
8
  extend self
9
9
 
10
- ALL = [
10
+ STRICT_INITIALIZE = 'strict_initialize'.freeze
11
+
12
+ ALL_VISIBLE = [
11
13
  DIFF = 'diff'.freeze,
12
14
  INITIALIZE = 'initialize'.freeze,
13
- STRICT_INITIALIZE = 'strict_initialize'.freeze,
14
15
  ACTIVEMODEL_VALIDATIONS = 'activemodel_validations'.freeze
15
16
  ].sort.freeze
16
17
 
18
+ ALL = (ALL_VISIBLE + [STRICT_INITIALIZE]).sort.freeze
19
+
17
20
  INVALID_NAME = [
18
21
  'Invalid feature name! Available options: ',
19
- ALL.map { |feature_name| ":#{feature_name}" }.join(', ')
22
+ ALL_VISIBLE.map { |feature_name| ":#{feature_name}" }.join(', ')
20
23
  ].join
21
24
 
22
25
  OPTIONS = {
@@ -56,48 +59,53 @@ module Micro
56
59
  end
57
60
  end
58
61
 
59
- def options(init, diff, activemodel_validations)
60
- [init].tap do |options|
61
- options << :diff if diff
62
- options << :activemodel_validations if activemodel_validations
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
63
68
  end
64
- end
65
69
 
66
- private
70
+ def normalize_names(args)
71
+ names = Array(args).dup
67
72
 
68
- def normalize_names(args)
69
- Array(args).map { |arg| arg.to_s.downcase }.uniq
70
- end
73
+ last_feature = fetch_feature_name(names.pop)
71
74
 
72
- def valid_names?(names)
73
- names.all? { |name| ALL.include?(name) }
74
- end
75
+ features = names.empty? ? [last_feature] : names + [last_feature]
76
+ features.map! { |name| name.to_s.downcase }
77
+ features.uniq
78
+ end
75
79
 
76
- def valid_names!(args)
77
- names = normalize_names(args)
80
+ def valid_names?(names)
81
+ names.all? { |name| ALL.include?(name) }
82
+ end
78
83
 
79
- raise ArgumentError, INVALID_NAME if args.empty? || !valid_names?(names)
84
+ def valid_names!(args)
85
+ names = normalize_names(args)
80
86
 
81
- yield(names)
82
- end
87
+ raise ArgumentError, INVALID_NAME if names.empty? || !valid_names?(names)
83
88
 
84
- def an_initialize?(name)
85
- name == INITIALIZE || name == STRICT_INITIALIZE
86
- end
89
+ yield(names)
90
+ end
91
+
92
+ def an_initialize?(name)
93
+ name == INITIALIZE || name == STRICT_INITIALIZE
94
+ end
87
95
 
88
- def delete_initialize_if_has_strict_initialize(names)
89
- return unless names.include?(STRICT_INITIALIZE)
96
+ def delete_initialize_if_has_strict_initialize(names)
97
+ return unless names.include?(STRICT_INITIALIZE)
90
98
 
91
- names.delete_if { |name| name == INITIALIZE }
92
- end
99
+ names.delete_if { |name| name == INITIALIZE }
100
+ end
93
101
 
94
- def except_options(names_to_exclude)
95
- (ALL - names_to_exclude).tap do |names|
96
- names.delete_if { |name| an_initialize?(name) } if names_to_exclude.include?(INITIALIZE)
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)
97
105
 
98
- delete_initialize_if_has_strict_initialize(names)
106
+ delete_initialize_if_has_strict_initialize(names)
107
+ end
99
108
  end
100
- end
101
109
  end
102
110
  end
103
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
- if !@@__active_model_load_error && !@@__active_model_required
13
- begin
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_assign__(attr_name, options)
18
+ validate, validates = options.values_at(:validate, :validates)
23
19
 
24
- if ::ActiveModel::VERSION::STRING >= V32
25
- base.class_eval(<<-RUBY)
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.dup) if validates
32
22
  end
33
23
  end
34
24
 
35
- private_constant :V32
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