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.
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