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.
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,65 +1,39 @@
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_set, :__attribute_reader
17
18
  end
18
19
 
19
20
  def base.inherited(subclass)
20
- subclass.attributes(self.attributes_data({}))
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.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))
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.feature(name)
38
- self.with(name)
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 "micro/attributes/with"
3
+ require 'micro/attributes/with'
4
4
 
5
5
  module Micro
6
6
  module Attributes
7
7
  module Features
8
- INVALID_FEATURES = 'Invalid feature name! Available options: :initialize, :strict_initialize, :diff, :activemodel_validations'.freeze
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
- 'diff' => With::Diff,
13
- 'initialize' => With::Initialize,
14
- 'strict_initialize' => With::StrictInitialize,
15
- 'activemodel_validations' => With::ActiveModelValidations,
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 self.all
29
- With::ActiveModelValidationsAndDiffAndInitialize
43
+ def all
44
+ @all ||= self.with(ALL)
30
45
  end
31
46
 
32
- def self.with(names)
33
- option = OPTIONS[names.map { |name| name.to_s.downcase }.sort.join(':')]
34
- return option if option
35
- raise ArgumentError, INVALID_FEATURES
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 self.options(init, diff, activemodel_validations)
39
- [init].tap do |options|
40
- options << :diff if diff
41
- options << :activemodel_validations if activemodel_validations
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
- 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_set__(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) 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